diff --git a/.eslintrc.js b/.eslintrc.js index 2ce6d279d93a9..dc2eaa993ce8b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -109,7 +109,7 @@ module.exports = { }, }, { - files: ['x-pack/legacy/plugins/lens/**/*.{js,ts,tsx}'], + files: ['x-pack/plugins/lens/**/*.{js,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', 'react-hooks/rules-of-hooks': 'off', @@ -536,9 +536,15 @@ module.exports = { * ML overrides */ { - files: ['x-pack/legacy/plugins/ml/**/*.js'], + files: ['x-pack/plugins/ml/**/*.js'], rules: { 'no-shadow': 'error', + 'import/no-extraneous-dependencies': [ + 'error', + { + packageDir: './x-pack', + }, + ], }, }, @@ -728,7 +734,7 @@ module.exports = { * Lens overrides */ { - files: ['x-pack/legacy/plugins/lens/**/*.{ts,tsx}', 'x-pack/plugins/lens/**/*.{ts,tsx}'], + files: ['x-pack/plugins/lens/**/*.{ts,tsx}'], rules: { '@typescript-eslint/no-explicit-any': 'error', }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 267f3dde0b66f..4a061d7415990 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,8 +3,8 @@ # For more info, see https://help.github.com/articles/about-codeowners/ # App -/x-pack/legacy/plugins/lens/ @elastic/kibana-app -/x-pack/legacy/plugins/graph/ @elastic/kibana-app +/x-pack/plugins/lens/ @elastic/kibana-app +/x-pack/plugins/graph/ @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 @@ -12,13 +12,13 @@ /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/dev_tools/ @elastic/kibana-app -/src/legacy/core_plugins/metrics/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app -/src/legacy/core_plugins/vis_type_xy/ @elastic/kibana-app +/src/plugins/vis_type_xy/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app +/src/plugins/vis_type_timeseries/ @elastic/kibana-app # Core UI # Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon @@ -80,6 +80,8 @@ /x-pack/legacy/plugins/ingest_manager/ @elastic/ingest-management /x-pack/plugins/observability/ @elastic/logs-metrics-ui @elastic/apm-ui @elastic/uptime @elastic/ingest-management /x-pack/legacy/plugins/monitoring/ @elastic/stack-monitoring-ui +/x-pack/legacy/plugins/uptime @elastic/uptime +/x-pack/plugins/uptime @elastic/uptime # Machine Learning /x-pack/legacy/plugins/ml/ @elastic/ml-ui @@ -95,6 +97,7 @@ # Maps /x-pack/legacy/plugins/maps/ @elastic/kibana-gis +/x-pack/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 diff --git a/.sass-lint.yml b/.sass-lint.yml index dd7bc0576692b..5c2c88a1dad5d 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -8,10 +8,11 @@ files: - 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' - 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss' + - 'x-pack/plugins/lens/**/*.s+(a|c)ss' + - 'x-pack/legacy/plugins/maps/**/*.s+(a|c)ss' + - 'x-pack/plugins/maps/**/*.s+(a|c)ss' ignore: - 'x-pack/legacy/plugins/canvas/shareable_runtime/**/*.s+(a|c)ss' - - 'x-pack/legacy/plugins/lens/**/*.s+(a|c)ss' - - 'x-pack/legacy/plugins/maps/**/*.s+(a|c)ss' rules: quotes: - 2 diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc index add6f601489e1..f89f994e59e57 100644 --- a/docs/apm/advanced-queries.asciidoc +++ b/docs/apm/advanced-queries.asciidoc @@ -1,14 +1,11 @@ +[role="xpack"] [[advanced-queries]] -=== Advanced queries +=== Query your data -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. - -You can begin to see some of the transaction fields available for filtering: - -[role="screenshot"] -image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] +Querying your APM data is a powerful tool that can make finding bottlenecks in your code even easier. +Imagine you have a user that complains about a slow response time in a specific service. +With the query bar, you can easily filter the APM app to only display trace data for that user, +or, to only show transactions that are slower than a specified time threshold. [float] ==== Example APM app queries @@ -17,15 +14,24 @@ image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app i * Filter by response status code: `context.response.status_code >= 400` * Filter by single user ID: `context.user.id : 12` +When querying in the APM app, you're merely 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. + +When you type, you can begin to see some of the transaction fields available for filtering: + +[role="screenshot"] +image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] + 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 Discover -It may also be helpful to view your APM data in {kibana-ref}/discover.html[*Discover*]. +Alternatively, you can query your APM documents in {kibana-ref}/discover.html[*Discover*]. Querying documents in *Discover* works the same way as querying in the APM app, -and all of the example APM app queries can also be used in *Discover*. +and *Discover* supports all of the example APM app queries shown on this page. [float] ==== Example Discover query @@ -33,7 +39,7 @@ and all of the example APM app queries can also be used in *Discover*. One example where you may want to make use of *Discover*, 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. +TIP: Starting in v7.6, you can view ten 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 *Discover* and make a search: diff --git a/docs/apm/agent-configuration.asciidoc b/docs/apm/agent-configuration.asciidoc index 0d2834c1a400e..d911c2154ea4c 100644 --- a/docs/apm/agent-configuration.asciidoc +++ b/docs/apm/agent-configuration.asciidoc @@ -1,12 +1,16 @@ [role="xpack"] [[agent-configuration]] -=== APM Agent configuration +=== APM Agent central configuration -APM Agent configuration allows you to fine-tune your agent configuration directly in Kibana. -Best of all, changes are automatically propagated to your APM agents so there's no need to redeploy. +++++ +Configure APM agents with central config +++++ -To get started, simply choose the services and environments you wish to configure. -The APM app will let you know when your configurations have been applied by your agents. +APM Agent configuration allows you to fine-tune your agent configuration from within the APM app. +Changes are automatically propagated to your APM agents, so there's no need to redeploy. + +To get started, choose the services and environments you wish to configure. +The APM app will let you know when your agents have applied your configurations. [role="screenshot"] image::apm/images/apm-agent-configuration.png[APM Agent configuration in Kibana] @@ -14,29 +18,28 @@ image::apm/images/apm-agent-configuration.png[APM Agent configuration in Kibana] [float] ==== Precedence -Configurations set with APM Agent configuration take precedence over configurations set locally in the Agent. +Configurations set from the APM app take precedence over configurations set locally in each Agent. However, if APM Server is slow to respond, is offline, reports an error, etc., APM agents will use local defaults until they're able to update the configuration. -For this reason, it is still important to set custom default configurations locally in each of your agents. +For this reason, it is still essential to set custom default configurations locally in each of your agents. [float] ==== APM Server setup This feature requires {apm-server-ref}/setup-kibana-endpoint.html[Kibana endpoint configuration] in APM Server. -Why is additional configuration needed in APM Server? -That's because APM Server acts as a proxy between the agents and Kibana. +APM Server acts as a proxy between the agents and Kibana. Kibana communicates any changed settings to APM Server so that your agents only need to poll APM Server to determine which settings have changed. [float] ==== Supported configurations -Each Agent has its own list of supported configurations. +Each Agent has a list of supported configurations. After selecting a Service name and environment in the APM app, -a list of all available configuration options, +a list of all supported configuration options, including descriptions and default values, will be displayed. -Supported configurations are also marked in each Agent's configuration documentation: +Supported configurations are also tagged with the image:./images/dynamic-config.svg[] badge in each Agent's configuration reference: [horizontal] Go Agent:: {apm-go-ref}/configuration.html[Configuration reference] diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index a8f4f4bf0baaa..93733f5990a46 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -1,6 +1,10 @@ [role="xpack"] [[apm-api]] -== API +== APM app API + +++++ +REST API +++++ Some APM app features are provided via a REST API: diff --git a/docs/apm/apm-alerts.asciidoc b/docs/apm/apm-alerts.asciidoc index b8552c007b13d..75ce5f56c96c6 100644 --- a/docs/apm/apm-alerts.asciidoc +++ b/docs/apm/apm-alerts.asciidoc @@ -1,12 +1,16 @@ [role="xpack"] [[apm-alerts]] -=== Create an alert +=== Alerts + +++++ +Create an alert +++++ beta::[] -The APM app is integrated with Kibana's {kibana-ref}/alerting-getting-started.html[alerting and actions] feature. -It provides a set of built-in **actions** and APM specific threshold **alerts** for you to use, -and allows all alerts to be centrally managed from <>. +The APM app integrates with Kibana's {kibana-ref}/alerting-getting-started.html[alerting and actions] feature. +It provides a set of built-in **actions** and APM specific threshold **alerts** for you to use +and enables central management of all alerts from <>. [role="screenshot"] image::apm/images/apm-alert.png[Create an alert in the APM app] @@ -28,9 +32,9 @@ This guide creates an alert for the `opbeans-java` service based on the followin From the APM app, navigate to the `opbeans-java` service and select **Alerts** > **Create threshold alert** > **Transaction duration**. -The name of your alert will automatically be set as `Transaction duration | opbeans-java`, -and the alert will be tagged with `apm` and `service.name:opbeans-java`. -Feel free to edit either of these defaults. +`Transaction duration | opbeans-java` is automatically set as the name of the alert, +and `apm` and `service.name:opbeans-java` are added as tags. +It's fine to change the name of the alert, but do not edit the tags. Based on the alert criteria, define the following alert details: @@ -42,7 +46,7 @@ Based on the alert criteria, define the following alert details: * **FOR THE LAST** - `5 minutes` Select an action type. -Multiple action types can be selected, but in this example we want to post to a slack channel. +Multiple action types can be selected, but in this example, we want to post to a Slack channel. Select **Slack** > **Create a connector**. Enter a name for the connector, and paste the webhook URL. @@ -63,9 +67,9 @@ This guide creates an alert for the `opbeans-python` service based on the follow From the APM app, navigate to the `opbeans-python` service and select **Alerts** > **Create threshold alert** > **Error rate**. -The name of your alert will automatically be set as `Error rate | opbeans-python`, -and the alert will be tagged with `apm` and `service.name:opbeans-python`. -Feel free to edit either of these defaults. +`Error rate | opbeans-python` is automatically set as the name of the alert, +and `apm` and `service.name:opbeans-python` are added as tags. +It's fine to change the name of the alert, but do not edit the tags. Based on the alert criteria, define the following alert details: @@ -93,5 +97,5 @@ From this page, you can create, edit, disable, mute, and delete alerts, and crea See {kibana-ref}/alerting-getting-started.html[alerting and actions] for more information. NOTE: If you are using an **on-premise** Elastic Stack deployment with security, -TLS must be configured for communication between Elasticsearch and Kibana. +communication between Elasticsearch and Kibana must have TLS configured. More information is in the alerting {kibana-ref}/alerting-getting-started.html#alerting-setup-prerequisites[prerequisites]. \ No newline at end of file diff --git a/docs/apm/bottlenecks.asciidoc b/docs/apm/bottlenecks.asciidoc deleted file mode 100644 index fbde3e9ddcbd6..0000000000000 --- a/docs/apm/bottlenecks.asciidoc +++ /dev/null @@ -1,25 +0,0 @@ -[role="xpack"] -[[apm-bottlenecks]] -== Visualizing Application Bottlenecks - -Elastic APM captures different types of information from within instrumented applications: - -* {apm-overview-ref-v}/transaction-spans.html[*Spans*] contain information about a specific code path that has been executed. -They measure from the start to end of an activity, -and they can have a parent/child relationship with other spans. -* {apm-overview-ref-v}/transactions.html[*Transactions*] are a special kind of span that have extra metadata associated with them. -You can think of transactions as the highest level of work you’re measuring within a service. -As an example, a transaction could be a request to your server, a batch job, or a custom transaction type. -* {apm-overview-ref-v}/errors.html[*Errors*] contain information about the original exception that occurred or about a log created when the exception occurred. - -Each of these information types have a specific page associated with them in the APM app. -These various pages display the captured data in curated charts and tables that allow you to easily compare and debug your applications. - -For example, you can see information about response times, requests per minute, and status codes per endpoint. -You can even dive into a specific request sample and get a complete waterfall view of what your application is spending its time on. -You might see that your bottlenecks are in database queries, cache calls, or external requests. -For each incoming request and each application error, -you can also see contextual information such as the request header, user information, -system values, or custom data that you manually attached to the request. - -Having access to application-level insights with just a few clicks can drastically decrease the time you spend debugging errors, slow response times, and crashes. diff --git a/docs/apm/custom-links.asciidoc b/docs/apm/custom-links.asciidoc index 75c1c9d0009a2..4fdf39b643f94 100644 --- a/docs/apm/custom-links.asciidoc +++ b/docs/apm/custom-links.asciidoc @@ -1,6 +1,11 @@ +[role="xpack"] [[custom-links]] === Custom links +++++ +Create custom links +++++ + Elastic's custom link feature allows you to easily create up to 500 dynamic links based on your specific APM data. Custom links can be filtered to only appear in the APM app for relevant services, @@ -12,7 +17,7 @@ Ready to dive in? Jump straight to the <>. [[custom-links-create]] === Create a link -Each custom link consists of a label, url, and optional filter. +Each custom link consists of a label, URL, and optional filter. The easiest way to create a custom link is from within the actions dropdown in the transaction detail page. This method will automatically apply filters, scoping the link to that specific service, environment, transaction type, and transaction name. @@ -25,8 +30,7 @@ and selecting **Create custom link**. ==== Label The name of your custom link. -This text will be shown in the actions context menu, -so keep it as short as possible. +The actions context menu displays this text, so keep it as short as possible. TIP: Custom links are displayed alphabetically in the actions menu. @@ -39,8 +43,8 @@ URLs support dynamic field name variables, encapsulated in double curly brackets These variables will be replaced with transaction metadata when the link is clicked. Because everyone's data is different, -you'll need to examine your own traces to see what metadata is available for use. -The easiest way to do this is to select a trace in the APM app, and click **Metadata** in the **Trace Sample** table. +you'll need to examine your traces to see what metadata is available for use. +To do this, select a trace in the APM app, and click **Metadata** in the **Trace Sample** table. [role="screenshot"] image::apm/images/example-metadata.png[Example metadata] @@ -49,7 +53,7 @@ image::apm/images/example-metadata.png[Example metadata] [[custom-links-filters]] ==== Filters -Filter each link to only appear so it only appears for specific services or transactions. +Filter each link to only appear for specific services or transactions. You can filter on the following fields: * `service.name` @@ -57,7 +61,7 @@ You can filter on the following fields: * `transaction.type` * `transaction.name` -Multiple values are allowed when comma separated. +Multiple values are allowed when comma-separated. [float] [[custom-links-examples]] @@ -68,7 +72,7 @@ Multiple values are allowed when comma separated. :github-query-params: https://help.github.com/en/github/managing-your-work-on-github/about-automation-for-issues-and-pull-requests-with-query-parameters Not sure where to start with custom links? -Take a look at the examples below, and customize them to your liking! +Take a look at the examples below and customize them to your liking! [float] [[custom-links-examples-email]] diff --git a/docs/apm/deployment-annotations.asciidoc b/docs/apm/deployment-annotations.asciidoc new file mode 100644 index 0000000000000..6feadf8463226 --- /dev/null +++ b/docs/apm/deployment-annotations.asciidoc @@ -0,0 +1,17 @@ +[role="xpack"] +[[transactions-annotations]] +=== Track deployments with annotations + +++++ +Track deployments +++++ + +For enhanced visibility into your deployments, we offer deployment annotations on all transaction charts. +This feature automatically tags new deployments, so you can easily see if your deploy has increased response times +for an end-user, or if the memory/CPU footprint of your application has changed. +Being able to identify bad deployments quickly enables you to rollback and fix issues without causing costly outages. + +Deployment annotations are automatically enabled, and appear when the `service.version` of your app changes. + +[role="screenshot"] +image::apm/images/apm-transaction-annotation.png[Example view of transactions annotation in the APM app in Kibana] diff --git a/docs/apm/error-reports-watcher.asciidoc b/docs/apm/error-reports-watcher.asciidoc new file mode 100644 index 0000000000000..f41597932b751 --- /dev/null +++ b/docs/apm/error-reports-watcher.asciidoc @@ -0,0 +1,18 @@ +[role="xpack"] +[[errors-alerts-with-watcher]] +=== Error reports with Watcher + +++++ +Enable error reports +++++ + +You can use the power of the alerting features with Watcher to get reports on error occurrences. +The Watcher assistant, which is available on the errors overview, can help you set up a watch per service. + +Configure the watch with an occurrences threshold, time interval, and the desired actions, such as email or Slack notifications. +With Watcher, your team can set up reports within minutes. + +Watches are managed separately in the dedicated Watcher UI available in Advanced Settings. + +[role="screenshot"] +image::apm/images/apm-errors-watcher-assistant.png[Example view of the Watcher assistant for errors in APM app in Kibana] diff --git a/docs/apm/errors.asciidoc b/docs/apm/errors.asciidoc index 689fa1fffa89e..49351ec255858 100644 --- a/docs/apm/errors.asciidoc +++ b/docs/apm/errors.asciidoc @@ -1,7 +1,8 @@ +[role="xpack"] [[errors]] === Errors overview -TIP: {apm-overview-ref-v}/errors.html[Errors] are defined as groups of exceptions with matching exception or log messages. +TIP: {apm-overview-ref-v}/errors.html[Errors] are groups of exceptions with a similar exception or log message. The *Errors* overview provides a high-level view of the error message and culprit, the number of occurrences, and the most recent occurrence. @@ -20,7 +21,7 @@ image::apm/images/apm-error-group.png[Example view of the error group page in th Here, you'll see the error message, culprit, and the number of occurrences over time. Further down, you'll see the Error occurrence table. -This is where you can see the details of a sampled error within this group. +This table shows the details of a sampled error within this group. The error shown is always the most recent to occur. Each error occurrence features a breakdown of the exception, including the stack trace from when the error occurred, @@ -28,19 +29,4 @@ and additional contextual information to help debug the issue. In some cases, you might also see a Transaction sample ID. This feature allows you to make a connection between the errors and transactions, by linking you to the specific transaction where the error occurred. -This allows you to see the whole trace, including which services the request went through. - -[float] -[[errors-alerts-with-watcher]] -==== Error reports with Watcher - -You can use the power of the alerting features with Watcher to get reports on error occurrences. -The Watcher assistant, which is available on the errors overview, can help you set up a watch per service. - -Configure the watch with an occurrences threshold, time interval, and the desired actions, such as email or Slack notifications. -With Watcher, your team can set up reports within minutes. - -Watches are managed separately in the dedicated Watcher UI available in Advanced Settings. - -[role="screenshot"] -image::apm/images/apm-errors-watcher-assistant.png[Example view of the Watcher assistant for errors in APM app in Kibana] \ No newline at end of file +This allows you to see the whole trace, including which services the request went through. diff --git a/docs/apm/filters.asciidoc b/docs/apm/filters.asciidoc index 99ba827b0198d..d53adb439f0c8 100644 --- a/docs/apm/filters.asciidoc +++ b/docs/apm/filters.asciidoc @@ -1,6 +1,11 @@ +[role="xpack"] [[filters]] === Filters +++++ +Filter data +++++ + APM provides two different ways you can filter your data within the APM App: * <> @@ -42,7 +47,7 @@ It allows you to view only relevant data, and is especially useful for separatin By default, all environments are displayed. If there are no environment options, you'll see "not defined". Service environments are defined when configuring your APM agents. -It's very important to be consistent when naming environments in your agents. +It's vital to be consistent when naming environments in your agents. See the documentation for each agent you're using to learn how to configure service environments: * *Go:* {apm-go-ref}/configuration.html#config-environment[`ELASTIC_APM_ENVIRONMENT`] @@ -62,9 +67,9 @@ but only where they are applicable -- they are typically most useful in their or As an example, if you select a host on the Services overview, then select a transaction group, the host filter will still be applied. -These filters are very useful for quickly and easily removing noise from your data. +These filters are very useful for quickly and easily removing noise from your data. With just a click, you can filter your transactions by the transaction result, -host, container ID, and more. +host, container ID, and more. [role="screenshot"] image::apm/images/local-filter.png[Local filters available in the APM app in Kibana] \ No newline at end of file diff --git a/docs/apm/getting-started.asciidoc b/docs/apm/getting-started.asciidoc index 4a391f1a49672..89ce0be1499c5 100644 --- a/docs/apm/getting-started.asciidoc +++ b/docs/apm/getting-started.asciidoc @@ -1,22 +1,45 @@ [role="xpack"] [[apm-getting-started]] -== Getting Started +== Get started with the APM app -If you have not already installed and configured Elastic APM, -the *Setup Instructions* will get you started. +++++ +Get started +++++ -[role="screenshot"] -image::apm/images/apm-setup.png[Installation instructions on the APM page in Kibana] +Elastic APM captures different types of information from within instrumented applications: +* *Spans* contain information about the execution of a specific code path. +They measure from the start to end of an activity, +and they can have a parent/child relationship with other spans. +* *Transactions* are a special kind of span; +they are the first span for a particular service and have extra metadata associated with them. +As an example, a transaction could be a request to your server, a batch job, or a custom transaction type. +*Traces* link together related transactions to show an end-to-end performance of how a request was served and which services were part of it. +* *Errors* contain information about the original exception that occurred or about a log created when the exception occurred. -Index patterns tell Kibana which Elasticsearch indices you want to explore. -An APM index pattern is necessary for certain features in the APM app, like the query bar. -To set up the correct index pattern, -simply click *Load Kibana objects* at the bottom of the Setup Instructions. +Curated charts and tables display the different types of APM data, which allows you to compare and debug your applications easily. -After you install an Elastic APM agent library in your application, -the application automatically appears in the APM app in {kib}. -No further configuration is required. +* <> +* <> +* <> +* <> +* <> +* <> +* <> -[role="screenshot"] -image::apm/images/apm-index-pattern.png[Setup index pattern for APM in Kibana] +TIP: Want to learn more about the Elastic APM ecosystem? +See the {apm-get-started-ref}/overview.html[APM Overview]. + +include::services.asciidoc[] + +include::traces.asciidoc[] + +include::transactions.asciidoc[] + +include::spans.asciidoc[] + +include::errors.asciidoc[] + +include::metrics.asciidoc[] + +include::service-maps.asciidoc[] diff --git a/docs/apm/how-to-guides.asciidoc b/docs/apm/how-to-guides.asciidoc new file mode 100644 index 0000000000000..9b0efb4f7a359 --- /dev/null +++ b/docs/apm/how-to-guides.asciidoc @@ -0,0 +1,32 @@ +[role="xpack"] +[[apm-how-to]] +== How-to guides + +Learn how to perform common APM app tasks. + + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + + +include::agent-configuration.asciidoc[] + +include::apm-alerts.asciidoc[] + +include::custom-links.asciidoc[] + +include::error-reports-watcher.asciidoc[] + +include::filters.asciidoc[] + +include::machine-learning.asciidoc[] + +include::advanced-queries.asciidoc[] + +include::deployment-annotations.asciidoc[] \ No newline at end of file diff --git a/docs/apm/images/dynamic-config.svg b/docs/apm/images/dynamic-config.svg new file mode 100644 index 0000000000000..df62a3c84f4b4 --- /dev/null +++ b/docs/apm/images/dynamic-config.svg @@ -0,0 +1 @@ + DynamicDynamic \ No newline at end of file diff --git a/docs/apm/index.asciidoc b/docs/apm/index.asciidoc index d3f0dc5b7f11f..79190efccdff2 100644 --- a/docs/apm/index.asciidoc +++ b/docs/apm/index.asciidoc @@ -4,25 +4,35 @@ [partintro] -- -Elastic Application Performance Monitoring (APM) automatically collects in-depth -performance metrics and errors from inside your applications. - -The **APM** app in {kib} is provided with the basic license. It -enables developers to drill down into the performance data for their applications -and quickly locate the performance bottlenecks. - -* <> -* <> -* <> - -NOTE: For more information about the components of Elastic APM, -see the {apm-get-started-ref}/overview.html[APM Overview]. +The APM app in {kib} is provided with the basic license. +It allows you to monitor your software services and applications in real-time; +visualize detailed performance information on your services, +identify and analyze errors, +and monitor host-level and agent-specific metrics like JVM and Go runtime metrics. + +[float] +[[apm-bottlenecks]] +== Visualizing application bottlenecks + +Having access to application-level insights with just a few clicks can drastically decrease the time you spend +debugging errors, slow response times, and crashes. + +For example, you can see information about response times, requests per minute, and status codes per endpoint. +You can even dive into a specific request sample and get a complete waterfall view of what your application is spending its time on. +You might see that your bottlenecks are in database queries, cache calls, or external requests. +For each incoming request and each application error, +you can also see contextual information such as the request header, user information, +system values, or custom data that you manually attached to the request. -- +include::set-up.asciidoc[] + include::getting-started.asciidoc[] -include::bottlenecks.asciidoc[] +include::how-to-guides.asciidoc[] -include::using-the-apm-ui.asciidoc[] +include::settings.asciidoc[] include::api.asciidoc[] + +include::troubleshooting.asciidoc[] diff --git a/docs/apm/machine-learning.asciidoc b/docs/apm/machine-learning.asciidoc new file mode 100644 index 0000000000000..9d347fc4f1111 --- /dev/null +++ b/docs/apm/machine-learning.asciidoc @@ -0,0 +1,27 @@ +[role="xpack"] +[[machine-learning-integration]] +=== Machine Learning integration + +++++ +Integrate with machine learning +++++ + +The Machine Learning integration will initiate a new job predefined to calculate anomaly scores on transaction response times. +The response time graph will show the expected bounds and add an annotation when the anomaly score is 75 or above. +Jobs can be created per transaction type, and based on the average response time. +Manage jobs in the *Machine Learning jobs management*. + +[role="screenshot"] +image::apm/images/apm-ml-integration.png[Example view of anomaly scores on response times in APM app in Kibana] + +[float] +[[create-ml-integration]] +=== Create a new machine learning job + +To enable machine learning anomaly detection, first choose a service to monitor. +Then, select **Integrations** > **Enable ML anomaly detection** and click **Create job**. +That's it! After a few minutes, the job will begin calculating results; +it might take additional time for results to appear on your graph. + +APM specific anomaly detection wizards are also available for certain Agents. +See the machine learning {ml-docs}/ootb-ml-jobs-apm.html[APM anomaly detection configurations] for more information. diff --git a/docs/apm/metrics.asciidoc b/docs/apm/metrics.asciidoc index ab394b785ef84..e82a4fbd5c291 100644 --- a/docs/apm/metrics.asciidoc +++ b/docs/apm/metrics.asciidoc @@ -1,3 +1,4 @@ +[role="xpack"] [[metrics]] === Metrics overview @@ -5,7 +6,7 @@ The *Metrics* overview provides agent-specific metrics, which lets you perform more in-depth root cause analysis investigations within the APM app. If you're experiencing a problem with your service, you can use this page to attempt to find the underlying cause. -For example, you might be able to correlate a high number of errors with a long transaction duration, high CPU usage, or a memory leak. +For example, you might be able to correlate a high number of errors with a long transaction duration, high CPU usage, or a memory leak. [role="screenshot"] image::apm/images/apm-metrics.png[Example view of the Metrics overview in APM app in Kibana] @@ -17,19 +18,3 @@ thread count, garbage collection rate, and garbage collection time spent per min [role="screenshot"] image::apm/images/jvm-metrics.png[Example view of the Metrics overview for the Java Agent] - -[[machine-learning-integration]] -=== Machine Learning integration - -The Machine Learning integration will initiate a new job predefined to calculate anomaly scores on transaction response times. -The response time graph will show the expected bounds and annotate the graph when the anomaly score is 75 or above. - -[role="screenshot"] -image::apm/images/apm-ml-integration.png[Example view of anomaly scores on response times in APM app in Kibana] - -Jobs can be created per transaction type and based on the average response time. -You can manage jobs in the *Machine Learning jobs management*. -It might take some time for results to appear on the graph. - -Machine learning is a platinum feature. For a comparison of the Elastic license levels, -see https://www.elastic.co/subscriptions[the subscription page]. \ No newline at end of file diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc index e0d84f33b4dcb..be86b9d522ac5 100644 --- a/docs/apm/service-maps.asciidoc +++ b/docs/apm/service-maps.asciidoc @@ -1,37 +1,53 @@ +[role="xpack"] [[service-maps]] === Service maps beta::[] -A service map is a real-time diagram of the interactions occurring in your application’s architecture. -It allows you to easily visualize data flow and high-level statistics, like average transaction duration, -requests per minute, errors per minute, and metrics, allowing you to quickly assess the status of your services. +WARNING: Service map support for Internet Explorer 11 is extremely limited. +Please use Chrome or Firefox if available. -Our beta offering creates two types of service maps: +A service map is a real-time visual representation of the instrumented services in your application's architecture. +It shows you how these services are connected, along with high-level metrics like average transaction duration, +requests per minute, and errors per minute, that allow you to quickly assess the status of your services. -* Global: All services and connections are shown. -* Service-specific: Selecting a specific service will highlight it's connections. +We currently surface two types of service maps: + +* Global: All services instrumented with APM agents and the connections between them are shown. +* Service-specific: Highlight connections for a selected service. [role="screenshot"] image::apm/images/service-maps.png[Example view of service maps in the APM app in Kibana] +[float] +[[service-maps-how]] +=== How do service maps work? + +Service maps rely on distributed traces to draw connections between services. +As {apm-overview-ref-v}/distributed-tracing.html[distributed tracing] is enabled out-of-the-box for supported technologies, so are service maps. +However, if a service isn't instrumented, +or a `traceparent` header isn't being propagated to it, +distributed tracing will not work, and the connection will not be drawn on the map. + [float] [[visualize-your-architecture]] === Visualize your architecture Select the **Service Map** tab to get started. -By default, all services and connections are shown. -Whether your onboarding a new engineer, or just trying to grasp the big picture, +By default, all instrumented services and connections are shown. +Whether you're onboarding a new engineer, or just trying to grasp the big picture, click around, zoom in and out, and begin to visualize how your services are connected. If there's a specific service that interests you, select that service to highlight its connections. Clicking **Focus map** will refocus the map on that specific service and lock the connection highlighting. -From here, select **Service Details**, or click on the **Transaction** tab to jump to the Transaction overview. +From here, select **Service Details**, or click on the **Transaction** tab to jump to the Transaction overview +for the selected service. You can also use the tabs at the top of the page to easily jump to the **Errors** or **Metrics** overview. -While it's not possible to query in service maps, it is possible to filter by environment. +Filter out your maps by picking the environment from the environment drop-down filter. This can be useful if you have two or more services, in separate environments, but with the same name. -Use the environment drop down to only see the data you're interested in, like `dev` or `production`. +Use the environment drop-down to only see the data you're interested in, like `dev` or `production`. +Additional filters are not currently available for service maps. [role="screenshot"] image::apm/images/service-maps-java.png[Example view of service maps with Java highlighted in the APM app in Kibana] @@ -46,3 +62,18 @@ Nodes appear on the map in one of two shapes: * **Diamond**: Databases, external, and messaging. Interior icons represent the generic type, with specific icons for known entities, like Elasticsearch. Type and subtype are based on `span.type`, and `span.subtype`. + +[float] +[[service-maps-supported]] +=== Supported APM Agents + +Service maps are supported for the following Agent versions: + +[horizontal] +Go Agent:: >= v1.7.0 +Java Agent:: >= v1.13.0 +.NET Agent:: >= v1.3.0 +Node.js Agent:: >= v3.6.0 +Python Agent:: >= v5.5.0 +Ruby Agent:: >= v3.6.0 +Real User Monitoring (RUM) Agent:: >= v4.7.0 diff --git a/docs/apm/services.asciidoc b/docs/apm/services.asciidoc index 9af3e74562dab..395e23c379306 100644 --- a/docs/apm/services.asciidoc +++ b/docs/apm/services.asciidoc @@ -1,9 +1,9 @@ +[role="xpack"] [[services]] === Services overview -The *Services* overview gives you quick insights into the health and general performance of each service. - -You can add services by setting the `service.name` configuration in each of the {apm-agents-ref}[APM agents] you’re instrumenting. +The *Services* overview gives you quick insights into the health and general performance of all of your instrumented services. +Services are sorted by the `service.name` configured in each of the {apm-agents-ref}[APM agents] you’ve installed. [role="screenshot"] image::apm/images/apm-services-overview.png[Example view of services table the APM app in Kibana] \ No newline at end of file diff --git a/docs/apm/set-up.asciidoc b/docs/apm/set-up.asciidoc new file mode 100644 index 0000000000000..c5bf5e13b640b --- /dev/null +++ b/docs/apm/set-up.asciidoc @@ -0,0 +1,35 @@ +[role="xpack"] +[[apm-ui]] +== Set up the APM app + +++++ +Set up +++++ + +APM is available via the navigation sidebar in {Kib}. +If you have not already installed and configured Elastic APM, +the *Setup Instructions* in Kibana will get you started. + +[role="screenshot"] +image::apm/images/apm-setup.png[Installation instructions on the APM page in Kibana] + +[float] +[[apm-configure-index-pattern]] +=== Load the index pattern + +Index patterns tell Kibana which Elasticsearch indices you want to explore. +An APM index pattern is necessary for certain features in the APM app, like the query bar. +To set up the correct index pattern, +simply click *Load Kibana objects* at the bottom of the Setup Instructions. + +[role="screenshot"] +image::apm/images/apm-index-pattern.png[Setup index pattern for APM in Kibana] + +To use a custom index pattern, see <>. + +[float] +[[apm-getting-started-next]] +=== Next steps + +No further configuration in the APM app is required. +Install an APM Agent library in your service to begin visualizing and analyzing your data! diff --git a/docs/apm/settings.asciidoc b/docs/apm/settings.asciidoc index 37122fc9c635d..44da63143f63f 100644 --- a/docs/apm/settings.asciidoc +++ b/docs/apm/settings.asciidoc @@ -1,18 +1,23 @@ // Do not link directly to this page. // Link to the anchor in `/docs/settings/apm-settings.asciidoc` instead. +[role="xpack"] [[apm-settings-in-kibana]] -=== APM settings in Kibana +== APM app settings + +++++ +Settings +++++ You do not need to configure any settings to use the APM app. It is enabled by default. [float] [[apm-indices-settings]] -==== APM Indices +=== APM Indices include::./../settings/apm-settings.asciidoc[tag=apm-indices-settings] [float] [[general-apm-settings]] -==== General APM settings +=== General APM settings include::./../settings/apm-settings.asciidoc[tag=general-apm-settings] diff --git a/docs/apm/spans.asciidoc b/docs/apm/spans.asciidoc index ef21e1c5333e0..2eed339160fc4 100644 --- a/docs/apm/spans.asciidoc +++ b/docs/apm/spans.asciidoc @@ -1,7 +1,8 @@ +[role="xpack"] [[spans]] === Span timeline -TIP: A {apm-overview-ref-v}/transaction-spans.html[span] is defined as the duration of a single event. +TIP: A {apm-overview-ref-v}/transaction-spans.html[span] is the duration of a single event. Spans are automatically captured by APM agents, and you can also define custom spans. Each span has a type and is defined by a different color in the timeline/waterfall visualization. @@ -28,7 +29,7 @@ Services in a distributed trace are separated by color and listed in the order t [role="screenshot"] image::apm/images/apm-services-trace.png[Example of distributed trace colors in the APM app in Kibana] -Don't forget, a distributed trace includes more than one transaction. +Don't forget; a distributed trace includes more than one transaction. When viewing these distributed traces in the timeline waterfall, you'll see this image:apm/images/transaction-icon.png[APM icon] icon, which indicates the next transaction in the trace. These transactions can be expanded and viewed in detail by clicking on them. diff --git a/docs/apm/traces.asciidoc b/docs/apm/traces.asciidoc index 09d8f52b92840..8eef3d9bed4db 100644 --- a/docs/apm/traces.asciidoc +++ b/docs/apm/traces.asciidoc @@ -1,12 +1,17 @@ +[role="xpack"] [[traces]] === Traces overview +TIP: Traces link together related transactions to show an end-to-end performance of how a request was served +and which services were part of it. +In addition to the Traces overview, you can view your application traces in the <>. + The *Traces* overview displays the entry transaction for all traces in your application. If you're using <>, this view is key to finding the critical paths within your application. Transactions with the same name are grouped together and only shown once in this table. By default, transactions are sorted by _Impact_. -Impact helps show the most used and slowest endpoints in your service - in other words, +Impact helps show the most used and slowest endpoints in your service--in other words, it's the collective amount of pain a specific endpoint is causing your users. If there's a particular endpoint you're worried about, you can click on it to view the <>. @@ -33,4 +38,4 @@ You can use the <> to view a waterfall displa [role="screenshot"] image::apm/images/apm-distributed-tracing.png[Example view of the distributed tracing in APM app in Kibana] -TIP: Distributed tracing is supported by all APM agents and there’s no additional configuration needed. \ No newline at end of file +TIP: Distributed tracing is supported by all APM agents, and there's no additional configuration needed. \ No newline at end of file diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index 1eb037009efff..2e1022e6d684c 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -1,3 +1,4 @@ +[role="xpack"] [[transactions]] === Transaction overview @@ -56,20 +57,6 @@ For further details, including troubleshooting and custom implementation instruc refer to the documentation for each {apm-agents-ref}[APM Agent] you've implemented. ==== -[[transactions-annotations]] -==== Transaction annotations - -For enhanced visibility into your deployments, we offer deployment annotations on all transaction charts. -This feature automatically tags new deployments, so you can easily see if your deploy has increased response times -for an end-user, or if the memory/CPU footprint of your application has increased. -Being able to quickly identify bad deployments enables you to rollback and fix issues without causing costly outages. - -Deployment annotations are automatically enabled, and appear when the `service.version` of your app changes. - -[role="screenshot"] -image::apm/images/apm-transaction-annotation.png[Example view of transactions annotation in the APM app in Kibana] - - [[rum-transaction-overview]] ==== RUM Transaction overview @@ -82,7 +69,7 @@ image::apm/images/apm-geo-ui.jpg[average page load duration distribution] This data is available due to the geo-ip and user agent pipelines being enabled by default, which allows for the capture of geo-location and user agent data. These visualizations make it easy for you to visualize performance information about your -end users' experience based on their location. +end-users' experience based on their location. [[transaction-details]] ==== Transaction details @@ -103,7 +90,7 @@ The number of requests per bucket is displayed when hovering over the graph, and [role="screenshot"] image::apm/images/apm-transaction-duration-dist.png[Example view of transactions duration distribution graph] -This graph shows a typical distribution, and indicates most of our requests were served quickly - awesome! +This graph shows a typical distribution, and indicates most of our requests were served quickly--awesome! It's the requests on the right, the ones taking longer than average, that we probably want to focus on. When you select one of these buckets, diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index c6174e1786c78..eb4fb790afd7f 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -1,19 +1,24 @@ -[[troubleshooting]] -=== Troubleshooting common problems +[[troubleshooting]] +== Troubleshoot common problems + +++++ +Troubleshooting +++++ If you have something to add to this section, please consider creating a pull request with your proposed changes at https://github.com/elastic/kibana. -Also check out the https://discuss.elastic.co/c/apm[APM discussion forum]. +Also, check out the https://discuss.elastic.co/c/apm[APM discussion forum]. +[float] [[no-apm-data-found]] -==== No APM data found +=== No APM data found This section can help with any of the following: * Data isn't displaying in the APM app -* You're seeing a message like "No Services Found", -* You're seeing errors like "Fielddata is disabled on text fields by default..." +* You see a message like "No Services Found", +* You see errors like "Fielddata is disabled on text fields by default..." There are a number of factors that could be at play here. One important thing to double-check first is your index template. @@ -52,12 +57,13 @@ you can customize the indices that the APM app uses to display data. Navigate to *APM* > *Settings* > *Indices*, and change all `apm_oss.*Pattern` values to include the new index pattern. For example: `customIndexName-*`. -==== Unknown route +[float] +=== Unknown route The {apm-app-ref}/transactions.html[transaction overview] will only display helpful information when the transactions in your services are named correctly. If you're seeing "GET unknown route" or "unknown route" in the APM app, -it could be a sign that something isn't working like it should. +it could be a sign that something isn't working as it should. Elastic APM Agents come with built-in support for popular frameworks out-of-the-box. This means, among other things, that the Agent will try to automatically name HTTP requests. @@ -71,7 +77,8 @@ To resolve this, you'll need to head over to the relevant {apm-agents-ref}[Agent Specifically, view the Agent's supported technologies page. You can also use the Agent's public API to manually set a name for the transaction. -==== Fields are not searchable +[float] +=== Fields are not searchable In Elasticsearch, index templates are used to define settings and mappings that determine how fields should be analyzed. The recommended index template file for APM Server is installed by the APM Server packages. @@ -92,7 +99,7 @@ Selecting the `apm-*` index pattern shows a listing of every field defined in th *Ensure a field is searchable* There are two things you can do to if you'd like to ensure a field is searchable: -1. Index your additional data as {apm-overview-ref}/metadata.html[labels] instead. +1. Index your additional data as {apm-overview-ref-v}/metadata.html[labels] instead. These are dynamic by default, which means they will be indexed and become searchable and aggregatable. 2. Use the {apm-server-ref}/configuration-template.html[`append_fields`] feature. As an example, diff --git a/docs/apm/using-the-apm-ui.asciidoc b/docs/apm/using-the-apm-ui.asciidoc deleted file mode 100644 index 904718999069d..0000000000000 --- a/docs/apm/using-the-apm-ui.asciidoc +++ /dev/null @@ -1,51 +0,0 @@ -[role="xpack"] -[[apm-ui]] -== Using APM - -APM is designed to be as intuitive as possible, -but you might come across certain terms or concepts that don’t feel native to you. -Not to worry, we've created this guide to help you get the most out of Elastic APM. - -APM is available via the navigation sidebar in {Kib}. - -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> - -include::filters.asciidoc[] - -include::services.asciidoc[] - -include::traces.asciidoc[] - -include::transactions.asciidoc[] - -include::spans.asciidoc[] - -include::service-maps.asciidoc[] - -include::errors.asciidoc[] - -include::metrics.asciidoc[] - -include::apm-alerts.asciidoc[] - -include::agent-configuration.asciidoc[] - -include::custom-links.asciidoc[] - -include::advanced-queries.asciidoc[] - -include::settings.asciidoc[] - -include::troubleshooting.asciidoc[] diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index 430590c7a2505..121ae80734dfd 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -29,7 +29,6 @@ export declare class Field implements IFieldType | [format](./kibana-plugin-plugins-data-public.indexpatternfield.format.md) | | any | | | [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | string | | -| [routes](./kibana-plugin-plugins-data-public.indexpatternfield.routes.md) | | Record<string, string> | | | [script](./kibana-plugin-plugins-data-public.indexpatternfield.script.md) | | string | | | [scripted](./kibana-plugin-plugins-data-public.indexpatternfield.scripted.md) | | boolean | | | [searchable](./kibana-plugin-plugins-data-public.indexpatternfield.searchable.md) | | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.routes.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.routes.md deleted file mode 100644 index 664a7b7b7ca0e..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.routes.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [routes](./kibana-plugin-plugins-data-public.indexpatternfield.routes.md) - -## IndexPatternField.routes property - -Signature: - -```typescript -routes: Record; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md index 4b7184b7dc151..478b73f5f8581 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md @@ -24,5 +24,5 @@ export declare class FieldList extends Array implements IFieldList | [getByName](./kibana-plugin-plugins-data-public.indexpatternfieldlist.getbyname.md) | | (name: string) => Field | undefined | | | [getByType](./kibana-plugin-plugins-data-public.indexpatternfieldlist.getbytype.md) | | (type: string) => any[] | | | [remove](./kibana-plugin-plugins-data-public.indexpatternfieldlist.remove.md) | | (field: IFieldType) => void | | -| [update](./kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md) | | (field: Field) => void | | +| [update](./kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md) | | (field: Record<string, any>) => void | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md index ca03ec4b72893..d5156ed41e493 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md @@ -7,5 +7,5 @@ Signature: ```typescript -update: (field: Field) => void; +update: (field: Record) => void; ``` diff --git a/docs/ingest_manager/index.asciidoc b/docs/ingest_manager/index.asciidoc index 1254f412e14c5..22afa88c919e4 100644 --- a/docs/ingest_manager/index.asciidoc +++ b/docs/ingest_manager/index.asciidoc @@ -113,7 +113,7 @@ Ingest Management enforces an indexing strategy to allow the system to automatic {type}-{dataset}-{namespace} ``` -The `{type}` can be `logs` or `metrics`. The `{namespace}` is the part where the user can use free form. The only two requirement are that it has only characters allowed in an Elasticsearch index name and does NOT contain a `-`. The `dataset` is defined by the data that is indexed. The same requirements as for the namespace apply. It is expected that the fields for type, namespace and dataset are part of each event and are constant keywords. +The `{type}` can be `logs` or `metrics`. The `{namespace}` is the part where the user can use free form. The only two requirement are that it has only characters allowed in an Elasticsearch index name and does NOT contain a `-`. The `dataset` is defined by the data that is indexed. The same requirements as for the namespace apply. It is expected that the fields for type, namespace and dataset are part of each event and are constant keywords. If there is a dataset or a namespace with a `-` inside, it is recommended to replace it either by a `.` or a `_`. Note: More `{type}`s might be added in the future like `apm` and `endpoint`. @@ -126,6 +126,8 @@ This indexing strategy has a few advantages: * Having a global metrics and logs template, allows to create new indices on demand which still follow the convention. This is common in the case of k8s as an example. * Constant keywords allow to narrow down the indices we need to access for querying very efficiently. This is especially relevant in environments which a large number of indices or with indices on slower nodes. +Overall it creates smaller indices in size, makes querying more efficient and allows users to define their own naming parts in namespace and still benefiting from all features that can be built on top of the indexing startegy. + === Ingest Pipeline The ingest pipelines for a specific dataset will have the following naming scheme: diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 91bbef5690fd5..fd53c3aeb3605 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -5,7 +5,10 @@ APM settings ++++ -You do not need to configure any settings to use the APM app. It is enabled by default. +These settings allow the APM app to function, and specify the data that it surfaces. +Unless you've customized your setup, +you do not need to configure any settings to use the APM app. +It is enabled by default. [float] [[apm-indices-settings-kb]] @@ -33,29 +36,29 @@ image::settings/images/apm-settings.png[APM app settings in Kibana] If you'd like to change any of the default values, copy and paste the relevant settings into your `kibana.yml` configuration file. +Changing these settings may disable features of the APM App. -xpack.apm.enabled:: Set to `false` to disabled the APM plugin {kib}. Defaults to -`true`. +xpack.apm.enabled:: Set to `false` to disable the APM app. Defaults to `true`. -xpack.apm.ui.enabled:: Set to `false` to hide the APM plugin {kib} from the menu. Defaults to -`true`. +xpack.apm.ui.enabled:: Set to `false` to hide the APM app from the menu. Defaults to `true`. -xpack.apm.ui.transactionGroupBucketSize:: Number of top transaction groups displayed in APM plugin in Kibana. Defaults to `100`. +xpack.apm.ui.transactionGroupBucketSize:: Number of top transaction groups displayed in the APM app. Defaults to `100`. -xpack.apm.ui.maxTraceItems:: Max number of child items displayed when viewing trace details. Defaults to `1000`. +xpack.apm.ui.maxTraceItems:: Maximum number of child items displayed when viewing trace details. Defaults to `1000`. -apm_oss.indexPattern:: Index pattern is used for integrations with Machine Learning and Kuery Bar. It must match all apm indices. Defaults to `apm-*`. +apm_oss.indexPattern:: The index pattern used for integrations with Machine Learning and Query Bar. +It must match all apm indices. Defaults to `apm-*`. -apm_oss.errorIndices:: Matcher for indices containing error documents. Defaults to `apm-*`. +apm_oss.errorIndices:: Matcher for all {apm-server-ref}/error-indices.html[error indices]. Defaults to `apm-*`. -apm_oss.onboardingIndices:: Matcher for indices containing onboarding documents. Defaults to `apm-*`. +apm_oss.onboardingIndices:: Matcher for all onboarding indices. Defaults to `apm-*`. -apm_oss.spanIndices:: Matcher for indices containing span documents. Defaults to `apm-*`. +apm_oss.spanIndices:: Matcher for all {apm-server-ref}/span-indices.html[span indices]. Defaults to `apm-*`. -apm_oss.transactionIndices:: Matcher for indices containing transaction documents. Defaults to `apm-*`. +apm_oss.transactionIndices:: Matcher for all {apm-server-ref}/transaction-indices.html[transaction indices]. Defaults to `apm-*`. -apm_oss.metricsIndices:: Matcher for indices containing metric documents. Defaults to `apm-*`. +apm_oss.metricsIndices:: Matcher for all {apm-server-ref}/metricset-indices.html[metrics indices]. Defaults to `apm-*`. -apm_oss.sourcemapIndices:: Matcher for indices containing sourcemap documents. Defaults to `apm-*`. +apm_oss.sourcemapIndices:: Matcher for all {apm-server-ref}/sourcemap-indices.html[source map indices]. Defaults to `apm-*`. // end::general-apm-settings[] diff --git a/docs/user/alerting/index.asciidoc b/docs/user/alerting/index.asciidoc index f556cf71bf06c..df11f5f03a7de 100644 --- a/docs/user/alerting/index.asciidoc +++ b/docs/user/alerting/index.asciidoc @@ -154,10 +154,13 @@ Pre-packaged *alert types* simplify setup, hide the details complex domain-speci [[alerting-setup-prerequisites]] == Setup and prerequisites +If you are using an *on-premises* Elastic Stack deployment: + +* In the kibana.yml configuration file, add the <> setting. + If you are using an *on-premises* Elastic Stack deployment with <>: -* TLS must be configured for communication <>. {kib} alerting uses <> to secure background alert checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. -* In the kibana.yml configuration file, add the <> +* Transport Layer Security (TLS) must be configured for communication <>. {kib} alerting uses <> to secure background alert checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. [float] [[alerting-security]] diff --git a/examples/embeddable_examples/common/index.ts b/examples/embeddable_examples/common/index.ts new file mode 100644 index 0000000000000..726420fb9bdc3 --- /dev/null +++ b/examples/embeddable_examples/common/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 { TodoSavedObjectAttributes } from './todo_saved_object_attributes'; diff --git a/test/functional/config.edge.js b/examples/embeddable_examples/common/todo_saved_object_attributes.ts similarity index 73% rename from test/functional/config.edge.js rename to examples/embeddable_examples/common/todo_saved_object_attributes.ts index ed68b41e8c89a..21b6df20fea90 100644 --- a/test/functional/config.edge.js +++ b/examples/embeddable_examples/common/todo_saved_object_attributes.ts @@ -17,18 +17,10 @@ * under the License. */ -export default async function({ readConfigFile }) { - const defaultConfig = await readConfigFile(require.resolve('./config')); +import { SavedObjectAttributes } from '../../../src/core/types'; - return { - ...defaultConfig.getAll(), - - browser: { - type: 'msedge', - }, - - junit: { - reportName: 'MS Chromium Edge UI Functional Tests', - }, - }; +export interface TodoSavedObjectAttributes extends SavedObjectAttributes { + task: string; + icon?: string; + title?: string; } diff --git a/examples/embeddable_examples/kibana.json b/examples/embeddable_examples/kibana.json index c70bc7009ff51..f446e7f31ac8e 100644 --- a/examples/embeddable_examples/kibana.json +++ b/examples/embeddable_examples/kibana.json @@ -3,7 +3,7 @@ "version": "0.0.1", "kibanaVersion": "kibana", "configPath": ["embeddable_examples"], - "server": false, + "server": true, "ui": true, "requiredPlugins": ["embeddable"], "optionalPlugins": [] diff --git a/examples/embeddable_examples/public/create_sample_data.ts b/examples/embeddable_examples/public/create_sample_data.ts new file mode 100644 index 0000000000000..bd5ade18aa91e --- /dev/null +++ b/examples/embeddable_examples/public/create_sample_data.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectsClientContract } from 'kibana/public'; +import { TodoSavedObjectAttributes } from '../common'; + +export async function createSampleData(client: SavedObjectsClientContract) { + await client.create( + 'todo', + { + task: 'Take the garbage out', + title: 'Garbage', + icon: 'trash', + }, + { + id: 'sample-todo-saved-object', + overwrite: true, + } + ); +} diff --git a/examples/embeddable_examples/public/index.ts b/examples/embeddable_examples/public/index.ts index 5fcd454b17a5c..4aac63fb52e2b 100644 --- a/examples/embeddable_examples/public/index.ts +++ b/examples/embeddable_examples/public/index.ts @@ -17,7 +17,6 @@ * under the License. */ -import { PluginInitializer } from 'kibana/public'; export { HELLO_WORLD_EMBEDDABLE, HelloWorldEmbeddable, @@ -26,18 +25,8 @@ export { export { ListContainer, LIST_CONTAINER } from './list_container'; export { TODO_EMBEDDABLE } from './todo'; -import { - EmbeddableExamplesPlugin, - EmbeddableExamplesSetupDependencies, - EmbeddableExamplesStartDependencies, -} from './plugin'; +import { EmbeddableExamplesPlugin } from './plugin'; export { SearchableListContainer, SEARCHABLE_LIST_CONTAINER } from './searchable_list_container'; export { MULTI_TASK_TODO_EMBEDDABLE } from './multi_task_todo'; - -export const plugin: PluginInitializer< - void, - void, - EmbeddableExamplesSetupDependencies, - EmbeddableExamplesStartDependencies -> = () => new EmbeddableExamplesPlugin(); +export const plugin = () => new EmbeddableExamplesPlugin(); diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts index 31a3037332dda..75d34d2d6878f 100644 --- a/examples/embeddable_examples/public/plugin.ts +++ b/examples/embeddable_examples/public/plugin.ts @@ -21,12 +21,20 @@ import { EmbeddableSetup, EmbeddableStart } from '../../../src/plugins/embeddabl import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public'; import { HelloWorldEmbeddableFactory, HELLO_WORLD_EMBEDDABLE } from './hello_world'; import { TODO_EMBEDDABLE, TodoEmbeddableFactory, TodoInput, TodoOutput } from './todo'; -import { MULTI_TASK_TODO_EMBEDDABLE, MultiTaskTodoEmbeddableFactory } from './multi_task_todo'; +import { + MULTI_TASK_TODO_EMBEDDABLE, + MultiTaskTodoEmbeddableFactory, + MultiTaskTodoInput, + MultiTaskTodoOutput, +} from './multi_task_todo'; import { SEARCHABLE_LIST_CONTAINER, SearchableListContainerFactory, } from './searchable_list_container'; import { LIST_CONTAINER, ListContainerFactory } from './list_container'; +import { createSampleData } from './create_sample_data'; +import { TodoRefInput, TodoRefOutput, TODO_REF_EMBEDDABLE } from './todo/todo_ref_embeddable'; +import { TodoRefEmbeddableFactory } from './todo/todo_ref_embeddable_factory'; export interface EmbeddableExamplesSetupDependencies { embeddable: EmbeddableSetup; @@ -36,9 +44,18 @@ export interface EmbeddableExamplesStartDependencies { embeddable: EmbeddableStart; } +export interface EmbeddableExamplesStart { + createSampleData: () => Promise; +} + export class EmbeddableExamplesPlugin implements - Plugin { + Plugin< + void, + EmbeddableExamplesStart, + EmbeddableExamplesSetupDependencies, + EmbeddableExamplesStartDependencies + > { public setup( core: CoreSetup, deps: EmbeddableExamplesSetupDependencies @@ -48,7 +65,7 @@ export class EmbeddableExamplesPlugin new HelloWorldEmbeddableFactory() ); - deps.embeddable.registerEmbeddableFactory( + deps.embeddable.registerEmbeddableFactory( MULTI_TASK_TODO_EMBEDDABLE, new MultiTaskTodoEmbeddableFactory() ); @@ -73,9 +90,21 @@ export class EmbeddableExamplesPlugin openModal: (await core.getStartServices())[0].overlays.openModal, })) ); + + deps.embeddable.registerEmbeddableFactory( + TODO_REF_EMBEDDABLE, + new TodoRefEmbeddableFactory(async () => ({ + savedObjectsClient: (await core.getStartServices())[0].savedObjects.client, + getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory, + })) + ); } - public start(core: CoreStart, deps: EmbeddableExamplesStartDependencies) {} + public start(core: CoreStart, deps: EmbeddableExamplesStartDependencies) { + return { + createSampleData: () => createSampleData(core.savedObjects.client), + }; + } public stop() {} } diff --git a/examples/embeddable_examples/public/todo/README.md b/examples/embeddable_examples/public/todo/README.md new file mode 100644 index 0000000000000..e782511f093b3 --- /dev/null +++ b/examples/embeddable_examples/public/todo/README.md @@ -0,0 +1,43 @@ +There are two examples in here: + - TodoEmbeddable + - TodoRefEmbeddable + + # TodoEmbeddable + + The first example you should review is the HelloWorldEmbeddable. That is as basic an embeddable as you can get. + This embeddable is the next step up - an embeddable that renders dynamic input data. The data is simple: + - a required task string + - an optional title + - an optional icon string + - an optional search string + +It also has output data, which is `hasMatch` - whether or not the search string has matched any input data. + +`hasMatch` is a better fit for output data than input data, because it's state that is _derived_ from input data. + +For example, if it was input data, you could create a TodoEmbeddable with input like this: + +```ts + todoEmbeddableFactory.create({ task: 'take out the garabage', search: 'garbage', hasMatch: false }); +``` + +That's wrong because there is actually a match from the search string inside the task. + +The TodoEmbeddable component itself doesn't do anything with the `hasMatch` variable other than set it, but +if you check out `SearchableListContainer`, you can see an example where this output data is being used. + +## TodoRefEmbeddable + +This is an example of an embeddable based off of a saved object. The input is just the `savedObjectId` and +the `search` string. It has even more output parameters, and this time, it does read it's own output parameters in +order to calculate `hasMatch`. + +Output: +```ts +{ + hasMatch: boolean, + savedAttributes?: TodoSavedAttributes +} +``` + +`savedAttributes` is optional because it's possible a TodoSavedObject could not be found with the given savedObjectId. diff --git a/examples/embeddable_examples/public/todo/todo_ref_component.tsx b/examples/embeddable_examples/public/todo/todo_ref_component.tsx new file mode 100644 index 0000000000000..8e0a17be1ec72 --- /dev/null +++ b/examples/embeddable_examples/public/todo/todo_ref_component.tsx @@ -0,0 +1,86 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; + +import { EuiText } from '@elastic/eui'; +import { EuiAvatar } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; +import { EuiFlexGrid } from '@elastic/eui'; +import { withEmbeddableSubscription } from '../../../../src/plugins/embeddable/public'; +import { TodoRefInput, TodoRefOutput, TodoRefEmbeddable } from './todo_ref_embeddable'; + +interface Props { + embeddable: TodoRefEmbeddable; + input: TodoRefInput; + output: TodoRefOutput; +} + +function wrapSearchTerms(task?: string, search?: string) { + if (!search) return task; + if (!task) return task; + const parts = task.split(new RegExp(`(${search})`, 'g')); + return parts.map((part, i) => + part === search ? ( + + {part} + + ) : ( + part + ) + ); +} + +export function TodoRefEmbeddableComponentInner({ + input: { search }, + output: { savedAttributes }, +}: Props) { + const icon = savedAttributes?.icon; + const title = savedAttributes?.title; + const task = savedAttributes?.task; + return ( + + + {icon ? ( + + ) : ( + + )} + + + + + +

{wrapSearchTerms(title || '', search)}

+
+
+ + {wrapSearchTerms(task, search)} + +
+
+
+ ); +} + +export const TodoRefEmbeddableComponent = withEmbeddableSubscription< + TodoRefInput, + TodoRefOutput, + TodoRefEmbeddable +>(TodoRefEmbeddableComponentInner); diff --git a/examples/embeddable_examples/public/todo/todo_ref_embeddable.tsx b/examples/embeddable_examples/public/todo/todo_ref_embeddable.tsx new file mode 100644 index 0000000000000..da2dfb2c1a290 --- /dev/null +++ b/examples/embeddable_examples/public/todo/todo_ref_embeddable.tsx @@ -0,0 +1,153 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Subscription } from 'rxjs'; +import { TodoSavedObjectAttributes } from 'examples/embeddable_examples/common'; +import { SavedObjectsClientContract } from 'kibana/public'; +import { + Embeddable, + IContainer, + EmbeddableOutput, + SavedObjectEmbeddableInput, +} from '../../../../src/plugins/embeddable/public'; +import { TodoRefEmbeddableComponent } from './todo_ref_component'; + +// Notice this is not the same value as the 'todo' saved object type. Many of our +// cases in prod today use the same value, but this is unnecessary. +export const TODO_REF_EMBEDDABLE = 'TODO_REF_EMBEDDABLE'; + +export interface TodoRefInput extends SavedObjectEmbeddableInput { + /** + * Optional search string which will be used to highlight search terms as + * well as calculate `output.hasMatch`. + */ + search?: string; +} + +export interface TodoRefOutput extends EmbeddableOutput { + /** + * Should be true if input.search is defined and the task or title contain + * search as a substring. + */ + hasMatch: boolean; + /** + * Will contain the saved object attributes of the Todo Saved Object that matches + * `input.savedObjectId`. If the id is invalid, this may be undefined. + */ + savedAttributes?: TodoSavedObjectAttributes; +} + +/** + * Returns whether any attributes contain the search string. If search is empty, true is returned. If + * there are no savedAttributes, false is returned. + * @param search - the search string + * @param savedAttributes - the saved object attributes for the saved object with id `input.savedObjectId` + */ +function getHasMatch(search?: string, savedAttributes?: TodoSavedObjectAttributes): boolean { + if (!search) return true; + if (!savedAttributes) return false; + return Boolean( + (savedAttributes.task && savedAttributes.task.match(search)) || + (savedAttributes.title && savedAttributes.title.match(search)) + ); +} + +/** + * This is an example of an embeddable that is backed by a saved object. It's essentially the + * same as `TodoEmbeddable` but that is "by value", while this is "by reference". + */ +export class TodoRefEmbeddable extends Embeddable { + public readonly type = TODO_REF_EMBEDDABLE; + private subscription: Subscription; + private node?: HTMLElement; + private savedObjectsClient: SavedObjectsClientContract; + private savedObjectId?: string; + + constructor( + initialInput: TodoRefInput, + { + parent, + savedObjectsClient, + }: { + parent?: IContainer; + savedObjectsClient: SavedObjectsClientContract; + } + ) { + super(initialInput, { hasMatch: false }, parent); + this.savedObjectsClient = savedObjectsClient; + + this.subscription = this.getInput$().subscribe(async () => { + // There is a little more work today for this embeddable because it has + // more output it needs to update in response to input state changes. + let savedAttributes: TodoSavedObjectAttributes | undefined; + + // Since this is an expensive task, we save a local copy of the previous + // savedObjectId locally and only retrieve the new saved object if the id + // actually changed. + if (this.savedObjectId !== this.input.savedObjectId) { + this.savedObjectId = this.input.savedObjectId; + const todoSavedObject = await this.savedObjectsClient.get( + 'todo', + this.input.savedObjectId + ); + savedAttributes = todoSavedObject?.attributes; + } + + // The search string might have changed as well so we need to make sure we recalculate + // hasMatch. + this.updateOutput({ + hasMatch: getHasMatch(this.input.search, savedAttributes), + savedAttributes, + }); + }); + } + + public render(node: HTMLElement) { + if (this.node) { + ReactDOM.unmountComponentAtNode(this.node); + } + this.node = node; + ReactDOM.render(, node); + } + + /** + * Lets re-sync our saved object to make sure it's up to date! + */ + public async reload() { + this.savedObjectId = this.input.savedObjectId; + const todoSavedObject = await this.savedObjectsClient.get( + 'todo', + this.input.savedObjectId + ); + const savedAttributes = todoSavedObject?.attributes; + this.updateOutput({ + hasMatch: getHasMatch(this.input.search, savedAttributes), + savedAttributes, + }); + } + + public destroy() { + super.destroy(); + this.subscription.unsubscribe(); + if (this.node) { + ReactDOM.unmountComponentAtNode(this.node); + } + } +} diff --git a/examples/embeddable_examples/public/todo/todo_ref_embeddable_factory.tsx b/examples/embeddable_examples/public/todo/todo_ref_embeddable_factory.tsx new file mode 100644 index 0000000000000..e585ddd89674f --- /dev/null +++ b/examples/embeddable_examples/public/todo/todo_ref_embeddable_factory.tsx @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { i18n } from '@kbn/i18n'; +import { SavedObjectsClientContract } from 'kibana/public'; +import { TodoSavedObjectAttributes } from 'examples/embeddable_examples/common'; +import { + IContainer, + EmbeddableStart, + ErrorEmbeddable, + EmbeddableFactoryDefinition, +} from '../../../../src/plugins/embeddable/public'; +import { + TodoRefEmbeddable, + TODO_REF_EMBEDDABLE, + TodoRefInput, + TodoRefOutput, +} from './todo_ref_embeddable'; + +interface StartServices { + getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + savedObjectsClient: SavedObjectsClientContract; +} + +export class TodoRefEmbeddableFactory + implements + EmbeddableFactoryDefinition< + TodoRefInput, + TodoRefOutput, + TodoRefEmbeddable, + TodoSavedObjectAttributes + > { + public readonly type = TODO_REF_EMBEDDABLE; + public readonly savedObjectMetaData = { + name: 'Todo', + includeFields: ['task', 'icon', 'title'], + type: 'todo', + getIconForSavedObject: () => 'pencil', + }; + + constructor(private getStartServices: () => Promise) {} + + public async isEditable() { + return true; + } + + public createFromSavedObject = ( + savedObjectId: string, + input: Partial & { id: string }, + parent?: IContainer + ): Promise => { + return this.create({ ...input, savedObjectId }, parent); + }; + + public async create(input: TodoRefInput, parent?: IContainer) { + const { savedObjectsClient } = await this.getStartServices(); + return new TodoRefEmbeddable(input, { + parent, + savedObjectsClient, + }); + } + + public getDisplayName() { + return i18n.translate('embeddableExamples.todo.displayName', { + defaultMessage: 'Todo (by reference)', + }); + } +} diff --git a/examples/embeddable_examples/server/index.ts b/examples/embeddable_examples/server/index.ts new file mode 100644 index 0000000000000..9ddc3bc2cf715 --- /dev/null +++ b/examples/embeddable_examples/server/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializer } from 'kibana/server'; + +import { EmbeddableExamplesPlugin } from './plugin'; + +export const plugin: PluginInitializer = () => new EmbeddableExamplesPlugin(); diff --git a/examples/embeddable_examples/server/plugin.ts b/examples/embeddable_examples/server/plugin.ts new file mode 100644 index 0000000000000..d956b834d0d3c --- /dev/null +++ b/examples/embeddable_examples/server/plugin.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 { Plugin, CoreSetup, CoreStart } from 'kibana/server'; +import { todoSavedObject } from './todo_saved_object'; + +export class EmbeddableExamplesPlugin implements Plugin { + public setup(core: CoreSetup) { + core.savedObjects.registerType(todoSavedObject); + } + + public start(core: CoreStart) {} + + public stop() {} +} diff --git a/examples/embeddable_examples/server/todo_saved_object.ts b/examples/embeddable_examples/server/todo_saved_object.ts new file mode 100644 index 0000000000000..0f67c53cfa3e1 --- /dev/null +++ b/examples/embeddable_examples/server/todo_saved_object.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectsType } from 'kibana/server'; + +export const todoSavedObject: SavedObjectsType = { + name: 'todo', + hidden: false, + namespaceAgnostic: true, + mappings: { + properties: { + title: { + type: 'keyword', + }, + task: { + type: 'text', + }, + icon: { + type: 'keyword', + }, + }, + }, + migrations: {}, +}; diff --git a/examples/embeddable_examples/tsconfig.json b/examples/embeddable_examples/tsconfig.json index 091130487791b..7fa03739119b4 100644 --- a/examples/embeddable_examples/tsconfig.json +++ b/examples/embeddable_examples/tsconfig.json @@ -6,6 +6,7 @@ }, "include": [ "index.ts", + "common/**/*.ts", "public/**/*.ts", "public/**/*.tsx", "server/**/*.ts", diff --git a/examples/embeddable_explorer/public/plugin.tsx b/examples/embeddable_explorer/public/plugin.tsx index 7c75b108d9912..bba1b1748e207 100644 --- a/examples/embeddable_explorer/public/plugin.tsx +++ b/examples/embeddable_explorer/public/plugin.tsx @@ -18,6 +18,7 @@ */ import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; +import { EmbeddableExamplesStart } from 'examples/embeddable_examples/public/plugin'; import { UiActionsService } from '../../../src/plugins/ui_actions/public'; import { EmbeddableStart } from '../../../src/plugins/embeddable/public'; import { Start as InspectorStart } from '../../../src/plugins/inspector/public'; @@ -26,6 +27,7 @@ interface StartDeps { uiActions: UiActionsService; embeddable: EmbeddableStart; inspector: InspectorStart; + embeddableExamples: EmbeddableExamplesStart; } export class EmbeddableExplorerPlugin implements Plugin { @@ -36,6 +38,7 @@ export class EmbeddableExplorerPlugin implements Plugin false; @@ -81456,11 +81449,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(719); var taskManager = __webpack_require__(720); -var reader_async_1 = __webpack_require__(871); -var reader_stream_1 = __webpack_require__(895); -var reader_sync_1 = __webpack_require__(896); -var arrayUtils = __webpack_require__(898); -var streamUtils = __webpack_require__(899); +var reader_async_1 = __webpack_require__(876); +var reader_stream_1 = __webpack_require__(900); +var reader_sync_1 = __webpack_require__(901); +var arrayUtils = __webpack_require__(903); +var streamUtils = __webpack_require__(904); /** * Synchronous API. */ @@ -82100,9 +82093,9 @@ var extend = __webpack_require__(837); */ var compilers = __webpack_require__(840); -var parsers = __webpack_require__(867); -var cache = __webpack_require__(868); -var utils = __webpack_require__(869); +var parsers = __webpack_require__(872); +var cache = __webpack_require__(873); +var utils = __webpack_require__(874); var MAX_LENGTH = 1024 * 64; /** @@ -100635,9 +100628,9 @@ var toRegex = __webpack_require__(728); */ var compilers = __webpack_require__(857); -var parsers = __webpack_require__(863); -var Extglob = __webpack_require__(866); -var utils = __webpack_require__(865); +var parsers = __webpack_require__(868); +var Extglob = __webpack_require__(871); +var utils = __webpack_require__(870); var MAX_LENGTH = 1024 * 64; /** @@ -101147,7 +101140,7 @@ var parsers = __webpack_require__(861); * Module dependencies */ -var debug = __webpack_require__(800)('expand-brackets'); +var debug = __webpack_require__(863)('expand-brackets'); var extend = __webpack_require__(737); var Snapdragon = __webpack_require__(767); var toRegex = __webpack_require__(728); @@ -101741,12 +101734,839 @@ exports.createRegex = function(pattern, include) { /* 863 */ /***/ (function(module, exports, __webpack_require__) { +/** + * Detect Electron renderer process, which is node, but we should + * treat as a browser. + */ + +if (typeof process !== 'undefined' && process.type === 'renderer') { + module.exports = __webpack_require__(864); +} else { + module.exports = __webpack_require__(867); +} + + +/***/ }), +/* 864 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(865); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + +/** + * Colors. + */ + +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { + return true; + } + + // is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +exports.formatters.j = function(v) { + try { + return JSON.stringify(v); + } catch (err) { + return '[UnexpectedJSONParseError]: ' + err.message; + } +}; + + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return; + + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit') + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + +function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ + +exports.enable(load()); + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + return window.localStorage; + } catch (e) {} +} + + +/***/ }), +/* 865 */ +/***/ (function(module, exports, __webpack_require__) { + + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = __webpack_require__(866); + +/** + * The currently active debug mode names, and names to skip. + */ + +exports.names = []; +exports.skips = []; + +/** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + +exports.formatters = {}; + +/** + * Previous log timestamp. + */ + +var prevTime; + +/** + * Select a color. + * @param {String} namespace + * @return {Number} + * @api private + */ + +function selectColor(namespace) { + var hash = 0, i; + + for (i in namespace) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return exports.colors[Math.abs(hash) % exports.colors.length]; +} + +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + +function createDebug(namespace) { + + function debug() { + // disabled? + if (!debug.enabled) return; + + var self = debug; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // turn the `arguments` into a proper Array + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %O + args.unshift('%O'); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // apply env-specific formatting (colors, etc.) + exports.formatArgs.call(self, args); + + var logFn = debug.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.enabled = exports.enabled(namespace); + debug.useColors = exports.useColors(); + debug.color = selectColor(namespace); + + // env-specific initialization logic for debug instances + if ('function' === typeof exports.init) { + exports.init(debug); + } + + return debug; +} + +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + +function enable(namespaces) { + exports.save(namespaces); + + exports.names = []; + exports.skips = []; + + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } +} + +/** + * Disable debug output. + * + * @api public + */ + +function disable() { + exports.enable(''); +} + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; +} + +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} + + +/***/ }), +/* 866 */ +/***/ (function(module, exports) { + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + if (ms >= d) { + return Math.round(ms / d) + 'd'; + } + if (ms >= h) { + return Math.round(ms / h) + 'h'; + } + if (ms >= m) { + return Math.round(ms / m) + 'm'; + } + if (ms >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + return plural(ms, d, 'day') || + plural(ms, h, 'hour') || + plural(ms, m, 'minute') || + plural(ms, s, 'second') || + ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) { + return; + } + if (ms < n * 1.5) { + return Math.floor(ms / n) + ' ' + name; + } + return Math.ceil(ms / n) + ' ' + name + 's'; +} + + +/***/ }), +/* 867 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * Module dependencies. + */ + +var tty = __webpack_require__(478); +var util = __webpack_require__(29); + +/** + * This is the Node.js implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(865); +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(function (key) { + return /^debug_/i.test(key); +}).reduce(function (obj, key) { + // camel-case + var prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); + + // coerce string value into JS value + var val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) val = true; + else if (/^(no|off|false|disabled)$/i.test(val)) val = false; + else if (val === 'null') val = null; + else val = Number(val); + + obj[prop] = val; + return obj; +}, {}); + +/** + * The file descriptor to write the `debug()` calls to. + * Set the `DEBUG_FD` env variable to override with another value. i.e.: + * + * $ DEBUG_FD=3 node script.js 3>debug.log + */ + +var fd = parseInt(process.env.DEBUG_FD, 10) || 2; + +if (1 !== fd && 2 !== fd) { + util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() +} + +var stream = 1 === fd ? process.stdout : + 2 === fd ? process.stderr : + createWritableStdioStream(fd); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts + ? Boolean(exports.inspectOpts.colors) + : tty.isatty(fd); +} + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +exports.formatters.o = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n').map(function(str) { + return str.trim() + }).join(' '); +}; + +/** + * Map %o to `util.inspect()`, allowing multiple lines if needed. + */ + +exports.formatters.O = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + var name = this.namespace; + var useColors = this.useColors; + + if (useColors) { + var c = this.color; + var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); + } else { + args[0] = new Date().toUTCString() + + ' ' + name + ' ' + args[0]; + } +} + +/** + * Invokes `util.format()` with the specified arguments and writes to `stream`. + */ + +function log() { + return stream.write(util.format.apply(util, arguments) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + if (null == namespaces) { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } else { + process.env.DEBUG = namespaces; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Copied from `node/src/node.js`. + * + * XXX: It's lame that node doesn't expose this API out-of-the-box. It also + * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. + */ + +function createWritableStdioStream (fd) { + var stream; + var tty_wrap = process.binding('tty_wrap'); + + // Note stream._type is used for test-module-load-list.js + + switch (tty_wrap.guessHandleType(fd)) { + case 'TTY': + stream = new tty.WriteStream(fd); + stream._type = 'tty'; + + // Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + case 'FILE': + var fs = __webpack_require__(23); + stream = new fs.SyncWriteStream(fd, { autoClose: false }); + stream._type = 'fs'; + break; + + case 'PIPE': + case 'TCP': + var net = __webpack_require__(805); + stream = new net.Socket({ + fd: fd, + readable: false, + writable: true + }); + + // FIXME Should probably have an option in net.Socket to create a + // stream from an existing fd which is writable only. But for now + // we'll just add this hack and set the `readable` member to false. + // Test: ./node test/fixtures/echo.js < /etc/passwd + stream.readable = false; + stream.read = null; + stream._type = 'pipe'; + + // FIXME Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + default: + // Probably an error on in uv_guess_handle() + throw new Error('Implement me. Unknown stream file type!'); + } + + // For supporting legacy API we put the FD here. + stream.fd = fd; + + stream._isStdio = true; + + return stream; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init (debug) { + debug.inspectOpts = {}; + + var keys = Object.keys(exports.inspectOpts); + for (var i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +/** + * Enable namespaces listed in `process.env.DEBUG` initially. + */ + +exports.enable(load()); + + +/***/ }), +/* 868 */ +/***/ (function(module, exports, __webpack_require__) { + "use strict"; var brackets = __webpack_require__(858); -var define = __webpack_require__(864); -var utils = __webpack_require__(865); +var define = __webpack_require__(869); +var utils = __webpack_require__(870); /** * Characters to use in text regex (we want to "not" match @@ -101901,7 +102721,7 @@ module.exports = parsers; /***/ }), -/* 864 */ +/* 869 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101939,7 +102759,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 865 */ +/* 870 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102015,7 +102835,7 @@ utils.createRegex = function(str) { /***/ }), -/* 866 */ +/* 871 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102026,7 +102846,7 @@ utils.createRegex = function(str) { */ var Snapdragon = __webpack_require__(767); -var define = __webpack_require__(864); +var define = __webpack_require__(869); var extend = __webpack_require__(737); /** @@ -102034,7 +102854,7 @@ var extend = __webpack_require__(737); */ var compilers = __webpack_require__(857); -var parsers = __webpack_require__(863); +var parsers = __webpack_require__(868); /** * Customize Snapdragon parser and renderer @@ -102100,7 +102920,7 @@ module.exports = Extglob; /***/ }), -/* 867 */ +/* 872 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102190,14 +103010,14 @@ function textRegex(pattern) { /***/ }), -/* 868 */ +/* 873 */ /***/ (function(module, exports, __webpack_require__) { module.exports = new (__webpack_require__(849))(); /***/ }), -/* 869 */ +/* 874 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102215,7 +103035,7 @@ utils.define = __webpack_require__(836); utils.diff = __webpack_require__(853); utils.extend = __webpack_require__(837); utils.pick = __webpack_require__(854); -utils.typeOf = __webpack_require__(870); +utils.typeOf = __webpack_require__(875); utils.unique = __webpack_require__(740); /** @@ -102513,7 +103333,7 @@ utils.unixify = function(options) { /***/ }), -/* 870 */ +/* 875 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -102648,7 +103468,7 @@ function isBuffer(val) { /***/ }), -/* 871 */ +/* 876 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102667,9 +103487,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(872); -var reader_1 = __webpack_require__(885); -var fs_stream_1 = __webpack_require__(889); +var readdir = __webpack_require__(877); +var reader_1 = __webpack_require__(890); +var fs_stream_1 = __webpack_require__(894); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -102730,15 +103550,15 @@ exports.default = ReaderAsync; /***/ }), -/* 872 */ +/* 877 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(873); -const readdirAsync = __webpack_require__(881); -const readdirStream = __webpack_require__(884); +const readdirSync = __webpack_require__(878); +const readdirAsync = __webpack_require__(886); +const readdirStream = __webpack_require__(889); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -102822,7 +103642,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 873 */ +/* 878 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102830,11 +103650,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(874); +const DirectoryReader = __webpack_require__(879); let syncFacade = { - fs: __webpack_require__(879), - forEach: __webpack_require__(880), + fs: __webpack_require__(884), + forEach: __webpack_require__(885), sync: true }; @@ -102863,7 +103683,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 874 */ +/* 879 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102872,9 +103692,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(27).Readable; const EventEmitter = __webpack_require__(379).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(875); -const stat = __webpack_require__(877); -const call = __webpack_require__(878); +const normalizeOptions = __webpack_require__(880); +const stat = __webpack_require__(882); +const call = __webpack_require__(883); /** * Asynchronously reads the contents of a directory and streams the results @@ -103250,14 +104070,14 @@ module.exports = DirectoryReader; /***/ }), -/* 875 */ +/* 880 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(876); +const globToRegExp = __webpack_require__(881); module.exports = normalizeOptions; @@ -103434,7 +104254,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 876 */ +/* 881 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -103571,13 +104391,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 877 */ +/* 882 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(878); +const call = __webpack_require__(883); module.exports = stat; @@ -103652,7 +104472,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 878 */ +/* 883 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103713,14 +104533,14 @@ function callOnce (fn) { /***/ }), -/* 879 */ +/* 884 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(878); +const call = __webpack_require__(883); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -103784,7 +104604,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 880 */ +/* 885 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103813,7 +104633,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 881 */ +/* 886 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103821,12 +104641,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(882); -const DirectoryReader = __webpack_require__(874); +const maybe = __webpack_require__(887); +const DirectoryReader = __webpack_require__(879); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(883), + forEach: __webpack_require__(888), async: true }; @@ -103868,7 +104688,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 882 */ +/* 887 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103895,7 +104715,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 883 */ +/* 888 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103931,7 +104751,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 884 */ +/* 889 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103939,11 +104759,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(874); +const DirectoryReader = __webpack_require__(879); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(883), + forEach: __webpack_require__(888), async: true }; @@ -103963,16 +104783,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 885 */ +/* 890 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(886); -var entry_1 = __webpack_require__(888); -var pathUtil = __webpack_require__(887); +var deep_1 = __webpack_require__(891); +var entry_1 = __webpack_require__(893); +var pathUtil = __webpack_require__(892); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -104038,13 +104858,13 @@ exports.default = Reader; /***/ }), -/* 886 */ +/* 891 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(887); +var pathUtils = __webpack_require__(892); var patternUtils = __webpack_require__(721); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -104128,7 +104948,7 @@ exports.default = DeepFilter; /***/ }), -/* 887 */ +/* 892 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104159,13 +104979,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 888 */ +/* 893 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(887); +var pathUtils = __webpack_require__(892); var patternUtils = __webpack_require__(721); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -104251,7 +105071,7 @@ exports.default = EntryFilter; /***/ }), -/* 889 */ +/* 894 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104271,8 +105091,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var fsStat = __webpack_require__(890); -var fs_1 = __webpack_require__(894); +var fsStat = __webpack_require__(895); +var fs_1 = __webpack_require__(899); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -104322,14 +105142,14 @@ exports.default = FileSystemStream; /***/ }), -/* 890 */ +/* 895 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(891); -const statProvider = __webpack_require__(893); +const optionsManager = __webpack_require__(896); +const statProvider = __webpack_require__(898); /** * Asynchronous API. */ @@ -104360,13 +105180,13 @@ exports.statSync = statSync; /***/ }), -/* 891 */ +/* 896 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(892); +const fsAdapter = __webpack_require__(897); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -104379,7 +105199,7 @@ exports.prepare = prepare; /***/ }), -/* 892 */ +/* 897 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104402,7 +105222,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 893 */ +/* 898 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104454,7 +105274,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 894 */ +/* 899 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104485,7 +105305,7 @@ exports.default = FileSystem; /***/ }), -/* 895 */ +/* 900 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104505,9 +105325,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var readdir = __webpack_require__(872); -var reader_1 = __webpack_require__(885); -var fs_stream_1 = __webpack_require__(889); +var readdir = __webpack_require__(877); +var reader_1 = __webpack_require__(890); +var fs_stream_1 = __webpack_require__(894); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -104575,7 +105395,7 @@ exports.default = ReaderStream; /***/ }), -/* 896 */ +/* 901 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104594,9 +105414,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(872); -var reader_1 = __webpack_require__(885); -var fs_sync_1 = __webpack_require__(897); +var readdir = __webpack_require__(877); +var reader_1 = __webpack_require__(890); +var fs_sync_1 = __webpack_require__(902); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -104656,7 +105476,7 @@ exports.default = ReaderSync; /***/ }), -/* 897 */ +/* 902 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104675,8 +105495,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(890); -var fs_1 = __webpack_require__(894); +var fsStat = __webpack_require__(895); +var fs_1 = __webpack_require__(899); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -104722,7 +105542,7 @@ exports.default = FileSystemSync; /***/ }), -/* 898 */ +/* 903 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104738,7 +105558,7 @@ exports.flatten = flatten; /***/ }), -/* 899 */ +/* 904 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104759,13 +105579,13 @@ exports.merge = merge; /***/ }), -/* 900 */ +/* 905 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(901); +const pathType = __webpack_require__(906); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -104831,13 +105651,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 901 */ +/* 906 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(902); +const pify = __webpack_require__(907); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -104880,7 +105700,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 902 */ +/* 907 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104971,7 +105791,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 903 */ +/* 908 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104979,9 +105799,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(717); -const gitIgnore = __webpack_require__(904); -const pify = __webpack_require__(905); -const slash = __webpack_require__(906); +const gitIgnore = __webpack_require__(909); +const pify = __webpack_require__(910); +const slash = __webpack_require__(911); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -105079,7 +105899,7 @@ module.exports.sync = options => { /***/ }), -/* 904 */ +/* 909 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -105548,7 +106368,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 905 */ +/* 910 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105623,7 +106443,7 @@ module.exports = (input, options) => { /***/ }), -/* 906 */ +/* 911 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105641,17 +106461,17 @@ module.exports = input => { /***/ }), -/* 907 */ +/* 912 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const pEvent = __webpack_require__(908); -const CpFileError = __webpack_require__(911); -const fs = __webpack_require__(915); -const ProgressEmitter = __webpack_require__(918); +const pEvent = __webpack_require__(913); +const CpFileError = __webpack_require__(916); +const fs = __webpack_require__(920); +const ProgressEmitter = __webpack_require__(923); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -105765,12 +106585,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 908 */ +/* 913 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(909); +const pTimeout = __webpack_require__(914); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -106061,12 +106881,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 909 */ +/* 914 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(910); +const pFinally = __webpack_require__(915); class TimeoutError extends Error { constructor(message) { @@ -106112,7 +106932,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 910 */ +/* 915 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106134,12 +106954,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 911 */ +/* 916 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(912); +const NestedError = __webpack_require__(917); class CpFileError extends NestedError { constructor(message, nested) { @@ -106153,10 +106973,10 @@ module.exports = CpFileError; /***/ }), -/* 912 */ +/* 917 */ /***/ (function(module, exports, __webpack_require__) { -var inherits = __webpack_require__(913); +var inherits = __webpack_require__(918); var NestedError = function (message, nested) { this.nested = nested; @@ -106207,7 +107027,7 @@ module.exports = NestedError; /***/ }), -/* 913 */ +/* 918 */ /***/ (function(module, exports, __webpack_require__) { try { @@ -106215,12 +107035,12 @@ try { if (typeof util.inherits !== 'function') throw ''; module.exports = util.inherits; } catch (e) { - module.exports = __webpack_require__(914); + module.exports = __webpack_require__(919); } /***/ }), -/* 914 */ +/* 919 */ /***/ (function(module, exports) { if (typeof Object.create === 'function') { @@ -106249,16 +107069,16 @@ if (typeof Object.create === 'function') { /***/ }), -/* 915 */ +/* 920 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(29); const fs = __webpack_require__(22); -const makeDir = __webpack_require__(916); -const pEvent = __webpack_require__(908); -const CpFileError = __webpack_require__(911); +const makeDir = __webpack_require__(921); +const pEvent = __webpack_require__(913); +const CpFileError = __webpack_require__(916); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -106355,7 +107175,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 916 */ +/* 921 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106363,7 +107183,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const {promisify} = __webpack_require__(29); -const semver = __webpack_require__(917); +const semver = __webpack_require__(922); const defaults = { mode: 0o777 & (~process.umask()), @@ -106512,7 +107332,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 917 */ +/* 922 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -108114,7 +108934,7 @@ function coerce (version, options) { /***/ }), -/* 918 */ +/* 923 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -108155,7 +108975,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 919 */ +/* 924 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -108201,12 +109021,12 @@ exports.default = module.exports; /***/ }), -/* 920 */ +/* 925 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(921); +const NestedError = __webpack_require__(926); class CpyError extends NestedError { constructor(message, nested) { @@ -108220,7 +109040,7 @@ module.exports = CpyError; /***/ }), -/* 921 */ +/* 926 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -108276,7 +109096,7 @@ module.exports = NestedError; /***/ }), -/* 922 */ +/* 927 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index f4b91d154cbb8..66f17ab579ec3 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -136,7 +136,7 @@ export const schema = Joi.object() browser: Joi.object() .keys({ type: Joi.string() - .valid('chrome', 'firefox', 'ie', 'msedge') + .valid('chrome', 'firefox', 'ie') .default('chrome'), logPollingMs: Joi.number().default(100), diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 5d7b467052029..368d1f47e9c3f 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -24,6 +24,7 @@ - [7. Switch to new platform services](#7-switch-to-new-platform-services) - [8. Migrate to the new plugin system](#8-migrate-to-the-new-plugin-system) - [Bonus: Tips for complex migration scenarios](#bonus-tips-for-complex-migration-scenarios) + - [Keep Kibana fast](#keep-kibana-fast) - [Frequently asked questions](#frequently-asked-questions) - [Is migrating a plugin an all-or-nothing thing?](#is-migrating-a-plugin-an-all-or-nothing-thing) - [Do plugins need to be converted to TypeScript?](#do-plugins-need-to-be-converted-to-typescript) @@ -933,6 +934,66 @@ For a few plugins, some of these steps (such as angular removal) could be a mont One convention that is useful for this is creating a dedicated `public/np_ready` directory to house the code that is ready to migrate, and gradually move more and more code into it until the rest of your plugin is essentially empty. At that point, you'll be able to copy your `index.ts`, `plugin.ts`, and the contents of `./np_ready` over into your plugin in the new platform, leaving your legacy shim behind. This carries the added benefit of providing a way for us to introduce helpful tooling in the future, such as [custom eslint rules](https://github.com/elastic/kibana/pull/40537), which could be run against that specific directory to ensure your code is ready to migrate. +## Keep Kibana fast +**tl;dr**: Load as much code lazily as possible. +Everyone loves snappy applications with responsive UI and hates spinners. Users deserve the best user experiences regardless of whether they run Kibana locally or in the cloud, regardless of their hardware & environment. +There are 2 main aspects of the perceived speed of an application: loading time and responsiveness to user actions. +New platform loads and bootstraps **all** the plugins whenever a user lands on any page. It means that adding every new application affects overall **loading performance** in the new platform, as plugin code is loaded **eagerly** to initialize the plugin and provide plugin API to dependent plugins. +However, it's usually not necessary that the whole plugin code should be loaded and initialized at once. The plugin could keep on loading code covering API functionality on Kibana bootstrap but load UI related code lazily on-demand, when an application page or management section is mounted. +Always prefer to require UI root components lazily when possible (such as in mount handlers). Even if their size may seem negligible, they are likely using some heavy-weight libraries that will also be removed from the initial plugin bundle, therefore, reducing its size by a significant amount. + +```typescript +import { Plugin, CoreSetup, AppMountParameters } from 'src/core/public'; +export class MyPlugin implements Plugin { + setup(core: CoreSetup, plugins: SetupDeps){ + core.application.register({ + id: 'app', + title: 'My app', + async mount(params: AppMountParameters) { + const { mountApp } = await import('./app/mount_app'); + return mountApp(await core.getStartServices(), params); + }, + }); + plugins.management.sections.getSection('another').registerApp({ + id: 'app', + title: 'My app', + order: 1, + async mount(params) { + const { mountManagementSection } = await import('./app/mount_management_section'); + return mountManagementSection(coreSetup, params); + }, + }) + return { + doSomething(){} + } + } +} +``` + +#### How to understand how big the bundle size of my plugin is? +New platform plugins are distributed as a pre-built with `@kbn/optimizer` package artifacts. It allows us to get rid of the shipping of `optimizer` in the distributable version of Kibana. +Every NP plugin artifact contains all plugin dependencies required to run the plugin, except some stateful dependencies shared across plugin bundles via `@kbn/ui-shared-deps`. +It means that NP plugin artifacts tend to have a bigger size than the legacy platform version. +To understand the current size of your plugin artifact, run `@kbn/optimizer` as +```bash +node scripts/build_kibana_platform_plugins.js --dist --no-examples +``` +and check the output in the `target` sub-folder of your plugin folder +```bash +ls -lh plugins/my_plugin/target/public/ +# output +# an async chunk loaded on demand +... 262K 0.plugin.js +# eagerly loaded chunk +... 50K my_plugin.plugin.js +``` +you might see at least one js bundle - `my_plugin.plugin.js`. This is the only artifact loaded by the platform during bootstrap in the browser. The rule of thumb is to keep its size as small as possible. +Other lazily loaded parts of your plugin present in the same folder as separate chunks under `{number}.plugin.js` names. +If you want to investigate what your plugin bundle consists of you need to run `@kbn/optimizer` with `--profile` flag to get generated [webpack stats file](https://webpack.js.org/api/stats/). +Many OSS tools are allowing you to analyze generated stats file +- [an official tool](http://webpack.github.io/analyse/#modules) from webpack authors +- [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/) + ## Frequently asked questions ### Is migrating a plugin an all-or-nothing thing? diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index 684f6e15caff9..18725f04a05b5 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -33,7 +33,12 @@ import { CoreService } from '../../types'; import { merge } from '../../utils'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { ClusterClient, ScopeableRequest } from './cluster_client'; +import { + ClusterClient, + ScopeableRequest, + IClusterClient, + ICustomClusterClient, +} from './cluster_client'; import { ElasticsearchClientConfig } from './elasticsearch_client_config'; import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config'; import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; @@ -58,12 +63,14 @@ export class ElasticsearchService implements CoreService { private readonly log: Logger; private readonly config$: Observable; - private subscription: Subscription | undefined; + private subscription?: Subscription; private stop$ = new Subject(); private kibanaVersion: string; - createClient: InternalElasticsearchServiceSetup['createClient'] | undefined; - dataClient: InternalElasticsearchServiceSetup['dataClient'] | undefined; - adminClient: InternalElasticsearchServiceSetup['adminClient'] | undefined; + private createClient?: ( + type: string, + clientConfig?: Partial + ) => ICustomClusterClient; + private adminClient?: IClusterClient; constructor(private readonly coreContext: CoreContext) { this.kibanaVersion = coreContext.env.packageInfo.version; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 07ea431dd3a0d..684f50a5666e1 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -201,7 +201,7 @@ export class Server { uiSettings: uiSettingsStart, }; - const pluginsStart = await this.plugins.start(this.coreStart!); + const pluginsStart = await this.plugins.start(this.coreStart); await this.legacy.start({ core: { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html index d068e824a3e0a..1221b01657e45 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html @@ -140,6 +140,7 @@

{{screenTitle}}

position="'top'" > this.props.helpers.redirectToRoute(field, 'edit')} + editField={field => this.props.helpers.redirectToRoute(field)} /> ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx index ba044296a693a..e8dfbd6496057 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx @@ -155,7 +155,7 @@ export class ScriptedFieldsTable extends Component< this.props.helpers.redirectToRoute(field, 'edit')} + editField={field => this.props.helpers.redirectToRoute(field)} deleteField={this.startDeleteField} /> diff --git a/src/legacy/core_plugins/vis_type_timeseries/index.ts b/src/legacy/core_plugins/vis_type_timeseries/index.ts deleted file mode 100644 index 596fd5b581a71..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; - -const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => - new Plugin({ - id: 'metrics', - require: ['kibana', 'elasticsearch'], - publicDir: resolve(__dirname, 'public'), - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - hacks: [resolve(__dirname, 'public/legacy')], - injectDefaultVars: server => ({}), - }, - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(true), - chartResolution: Joi.number().default(150), - minimumBucketSize: Joi.number().default(10), - }).default(); - }, - } as Legacy.PluginSpecOptions); - -// eslint-disable-next-line import/no-default-export -export default metricsPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_timeseries/package.json b/src/legacy/core_plugins/vis_type_timeseries/package.json deleted file mode 100644 index 6b4874dfe6a68..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "author": "Chris Cowan", - "name": "metrics", - "version": "kibana" -} - diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts index aa11e0ef41fba..579caa1cb88f6 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts @@ -30,6 +30,7 @@ const setupPlugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: npSetup.plugins.visualizations, charts: npSetup.plugins.charts, + visTypeXy: npSetup.plugins.visTypeXy, }; const startPlugins: Readonly = { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts index 2731fb6f5fbe6..ef3f664252856 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -24,6 +24,7 @@ import { PluginInitializerContext, } from 'kibana/public'; +import { VisTypeXyPluginSetup } from 'src/plugins/vis_type_xy/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; @@ -39,7 +40,6 @@ import { createGoalVisTypeDefinition, } from './vis_type_vislib_vis_types'; import { ChartsPluginSetup } from '../../../../plugins/charts/public'; -import { ConfigSchema as VisTypeXyConfigSchema } from '../../vis_type_xy'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; import { setFormatService, setDataActions } from './services'; @@ -53,6 +53,7 @@ export interface VisTypeVislibPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; charts: ChartsPluginSetup; + visTypeXy?: VisTypeXyPluginSetup; } /** @internal */ @@ -68,7 +69,7 @@ export class VisTypeVislibPlugin implements Plugin { public async setup( core: VisTypeVislibCoreSetup, - { expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies + { expressions, visualizations, charts, visTypeXy }: VisTypeVislibPluginSetupDependencies ) { const visualizationDependencies: Readonly = { uiSettings: core.uiSettings, @@ -86,12 +87,8 @@ export class VisTypeVislibPlugin implements Plugin { ]; const vislibFns = [createVisTypeVislibVisFn(), createPieVisFn()]; - const visTypeXy = core.injectedMetadata.getInjectedVar('visTypeXy') as - | VisTypeXyConfigSchema['visTypeXy'] - | undefined; - // if visTypeXy plugin is disabled it's config will be undefined - if (!visTypeXy || !visTypeXy.enabled) { + if (!visTypeXy) { const convertedTypes: any[] = []; const convertedFns: any[] = []; diff --git a/src/legacy/core_plugins/vis_type_xy/index.ts b/src/legacy/core_plugins/vis_type_xy/index.ts deleted file mode 100644 index 58d2e425eef40..0000000000000 --- a/src/legacy/core_plugins/vis_type_xy/index.ts +++ /dev/null @@ -1,56 +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 { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../types'; - -export interface ConfigSchema { - visTypeXy: { - enabled: boolean; - }; -} - -const visTypeXyPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => - new Plugin({ - id: 'visTypeXy', - require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter'], - publicDir: resolve(__dirname, 'public'), - uiExports: { - hacks: [resolve(__dirname, 'public/legacy')], - injectDefaultVars(server): ConfigSchema { - const config = server.config(); - - return { - visTypeXy: { - enabled: config.get('visTypeXy.enabled') as boolean, - }, - }; - }, - }, - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(false), - }).default(); - }, - } as Legacy.PluginSpecOptions); - -// eslint-disable-next-line import/no-default-export -export default visTypeXyPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_xy/package.json b/src/legacy/core_plugins/vis_type_xy/package.json deleted file mode 100644 index 920f7dcb44e87..0000000000000 --- a/src/legacy/core_plugins/vis_type_xy/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "visTypeXy", - "version": "kibana" -} diff --git a/src/legacy/core_plugins/vis_type_xy/public/legacy.ts b/src/legacy/core_plugins/vis_type_xy/public/legacy.ts deleted file mode 100644 index 740ceeaac6a7d..0000000000000 --- a/src/legacy/core_plugins/vis_type_xy/public/legacy.ts +++ /dev/null @@ -1,40 +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 { npSetup, npStart } from 'ui/new_platform'; -import { PluginInitializerContext } from 'kibana/public'; - -import { plugin } from '.'; -import { VisTypeXyPluginSetupDependencies, VisTypeXyPluginStartDependencies } from './plugin'; - -const setupPlugins: Readonly = { - expressions: npSetup.plugins.expressions, - visualizations: npSetup.plugins.visualizations, - charts: npSetup.plugins.charts, -}; - -const startPlugins: Readonly = { - expressions: npStart.plugins.expressions, - visualizations: npStart.plugins.visualizations, -}; - -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core, startPlugins); diff --git a/src/legacy/ui/public/field_editor/field_editor.js b/src/legacy/ui/public/field_editor/field_editor.js index 43461c4c689be..e90cb110ac304 100644 --- a/src/legacy/ui/public/field_editor/field_editor.js +++ b/src/legacy/ui/public/field_editor/field_editor.js @@ -66,7 +66,7 @@ import { ScriptingHelpFlyout } from './components/scripting_help'; import { FieldFormatEditor } from './components/field_format_editor'; import { FIELD_TYPES_BY_LANG, DEFAULT_FIELD_TYPES } from './constants'; -import { copyField, executeScript, isScriptValid } from './lib'; +import { executeScript, isScriptValid } from './lib'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -100,7 +100,6 @@ export class FieldEditor extends PureComponent { indexPattern: PropTypes.object.isRequired, field: PropTypes.object.isRequired, helpers: PropTypes.shape({ - Field: PropTypes.func.isRequired, getConfig: PropTypes.func.isRequired, $http: PropTypes.func.isRequired, fieldFormatEditors: PropTypes.object.isRequired, @@ -111,11 +110,7 @@ export class FieldEditor extends PureComponent { constructor(props) { super(props); - const { - field, - indexPattern, - helpers: { Field }, - } = props; + const { field, indexPattern } = props; this.state = { isReady: false, @@ -125,7 +120,7 @@ export class FieldEditor extends PureComponent { fieldTypes: [], fieldTypeFormats: [], existingFieldNames: indexPattern.fields.map(f => f.name), - field: copyField(field, indexPattern, Field), + field: { ...field, format: field.format }, fieldFormatId: undefined, fieldFormatParams: {}, showScriptingHelp: false, @@ -730,7 +725,7 @@ export class FieldEditor extends PureComponent { }; saveField = async () => { - const field = this.state.field.toActualField(); + const field = this.state.field; const { indexPattern } = this.props; const { fieldFormatId } = this.state; diff --git a/src/legacy/ui/public/field_editor/lib/__tests__/copy_field.test.js b/src/legacy/ui/public/field_editor/lib/__tests__/copy_field.test.js deleted file mode 100644 index 2cee45742ab81..0000000000000 --- a/src/legacy/ui/public/field_editor/lib/__tests__/copy_field.test.js +++ /dev/null @@ -1,48 +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 { copyField } from '../copy_field'; - -const field = { - name: 'test_field', - scripted: true, - type: 'number', - lang: 'painless', -}; - -describe('copyField', () => { - it('should copy a field', () => { - const copiedField = copyField(field, {}, {}); - copiedField.name = 'test_name_change'; - - // Check that copied field has `toActualField()` method - expect(typeof copiedField.toActualField).toEqual('function'); - - // Check that we did not modify the original field object when - // modifying copied field - expect(field.toActualField).toEqual(undefined); - expect(field.name).toEqual('test_field'); - - expect(copiedField).not.toEqual(field); - expect(copiedField.name).toEqual('test_name_change'); - expect(copiedField.scripted).toEqual(field.scripted); - expect(copiedField.type).toEqual(field.type); - expect(copiedField.lang).toEqual(field.lang); - }); -}); diff --git a/src/legacy/ui/public/field_editor/lib/copy_field.js b/src/legacy/ui/public/field_editor/lib/copy_field.js deleted file mode 100644 index bfc1cb8480d5d..0000000000000 --- a/src/legacy/ui/public/field_editor/lib/copy_field.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { has } from 'lodash'; - -/** - * Fully clones a Field object, so that modifications can be performed on - * the copy without affecting original field. Field objects contain - * enumerable and non-eumerable properties that may or may not be writable. - * The function copies all properties as property descriptors into - * `newFieldProps`, overrides getter and setter, and returns a new object - * created from that. - * - * @param {object} field - Field object to copy - * @param {object} indexPattern - index pattern object the field belongs to - * @param {object} Field - Field object type - * @return {object} the cloned object - */ -export const copyField = (field, indexPattern, Field) => { - const changes = {}; - const newFieldProps = { - // When we are ready to save the copied field back into the index pattern, - // we use `toActualField()` to retrieve an actual `Field` type object, using - // its original properties with our "changes" applied. - toActualField: { - value: () => { - return new Field(indexPattern, { - ...field.$$spec, - ...changes, - }); - }, - }, - }; - - // Index pattern `Field` objects are created with custom property - // descriptors using `ObjDefine`. - // - // Each property of a `Field` type object could be enumerable/non-enumerable, - // writable/not writable, configurable/not configurable, and have custom - // getter and setter. We can't use the original `field` object directly for - // creating a new field or editing a new field, since we need all the - // properties to be editable. - // - // A normal copy of `field` (i.e. `const newField = { ...field }`) will only - // copy enumerable properties and copy each property's descriptors (not - // writable, etc). - // - // So we copy `field`'s **property descriptors** into `newFieldProps` - // and modify them so that they are "writable" with a getter/setter that - // stores and retrieves changes into/from another object (`changes`). - Object.getOwnPropertyNames(field).forEach(function(prop) { - const desc = Object.getOwnPropertyDescriptor(field, prop); - - newFieldProps[prop] = { - enumerable: desc.enumerable, - get: function() { - return has(changes, prop) ? changes[prop] : field[prop]; - }, - set: function(v) { - changes[prop] = v; - }, - }; - }); - - return Object.create(null, newFieldProps); -}; diff --git a/src/legacy/ui/public/field_editor/lib/index.js b/src/legacy/ui/public/field_editor/lib/index.js index c74bb0cc2ef8a..c9dd9d03b74f7 100644 --- a/src/legacy/ui/public/field_editor/lib/index.js +++ b/src/legacy/ui/public/field_editor/lib/index.js @@ -17,5 +17,4 @@ * under the License. */ -export { copyField } from './copy_field'; export { executeScript, isScriptValid } from './validate_script'; 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 33a7fdad065b4..f577a29ce90b9 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 @@ -453,6 +453,7 @@ export const npStart = { createAggConfigs: (indexPattern, configStates = []) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: aggTypesRegistry.start(), + fieldFormats: getFieldFormatsRegistry(mockCoreStart), }); }, types: aggTypesRegistry.start(), diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index 21b80e827e4c2..80fb837258d4c 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -22,6 +22,7 @@ import { IScope } from 'angular'; import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public'; import { createBrowserHistory } from 'history'; +import { VisTypeXyPluginSetup } from 'src/plugins/vis_type_xy/public'; import { DashboardStart } from '../../../../plugins/dashboard/public'; import { setSetupServices, setStartServices } from './set_services'; import { @@ -93,6 +94,7 @@ export interface PluginsSetup { savedObjectsManagement: SavedObjectsManagementPluginSetup; mapsLegacy: MapsLegacyPluginSetup; indexPatternManagement: IndexPatternManagementSetup; + visTypeXy?: VisTypeXyPluginSetup; } export interface PluginsStart { diff --git a/src/plugins/console/public/application/models/sense_editor/__tests__/editor_input1.txt b/src/plugins/console/public/application/models/sense_editor/__tests__/editor_input1.txt index f9a4bcb85034d..398a0fdeab61f 100644 --- a/src/plugins/console/public/application/models/sense_editor/__tests__/editor_input1.txt +++ b/src/plugins/console/public/application/models/sense_editor/__tests__/editor_input1.txt @@ -25,3 +25,9 @@ GET index_1/type1/1/_source?_source_include=f DELETE index_2 + +POST /_sql?format=txt +{ + "query": "SELECT prenom FROM claude_index WHERE prenom = 'claude' ", + "fetch_size": 1 +} diff --git a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js index 6afc03df13b4c..34b4cad7fbb6b 100644 --- a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js +++ b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js @@ -470,6 +470,18 @@ curl -XGET "http://localhost:9200/_stats?level=shards" curl -XPUT "http://localhost:9200/index_1/type1/1" -H 'Content-Type: application/json' -d' { "f": 1 +}'`.trim() + ); + + multiReqCopyAsCurlTest( + 'with single quotes', + editorInput1, + { start: { lineNumber: 29 }, end: { lineNumber: 33 } }, + ` +curl -XPOST "http://localhost:9200/_sql?format=txt" -H 'Content-Type: application/json' -d' +{ + "query": "SELECT prenom FROM claude_index WHERE prenom = '\\''claude'\\'' ", + "fetch_size": 1 }'`.trim() ); }); diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts index 9bcd3a6872196..d326543bbe00b 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts @@ -484,8 +484,9 @@ export class SenseEditor { if (esData && esData.length) { ret += " -H 'Content-Type: application/json' -d'\n"; const dataAsString = collapseLiteralStrings(esData.join('\n')); - // since Sense doesn't allow single quote json string any single qoute is within a string. - ret += dataAsString.replace(/'/g, '\\"'); + + // We escape single quoted strings that that are wrapped in single quoted strings + ret += dataAsString.replace(/'/g, "'\\''"); if (esData.length > 1) { ret += '\n'; } // end with a new line diff --git a/src/plugins/console/server/lib/spec_definitions/js/ingest.ts b/src/plugins/console/server/lib/spec_definitions/js/ingest.ts index 1182dc075f42f..20dbeda5e0b3d 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/ingest.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/ingest.ts @@ -57,6 +57,49 @@ const bytesProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest-circle-processor.html +const circleProcessorDefinition = { + circle: { + __template: { + field: '', + error_distance: '', + shape_type: '', + }, + field: '', + target_field: '', + error_distance: '', + shape_type: { + __one_of: ['geo_shape', 'shape'], + }, + ignore_missing: { + __one_of: [false, true], + }, + ...commonPipelineParams, + }, +}; + +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/csv-processor.html +const csvProcessorDefinition = { + csv: { + __template: { + field: '', + target_fields: [''], + }, + field: '', + target_fields: [''], + separator: '', + quote: '', + empty_value: '', + trim: { + __one_of: [true, false], + }, + ignore_missing: { + __one_of: [false, true], + }, + ...commonPipelineParams, + }, +}; + // Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/convert-processor.html const convertProcessorDefinition = { convert: { @@ -174,6 +217,25 @@ const foreachProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/geoip-processor.html +const geoipProcessorDefinition = { + geoip: { + __template: { + field: '', + }, + field: '', + target_field: '', + database_file: '', + properties: [''], + ignore_missing: { + __one_of: [false, true], + }, + first_only: { + __one_of: [false, true], + }, + }, +}; + // Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/grok-processor.html const grokProcessorDefinition = { grok: { @@ -209,6 +271,37 @@ const gsubProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/htmlstrip-processor.html +const htmlStripProcessorDefinition = { + html_strip: { + __template: { + field: '', + }, + field: '', + target_field: '', + ignore_missing: { + __one_of: [false, true], + }, + ...commonPipelineParams, + }, +}; + +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/inference-processor.html +const inferenceProcessorDefinition = { + inference: { + __template: { + model_id: '', + field_map: {}, + inference_config: {}, + }, + model_id: '', + field_map: {}, + inference_config: {}, + target_field: '', + ...commonPipelineParams, + }, +}; + // Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/join-processor.html const joinProcessorDefinition = { join: { @@ -338,6 +431,18 @@ const setProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest-node-set-security-user-processor.html +const setSecurityUserProcessorDefinition = { + set_security_user: { + __template: { + field: '', + }, + field: '', + properties: [''], + ...commonPipelineParams, + }, +}; + // Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/split-processor.html const splitProcessorDefinition = { split: { @@ -394,10 +499,43 @@ const uppercaseProcessorDefinition = { }, }; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/urldecode-processor.html +const urlDecodeProcessorDefinition = { + urldecode: { + __template: { + field: '', + }, + field: '', + target_field: '', + ignore_missing: { + __one_of: [false, true], + }, + ...commonPipelineParams, + }, +}; + +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/user-agent-processor.html +const userAgentProcessorDefinition = { + user_agent: { + __template: { + field: '', + }, + field: '', + target_field: '', + regex_file: '', + properties: [''], + ignore_missing: { + __one_of: [false, true], + }, + }, +}; + const processorDefinition = { __one_of: [ appendProcessorDefinition, bytesProcessorDefinition, + csvProcessorDefinition, + circleProcessorDefinition, convertProcessorDefinition, dateProcessorDefinition, dateIndexNameProcessorDefinition, @@ -406,8 +544,11 @@ const processorDefinition = { dropProcessorDefinition, failProcessorDefinition, foreachProcessorDefinition, + geoipProcessorDefinition, grokProcessorDefinition, gsubProcessorDefinition, + htmlStripProcessorDefinition, + inferenceProcessorDefinition, joinProcessorDefinition, jsonProcessorDefinition, kvProcessorDefinition, @@ -417,10 +558,13 @@ const processorDefinition = { renameProcessorDefinition, scriptProcessorDefinition, setProcessorDefinition, + setSecurityUserProcessorDefinition, splitProcessorDefinition, sortProcessorDefinition, trimProcessorDefinition, uppercaseProcessorDefinition, + urlDecodeProcessorDefinition, + userAgentProcessorDefinition, ], }; diff --git a/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx index d182deb813e11..fe9a19030602e 100644 --- a/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx @@ -28,6 +28,7 @@ import { EmbeddableInput, EmbeddableOutput, EmbeddableStart, + SavedObjectEmbeddableInput, } from '../../embeddable_plugin'; interface Props { @@ -66,7 +67,7 @@ export class ReplacePanelFlyout extends React.Component { }); }; - public onReplacePanel = async (id: string, type: string, name: string) => { + public onReplacePanel = async (savedObjectId: string, type: string, name: string) => { const originalPanels = this.props.container.getInput().panels; const filteredPanels = { ...originalPanels }; @@ -76,7 +77,9 @@ export class ReplacePanelFlyout extends React.Component { const nny = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.y; // add the new view - const newObj = await this.props.container.addSavedObjectEmbeddable(type, id); + const newObj = await this.props.container.addNewEmbeddable(type, { + savedObjectId, + }); const finalPanels = _.cloneDeep(this.props.container.getInput().panels); (finalPanels[newObj.id] as DashboardPanelState).gridData.w = nnw; diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index babd1fac274eb..283fe9f0a83a4 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -53,6 +53,7 @@ import { isErrorEmbeddable, openAddPanelFlyout, ViewMode, + SavedObjectEmbeddableInput, ContainerOutput, } from '../../../embeddable/public'; import { NavAction, SavedDashboardPanel } from '../types'; @@ -394,7 +395,7 @@ export class DashboardAppController { if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) { const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]; const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID]; - container.addSavedObjectEmbeddable(type, id); + container.addNewEmbeddable(type, { savedObjectId: id }); removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE); removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID); } diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 7f3a2913daac3..50089f1f061f4 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -55,7 +55,7 @@ export interface DashboardContainerInput extends ContainerInput { description?: string; isFullScreenMode: boolean; panels: { - [panelId: string]: DashboardPanelState; + [panelId: string]: DashboardPanelState; }; isEmptyState?: boolean; } diff --git a/src/plugins/dashboard/public/application/embeddable/types.ts b/src/plugins/dashboard/public/application/embeddable/types.ts index 3df305b0d7f1b..6d0221cb10e8b 100644 --- a/src/plugins/dashboard/public/application/embeddable/types.ts +++ b/src/plugins/dashboard/public/application/embeddable/types.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { SavedObjectEmbeddableInput } from 'src/plugins/embeddable/public'; import { PanelState, EmbeddableInput } from '../../embeddable_plugin'; export type PanelId = string; export type SavedObjectId = string; @@ -28,7 +29,8 @@ export interface GridData { i: string; } -export interface DashboardPanelState - extends PanelState { +export interface DashboardPanelState< + TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput +> extends PanelState { readonly gridData: GridData; } diff --git a/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts index 447563bbfbcfa..25ce203332422 100644 --- a/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts +++ b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts @@ -23,11 +23,6 @@ import { } from './embeddable_saved_object_converters'; import { SavedDashboardPanel } from '../../types'; import { DashboardPanelState } from '../embeddable'; -import { EmbeddableInput } from 'src/plugins/embeddable/public'; - -interface CustomInput extends EmbeddableInput { - something: string; -} test('convertSavedDashboardPanelToPanelState', () => { const savedDashboardPanel: SavedDashboardPanel = { @@ -58,8 +53,8 @@ test('convertSavedDashboardPanelToPanelState', () => { explicitInput: { something: 'hi!', id: '123', + savedObjectId: 'savedObjectId', }, - savedObjectId: 'savedObjectId', type: 'search', }); }); @@ -86,7 +81,7 @@ test('convertSavedDashboardPanelToPanelState does not include undefined id', () }); test('convertPanelStateToSavedDashboardPanel', () => { - const dashboardPanel: DashboardPanelState = { + const dashboardPanel: DashboardPanelState = { gridData: { x: 0, y: 0, @@ -94,10 +89,10 @@ test('convertPanelStateToSavedDashboardPanel', () => { w: 15, i: '123', }, - savedObjectId: 'savedObjectId', explicitInput: { something: 'hi!', id: '123', + savedObjectId: 'savedObjectId', }, type: 'search', }; @@ -121,7 +116,7 @@ test('convertPanelStateToSavedDashboardPanel', () => { }); test('convertPanelStateToSavedDashboardPanel will not add an undefined id when not needed', () => { - const dashboardPanel: DashboardPanelState = { + const dashboardPanel: DashboardPanelState = { gridData: { x: 0, y: 0, diff --git a/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts index 01cd55df0d8e9..b19ef31ccb9ac 100644 --- a/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts +++ b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts @@ -19,6 +19,7 @@ import { omit } from 'lodash'; import { SavedDashboardPanel } from '../../types'; import { DashboardPanelState } from '../embeddable'; +import { SavedObjectEmbeddableInput } from '../../embeddable_plugin'; export function convertSavedDashboardPanelToPanelState( savedDashboardPanel: SavedDashboardPanel @@ -26,9 +27,9 @@ export function convertSavedDashboardPanelToPanelState( return { type: savedDashboardPanel.type, gridData: savedDashboardPanel.gridData, - ...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }), explicitInput: { id: savedDashboardPanel.panelIndex, + ...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }), ...(savedDashboardPanel.title !== undefined && { title: savedDashboardPanel.title }), ...savedDashboardPanel.embeddableConfig, }, @@ -42,13 +43,14 @@ export function convertPanelStateToSavedDashboardPanel( const customTitle: string | undefined = panelState.explicitInput.title ? (panelState.explicitInput.title as string) : undefined; + const savedObjectId = (panelState.explicitInput as SavedObjectEmbeddableInput).savedObjectId; return { version, type: panelState.type, gridData: panelState.gridData, panelIndex: panelState.explicitInput.id, - embeddableConfig: omit(panelState.explicitInput, 'id'), + embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId']), ...(customTitle && { title: customTitle }), - ...(panelState.savedObjectId !== undefined && { id: panelState.savedObjectId }), + ...(savedObjectId !== undefined && { id: savedObjectId }), }; } diff --git a/src/plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts index 1554565d1403e..6370dcdf2db6f 100644 --- a/src/plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -45,9 +45,6 @@ export class Field implements IFieldType { subType?: IFieldSubType; displayName?: string; format: any; - routes: Record = { - edit: '/management/kibana/index_patterns/{{indexPattern.id}}/field/{{name}}', - }; $$spec: FieldSpec; constructor( @@ -146,7 +143,3 @@ export class Field implements IFieldType { return obj.create(); } } - -Field.prototype.routes = { - edit: '/management/kibana/index_patterns/{{indexPattern.id}}/field/{{name}}', -}; diff --git a/src/plugins/data/public/index_patterns/fields/field_list.ts b/src/plugins/data/public/index_patterns/fields/field_list.ts index 03214a8c96427..d6067280fd7b6 100644 --- a/src/plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/plugins/data/public/index_patterns/fields/field_list.ts @@ -70,11 +70,12 @@ export class FieldList extends Array implements IFieldList { this.splice(fieldIndex, 1); }; - update = (field: Field) => { - const index = this.findIndex(f => f.name === field.name); - this.splice(index, 1, field); - this.setByName(field); - this.removeByGroup(field); - this.setByGroup(field); + update = (field: FieldSpec) => { + const newField = new Field(this.indexPattern, field, this.shortDotsEnable); + const index = this.findIndex(f => f.name === newField.name); + this.splice(index, 1, newField); + this.setByName(newField); + this.removeByGroup(newField); + this.setByGroup(newField); }; } diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 2ebe377b3b32f..1723545b32522 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -155,7 +155,7 @@ export class DataPublicPlugin implements Plugin; - // (undocumented) script?: string; // (undocumented) scripted?: boolean; @@ -978,7 +976,7 @@ export class IndexPatternFieldList extends Array implements I // (undocumented) remove: (field: IFieldType) => void; // (undocumented) - update: (field: IndexPatternField) => void; + update: (field: Record) => void; } // Warning: (ae-missing-release-tag) "indexPatterns" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/data/public/search/aggs/agg_config.test.ts b/src/plugins/data/public/search/aggs/agg_config.test.ts index 41d943ef0c94b..2813e3b9c5373 100644 --- a/src/plugins/data/public/search/aggs/agg_config.test.ts +++ b/src/plugins/data/public/search/aggs/agg_config.test.ts @@ -26,10 +26,12 @@ import { AggTypesRegistryStart } from './agg_types_registry'; import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; import { Field as IndexPatternField, IndexPattern } from '../../index_patterns'; import { stubIndexPatternWithFields } from '../../../public/stubs'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('AggConfig', () => { let indexPattern: IndexPattern; let typesRegistry: AggTypesRegistryStart; + const fieldFormats = fieldFormatsServiceMock.createStartContract(); beforeEach(() => { jest.restoreAllMocks(); @@ -40,7 +42,7 @@ describe('AggConfig', () => { describe('#toDsl', () => { it('calls #write()', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); const configStates = { enabled: true, type: 'date_histogram', @@ -55,7 +57,7 @@ describe('AggConfig', () => { }); it('uses the type name as the agg name', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); const configStates = { enabled: true, type: 'date_histogram', @@ -70,7 +72,7 @@ describe('AggConfig', () => { }); it('uses the params from #write() output as the agg params', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); const configStates = { enabled: true, type: 'date_histogram', @@ -100,7 +102,7 @@ describe('AggConfig', () => { params: {}, }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const histoConfig = ac.byName('date_histogram')[0]; const avgConfig = ac.byName('avg')[0]; @@ -210,8 +212,8 @@ describe('AggConfig', () => { testsIdentical.forEach((configState, index) => { it(`identical aggregations (${index})`, () => { - const ac1 = new AggConfigs(indexPattern, configState, { typesRegistry }); - const ac2 = new AggConfigs(indexPattern, configState, { typesRegistry }); + const ac1 = new AggConfigs(indexPattern, configState, { typesRegistry, fieldFormats }); + const ac2 = new AggConfigs(indexPattern, configState, { typesRegistry, fieldFormats }); expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); }); }); @@ -251,8 +253,8 @@ describe('AggConfig', () => { testsIdenticalDifferentOrder.forEach((test, index) => { it(`identical aggregations (${index}) - init json is in different order`, () => { - const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); - const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); + const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry, fieldFormats }); + const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry, fieldFormats }); expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); }); }); @@ -316,8 +318,8 @@ describe('AggConfig', () => { testsDifferent.forEach((test, index) => { it(`different aggregations (${index})`, () => { - const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); - const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); + const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry, fieldFormats }); + const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry, fieldFormats }); expect(ac1.jsonDataEquals(ac2.aggs)).toBe(false); }); }); @@ -325,7 +327,7 @@ describe('AggConfig', () => { describe('#toJSON', () => { it('includes the aggs id, params, type and schema', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); const configStates = { enabled: true, type: 'date_histogram', @@ -356,8 +358,8 @@ describe('AggConfig', () => { params: {}, }, ]; - const ac1 = new AggConfigs(indexPattern, configStates, { typesRegistry }); - const ac2 = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac1 = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac2 = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); // this relies on the assumption that js-engines consistently loop over properties in insertion order. // most likely the case, but strictly speaking not guaranteed by the JS and JSON specifications. @@ -369,7 +371,7 @@ describe('AggConfig', () => { let aggConfig: AggConfig; beforeEach(() => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); aggConfig = ac.createAggConfig({ type: 'count' } as CreateAggConfigParams); }); @@ -398,7 +400,7 @@ describe('AggConfig', () => { describe('#fieldFormatter - custom getFormat handler', () => { it('returns formatter from getFormat handler', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); const configStates = { enabled: true, type: 'count', @@ -439,7 +441,7 @@ describe('AggConfig', () => { }, }, }; - const ac = new AggConfigs(indexPattern, [configStates], { typesRegistry }); + const ac = new AggConfigs(indexPattern, [configStates], { typesRegistry, fieldFormats }); aggConfig = ac.createAggConfig(configStates); }); diff --git a/src/plugins/data/public/search/aggs/agg_config.ts b/src/plugins/data/public/search/aggs/agg_config.ts index d6948aaade63d..6188849e0e6d4 100644 --- a/src/plugins/data/public/search/aggs/agg_config.ts +++ b/src/plugins/data/public/search/aggs/agg_config.ts @@ -25,7 +25,7 @@ import { IAggConfigs } from './agg_configs'; import { FetchOptions } from '../fetch'; import { ISearchSource } from '../search_source'; import { FieldFormatsContentType, KBN_FIELD_TYPES } from '../../../common'; -import { getFieldFormats } from '../../../public/services'; +import { FieldFormatsStart } from '../../field_formats'; export interface AggConfigOptions { type: IAggType; @@ -35,6 +35,10 @@ export interface AggConfigOptions { schema?: string; } +export interface AggConfigDependencies { + fieldFormats: FieldFormatsStart; +} + /** * @name AggConfig * @@ -93,8 +97,13 @@ export class AggConfig { private __type: IAggType; private __typeDecorations: any; private subAggs: AggConfig[] = []; + private readonly fieldFormats: FieldFormatsStart; - constructor(aggConfigs: IAggConfigs, opts: AggConfigOptions) { + constructor( + aggConfigs: IAggConfigs, + opts: AggConfigOptions, + { fieldFormats }: AggConfigDependencies + ) { this.aggConfigs = aggConfigs; this.id = String(opts.id || AggConfig.nextId(aggConfigs.aggs as any)); this.enabled = typeof opts.enabled === 'boolean' ? opts.enabled : true; @@ -115,6 +124,8 @@ export class AggConfig { // @ts-ignore this.__type = this.__type; + + this.fieldFormats = fieldFormats; } /** @@ -341,12 +352,10 @@ export class AggConfig { } fieldOwnFormatter(contentType?: FieldFormatsContentType, defaultFormat?: any) { - const fieldFormatsService = getFieldFormats(); - const field = this.getField(); let format = field && field.format; if (!format) format = defaultFormat; - if (!format) format = fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.STRING); + if (!format) format = this.fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING); return format.getConverterFor(contentType); } diff --git a/src/plugins/data/public/search/aggs/agg_configs.test.ts b/src/plugins/data/public/search/aggs/agg_configs.test.ts index e20e6de6112a8..653bf6a266df6 100644 --- a/src/plugins/data/public/search/aggs/agg_configs.test.ts +++ b/src/plugins/data/public/search/aggs/agg_configs.test.ts @@ -24,10 +24,12 @@ import { AggTypesRegistryStart } from './agg_types_registry'; import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; import { Field as IndexPatternField, IndexPattern } from '../../index_patterns'; import { stubIndexPattern, stubIndexPatternWithFields } from '../../../public/stubs'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('AggConfigs', () => { let indexPattern: IndexPattern; let typesRegistry: AggTypesRegistryStart; + const fieldFormats = fieldFormatsServiceMock.createStartContract(); beforeEach(() => { mockDataServices(); @@ -45,7 +47,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); expect(ac.aggs).toHaveLength(1); }); @@ -70,7 +72,7 @@ describe('AggConfigs', () => { ]; const spy = jest.spyOn(AggConfig, 'ensureIds'); - new AggConfigs(indexPattern, configStates, { typesRegistry }); + new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); expect(spy).toHaveBeenCalledTimes(1); expect(spy.mock.calls[0]).toEqual([configStates]); spy.mockRestore(); @@ -92,16 +94,20 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); expect(ac.aggs).toHaveLength(2); ac.createAggConfig( - new AggConfig(ac, { - enabled: true, - type: typesRegistry.get('terms'), - params: {}, - schema: 'split', - }) + new AggConfig( + ac, + { + enabled: true, + type: typesRegistry.get('terms'), + params: {}, + schema: 'split', + }, + { fieldFormats } + ) ); expect(ac.aggs).toHaveLength(3); }); @@ -115,7 +121,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); expect(ac.aggs).toHaveLength(1); ac.createAggConfig({ @@ -136,7 +142,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); expect(ac.aggs).toHaveLength(1); ac.createAggConfig( @@ -164,7 +170,7 @@ describe('AggConfigs', () => { { type: 'percentiles', enabled: true, params: {}, schema: 'metric' }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const sorted = ac.getRequestAggs(); const aggs = indexBy(ac.aggs, agg => agg.type.name); @@ -187,7 +193,7 @@ describe('AggConfigs', () => { { type: 'count', enabled: true, params: {}, schema: 'metric' }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const sorted = ac.getResponseAggs(); const aggs = indexBy(ac.aggs, agg => agg.type.name); @@ -204,7 +210,7 @@ describe('AggConfigs', () => { { type: 'percentiles', enabled: true, params: { percents: [1, 2, 3] }, schema: 'metric' }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const sorted = ac.getResponseAggs(); const aggs = indexBy(ac.aggs, agg => agg.type.name); @@ -225,7 +231,7 @@ describe('AggConfigs', () => { it('uses the sorted aggs', () => { const configStates = [{ enabled: true, type: 'avg', params: { field: 'bytes' } }]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const spy = jest.spyOn(AggConfigs.prototype, 'getRequestAggs'); ac.toDsl(); expect(spy).toHaveBeenCalledTimes(1); @@ -241,6 +247,7 @@ describe('AggConfigs', () => { const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, + fieldFormats, }); const aggInfos = ac.aggs.map(aggConfig => { @@ -284,7 +291,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const dsl = ac.toDsl(); const histo = ac.byName('date_histogram')[0]; const count = ac.byName('count')[0]; @@ -311,6 +318,7 @@ describe('AggConfigs', () => { const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, + fieldFormats, }); const dsl = ac.toDsl(); const histo = ac.byName('date_histogram')[0]; @@ -336,7 +344,7 @@ describe('AggConfigs', () => { { enabled: true, type: 'max', schema: 'metric', params: { field: 'bytes' } }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const topLevelDsl = ac.toDsl(true); const buckets = ac.bySchemaName('buckets'); const metrics = ac.bySchemaName('metrics'); @@ -406,7 +414,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); const topLevelDsl = ac.toDsl(true)['2']; expect(Object.keys(topLevelDsl.aggs)).toContain('1'); diff --git a/src/plugins/data/public/search/aggs/agg_configs.ts b/src/plugins/data/public/search/aggs/agg_configs.ts index c441b2a0eb46f..5ad09f824d3e4 100644 --- a/src/plugins/data/public/search/aggs/agg_configs.ts +++ b/src/plugins/data/public/search/aggs/agg_configs.ts @@ -28,6 +28,7 @@ import { IndexPattern } from '../../index_patterns'; import { ISearchSource } from '../search_source'; import { FetchOptions } from '../fetch'; import { TimeRange } from '../../../common'; +import { FieldFormatsStart } from '../../field_formats'; function removeParentAggs(obj: any) { for (const prop in obj) { @@ -47,6 +48,7 @@ function parseParentAggs(dslLvlCursor: any, dsl: any) { export interface AggConfigsOptions { typesRegistry: AggTypesRegistryStart; + fieldFormats: FieldFormatsStart; } export type CreateAggConfigParams = Assign; @@ -68,6 +70,7 @@ export type IAggConfigs = AggConfigs; export class AggConfigs { public indexPattern: IndexPattern; public timeRange?: TimeRange; + private readonly fieldFormats: FieldFormatsStart; private readonly typesRegistry: AggTypesRegistryStart; aggs: IAggConfig[]; @@ -83,6 +86,7 @@ export class AggConfigs { this.aggs = []; this.indexPattern = indexPattern; + this.fieldFormats = opts.fieldFormats; configStates.forEach((params: any) => this.createAggConfig(params)); } @@ -113,6 +117,7 @@ export class AggConfigs { const aggConfigs = new AggConfigs(this.indexPattern, this.aggs.filter(filterAggs), { typesRegistry: this.typesRegistry, + fieldFormats: this.fieldFormats, }); return aggConfigs; @@ -129,10 +134,14 @@ export class AggConfigs { aggConfig = params; params.parent = this; } else { - aggConfig = new AggConfig(this, { - ...params, - type: typeof type === 'string' ? this.typesRegistry.get(type) : type, - }); + aggConfig = new AggConfig( + this, + { + ...params, + type: typeof type === 'string' ? this.typesRegistry.get(type) : type, + }, + { fieldFormats: this.fieldFormats } + ); } if (addToAggConfigs) { diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts index c664325a168b1..44d99375bbd30 100644 --- a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts @@ -26,6 +26,7 @@ import { AggConfigs, CreateAggConfigParams } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketAggConfig } from './bucket_agg_type'; import { mockAggTypesRegistry } from '../test_helpers'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; const indexPattern = { id: '1234', @@ -219,8 +220,10 @@ const nestedOtherResponse = { describe('Terms Agg Other bucket helper', () => { const typesRegistry = mockAggTypesRegistry(); + const fieldFormats = fieldFormatsServiceMock.createStartContract(); + const getAggConfigs = (aggs: CreateAggConfigParams[] = []) => { - return new AggConfigs(indexPattern, [...aggs], { typesRegistry }); + return new AggConfigs(indexPattern, [...aggs], { typesRegistry, fieldFormats }); }; describe('buildOtherBucketAgg', () => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index 97c940b4ff4b1..7778fcb36bcd6 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -78,7 +78,10 @@ describe('AggConfig Filters', () => { params: { field: field.name, interval, customInterval: '5d' }, }, ], - { typesRegistry: mockAggTypesRegistry([getDateHistogramBucketAgg(aggTypesDependencies)]) } + { + typesRegistry: mockAggTypesRegistry([getDateHistogramBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } ); const bucketKey = 1422579600000; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index 8c0466b769a7e..4207fa92736f8 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -72,7 +72,10 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]) } + { + typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index f5a0b5a7b9094..bf05f7463db6c 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -69,7 +69,10 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]) } + { + typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index 18b388be74877..396d515f3b580 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -23,10 +23,12 @@ import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../bucket_agg_type'; import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('histogram', () => { const getConfig = (() => {}) as FieldFormatsGetConfigFn; + const fieldFormats = fieldFormatsServiceMock.createStartContract(); const getAggConfigs = () => { const field = { name: 'bytes', @@ -55,7 +57,7 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry() } + { typesRegistry: mockAggTypesRegistry(), fieldFormats } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index b528313b080d0..d85576a0ccb14 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -29,10 +29,11 @@ import { notificationServiceMock } from '../../../../../../../core/public/mocks' describe('AggConfig Filters', () => { describe('IP range', () => { + const fieldFormats = fieldFormatsServiceMock.createStartContract(); const typesRegistry = mockAggTypesRegistry([ getIpRangeBucketAgg({ getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), + fieldFormats, notifications: notificationServiceMock.createStartContract(), }), }), @@ -52,7 +53,7 @@ describe('AggConfig Filters', () => { }, } as any; - return new AggConfigs(indexPattern, aggs, { typesRegistry }); + return new AggConfigs(indexPattern, aggs, { typesRegistry, fieldFormats }); }; test('should return a range filter for ip_range agg', () => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 14a7538aa95a4..cadd8e9fe13ed 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -71,7 +71,10 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]) } + { + typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index c11a7d1a4e6b8..d9ff63613b640 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -58,6 +58,7 @@ describe('AggConfig Filters', () => { return new AggConfigs(indexPattern, aggs, { typesRegistry: mockAggTypesRegistry([getTermsBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, }); }; diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts index c050620c3a856..f78f0cce732e7 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts @@ -74,7 +74,10 @@ describe('date_range params', () => { params, }, ], - { typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]) } + { + typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts index 24270dd33a576..226faefe43482 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -77,7 +77,10 @@ describe('Geohash Agg', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry() } + { + typesRegistry: mockAggTypesRegistry(), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts index bbfc263df4268..a55c32951232a 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -70,7 +70,10 @@ describe('Histogram Agg', () => { params, }, ], - { typesRegistry: mockAggTypesRegistry([getHistogramBucketAgg(aggTypesDependencies)]) } + { + typesRegistry: mockAggTypesRegistry([getHistogramBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/public/search/aggs/buckets/range.test.ts index a1f0ab6a2326a..144d2b779e950 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.test.ts @@ -95,7 +95,10 @@ describe('Range Agg', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]) } + { + typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts index 761d0ced6a114..d0ace5a50c28d 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -70,6 +70,7 @@ describe('Significant Terms Agg', () => { typesRegistry: mockAggTypesRegistry([ getSignificantTermsBucketAgg(aggTypesDependencies), ]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/terms.test.ts index 5afe7d0b0c35c..0dc052bd1fdf6 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.test.ts @@ -20,9 +20,11 @@ import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; describe('Terms Agg', () => { describe('order agg editor UI', () => { + const fieldFormats = fieldFormatsServiceMock.createStartContract(); const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -47,7 +49,7 @@ describe('Terms Agg', () => { type: BUCKET_TYPES.TERMS, }, ], - { typesRegistry: mockAggTypesRegistry() } + { typesRegistry: mockAggTypesRegistry(), fieldFormats } ); }; diff --git a/src/plugins/data/public/search/aggs/metrics/median.test.ts b/src/plugins/data/public/search/aggs/metrics/median.test.ts index f80c46026f50a..de3ca646ead9e 100644 --- a/src/plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/median.test.ts @@ -59,7 +59,10 @@ describe('AggTypeMetricMedianProvider class', () => { }, }, ], - { typesRegistry } + { + typesRegistry, + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } ); }); diff --git a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index af983a50f6c23..3beb92a2fa000 100644 --- a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -110,7 +110,7 @@ describe('parent pipeline aggs', function() { schema: 'metric', }, ], - { typesRegistry } + { typesRegistry, fieldFormats: getInternalStartServices().fieldFormats } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 2944fc8c11b23..1b94ecd602075 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -70,7 +70,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() { }, }, ], - { typesRegistry } + { typesRegistry, fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats } ); }); diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts index 33bd42df74cc7..76da2fe3eb62c 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -70,7 +70,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => { }, }, ], - { typesRegistry } + { typesRegistry, fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats } ); }); diff --git a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index ab480fe44227e..a47aa2c677ade 100644 --- a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -111,7 +111,7 @@ describe('sibling pipeline aggs', () => { }, }, ], - { typesRegistry } + { typesRegistry, fieldFormats: getInternalStartServices().fieldFormats } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts index 6bbff3009cc11..d2370e1fed02c 100644 --- a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts @@ -64,7 +64,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { }, }, ], - { typesRegistry } + { typesRegistry, fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats } ); }; diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts index 8294ad09bae22..142b8e4c83301 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -89,7 +89,7 @@ describe('Top hit metric', () => { params, }, ], - { typesRegistry } + { typesRegistry, fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) diff --git a/src/plugins/data/public/search/aggs/mocks.ts b/src/plugins/data/public/search/aggs/mocks.ts index 7a5dcc9be4592..16544fd8f46b0 100644 --- a/src/plugins/data/public/search/aggs/mocks.ts +++ b/src/plugins/data/public/search/aggs/mocks.ts @@ -27,6 +27,7 @@ import { } from './'; import { SearchAggsSetup, SearchAggsStart } from './types'; import { mockAggTypesRegistry } from './test_helpers'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; const aggTypeBaseParamMock = () => ({ name: 'some_param', @@ -72,6 +73,7 @@ export const searchAggsStartMock = (): SearchAggsStart => ({ createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: mockAggTypesRegistry(), + fieldFormats: fieldFormatsServiceMock.createStartContract(), }); }), types: mockAggTypesRegistry(), diff --git a/src/plugins/data/public/search/expressions/create_filter.test.ts b/src/plugins/data/public/search/expressions/create_filter.test.ts index 23da060cba203..51b5e175761bd 100644 --- a/src/plugins/data/public/search/expressions/create_filter.test.ts +++ b/src/plugins/data/public/search/expressions/create_filter.test.ts @@ -22,10 +22,12 @@ import { AggConfigs, IAggConfig } from '../aggs'; import { TabbedTable } from '../tabify'; import { isRangeFilter, BytesFormat, FieldFormatsGetConfigFn } from '../../../common'; import { mockDataServices, mockAggTypesRegistry } from '../aggs/test_helpers'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('createFilter', () => { let table: TabbedTable; let aggConfig: IAggConfig; + const fieldFormats = fieldFormatsServiceMock.createStartContract(); const typesRegistry = mockAggTypesRegistry(); @@ -58,7 +60,7 @@ describe('createFilter', () => { params, }, ], - { typesRegistry } + { typesRegistry, fieldFormats } ); }; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 6124682184821..a539736991adb 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -44,12 +44,19 @@ import { siblingPipelineAggHelper, } from './aggs'; +import { FieldFormatsStart } from '../field_formats'; + interface SearchServiceSetupDependencies { packageInfo: PackageInfo; query: QuerySetup; getInternalStartServices: GetInternalStartServicesFn; } +interface SearchStartDependencies { + fieldFormats: FieldFormatsStart; + indexPatterns: IndexPatternsContract; +} + /** * The search plugin exposes two registration methods for other plugins: * - registerSearchStrategyProvider for plugins to add their own custom @@ -110,7 +117,10 @@ export class SearchService implements Plugin { }; } - public start(core: CoreStart, indexPatterns: IndexPatternsContract): ISearchStart { + public start( + core: CoreStart, + { fieldFormats, indexPatterns }: SearchStartDependencies + ): ISearchStart { /** * A global object that intercepts all searches and provides convenience methods for cancelling * all pending search requests, as well as getting the number of pending search requests. @@ -131,6 +141,7 @@ export class SearchService implements Plugin { createAggConfigs: (indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: aggTypesStart, + fieldFormats, }); }, types: aggTypesStart, diff --git a/src/plugins/data/public/search/tabify/get_columns.test.ts b/src/plugins/data/public/search/tabify/get_columns.test.ts index b7dadc3f65d82..1072e9318b40e 100644 --- a/src/plugins/data/public/search/tabify/get_columns.test.ts +++ b/src/plugins/data/public/search/tabify/get_columns.test.ts @@ -21,6 +21,7 @@ import { tabifyGetColumns } from './get_columns'; import { TabbedAggColumn } from './types'; import { AggConfigs } from '../aggs'; import { mockAggTypesRegistry, mockDataServices } from '../aggs/test_helpers'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('get columns', () => { beforeEach(() => { @@ -28,6 +29,7 @@ describe('get columns', () => { }); const typesRegistry = mockAggTypesRegistry(); + const fieldFormats = fieldFormatsServiceMock.createStartContract(); const createAggConfigs = (aggs: any[] = []) => { const field = { @@ -45,6 +47,7 @@ describe('get columns', () => { return new AggConfigs(indexPattern, aggs, { typesRegistry, + fieldFormats, }); }; diff --git a/src/plugins/data/public/search/tabify/response_writer.test.ts b/src/plugins/data/public/search/tabify/response_writer.test.ts index 52338ae79ccbb..3334d858ce54e 100644 --- a/src/plugins/data/public/search/tabify/response_writer.test.ts +++ b/src/plugins/data/public/search/tabify/response_writer.test.ts @@ -21,6 +21,7 @@ import { TabbedAggResponseWriter } from './response_writer'; import { AggConfigs, BUCKET_TYPES } from '../aggs'; import { mockDataServices, mockAggTypesRegistry } from '../aggs/test_helpers'; import { TabbedResponseWriterOptions } from './types'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('TabbedAggResponseWriter class', () => { beforeEach(() => { @@ -30,6 +31,7 @@ describe('TabbedAggResponseWriter class', () => { let responseWriter: TabbedAggResponseWriter; const typesRegistry = mockAggTypesRegistry(); + const fieldFormats = fieldFormatsServiceMock.createStartContract(); const splitAggConfig = [ { @@ -74,6 +76,7 @@ describe('TabbedAggResponseWriter class', () => { return new TabbedAggResponseWriter( new AggConfigs(indexPattern, aggs, { typesRegistry, + fieldFormats, }), { metricsAtAllLevels: false, diff --git a/src/plugins/data/public/search/tabify/tabify.test.ts b/src/plugins/data/public/search/tabify/tabify.test.ts index c9bf04ae9f0fc..63685cc87f5cf 100644 --- a/src/plugins/data/public/search/tabify/tabify.test.ts +++ b/src/plugins/data/public/search/tabify/tabify.test.ts @@ -22,9 +22,11 @@ import { IndexPattern } from '../../index_patterns'; import { AggConfigs, IAggConfig, IAggConfigs } from '../aggs'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { metricOnly, threeTermBuckets } from 'fixtures/fake_hierarchical_data'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('tabifyAggResponse Integration', () => { const typesRegistry = mockAggTypesRegistry(); + const fieldFormats = fieldFormatsServiceMock.createStartContract(); const createAggConfigs = (aggs: IAggConfig[] = []) => { const field = { @@ -42,6 +44,7 @@ describe('tabifyAggResponse Integration', () => { return new AggConfigs(indexPattern, aggs, { typesRegistry, + fieldFormats, }); }; diff --git a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx index 63f6997ce2fc3..ded48d462722d 100644 --- a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -61,6 +61,7 @@ export function QueryLanguageSwitcher(props: Props) { size="xs" onClick={() => setIsPopoverOpen(!isPopoverOpen)} className="euiFormControlLayout__append" + data-test-subj={'switchQueryLanguageButton'} > {props.language === 'lucene' ? luceneLabel : kqlLabel} diff --git a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx index 36dcd4a00c05e..5550ea16c22df 100644 --- a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx +++ b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { EuiButtonEmpty, EuiOverlayMask, @@ -63,6 +63,7 @@ export function SaveQueryForm({ showTimeFilterOption = true, }: Props) { const [title, setTitle] = useState(savedQuery ? savedQuery.title : ''); + const [enabledSaveButton, setEnabledSaveButton] = useState(Boolean(savedQuery)); const [description, setDescription] = useState(savedQuery ? savedQuery.description : ''); const [savedQueries, setSavedQueries] = useState([]); const [shouldIncludeFilters, setShouldIncludeFilters] = useState( @@ -76,49 +77,31 @@ export function SaveQueryForm({ ); const [formErrors, setFormErrors] = useState([]); - useEffect(() => { - const fetchQueries = async () => { - const allSavedQueries = await savedQueryService.getAllSavedQueries(); - const sortedAllSavedQueries = sortBy(allSavedQueries, 'attributes.title') as SavedQuery[]; - setSavedQueries(sortedAllSavedQueries); - }; - fetchQueries(); - }, [savedQueryService]); - - const savedQueryDescriptionText = i18n.translate( - 'data.search.searchBar.savedQueryDescriptionText', - { - defaultMessage: 'Save query text and filters that you want to use again.', - } - ); - const titleConflictErrorText = i18n.translate( 'data.search.searchBar.savedQueryForm.titleConflictText', { defaultMessage: 'Name conflicts with an existing saved query', } ); - const titleMissingErrorText = i18n.translate( - 'data.search.searchBar.savedQueryForm.titleMissingText', - { - defaultMessage: 'Name is required', - } - ); - const whitespaceErrorText = i18n.translate( - 'data.search.searchBar.savedQueryForm.whitespaceErrorText', + + const savedQueryDescriptionText = i18n.translate( + 'data.search.searchBar.savedQueryDescriptionText', { - defaultMessage: 'Name cannot contain leading or trailing whitespace', + defaultMessage: 'Save query text and filters that you want to use again.', } ); - const validate = () => { + useEffect(() => { + const fetchQueries = async () => { + const allSavedQueries = await savedQueryService.getAllSavedQueries(); + const sortedAllSavedQueries = sortBy(allSavedQueries, 'attributes.title') as SavedQuery[]; + setSavedQueries(sortedAllSavedQueries); + }; + fetchQueries(); + }, [savedQueryService]); + + const validate = useCallback(() => { const errors = []; - if (!title.length) { - errors.push(titleMissingErrorText); - } - if (title.length > title.trim().length) { - errors.push(whitespaceErrorText); - } if ( !!savedQueries.find( existingSavedQuery => !savedQuery && existingSavedQuery.attributes.title === title @@ -129,14 +112,37 @@ export function SaveQueryForm({ if (!isEqual(errors, formErrors)) { setFormErrors(errors); + return false; } - }; - const hasErrors = formErrors.length > 0; + return !formErrors.length; + }, [savedQueries, savedQuery, title, titleConflictErrorText, formErrors]); + + const onClickSave = useCallback(() => { + if (validate()) { + onSave({ + title, + description, + shouldIncludeFilters, + shouldIncludeTimefilter, + }); + } + }, [validate, onSave, title, description, shouldIncludeFilters, shouldIncludeTimefilter]); + + const onInputChange = useCallback(event => { + setEnabledSaveButton(Boolean(event.target.value)); + setFormErrors([]); + setTitle(event.target.value); + }, []); + + const autoTrim = useCallback(() => { + const trimmedTitle = title.trim(); + if (title.length > trimmedTitle.length) { + setTitle(trimmedTitle); + } + }, [title]); - if (hasErrors) { - validate(); - } + const hasErrors = formErrors.length > 0; const saveQueryForm = ( @@ -157,12 +163,10 @@ export function SaveQueryForm({ disabled={!!savedQuery} value={title} name="title" - onChange={event => { - setTitle(event.target.value); - }} + onChange={onInputChange} data-test-subj="saveQueryFormTitle" isInvalid={hasErrors} - onBlur={validate} + onBlur={autoTrim} /> @@ -235,17 +239,10 @@ export function SaveQueryForm({ - onSave({ - title, - description, - shouldIncludeFilters, - shouldIncludeTimefilter, - }) - } + onClick={onClickSave} fill data-test-subj="savedQueryFormSaveButton" - disabled={hasErrors} + disabled={hasErrors || !enabledSaveButton} > {i18n.translate('data.search.searchBar.savedQueryFormSaveButtonText', { defaultMessage: 'Save', diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 5ca334d6bdcfe..7723254f3aa51 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -124,7 +124,8 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) const onQuerySubmitRef = useRef(props.onQuerySubmit); const defaultQuery = { query: '', - language: core.uiSettings.get('search:queryLanguage'), + language: + storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage'), }; const [query, setQuery] = useState(props.query || defaultQuery); @@ -161,7 +162,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) setQuery, savedQueryId: props.savedQueryId, notifications: core.notifications, - uiSettings: core.uiSettings, + defaultLanguage: defaultQuery.language, }); // Fire onQuerySubmit on query or timerange change diff --git a/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts b/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts index 817e890b7b42b..79aee3438d7aa 100644 --- a/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts +++ b/src/plugins/data/public/ui/search_bar/lib/use_saved_query.ts @@ -29,8 +29,8 @@ interface UseSavedQueriesProps { queryService: DataPublicPluginStart['query']; setQuery: Function; notifications: CoreStart['notifications']; - uiSettings: CoreStart['uiSettings']; savedQueryId?: string; + defaultLanguage: string; } interface UseSavedQueriesReturn { @@ -41,7 +41,7 @@ interface UseSavedQueriesReturn { export const useSavedQuery = (props: UseSavedQueriesProps): UseSavedQueriesReturn => { // Handle saved queries - const defaultLanguage = props.uiSettings.get('search:queryLanguage'); + const defaultLanguage = props.defaultLanguage; const [savedQuery, setSavedQuery] = useState(); // Effect is used to convert a saved query id into an object diff --git a/src/plugins/embeddable/README.md b/src/plugins/embeddable/README.md index 1fe91f426f43f..343a1cc32cd10 100644 --- a/src/plugins/embeddable/README.md +++ b/src/plugins/embeddable/README.md @@ -1,39 +1,25 @@ -# The Embeddable API V2 +# Embeddables -The Embeddable API's main goal is to have documented and standardized ways to share and exchange information and functionality across applications and plugins. +Embeddables are re-usable widgets that can be rendered in any environment or plugin. Developers can embed them directly in their plugin. End users can dynamically add them to any embeddable _containers_. -There are three main pieces of this infrastructure: - - Embeddables & Containers - - Actions - - Triggers +## Embeddable containers -## Embeddables & Containers +Containers are a special type of embeddable that can contain nested embeddables. Embeddables can be dynamically added to embeddable _containers_. Currently only dashboard uses this interface. -Embeddables are isolated, serializable, renderable widgets. A developer can hard code an embeddable inside their -application, or they can use some built in actions to allow users to dynamically add them to *containers*. - -Containers are a special type of embeddable that can contain nested embeddables. - -## Actions - -Actions are pluggable pieces of functionality exposed to the user that take an embeddable as context, plus an optional action context. - -## Triggers - -Triggers are the way actions are connected to a user action. We ship with two default triggers, `CONTEXT_MENU_TRIGGER` and `APPLY_FILTER`. - -Actions attached to the `CONTEXT_MENU_TRIGGER` will be displayed in supported embeddables context menu to the user. Actions attached to the `APPLY_FILTER` trigger will show up when any embeddable emits this trigger. +## Examples -A developer can register new triggers that their embeddables, or external components, can emit (as long as they have an embeddable to pass along as context). +Many example embeddables are implemented and registered [here](https://github.com/elastic/kibana/tree/master/examples/embeddable_examples). They can be played around with and explored [in the Embeddable Explorer example plugin](https://github.com/elastic/kibana/tree/master/examples/embeddable_explorer). Just run kibana with -## Examples +``` +yarn start --run-examples +``` -Many examples can be viewed in the functionally tested `kbn_tp_embeddable_explorer` plugin, as well as the jest tested classes inside the `embeddable_api/public/test_samples` folder. +and navigate to the Embeddable explorer app. ## Testing Run unit tests ```shell -node scripts/jest embeddable_api +node scripts/jest embeddable ``` diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 23275fbe8e8f0..bdb7bfbddc308 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -61,6 +61,8 @@ export { PropertySpec, ViewMode, withEmbeddableSubscription, + SavedObjectEmbeddableInput, + isSavedObjectEmbeddableInput, } from './lib'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx index ce733bba6dda5..d07bf915845e9 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx @@ -56,7 +56,7 @@ test('getHref returns the edit urls', async () => { if (action.getHref) { const embeddable = new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true); expect( - action.getHref({ + await action.getHref({ embeddable, }) ).toBe(embeddable.getOutput().editUrl); diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index 9125dc0813f98..044e7b5d35ad8 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -62,11 +62,16 @@ export class EditPanelAction implements Action { return Boolean(canEditEmbeddable && inDashboardEditMode); } - public async execute() { - return; + public async execute(context: ActionContext) { + const href = await this.getHref(context); + if (href) { + // TODO: when apps start using browser router instead of hash router this has to be fixed + // https://github.com/elastic/kibana/issues/58217 + window.location.href = href; + } } - public getHref({ embeddable }: ActionContext): string { + public async getHref({ embeddable }: ActionContext): Promise { const editUrl = embeddable ? embeddable.getOutput().editUrl : undefined; return editUrl ? editUrl : ''; } diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index 4ab74e1883917..ffbe75f66b581 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -30,6 +30,7 @@ import { import { IContainer, ContainerInput, ContainerOutput, PanelState } from './i_container'; import { PanelNotFoundError, EmbeddableFactoryNotFoundError } from '../errors'; import { EmbeddableStart } from '../../plugin'; +import { isSavedObjectEmbeddableInput } from '../embeddables/saved_object_embeddable'; const getKeys = (o: T): Array => Object.keys(o) as Array; @@ -98,17 +99,6 @@ export abstract class Container< return this.createAndSaveEmbeddable(type, panelState); } - public async addSavedObjectEmbeddable< - TEmbeddableInput extends EmbeddableInput = EmbeddableInput, - TEmbeddable extends IEmbeddable = IEmbeddable - >(type: string, savedObjectId: string): Promise { - const factory = this.getFactory(type) as EmbeddableFactory; - const panelState = this.createNewPanelState(factory); - panelState.savedObjectId = savedObjectId; - - return this.createAndSaveEmbeddable(type, panelState); - } - public removeEmbeddable(embeddableId: string) { // Just a shortcut for removing the panel from input state, all internal state will get cleaned up naturally // by the listener. @@ -304,8 +294,10 @@ export abstract class Container< throw new EmbeddableFactoryNotFoundError(panel.type); } - embeddable = panel.savedObjectId - ? await factory.createFromSavedObject(panel.savedObjectId, inputForChild, this) + // TODO: lets get rid of this distinction with factories, I don't think it will be needed + // anymore after this change. + embeddable = isSavedObjectEmbeddableInput(inputForChild) + ? await factory.createFromSavedObject(inputForChild.savedObjectId, inputForChild, this) : await factory.create(inputForChild, this); } catch (e) { embeddable = new ErrorEmbeddable(e, { id: panel.explicitInput.id }, this); @@ -323,23 +315,6 @@ export abstract class Container< return; } - if (embeddable.getOutput().savedObjectId) { - this.updateInput({ - panels: { - ...this.input.panels, - [panel.explicitInput.id]: { - ...this.input.panels[panel.explicitInput.id], - ...(embeddable.getOutput().savedObjectId - ? { savedObjectId: embeddable.getOutput().savedObjectId } - : undefined), - explicitInput: { - ...this.input.panels[panel.explicitInput.id].explicitInput, - }, - }, - }, - } as Partial); - } - this.children[embeddable.id] = embeddable; this.updateOutput({ embeddableLoaded: { diff --git a/src/plugins/embeddable/public/lib/containers/i_container.ts b/src/plugins/embeddable/public/lib/containers/i_container.ts index 7da5f92ec92c1..31a7cd4f2e559 100644 --- a/src/plugins/embeddable/public/lib/containers/i_container.ts +++ b/src/plugins/embeddable/public/lib/containers/i_container.ts @@ -25,9 +25,7 @@ import { IEmbeddable, } from '../embeddables'; -export interface PanelState { - savedObjectId?: string; - +export interface PanelState { // The type of embeddable in this panel. Will be used to find the factory in which to // load the embeddable. type: string; @@ -89,17 +87,6 @@ export interface IContainer< */ removeEmbeddable(embeddableId: string): void; - /** - * Adds a new embeddable that is backed off of a saved object. - */ - addSavedObjectEmbeddable< - EEI extends EmbeddableInput = EmbeddableInput, - E extends Embeddable = Embeddable - >( - type: string, - savedObjectId: string - ): Promise; - /** * Adds a new embeddable to the container. `explicitInput` may partially specify the required embeddable input, * but the remainder must come from inherited container state. diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 6345c34b0dda2..c0fb98d2559db 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -26,6 +26,11 @@ import { TriggerContextMapping } from '../../../../ui_actions/public'; export interface EmbeddableInput { viewMode?: ViewMode; title?: string; + /** + * Note this is not a saved object id. It is used to uniquely identify this + * Embeddable instance from others (e.g. inside a container). It's possible to + * have two Embeddables where everything else is the same but the id. + */ id: string; lastReloadRequestTime?: number; hidePanelTitles?: boolean; @@ -44,6 +49,8 @@ export interface EmbeddableInput { * Whether this embeddable should not execute triggers. */ disableTriggers?: boolean; + + [key: string]: unknown; } export interface EmbeddableOutput { diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts index 4d6ab37a50c05..ad8b9c35e60be 100644 --- a/src/plugins/embeddable/public/lib/embeddables/index.ts +++ b/src/plugins/embeddable/public/lib/embeddables/index.ts @@ -25,3 +25,4 @@ export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable'; export { withEmbeddableSubscription } from './with_subscription'; export { EmbeddableFactoryRenderer } from './embeddable_factory_renderer'; export { EmbeddableRoot } from './embeddable_root'; +export * from './saved_object_embeddable'; diff --git a/src/plugins/embeddable/public/lib/embeddables/saved_object_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/saved_object_embeddable.ts new file mode 100644 index 0000000000000..6ca1800b16de4 --- /dev/null +++ b/src/plugins/embeddable/public/lib/embeddables/saved_object_embeddable.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { EmbeddableInput } from '..'; + +export interface SavedObjectEmbeddableInput extends EmbeddableInput { + savedObjectId: string; +} + +export function isSavedObjectEmbeddableInput( + input: EmbeddableInput | SavedObjectEmbeddableInput +): input is SavedObjectEmbeddableInput { + return (input as SavedObjectEmbeddableInput).savedObjectId !== undefined; +} diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index 50d8bcef8506c..5bf3f69a95c30 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -33,6 +33,7 @@ import { EmbeddableStart } from 'src/plugins/embeddable/public'; import { IContainer } from '../../../../containers'; import { EmbeddableFactoryNotFoundError } from '../../../../errors'; import { SavedObjectFinderCreateNew } from './saved_object_finder_create_new'; +import { SavedObjectEmbeddableInput } from '../../../../embeddables'; interface Props { onClose: () => void; @@ -98,8 +99,18 @@ export class AddPanelFlyout extends React.Component { } }; - public onAddPanel = async (id: string, type: string, name: string) => { - this.props.container.addSavedObjectEmbeddable(type, id); + public onAddPanel = async (savedObjectId: string, savedObjectType: string, name: string) => { + const factoryForSavedObjectType = [...this.props.getAllFactories()].find( + factory => factory.savedObjectMetaData && factory.savedObjectMetaData.type === savedObjectType + ); + if (!factoryForSavedObjectType) { + throw new EmbeddableFactoryNotFoundError(savedObjectType); + } + + this.props.container.addNewEmbeddable( + factoryForSavedObjectType.type, + { savedObjectId } + ); this.showToast(name); }; diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts index 87076399465d3..1aae43550ec6f 100644 --- a/src/plugins/embeddable/public/tests/container.test.ts +++ b/src/plugins/embeddable/public/tests/container.test.ts @@ -641,8 +641,7 @@ test('container stores ErrorEmbeddables when a saved object cannot be found', as panels: { '123': { type: 'vis', - explicitInput: { id: '123' }, - savedObjectId: '456', + explicitInput: { id: '123', savedObjectId: '456' }, }, }, viewMode: ViewMode.EDIT, @@ -663,8 +662,7 @@ test('ErrorEmbeddables get updated when parent does', async done => { panels: { '123': { type: 'vis', - explicitInput: { id: '123' }, - savedObjectId: '456', + explicitInput: { id: '123', savedObjectId: '456' }, }, }, viewMode: ViewMode.EDIT, diff --git a/src/plugins/expressions/server/legacy.ts b/src/plugins/expressions/server/legacy.ts index 17aa1c66a6835..1487f9f6734e9 100644 --- a/src/plugins/expressions/server/legacy.ts +++ b/src/plugins/expressions/server/legacy.ts @@ -26,7 +26,7 @@ import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common import Boom from 'boom'; import { schema } from '@kbn/config-schema'; -import { CoreSetup, Logger } from 'src/core/server'; +import { CoreSetup, Logger, APICaller } from 'src/core/server'; import { ExpressionsServerSetupDependencies } from './plugin'; import { typeSpecs, ExpressionType } from '../common'; import { serializeProvider } from '../common'; @@ -97,7 +97,10 @@ export const createLegacyServerEndpoints = ( * @param {*} handlers - The Canvas handlers * @param {*} fnCall - Describes the function being run `{ functionName, args, context }` */ - async function runFunction(handlers: any, fnCall: any) { + async function runFunction( + handlers: { environment: string; elasticsearchClient: APICaller }, + fnCall: any + ) { const { functionName, args, context } = fnCall; const { deserialize } = serializeProvider(registries.types.toJS()); const fnDef = registries.serverFunctions.toJS()[functionName]; @@ -112,18 +115,14 @@ export const createLegacyServerEndpoints = ( * results back using ND-JSON. */ plugins.bfetch.addBatchProcessingRoute(`/api/interpreter/fns`, request => { - const scopedClient = core.elasticsearch.dataClient.asScoped(request); - const handlers = { - environment: 'server', - elasticsearchClient: async ( - endpoint: string, - clientParams: Record = {}, - options?: any - ) => scopedClient.callAsCurrentUser(endpoint, clientParams, options), - }; - return { onBatchItem: async (fnCall: any) => { + const [coreStart] = await core.getStartServices(); + const handlers = { + environment: 'server', + elasticsearchClient: coreStart.elasticsearch.legacy.client.asScoped(request) + .callAsCurrentUser, + }; const result = await runFunction(handlers, fnCall); if (typeof result === 'undefined') { throw new Error(`Function ${fnCall.functionName} did not return anything.`); diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index f532c2c8aa219..feaa1f6a60e2f 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -63,9 +63,11 @@ export interface Action { isCompatible(context: Context): Promise; /** - * If this returns something truthy, this is used in addition to the `execute` method when clicked. + * If this returns something truthy, this will be used as [href] attribute on a link if possible (e.g. in context menu item) + * to support right click -> open in a new tab behavior. + * For regular click navigation is prevented and `execute()` takes control. */ - getHref?(context: Context): string | undefined; + getHref?(context: Context): Promise; /** * Executes the action. diff --git a/src/plugins/ui_actions/public/actions/action_definition.ts b/src/plugins/ui_actions/public/actions/action_definition.ts index 3eaa13572a826..79fda78401abd 100644 --- a/src/plugins/ui_actions/public/actions/action_definition.ts +++ b/src/plugins/ui_actions/public/actions/action_definition.ts @@ -63,7 +63,7 @@ export interface ActionDefinition { /** * If this returns something truthy, this is used in addition to the `execute` method when clicked. */ - getHref?(context: ActionContextMapping[T]): string | undefined; + getHref?(context: ActionContextMapping[T]): Promise; /** * Executes the action. diff --git a/src/plugins/ui_actions/public/actions/create_action.ts b/src/plugins/ui_actions/public/actions/create_action.ts index 90a9415c0b497..cc66f221e4082 100644 --- a/src/plugins/ui_actions/public/actions/create_action.ts +++ b/src/plugins/ui_actions/public/actions/create_action.ts @@ -28,7 +28,6 @@ export function createAction(action: ActionDefinition): id: action.type, isCompatible: () => Promise.resolve(true), getDisplayName: () => '', - getHref: () => undefined, ...action, }; } diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx index 3dce2c1f4c257..d26740ffdf033 100644 --- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx +++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx @@ -71,7 +71,7 @@ async function buildEuiContextMenuPanelItems({ } items.push( - convertPanelActionToContextMenuItem({ + await convertPanelActionToContextMenuItem({ action, actionContext, closeMenu, @@ -88,9 +88,9 @@ async function buildEuiContextMenuPanelItems({ * * @param {ContextMenuAction} action * @param {Embeddable} embeddable - * @return {EuiContextMenuPanelItemDescriptor} + * @return {Promise} */ -function convertPanelActionToContextMenuItem({ +async function convertPanelActionToContextMenuItem({ action, actionContext, closeMenu, @@ -98,7 +98,7 @@ function convertPanelActionToContextMenuItem({ action: Action; actionContext: A; closeMenu: () => void; -}): EuiContextMenuPanelItemDescriptor { +}): Promise { const menuPanelItem: EuiContextMenuPanelItemDescriptor = { name: action.MenuItem ? React.createElement(uiToReactComponent(action.MenuItem), { @@ -110,13 +110,33 @@ function convertPanelActionToContextMenuItem({ 'data-test-subj': `embeddablePanelAction-${action.id}`, }; - menuPanelItem.onClick = () => { - action.execute(actionContext); + menuPanelItem.onClick = event => { + if (event.currentTarget instanceof HTMLAnchorElement) { + // from react-router's + if ( + !event.defaultPrevented && // onClick prevented default + event.button === 0 && // ignore everything but left clicks + (!event.currentTarget.target || event.currentTarget.target === '_self') && // let browser handle "target=_blank" etc. + !(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) // ignore clicks with modifier keys + ) { + event.preventDefault(); + action.execute(actionContext); + } else { + // let browser handle navigation + } + } else { + // not a link + action.execute(actionContext); + } + closeMenu(); }; - if (action.getHref && action.getHref(actionContext)) { - menuPanelItem.href = action.getHref(actionContext); + if (action.getHref) { + const href = await action.getHref(actionContext); + if (href) { + menuPanelItem.href = href; + } } return menuPanelItem; diff --git a/src/plugins/ui_actions/public/triggers/trigger_internal.ts b/src/plugins/ui_actions/public/triggers/trigger_internal.ts index 5b670df354f78..1fc92d7c0cb1b 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_internal.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_internal.ts @@ -55,13 +55,6 @@ export class TriggerInternal { action: Action, context: TriggerContextMapping[T] ) { - const href = action.getHref && action.getHref(context); - - if (href) { - window.location.href = href; - return; - } - await action.execute(context); } diff --git a/src/plugins/vis_type_timeseries/kibana.json b/src/plugins/vis_type_timeseries/kibana.json index d77f4ac92da16..305e159ac2505 100644 --- a/src/plugins/vis_type_timeseries/kibana.json +++ b/src/plugins/vis_type_timeseries/kibana.json @@ -3,5 +3,7 @@ "version": "8.0.0", "kibanaVersion": "kibana", "server": true, + "ui": true, + "requiredPlugins": ["data", "expressions", "visualizations"], "optionalPlugins": ["usageCollection"] } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss b/src/plugins/vis_type_timeseries/public/application/_mixins.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss rename to src/plugins/vis_type_timeseries/public/application/_mixins.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/_tvb_editor.scss b/src/plugins/vis_type_timeseries/public/application/_tvb_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/_tvb_editor.scss rename to src/plugins/vis_type_timeseries/public/application/_tvb_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/_variables.scss b/src/plugins/vis_type_timeseries/public/application/_variables.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/_variables.scss rename to src/plugins/vis_type_timeseries/public/application/_variables.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss b/src/plugins/vis_type_timeseries/public/application/components/_annotations_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss rename to src/plugins/vis_type_timeseries/public/application/components/_annotations_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_color_picker.scss b/src/plugins/vis_type_timeseries/public/application/components/_color_picker.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_color_picker.scss rename to src/plugins/vis_type_timeseries/public/application/components/_color_picker.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_color_rules.scss b/src/plugins/vis_type_timeseries/public/application/components/_color_rules.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_color_rules.scss rename to src/plugins/vis_type_timeseries/public/application/components/_color_rules.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_custom_color_picker.scss b/src/plugins/vis_type_timeseries/public/application/components/_custom_color_picker.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_custom_color_picker.scss rename to src/plugins/vis_type_timeseries/public/application/components/_custom_color_picker.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_error.scss b/src/plugins/vis_type_timeseries/public/application/components/_error.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_error.scss rename to src/plugins/vis_type_timeseries/public/application/components/_error.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_index.scss b/src/plugins/vis_type_timeseries/public/application/components/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_index.scss rename to src/plugins/vis_type_timeseries/public/application/components/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_markdown_editor.scss b/src/plugins/vis_type_timeseries/public/application/components/_markdown_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_markdown_editor.scss rename to src/plugins/vis_type_timeseries/public/application/components/_markdown_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_no_data.scss b/src/plugins/vis_type_timeseries/public/application/components/_no_data.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_no_data.scss rename to src/plugins/vis_type_timeseries/public/application/components/_no_data.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss b/src/plugins/vis_type_timeseries/public/application/components/_series_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss rename to src/plugins/vis_type_timeseries/public/application/components/_series_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_editor.scss b/src/plugins/vis_type_timeseries/public/application/components/_vis_editor.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_editor.scss rename to src/plugins/vis_type_timeseries/public/application/components/_vis_editor.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_editor_visualization.scss b/src/plugins/vis_type_timeseries/public/application/components/_vis_editor_visualization.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_editor_visualization.scss rename to src/plugins/vis_type_timeseries/public/application/components/_vis_editor_visualization.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_picker.scss b/src/plugins/vis_type_timeseries/public/application/components/_vis_picker.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_picker.scss rename to src/plugins/vis_type_timeseries/public/application/components/_vis_picker.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_with_splits.scss b/src/plugins/vis_type_timeseries/public/application/components/_vis_with_splits.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/_vis_with_splits.scss rename to src/plugins/vis_type_timeseries/public/application/components/_vis_with_splits.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/add_delete_buttons.js b/src/plugins/vis_type_timeseries/public/application/components/add_delete_buttons.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/add_delete_buttons.js rename to src/plugins/vis_type_timeseries/public/application/components/add_delete_buttons.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/add_delete_buttons.test.js b/src/plugins/vis_type_timeseries/public/application/components/add_delete_buttons.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/add_delete_buttons.test.js rename to src/plugins/vis_type_timeseries/public/application/components/add_delete_buttons.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/_agg_row.scss b/src/plugins/vis_type_timeseries/public/application/components/aggs/_agg_row.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/_agg_row.scss rename to src/plugins/vis_type_timeseries/public/application/components/aggs/_agg_row.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/_index.scss b/src/plugins/vis_type_timeseries/public/application/components/aggs/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/_index.scss rename to src/plugins/vis_type_timeseries/public/application/components/aggs/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_row.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_row.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_row.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/agg_row.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_select.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_select.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/aggs.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/aggs.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/calculation.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/calculation.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/cumulative_sum.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/cumulative_sum.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/derivative.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/derivative.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/field_select.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/field_select.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/math.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/metric_select.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/metric_select.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/moving_average.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/moving_average.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/index.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/index.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/index.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/index.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/multi_value_row.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/multi_value_row.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/multi_value_row.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/multi_value_row.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank_values.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank_values.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank_values.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_rank/percentile_rank_values.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_ui.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_ui.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_ui.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/percentile_ui.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_only.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_only.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_rate.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_rate.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/serial_diff.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/serial_diff.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/series_agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/series_agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/series_agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/series_agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/static.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/static.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/static.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/static.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/std_agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_sibling.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_sibling.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/temporary_unsupported_agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/temporary_unsupported_agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/temporary_unsupported_agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/temporary_unsupported_agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/unsupported_agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/unsupported_agg.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/unsupported_agg.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/unsupported_agg.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/vars.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/vars.js rename to src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/annotations_editor.js b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/annotations_editor.js rename to src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_picker.js b/src/plugins/vis_type_timeseries/public/application/components/color_picker.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/color_picker.js rename to src/plugins/vis_type_timeseries/public/application/components/color_picker.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_picker.test.js b/src/plugins/vis_type_timeseries/public/application/components/color_picker.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/color_picker.test.js rename to src/plugins/vis_type_timeseries/public/application/components/color_picker.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.js b/src/plugins/vis_type_timeseries/public/application/components/color_rules.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.js rename to src/plugins/vis_type_timeseries/public/application/components/color_rules.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.test.js b/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.test.js rename to src/plugins/vis_type_timeseries/public/application/components/color_rules.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js b/src/plugins/vis_type_timeseries/public/application/components/custom_color_picker.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js rename to src/plugins/vis_type_timeseries/public/application/components/custom_color_picker.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/data_format_picker.js b/src/plugins/vis_type_timeseries/public/application/components/data_format_picker.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/data_format_picker.js rename to src/plugins/vis_type_timeseries/public/application/components/data_format_picker.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/error.js b/src/plugins/vis_type_timeseries/public/application/components/error.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/error.js rename to src/plugins/vis_type_timeseries/public/application/components/error.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/__snapshots__/icon_select.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/icon_select/__snapshots__/icon_select.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/__snapshots__/icon_select.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/components/icon_select/__snapshots__/icon_select.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/icon_select.js b/src/plugins/vis_type_timeseries/public/application/components/icon_select/icon_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/icon_select.js rename to src/plugins/vis_type_timeseries/public/application/components/icon_select/icon_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/icon_select.test.js b/src/plugins/vis_type_timeseries/public/application/components/icon_select/icon_select.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/icon_select/icon_select.test.js rename to src/plugins/vis_type_timeseries/public/application/components/icon_select/icon_select.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js rename to src/plugins/vis_type_timeseries/public/application/components/index_pattern.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/agg_to_component.js b/src/plugins/vis_type_timeseries/public/application/components/lib/agg_to_component.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/agg_to_component.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/agg_to_component.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.js b/src/plugins/vis_type_timeseries/public/application/components/lib/calculate_siblings.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/calculate_siblings.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/calculate_siblings.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/calculate_siblings.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/charts.js b/src/plugins/vis_type_timeseries/public/application/components/lib/charts.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/charts.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/charts.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.js b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js similarity index 82% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js index 79cbe98b3d3db..3eae18111bb4d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.js @@ -18,7 +18,6 @@ */ import uuid from 'uuid'; -import _ from 'lodash'; const newFn = () => ({ id: uuid.v1() }); @@ -30,9 +29,7 @@ export function handleChange(props, doc) { if (row.id === doc.id) return doc; return row; }); - if (_.isFunction(props.onChange)) { - props.onChange(_.assign({}, model, part)); - } + props.onChange?.({ ...model, ...part }); } export function handleDelete(props, doc) { @@ -40,20 +37,15 @@ export function handleDelete(props, doc) { const collection = model[name] || []; const part = {}; part[name] = collection.filter(row => row.id !== doc.id); - if (_.isFunction(props.onChange)) { - props.onChange(_.assign({}, model, part)); - } + props.onChange?.({ ...model, ...part }); } export function handleAdd(props, fn = newFn) { - if (!_.isFunction(fn)) fn = newFn; const { model, name } = props; const collection = model[name] || []; const part = {}; part[name] = collection.concat([fn()]); - if (_.isFunction(props.onChange)) { - props.onChange(_.assign({}, model, part)); - } + props.onChange?.({ ...model, ...part }); } export const collectionActions = { handleAdd, handleDelete, handleChange }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/collection_actions.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.js b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_change_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_change_handler.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_change_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_change_handler.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js similarity index 92% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js index fd432add43295..3bc8fd87f73dd 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.js @@ -25,8 +25,6 @@ export const createNumberHandler = handleChange => { if (!detectIE() || e.keyCode === 13) e.preventDefault(); const value = Number(_.get(e, 'target.value', defaultValue)); - if (_.isFunction(handleChange)) { - return handleChange({ [name]: value }); - } + return handleChange?.({ [name]: value }); }; }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_number_handler.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.js similarity index 86% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.js index 30937b97d353f..cfc6a4dc57871 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.js @@ -21,10 +21,8 @@ import _ from 'lodash'; export const createSelectHandler = handleChange => { return name => selectedOptions => { - if (_.isFunction(handleChange)) { - return handleChange({ - [name]: _.get(selectedOptions, '[0].value', null), - }); - } + return handleChange?.({ + [name]: _.get(selectedOptions, '[0].value', null), + }); }; }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_select_handler.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js similarity index 92% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js index 8e2624488d954..82cc071b59d51 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.js @@ -26,8 +26,6 @@ export const createTextHandler = handleChange => { if (!detectIE() || e.keyCode === 13) e.preventDefault(); const value = _.get(e, 'target.value', defaultValue); - if (_.isFunction(handleChange)) { - return handleChange({ [name]: value }); - } + return handleChange?.({ [name]: value }); }; }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_text_handler.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_xaxis_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_xaxis_formatter.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_xaxis_formatter.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_xaxis_formatter.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/detect_ie.js b/src/plugins/vis_type_timeseries/public/application/components/lib/detect_ie.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/detect_ie.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/detect_ie.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/durations.js b/src/plugins/vis_type_timeseries/public/application/components/lib/durations.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/durations.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/durations.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/durations.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/durations.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/durations.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/durations.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js similarity index 94% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js index 26723da5ab5c9..972f937ad109d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_default_query_language.js @@ -17,7 +17,7 @@ * under the License. */ -import { getUISettings } from '../../services'; +import { getUISettings } from '../../../services'; export function getDefaultQueryLanguage() { return getUISettings().get('search:queryLanguage'); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_display_name.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_display_name.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_display_name.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_display_name.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_interval.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_interval.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/new_metric_agg_fn.js b/src/plugins/vis_type_timeseries/public/application/components/lib/new_metric_agg_fn.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/new_metric_agg_fn.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/new_metric_agg_fn.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/new_series_fn.js b/src/plugins/vis_type_timeseries/public/application/components/lib/new_series_fn.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/new_series_fn.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/new_series_fn.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.js b/src/plugins/vis_type_timeseries/public/application/components/lib/re_id_series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/re_id_series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/re_id_series.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/re_id_series.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/reorder.js b/src/plugins/vis_type_timeseries/public/application/components/lib/reorder.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/reorder.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/reorder.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.js b/src/plugins/vis_type_timeseries/public/application/components/lib/replace_vars.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/replace_vars.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/replace_vars.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/replace_vars.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/series_change_handler.js b/src/plugins/vis_type_timeseries/public/application/components/lib/series_change_handler.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/series_change_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/series_change_handler.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/stacked.js b/src/plugins/vis_type_timeseries/public/application/components/lib/stacked.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/stacked.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/stacked.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js similarity index 97% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js index 3ab8e0f6b885e..fd316e66a16fb 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js @@ -20,7 +20,7 @@ import handlebars from 'handlebars/dist/handlebars'; import { isNumber } from 'lodash'; import { inputFormats, outputFormats, isDuration } from '../lib/durations'; -import { getFieldFormats } from '../../services'; +import { getFieldFormats } from '../../../services'; export const createTickFormatter = (format = '0,0.[00]', template, getConfig = null) => { const fieldFormats = getFieldFormats(); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js similarity index 98% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js index e87cba126bb46..ee10b254a9e15 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.test.js @@ -19,7 +19,7 @@ import { createTickFormatter } from './tick_formatter'; import { getFieldFormatsRegistry } from '../../../../../../test_utils/public/stub_field_formats'; -import { setFieldFormats } from '../../services'; +import { setFieldFormats } from '../../../services'; const mockUiSettings = { get: item => { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/markdown_editor.js b/src/plugins/vis_type_timeseries/public/application/components/markdown_editor.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/markdown_editor.js rename to src/plugins/vis_type_timeseries/public/application/components/markdown_editor.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/no_data.js b/src/plugins/vis_type_timeseries/public/application/components/no_data.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/no_data.js rename to src/plugins/vis_type_timeseries/public/application/components/no_data.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/_index.scss b/src/plugins/vis_type_timeseries/public/application/components/panel_config/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/_index.scss rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/_panel_config.scss b/src/plugins/vis_type_timeseries/public/application/components/panel_config/_panel_config.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/_panel_config.scss rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/_panel_config.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/markdown.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/markdown.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/timeseries.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/timeseries.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js rename to src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js b/src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js rename to src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/series.js b/src/plugins/vis_type_timeseries/public/application/components/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/series.js rename to src/plugins/vis_type_timeseries/public/application/components/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/series_config.js b/src/plugins/vis_type_timeseries/public/application/components/series_config.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/series_config.js rename to src/plugins/vis_type_timeseries/public/application/components/series_config.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/series_drag_handler.js b/src/plugins/vis_type_timeseries/public/application/components/series_drag_handler.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/series_drag_handler.js rename to src/plugins/vis_type_timeseries/public/application/components/series_drag_handler.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/series_editor.js b/src/plugins/vis_type_timeseries/public/application/components/series_editor.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/series_editor.js rename to src/plugins/vis_type_timeseries/public/application/components/series_editor.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js b/src/plugins/vis_type_timeseries/public/application/components/split.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/split.js rename to src/plugins/vis_type_timeseries/public/application/components/split.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/__snapshots__/terms.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/__snapshots__/terms.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/everything.js b/src/plugins/vis_type_timeseries/public/application/components/splits/everything.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/everything.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/everything.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filter.js b/src/plugins/vis_type_timeseries/public/application/components/splits/filter.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filter.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/filter.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filter_items.js b/src/plugins/vis_type_timeseries/public/application/components/splits/filter_items.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filter_items.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/filter_items.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filters.js b/src/plugins/vis_type_timeseries/public/application/components/splits/filters.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/filters.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/filters.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/group_by_select.js b/src/plugins/vis_type_timeseries/public/application/components/splits/group_by_select.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/group_by_select.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/group_by_select.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/terms.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/terms.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/terms.test.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/terms.test.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/terms.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/unsupported_split.js b/src/plugins/vis_type_timeseries/public/application/components/splits/unsupported_split.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/splits/unsupported_split.js rename to src/plugins/vis_type_timeseries/public/application/components/splits/unsupported_split.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/bomb_icon.js b/src/plugins/vis_type_timeseries/public/application/components/svg/bomb_icon.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/svg/bomb_icon.js rename to src/plugins/vis_type_timeseries/public/application/components/svg/bomb_icon.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/svg/fire_icon.js b/src/plugins/vis_type_timeseries/public/application/components/svg/fire_icon.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/svg/fire_icon.js rename to src/plugins/vis_type_timeseries/public/application/components/svg/fire_icon.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js similarity index 99% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_editor.js index b4845696fc8c0..7075e86eb56bf 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js @@ -30,7 +30,7 @@ import { createBrushHandler } from '../lib/create_brush_handler'; import { fetchFields } from '../lib/fetch_fields'; import { extractIndexPatterns } from '../../../../../plugins/vis_type_timeseries/common/extract_index_patterns'; import { esKuery } from '../../../../../plugins/data/public'; -import { getSavedObjectsClient, getUISettings, getDataStart, getCoreStart } from '../services'; +import { getSavedObjectsClient, getUISettings, getDataStart, getCoreStart } from '../../services'; import { CoreStartContextProvider } from '../contexts/query_input_bar_context'; import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; @@ -96,7 +96,7 @@ export class VisEditor extends Component { return true; }; - handleChange = async partialModel => { + handleChange = partialModel => { if (isEmpty(partialModel)) { return; } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_picker.js b/src/plugins/vis_type_timeseries/public/application/components/vis_picker.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_picker.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_picker.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_index.scss b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_index.scss rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_vis_types.scss b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_vis_types.scss rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/_markdown.scss b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/_markdown.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/_markdown.scss rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/_markdown.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/is_sortable.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/is_sortable.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/is_sortable.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/table/is_sortable.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js similarity index 99% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js index 1fe9358cbfea9..c6f1db149928c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js @@ -27,7 +27,7 @@ import { EuiToolTip, EuiIcon } from '@elastic/eui'; import { replaceVars } from '../../lib/replace_vars'; import { fieldFormats } from '../../../../../../../plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getFieldFormats } from '../../../services'; +import { getFieldFormats } from '../../../../services'; import { METRIC_TYPES } from '../../../../../../../plugins/vis_type_timeseries/common/metric_types'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/config.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js similarity index 99% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js index f559bc38b6c58..1004f7ee96a54 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js @@ -34,7 +34,7 @@ import { getInterval } from '../../lib/get_interval'; import { areFieldsDifferent } from '../../lib/charts'; import { createXaxisFormatter } from '../../lib/create_xaxis_formatter'; import { STACKED_OPTIONS } from '../../../visualizations/constants'; -import { getCoreStart, getUISettings } from '../../../services'; +import { getCoreStart, getUISettings } from '../../../../services'; export class TimeseriesVisualization extends Component { static propTypes = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/top_n/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/top_n/series.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/top_n/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/top_n/vis.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_with_splits.js b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/vis_with_splits.js rename to src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/visualization.js b/src/plugins/vis_type_timeseries/public/application/components/visualization.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/visualization.js rename to src/plugins/vis_type_timeseries/public/application/components/visualization.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.js b/src/plugins/vis_type_timeseries/public/application/components/yes_no.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.js rename to src/plugins/vis_type_timeseries/public/application/components/yes_no.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.test.js b/src/plugins/vis_type_timeseries/public/application/components/yes_no.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.test.js rename to src/plugins/vis_type_timeseries/public/application/components/yes_no.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/form_validation_context.js b/src/plugins/vis_type_timeseries/public/application/contexts/form_validation_context.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/contexts/form_validation_context.js rename to src/plugins/vis_type_timeseries/public/application/contexts/form_validation_context.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts b/src/plugins/vis_type_timeseries/public/application/contexts/query_input_bar_context.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts rename to src/plugins/vis_type_timeseries/public/application/contexts/query_input_bar_context.ts diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/vis_data_context.js b/src/plugins/vis_type_timeseries/public/application/contexts/vis_data_context.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/contexts/vis_data_context.js rename to src/plugins/vis_type_timeseries/public/application/contexts/vis_data_context.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js b/src/plugins/vis_type_timeseries/public/application/editor_controller.js similarity index 94% rename from src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js rename to src/plugins/vis_type_timeseries/public/application/editor_controller.js index 16a6348712065..af50d3a06d1fc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js +++ b/src/plugins/vis_type_timeseries/public/application/editor_controller.js @@ -20,7 +20,8 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { fetchIndexPatternFields } from './lib/fetch_fields'; -import { getSavedObjectsClient, getUISettings, getI18n } from './services'; +import { getSavedObjectsClient, getUISettings, getI18n } from '../services'; +import { VisEditor } from './components/vis_editor'; export class EditorController { constructor(el, vis, eventEmitter, embeddableHandler) { @@ -55,19 +56,14 @@ export class EditorController { this.state.isLoaded = true; }; - getComponent = () => { - return this.state.vis.type.editorConfig.component; - }; - async render(params) { - const Component = this.getComponent(); const I18nContext = getI18n().Context; !this.state.isLoaded && (await this.fetchDefaultParams()); render( - getUISettings().get('theme:darkMode'); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js b/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js rename to src/plugins/vis_type_timeseries/public/application/lib/validate_interval.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/chart.js b/src/plugins/vis_type_timeseries/public/application/visualizations/constants/chart.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/chart.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/constants/chart.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/icons.js b/src/plugins/vis_type_timeseries/public/application/visualizations/constants/icons.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/icons.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/constants/icons.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/constants/index.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/constants/index.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/constants/index.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/active_cursor.js b/src/plugins/vis_type_timeseries/public/application/visualizations/lib/active_cursor.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/active_cursor.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/lib/active_cursor.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/calc_dimensions.js b/src/plugins/vis_type_timeseries/public/application/visualizations/lib/calc_dimensions.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/calc_dimensions.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/lib/calc_dimensions.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/calculate_coordinates.js b/src/plugins/vis_type_timeseries/public/application/visualizations/lib/calculate_coordinates.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/calculate_coordinates.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/lib/calculate_coordinates.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/get_value_by.js b/src/plugins/vis_type_timeseries/public/application/visualizations/lib/get_value_by.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/lib/get_value_by.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/lib/get_value_by.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_annotation.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_annotation.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_annotation.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_annotation.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_gauge.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_gauge.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_gauge.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_gauge.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_index.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_index.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_index.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_index.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_metric.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_metric.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_metric.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_metric.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_top_n.scss b/src/plugins/vis_type_timeseries/public/application/visualizations/views/_top_n.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/_top_n.scss rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/_top_n.scss diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/annotation.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/annotation.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/annotation.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/annotation.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge_vis.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge_vis.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/metric.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/metric.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/__mocks__/@elastic/charts.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/__mocks__/@elastic/charts.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js similarity index 99% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index 3ce3aae2649e1..b1c3c7ac6b67a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -33,7 +33,7 @@ import { import { EuiIcon } from '@elastic/eui'; import { getTimezone } from '../../../lib/get_timezone'; import { eventBus, ACTIVE_CURSOR } from '../../lib/active_cursor'; -import { getUISettings } from '../../../services'; +import { getUISettings } from '../../../../services'; import { GRID_LINE_CONFIG, ICON_TYPES_MAP, STACKED_OPTIONS } from '../../constants'; import { AreaSeriesDecorator } from './decorators/area_decorator'; import { BarSeriesDecorator } from './decorators/bar_decorator'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/charts.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/charts.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/charts.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/model/charts.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_styles.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_styles.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_styles.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_styles.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/stack_format.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/stack_format.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/stack_format.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/stack_format.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/stack_format.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/stack_format.test.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/stack_format.test.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/stack_format.test.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.test.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.test.ts rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/top_n.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/top_n.js rename to src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/index.ts b/src/plugins/vis_type_timeseries/public/index.ts similarity index 93% rename from src/legacy/core_plugins/vis_type_timeseries/public/index.ts rename to src/plugins/vis_type_timeseries/public/index.ts index 16b099ba16ae9..fbf4a81b6ad1b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/index.ts +++ b/src/plugins/vis_type_timeseries/public/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { PluginInitializerContext } from '../../../../core/public'; +import { PluginInitializerContext } from '../../../core/public'; import { MetricsPlugin as Plugin } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts b/src/plugins/vis_type_timeseries/public/metrics_fn.ts similarity index 92% rename from src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts rename to src/plugins/vis_type_timeseries/public/metrics_fn.ts index 1f9cbecc2a354..008b13cce6565 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_fn.ts @@ -19,12 +19,8 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - ExpressionFunctionDefinition, - KibanaContext, - Render, -} from '../../../../plugins/expressions/public'; -import { PersistedState } from '../../../../plugins/visualizations/public'; +import { ExpressionFunctionDefinition, KibanaContext, Render } from '../../expressions/public'; +import { PersistedState } from '../../visualizations/public'; // @ts-ignore import { metricsRequestHandler } from './request_handler'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts similarity index 85% rename from src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts rename to src/plugins/vis_type_timeseries/public/metrics_type.ts index 1db35c406eb13..f83881a619a59 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -21,11 +21,10 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { metricsRequestHandler } from './request_handler'; +import { EditorController } from './application'; // @ts-ignore -import { EditorController } from './editor_controller'; -// @ts-ignore -import { PANEL_TYPES } from '../../../../plugins/vis_type_timeseries/common/panel_types'; -import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/public'; +import { PANEL_TYPES } from '../common/panel_types'; +import { defaultFeedbackMessage } from '../../kibana_utils/public'; export const metricsVisDefinition = { name: 'metrics', @@ -69,12 +68,9 @@ export const metricsVisDefinition = { show_legend: 1, show_grid: 1, }, - component: require('./components/vis_editor').VisEditor, + component: require('./application/components/vis_editor').VisEditor, }, editor: EditorController, - editorConfig: { - component: require('./components/vis_editor').VisEditor, - }, options: { showQueryBar: false, showFilterBar: false, diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts b/src/plugins/vis_type_timeseries/public/plugin.ts similarity index 89% rename from src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts rename to src/plugins/vis_type_timeseries/public/plugin.ts index 0310ecf6cfd87..e6c92c31450e7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts +++ b/src/plugins/vis_type_timeseries/public/plugin.ts @@ -16,9 +16,12 @@ * specific language governing permissions and limitations * under the License. */ + +import './application/index.scss'; + import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricsFn } from './metrics_fn'; import { metricsVisDefinition } from './metrics_type'; @@ -30,7 +33,7 @@ import { setCoreStart, setDataStart, } from './services'; -import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { DataPublicPluginStart } from '../../data/public'; /** @internal */ export interface MetricsPluginSetupDependencies { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js b/src/plugins/vis_type_timeseries/public/request_handler.js similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js rename to src/plugins/vis_type_timeseries/public/request_handler.js index 2cac1567a6eb7..bd6c6d9553930 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js +++ b/src/plugins/vis_type_timeseries/public/request_handler.js @@ -17,8 +17,7 @@ * under the License. */ -import { validateInterval } from './lib/validate_interval'; -import { getTimezone } from './lib/get_timezone'; +import { getTimezone, validateInterval } from './application'; import { getUISettings, getDataStart, getCoreStart } from './services'; export const metricsRequestHandler = async ({ diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/services.ts b/src/plugins/vis_type_timeseries/public/services.ts similarity index 90% rename from src/legacy/core_plugins/vis_type_timeseries/public/services.ts rename to src/plugins/vis_type_timeseries/public/services.ts index 64ee897247b89..d93a376584eac 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/services.ts +++ b/src/plugins/vis_type_timeseries/public/services.ts @@ -18,8 +18,8 @@ */ import { I18nStart, SavedObjectsStart, IUiSettingsClient, CoreStart } from 'src/core/public'; -import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; -import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { createGetterSetter } from '../../kibana_utils/public'; +import { DataPublicPluginStart } from '../../data/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); diff --git a/src/plugins/vis_type_xy/kibana.json b/src/plugins/vis_type_xy/kibana.json new file mode 100644 index 0000000000000..ca02da45e9112 --- /dev/null +++ b/src/plugins/vis_type_xy/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "visTypeXy", + "version": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["charts", "expressions", "visualizations"] +} diff --git a/src/legacy/core_plugins/vis_type_xy/public/index.ts b/src/plugins/vis_type_xy/public/index.ts similarity index 89% rename from src/legacy/core_plugins/vis_type_xy/public/index.ts rename to src/plugins/vis_type_xy/public/index.ts index 218dc8aa8a683..9af75ce9059e9 100644 --- a/src/legacy/core_plugins/vis_type_xy/public/index.ts +++ b/src/plugins/vis_type_xy/public/index.ts @@ -17,9 +17,11 @@ * under the License. */ -import { PluginInitializerContext } from '../../../../core/public'; +import { PluginInitializerContext } from '../../../core/public'; import { VisTypeXyPlugin as Plugin } from './plugin'; +export { VisTypeXyPluginSetup } from './plugin'; + export function plugin(initializerContext: PluginInitializerContext) { return new Plugin(initializerContext); } diff --git a/src/legacy/core_plugins/vis_type_xy/public/plugin.ts b/src/plugins/vis_type_xy/public/plugin.ts similarity index 85% rename from src/legacy/core_plugins/vis_type_xy/public/plugin.ts rename to src/plugins/vis_type_xy/public/plugin.ts index ab01b6b3153fb..667018c1e6e30 100644 --- a/src/legacy/core_plugins/vis_type_xy/public/plugin.ts +++ b/src/plugins/vis_type_xy/public/plugin.ts @@ -25,18 +25,18 @@ import { PluginInitializerContext, } from 'kibana/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { - VisualizationsSetup, - VisualizationsStart, -} from '../../../../plugins/visualizations/public'; -import { ChartsPluginSetup } from '../../../../plugins/charts/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; +import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; +import { ChartsPluginSetup } from '../../charts/public'; export interface VisTypeXyDependencies { uiSettings: IUiSettingsClient; charts: ChartsPluginSetup; } +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface VisTypeXyPluginSetup {} + /** @internal */ export interface VisTypeXyPluginSetupDependencies { expressions: ReturnType; @@ -53,7 +53,7 @@ export interface VisTypeXyPluginStartDependencies { type VisTypeXyCoreSetup = CoreSetup; /** @internal */ -export class VisTypeXyPlugin implements Plugin { +export class VisTypeXyPlugin implements Plugin { constructor(public initializerContext: PluginInitializerContext) {} public async setup( @@ -77,6 +77,8 @@ export class VisTypeXyPlugin implements Plugin { visTypeDefinitions.forEach((vis: any) => visualizations.createBaseVisualization(vis(visualizationDependencies)) ); + + return {}; } public start(core: CoreStart, deps: VisTypeXyPluginStartDependencies) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts b/src/plugins/vis_type_xy/server/index.ts similarity index 59% rename from src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts rename to src/plugins/vis_type_xy/server/index.ts index 42f116701be51..afc879dc9c845 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts +++ b/src/plugins/vis_type_xy/server/index.ts @@ -17,17 +17,13 @@ * under the License. */ -import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from 'ui/new_platform'; -import { MetricsPluginSetupDependencies } from './plugin'; -import { plugin } from '.'; +import { schema } from '@kbn/config-schema'; -const plugins: Readonly = { - expressions: npSetup.plugins.expressions, - visualizations: npSetup.plugins.visualizations, +export const config = { + schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), }; -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, plugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); +export const plugin = () => ({ + setup() {}, + start() {}, +}); diff --git a/test/examples/embeddables/adding_children.ts b/test/examples/embeddables/adding_children.ts index 110b8ce573332..5fe88b5dd33f0 100644 --- a/test/examples/embeddables/adding_children.ts +++ b/test/examples/embeddables/adding_children.ts @@ -23,6 +23,7 @@ import { PluginFunctionalProviderContext } from 'test/plugin_functional/services // eslint-disable-next-line import/no-default-export export default function({ getService }: PluginFunctionalProviderContext) { const testSubjects = getService('testSubjects'); + const flyout = getService('flyout'); describe('creating and adding children', () => { before(async () => { @@ -39,5 +40,15 @@ export default function({ getService }: PluginFunctionalProviderContext) { const tasks = await testSubjects.getVisibleTextAll('todoEmbeddableTask'); expect(tasks).to.eql(['Goes out on Wednesdays!', 'new task']); }); + + it('Can add a child backed off a saved object', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelAction-ACTION_ADD_PANEL'); + await testSubjects.click('savedObjectTitleGarbage'); + await testSubjects.moveMouseTo('euiFlyoutCloseButton'); + await flyout.ensureClosed('dashboardAddPanel'); + const tasks = await testSubjects.getVisibleTextAll('todoEmbeddableTask'); + expect(tasks).to.eql(['Goes out on Wednesdays!', 'new task', 'Take the garbage out']); + }); }); } diff --git a/test/functional/apps/discover/_discover_histogram.js b/test/functional/apps/discover/_discover_histogram.js index f815c505a8c27..eeef3333aab0f 100644 --- a/test/functional/apps/discover/_discover_histogram.js +++ b/test/functional/apps/discover/_discover_histogram.js @@ -66,9 +66,8 @@ export default function({ getService, getPageObjects }) { }); it('should visualize monthly data with different day intervals', async () => { - //Nov 1, 2017 @ 01:00:00.000 - Mar 21, 2018 @ 02:00:00.000 - const fromTime = '2017-11-01 00:00:00.000'; - const toTime = '2018-03-21 00:00:00.000'; + const fromTime = 'Nov 01, 2017 @ 00:00:00.000'; + const toTime = 'Mar 21, 2018 @ 00:00:00.000'; await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.setChartInterval('Monthly'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -76,24 +75,25 @@ export default function({ getService, getPageObjects }) { expect(chartCanvasExist).to.be(true); }); it('should visualize weekly data with within DST changes', async () => { - //Nov 1, 2017 @ 01:00:00.000 - Mar 21, 2018 @ 02:00:00.000 - const fromTime = '2018-03-01 00:00:00.000'; - const toTime = '2018-05-01 00:00:00.000'; + const fromTime = 'Mar 01, 2018 @ 00:00:00.000'; + const toTime = 'May 01, 2018 @ 00:00:00.000'; await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.setChartInterval('Weekly'); await PageObjects.header.waitUntilLoadingHasFinished(); const chartCanvasExist = await elasticChart.canvasExists(); expect(chartCanvasExist).to.be(true); }); - it('should visualize monthly data with different years Scaled to 30d', async () => { - //Nov 1, 2017 @ 01:00:00.000 - Mar 21, 2018 @ 02:00:00.000 - const fromTime = '2010-01-01 00:00:00.000'; - const toTime = '2018-03-21 00:00:00.000'; + it('should visualize monthly data with different years Scaled to 30 days', async () => { + const fromTime = 'Jan 01, 2010 @ 00:00:00.000'; + const toTime = 'Mar 21, 2019 @ 00:00:00.000'; + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.setChartInterval('Daily'); await PageObjects.header.waitUntilLoadingHasFinished(); const chartCanvasExist = await elasticChart.canvasExists(); expect(chartCanvasExist).to.be(true); + const chartIntervalScaledDesc = await PageObjects.discover.getChartIntervalScaledToDesc(); + expect(chartIntervalScaledDesc).to.be('Scaled to 30 days'); }); }); } diff --git a/test/functional/apps/discover/_saved_queries.js b/test/functional/apps/discover/_saved_queries.js index 3cdaccf32cdc3..76f3a3aea365f 100644 --- a/test/functional/apps/discover/_saved_queries.js +++ b/test/functional/apps/discover/_saved_queries.js @@ -147,6 +147,25 @@ export default function({ getService, getPageObjects }) { await savedQueryManagementComponent.clearCurrentlyLoadedQuery(); expect(await queryBar.getQueryString()).to.eql(''); }); + + // https://github.com/elastic/kibana/issues/63505 + it('allows clearing if non default language was remembered in localstorage', async () => { + await queryBar.switchQueryLanguage('lucene'); + await PageObjects.common.navigateToApp('discover'); // makes sure discovered is reloaded without any state in url + await queryBar.expectQueryLanguageOrFail('lucene'); // make sure lucene is remembered after refresh (comes from localstorage) + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + await queryBar.expectQueryLanguageOrFail('kql'); + await savedQueryManagementComponent.clearCurrentlyLoadedQuery(); + await queryBar.expectQueryLanguageOrFail('lucene'); + }); + + // fails: bug in discover https://github.com/elastic/kibana/issues/63561 + // unskip this test when bug is fixed + it.skip('changing language removes saved query', async () => { + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + await queryBar.switchQueryLanguage('lucene'); + expect(await queryBar.getQueryString()).to.eql(''); + }); }); }); } diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index f06baeb7a4167..862e5127bb670 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -247,25 +247,11 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo } currentUrl = (await browser.getCurrentUrl()).replace(/\/\/\w+:\w+@/, '//'); - const maxAdditionalLengthOnNavUrl = 230; - - // On several test failures at the end of the TileMap test we try to navigate back to - // Visualize so we can create the next Vertical Bar Chart, but we can see from the - // logging and the screenshot that it's still on the TileMap page. Why didn't the "get" - // with a new timestamped URL go? I thought that sleep(700) between the get and the - // refresh would solve the problem but didn't seem to always work. - // So this hack fails the navSuccessful check if the currentUrl doesn't match the - // appUrl plus up to 230 other chars. - // Navigating to Settings when there is a default index pattern has a URL length of 196 - // (from debug output). Some other tabs may also be long. But a rather simple configured - // visualization is about 1000 chars long. So at least we catch that case. - - // Browsers don't show the ':port' if it's 80 or 443 so we have to - // remove that part so we can get a match in the tests. - const navSuccessful = new RegExp( - appUrl.replace(':80/', '/').replace(':443/', '/') + - `.{0,${maxAdditionalLengthOnNavUrl}}$` - ).test(currentUrl); + + const navSuccessful = currentUrl + .replace(':80/', '/') + .replace(':443/', '/') + .startsWith(appUrl); if (!navSuccessful) { const msg = `App failed to load: ${appName} in ${defaultFindTimeout}ms appUrl=${appUrl} currentUrl=${currentUrl}`; diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 00bf87621864a..e43a774940391 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -162,6 +162,11 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider return selectedOption.getVisibleText(); } + public async getChartIntervalScaledToDesc() { + await header.waitUntilLoadingHasFinished(); + return await testSubjects.getVisibleText('discoverIntervalSelectScaledToDesc'); + } + public async setChartInterval(interval: string) { const optionElement = await find.byCssSelector(`option[label="${interval}"]`, 5000); await optionElement.click(); diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index 13d2365c07191..5017947e95d03 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -47,9 +47,7 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { */ public readonly browserType: string = browserType; - public readonly isChromium: boolean = [Browsers.Chrome, Browsers.ChromiumEdge].includes( - browserType - ); + public readonly isChrome: boolean = browserType === Browsers.Chrome; public readonly isFirefox: boolean = browserType === Browsers.Firefox; diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index 8b57ecd3c8235..157918df874c8 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -55,7 +55,6 @@ export class WebElementWrapper { private driver: WebDriver = this.webDriver.driver; private Keys = Key; public isW3CEnabled: boolean = (this.webDriver.driver as any).executor_.w3c === true; - public isChromium: boolean = [Browsers.Chrome, Browsers.ChromiumEdge].includes(this.browserType); public static create( webElement: WebElement | WebElementWrapper, @@ -64,7 +63,7 @@ export class WebElementWrapper { timeout: number, fixedHeaderHeight: number, logger: ToolingLog, - browserType: Browsers + browserType: string ): WebElementWrapper { if (webElement instanceof WebElementWrapper) { return webElement; @@ -88,7 +87,7 @@ export class WebElementWrapper { private timeout: number, private fixedHeaderHeight: number, private logger: ToolingLog, - private browserType: Browsers + private browserType: string ) {} private async _findWithCustomTimeout( @@ -244,7 +243,7 @@ export class WebElementWrapper { return this.clearValueWithKeyboard(); } await this.retryCall(async function clearValue(wrapper) { - if (wrapper.isChromium || options.withJS) { + if (wrapper.browserType === Browsers.Chrome || options.withJS) { // https://bugs.chromium.org/p/chromedriver/issues/detail?id=2702 await wrapper.driver.executeScript(`arguments[0].value=''`, wrapper._webElement); } else { @@ -276,7 +275,7 @@ export class WebElementWrapper { await delay(100); } } else { - if (this.isChromium) { + if (this.browserType === Browsers.Chrome) { // https://bugs.chromium.org/p/chromedriver/issues/detail?id=30 await this.retryCall(async function clearValueWithKeyboard(wrapper) { await wrapper.driver.executeScript(`arguments[0].select();`, wrapper._webElement); diff --git a/test/functional/services/query_bar.ts b/test/functional/services/query_bar.ts index ace8b97155c09..7c7fd2d81f170 100644 --- a/test/functional/services/query_bar.ts +++ b/test/functional/services/query_bar.ts @@ -17,6 +17,7 @@ * under the License. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; export function QueryBarProvider({ getService, getPageObjects }: FtrProviderContext) { @@ -25,6 +26,7 @@ export function QueryBarProvider({ getService, getPageObjects }: FtrProviderCont const log = getService('log'); const PageObjects = getPageObjects(['header', 'common']); const find = getService('find'); + const browser = getService('browser'); class QueryBar { async getQueryString(): Promise { @@ -62,6 +64,24 @@ export function QueryBarProvider({ getService, getPageObjects }: FtrProviderCont public async clickQuerySubmitButton(): Promise { await testSubjects.click('querySubmitButton'); } + + public async switchQueryLanguage(lang: 'kql' | 'lucene'): Promise { + await testSubjects.click('switchQueryLanguageButton'); + const kqlToggle = await testSubjects.find('languageToggle'); + const currentLang = + (await kqlToggle.getAttribute('aria-checked')) === 'true' ? 'kql' : 'lucene'; + if (lang !== currentLang) { + await kqlToggle.click(); + } + + await browser.pressKeys(browser.keys.ESCAPE); // close popover + await this.expectQueryLanguageOrFail(lang); // make sure lang is switched + } + + public async expectQueryLanguageOrFail(lang: 'kql' | 'lucene'): Promise { + const queryLanguageButton = await testSubjects.find('switchQueryLanguageButton'); + expect((await queryLanguageButton.getVisibleText()).toLowerCase()).to.eql(lang); + } } return new QueryBar(); diff --git a/test/functional/services/remote/browsers.ts b/test/functional/services/remote/browsers.ts index aa6e364d0a09d..46d81f1737a55 100644 --- a/test/functional/services/remote/browsers.ts +++ b/test/functional/services/remote/browsers.ts @@ -21,5 +21,4 @@ export enum Browsers { Chrome = 'chrome', Firefox = 'firefox', InternetExplorer = 'ie', - ChromiumEdge = 'msedge', } diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index b0724488cb5db..e571a1a7e5551 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -64,23 +64,18 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { lifecycle, config.get('browser.logPollingMs') ); - const isW3CEnabled = (driver as any).executor_.w3c; const caps = await driver.getCapabilities(); - const browserVersion = caps.get( - isW3CEnabled || browserType === Browsers.ChromiumEdge ? 'browserVersion' : 'version' - ); + const browserVersion = caps.get(isW3CEnabled ? 'browserVersion' : 'version'); - log.info( - `Remote initialized: ${caps.get( - 'browserName' - )} ${browserVersion}, w3c compliance=${isW3CEnabled}, collectingCoverage=${collectCoverage}` - ); + log.info(`Remote initialized: ${caps.get('browserName')} ${browserVersion}`); - if ([Browsers.Chrome, Browsers.ChromiumEdge].includes(browserType)) { + if (browserType === Browsers.Chrome) { log.info( - `${browserType}driver version: ${caps.get(browserType)[`${browserType}driverVersion`]}` + `Chromedriver version: ${ + caps.get('chrome').chromedriverVersion + }, w3c=${isW3CEnabled}, codeCoverage=${collectCoverage}` ); } diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index fc0b5bbb787c8..3bf5b865aa7ba 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -31,12 +31,10 @@ import { Builder, Capabilities, By, logging, until } from 'selenium-webdriver'; import chrome from 'selenium-webdriver/chrome'; import firefox from 'selenium-webdriver/firefox'; // @ts-ignore internal modules are not typed -import edge from 'selenium-webdriver/edge'; -import { installDriver } from 'ms-chromium-edge-driver'; -// @ts-ignore internal modules are not typed import { Executor } from 'selenium-webdriver/lib/http'; // @ts-ignore internal modules are not typed import { getLogger } from 'selenium-webdriver/lib/logging'; + import { pollForLogEntry$ } from './poll_for_log_entry'; import { createStdoutSocket } from './create_stdout_stream'; import { preventParallelCalls } from './prevent_parallel_calls'; @@ -65,7 +63,6 @@ Executor.prototype.execute = preventParallelCalls( ); let attemptCounter = 0; -let edgePaths: { driverPath: string | undefined; browserPath: string | undefined }; async function attemptToCreateCommand( log: ToolingLog, browserType: Browsers, @@ -77,46 +74,6 @@ async function attemptToCreateCommand( const buildDriverInstance = async () => { switch (browserType) { - case 'msedge': { - if (edgePaths && edgePaths.browserPath && edgePaths.driverPath) { - const edgeOptions = new edge.Options(); - if (headlessBrowser === '1') { - // @ts-ignore internal modules are not typed - edgeOptions.headless(); - } - // @ts-ignore internal modules are not typed - edgeOptions.setEdgeChromium(true); - // @ts-ignore internal modules are not typed - edgeOptions.setBinaryPath(edgePaths.browserPath); - const session = await new Builder() - .forBrowser('MicrosoftEdge') - .setEdgeOptions(edgeOptions) - .setEdgeService(new edge.ServiceBuilder(edgePaths.driverPath)) - .build(); - return { - session, - consoleLog$: pollForLogEntry$( - session, - logging.Type.BROWSER, - logPollingMs, - lifecycle.cleanup.after$ - ).pipe( - takeUntil(lifecycle.cleanup.after$), - map(({ message, level: { name: level } }) => ({ - message: message.replace(/\\n/g, '\n'), - level, - })) - ), - }; - } else { - throw new Error( - `Chromium Edge session requires browser or driver path to be defined: ${JSON.stringify( - edgePaths - )}` - ); - } - } - case 'chrome': { const chromeCapabilities = Capabilities.chrome(); const chromeOptions = [ @@ -308,11 +265,6 @@ export async function initWebDriver( log.verbose(entry.message); }); - // download Edge driver only in case of usage - if (browserType === Browsers.ChromiumEdge) { - edgePaths = await installDriver(); - } - return await Promise.race([ (async () => { await delay(2 * MINUTE); diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/public/sample_panel_link.ts b/test/plugin_functional/plugins/kbn_sample_panel_action/public/sample_panel_link.ts index b0f1219a815a3..faa774b8485b1 100644 --- a/test/plugin_functional/plugins/kbn_sample_panel_action/public/sample_panel_link.ts +++ b/test/plugin_functional/plugins/kbn_sample_panel_action/public/sample_panel_link.ts @@ -26,6 +26,8 @@ export const createSamplePanelLink = (): Action => createAction({ type: SAMPLE_PANEL_LINK, getDisplayName: () => 'Sample panel Link', - execute: async () => {}, - getHref: () => 'https://example.com/kibana/test', + execute: async () => { + window.location.href = 'https://example.com/kibana/test'; + }, + getHref: async () => 'https://example.com/kibana/test', }); diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_input.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_input.ts index bb8951680be35..37ef8cad948cb 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_input.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_input.ts @@ -47,7 +47,7 @@ export const dashboardInput: DashboardContainerInput = { explicitInput: { id: '2', firstName: 'Sue', - } as any, + }, }, '822cd0f0-ce7c-419d-aeaa-1171cf452745': { gridData: { @@ -60,8 +60,8 @@ export const dashboardInput: DashboardContainerInput = { type: 'visualization', explicitInput: { id: '822cd0f0-ce7c-419d-aeaa-1171cf452745', + savedObjectId: '3fe22200-3dcb-11e8-8660-4d65aa086b3c', }, - savedObjectId: '3fe22200-3dcb-11e8-8660-4d65aa086b3c', }, '66f0a265-7b06-4974-accd-d05f74f7aa82': { gridData: { @@ -74,8 +74,8 @@ export const dashboardInput: DashboardContainerInput = { type: 'visualization', explicitInput: { id: '66f0a265-7b06-4974-accd-d05f74f7aa82', + savedObjectId: '4c0f47e0-3dcd-11e8-8660-4d65aa086b3c', }, - savedObjectId: '4c0f47e0-3dcd-11e8-8660-4d65aa086b3c', }, 'b2861741-40b9-4dc8-b82b-080c6e29a551': { gridData: { @@ -88,8 +88,8 @@ export const dashboardInput: DashboardContainerInput = { type: 'search', explicitInput: { id: 'b2861741-40b9-4dc8-b82b-080c6e29a551', + savedObjectId: 'be5accf0-3dca-11e8-8660-4d65aa086b3c', }, - savedObjectId: 'be5accf0-3dca-11e8-8660-4d65aa086b3c', }, }, isFullScreenMode: false, diff --git a/test/typings/rison_node.d.ts b/test/typings/rison_node.d.ts index 2592c36e8ae9a..a0497f421c3fe 100644 --- a/test/typings/rison_node.d.ts +++ b/test/typings/rison_node.d.ts @@ -18,7 +18,7 @@ */ declare module 'rison-node' { - export type RisonValue = null | boolean | number | string | RisonObject | RisonArray; + export type RisonValue = undefined | null | boolean | number | string | RisonObject | RisonArray; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface RisonArray extends Array {} diff --git a/typings/rison_node.d.ts b/typings/rison_node.d.ts index 2592c36e8ae9a..a0497f421c3fe 100644 --- a/typings/rison_node.d.ts +++ b/typings/rison_node.d.ts @@ -18,7 +18,7 @@ */ declare module 'rison-node' { - export type RisonValue = null | boolean | number | string | RisonObject | RisonArray; + export type RisonValue = undefined | null | boolean | number | string | RisonObject | RisonArray; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface RisonArray extends Array {} diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index bbbcc062786b0..50f36ddd21c97 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -21,10 +21,10 @@ "xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management", "xpack.infra": "plugins/infra", "xpack.ingestManager": "plugins/ingest_manager", - "xpack.lens": "legacy/plugins/lens", + "xpack.lens": "plugins/lens", "xpack.licenseMgmt": "plugins/license_management", "xpack.licensing": "plugins/licensing", - "xpack.logstash": "legacy/plugins/logstash", + "xpack.logstash": ["plugins/logstash", "legacy/plugins/logstash"], "xpack.main": "legacy/plugins/xpack_main", "xpack.maps": ["plugins/maps", "legacy/plugins/maps"], "xpack.ml": ["plugins/ml", "legacy/plugins/ml"], diff --git a/x-pack/index.js b/x-pack/index.js index 3126dc17a7107..61fd4f1752316 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -29,7 +29,6 @@ import { uptime } from './legacy/plugins/uptime'; import { encryptedSavedObjects } from './legacy/plugins/encrypted_saved_objects'; import { actions } from './legacy/plugins/actions'; import { alerting } from './legacy/plugins/alerting'; -import { lens } from './legacy/plugins/lens'; import { ingestManager } from './legacy/plugins/ingest_manager'; import { triggersActionsUI } from './legacy/plugins/triggers_actions_ui'; @@ -58,7 +57,6 @@ module.exports = function(kibana) { upgradeAssistant(kibana), uptime(kibana), encryptedSavedObjects(kibana), - lens(kibana), actions(kibana), alerting(kibana), ingestManager(kibana), diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx index 52263878ca915..491ebdc5aad15 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx @@ -27,6 +27,30 @@ interface ContentsProps { selectedNodeServiceName: string; } +// IE 11 does not handle flex properties as expected. With browser detection, +// we can use regular div elements to render contents that are almost identical. +// +// This method of detecting IE is from a Stack Overflow answer: +// https://stackoverflow.com/a/21825207 +// +// @ts-ignore `documentMode` is not recognized as a valid property of `document`. +const isIE11 = !!window.MSInputMethodContext && !!document.documentMode; + +const FlexColumnGroup = (props: { + children: React.ReactNode; + style: React.CSSProperties; + direction: 'column'; + gutterSize: 's'; +}) => { + if (isIE11) { + const { direction, gutterSize, ...rest } = props; + return
; + } + return ; +}; +const FlexColumnItem = (props: { children: React.ReactNode }) => + isIE11 ?
: ; + export function Contents({ selectedNodeData, isService, @@ -36,18 +60,18 @@ export function Contents({ }: ContentsProps) { const frameworkName = selectedNodeData[SERVICE_FRAMEWORK_NAME]; return ( - - +

{label}

-
- + + {isService ? ( )} - + {isService && ( )} -
+ ); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 92f66f698f044..bf0e052b951ae 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -12,6 +12,17 @@ import { } from '../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; import { defaultIcon, iconForNode } from './icons'; +// IE 11 does not properly load some SVGs or draw certain shapes. This causes +// a runtime error and the map fails work at all. We would prefer to do some +// kind of feature detection rather than browser detection, but some of these +// limitations are not well documented for older browsers. +// +// This method of detecting IE is from a Stack Overflow answer: +// https://stackoverflow.com/a/21825207 +// +// @ts-ignore `documentMode` is not recognized as a valid property of `document`. +const isIE11 = !!window.MSInputMethodContext && !!document.documentMode; + export const animationOptions: cytoscape.AnimationOptions = { duration: parseInt(theme.euiAnimSpeedNormal, 10), // @ts-ignore The cubic-bezier options here are not recognized by the cytoscape types @@ -37,8 +48,9 @@ const style: cytoscape.Stylesheet[] = [ // used here. // // @ts-ignore - 'background-image': (el: cytoscape.NodeSingular) => - iconForNode(el) ?? defaultIcon, + 'background-image': isIE11 + ? undefined + : (el: cytoscape.NodeSingular) => iconForNode(el) ?? defaultIcon, 'background-height': (el: cytoscape.NodeSingular) => isService(el) ? '60%' : '40%', 'background-width': (el: cytoscape.NodeSingular) => @@ -65,7 +77,7 @@ const style: cytoscape.Stylesheet[] = [ 'min-zoomed-font-size': parseInt(theme.euiSizeL, 10), 'overlay-opacity': 0, shape: (el: cytoscape.NodeSingular) => - isService(el) ? 'ellipse' : 'diamond', + isService(el) ? (isIE11 ? 'rectangle' : 'ellipse') : 'diamond', 'text-background-color': theme.euiColorLightestShade, 'text-background-opacity': 0, 'text-background-padding': theme.paddingSizes.xs, @@ -87,12 +99,12 @@ const style: cytoscape.Stylesheet[] = [ 'line-color': lineColor, 'overlay-opacity': 0, 'target-arrow-color': lineColor, - 'target-arrow-shape': 'triangle', + 'target-arrow-shape': isIE11 ? 'none' : 'triangle', // The DefinitelyTyped definitions don't specify this property since it's // fairly new. // // @ts-ignore - 'target-distance-from-node': theme.paddingSizes.xs, + 'target-distance-from-node': isIE11 ? undefined : theme.paddingSizes.xs, width: 1, 'source-arrow-shape': 'none', 'z-index': zIndexEdge @@ -101,12 +113,16 @@ const style: cytoscape.Stylesheet[] = [ { selector: 'edge[bidirectional]', style: { - 'source-arrow-shape': 'triangle', + 'source-arrow-shape': isIE11 ? 'none' : 'triangle', 'source-arrow-color': lineColor, - 'target-arrow-shape': 'triangle', + 'target-arrow-shape': isIE11 ? 'none' : 'triangle', // @ts-ignore - 'source-distance-from-node': parseInt(theme.paddingSizes.xs, 10), - 'target-distance-from-node': parseInt(theme.paddingSizes.xs, 10) + 'source-distance-from-node': isIE11 + ? undefined + : parseInt(theme.paddingSizes.xs, 10), + 'target-distance-from-node': isIE11 + ? undefined + : parseInt(theme.paddingSizes.xs, 10) } }, // @ts-ignore DefinitelyTyped says visibility is "none" but it's diff --git a/x-pack/legacy/plugins/canvas/public/application.tsx b/x-pack/legacy/plugins/canvas/public/application.tsx index 79b3918fef99b..f75b3b427c41b 100644 --- a/x-pack/legacy/plugins/canvas/public/application.tsx +++ b/x-pack/legacy/plugins/canvas/public/application.tsx @@ -104,8 +104,9 @@ export const initializeCanvas = async ( href: getDocumentationLinks().canvas, }, ], - content: domNode => () => { + content: domNode => { ReactDOM.render(, domNode); + return () => ReactDOM.unmountComponentAtNode(domNode); }, }); diff --git a/x-pack/legacy/plugins/lens/index.ts b/x-pack/legacy/plugins/lens/index.ts deleted file mode 100644 index e9a901c58cd90..0000000000000 --- a/x-pack/legacy/plugins/lens/index.ts +++ /dev/null @@ -1,38 +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 Joi from 'joi'; -import { resolve } from 'path'; -import { LegacyPluginInitializer } from 'src/legacy/types'; -import { PLUGIN_ID, NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../../plugins/lens/common'; - -export const lens: LegacyPluginInitializer = kibana => { - return new kibana.Plugin({ - id: PLUGIN_ID, - configPrefix: `xpack.${PLUGIN_ID}`, - // task_manager could be required, but is only used for telemetry - require: ['kibana', 'elasticsearch', 'xpack_main', 'interpreter'], - publicDir: resolve(__dirname, 'public'), - - uiExports: { - app: { - title: NOT_INTERNATIONALIZED_PRODUCT_NAME, - description: 'Explore and visualize data.', - main: `plugins/${PLUGIN_ID}/redirect`, - listed: false, - }, - visualize: [`plugins/${PLUGIN_ID}/legacy`], - embeddableFactories: [`plugins/${PLUGIN_ID}/legacy`], - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - }, - - config: () => { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - }); -}; diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/_index.scss b/x-pack/legacy/plugins/lens/public/app_plugin/_index.scss deleted file mode 100644 index 2ac86f0e58a61..0000000000000 --- a/x-pack/legacy/plugins/lens/public/app_plugin/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './app'; diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization/_index.scss b/x-pack/legacy/plugins/lens/public/datatable_visualization/_index.scss deleted file mode 100644 index 99c357b53952f..0000000000000 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './visualization'; diff --git a/x-pack/legacy/plugins/lens/public/drag_drop/_index.scss b/x-pack/legacy/plugins/lens/public/drag_drop/_index.scss deleted file mode 100644 index 1b3d0cf0a3c2a..0000000000000 --- a/x-pack/legacy/plugins/lens/public/drag_drop/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './drag_drop' diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/_index.scss b/x-pack/legacy/plugins/lens/public/editor_frame_service/_index.scss deleted file mode 100644 index 4d7e054ff03c3..0000000000000 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './editor_frame/index'; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/index.scss b/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/index.scss deleted file mode 100644 index 6c6a63c8c7eb6..0000000000000 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/index.scss +++ /dev/null @@ -1,8 +0,0 @@ -@import './chart_switch'; -@import './config_panel_wrapper'; -@import './data_panel_wrapper'; -@import './expression_renderer'; -@import './frame_layout'; -@import './suggestion_panel'; -@import './workspace_panel_wrapper'; - diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_index.scss b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_index.scss deleted file mode 100644 index a283198d6cf73..0000000000000 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_index.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import './datapanel'; -@import './field_item'; - -@import './dimension_panel/index'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/_index.scss b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/_index.scss deleted file mode 100644 index 26f805fe735f0..0000000000000 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './field_select'; -@import './popover'; diff --git a/x-pack/legacy/plugins/lens/public/legacy.ts b/x-pack/legacy/plugins/lens/public/legacy.ts deleted file mode 100644 index 3b7b6a7a1b510..0000000000000 --- a/x-pack/legacy/plugins/lens/public/legacy.ts +++ /dev/null @@ -1,17 +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 { npSetup, npStart } from 'ui/new_platform'; - -export * from './types'; - -import { plugin } from './index'; - -const pluginInstance = plugin(); -pluginInstance.setup(npSetup.core, { - ...npSetup.plugins, -}); -pluginInstance.start(npStart.core, npStart.plugins); diff --git a/x-pack/legacy/plugins/lens/public/redirect.ts b/x-pack/legacy/plugins/lens/public/redirect.ts deleted file mode 100644 index 25b0188214c5e..0000000000000 --- a/x-pack/legacy/plugins/lens/public/redirect.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// This file redirects lens urls starting with app/lens#... to their counterpart on app/kibana#lens/... to -// make sure it's compatible with the 7.5 release - -import { npSetup } from 'ui/new_platform'; -import chrome from 'ui/chrome'; - -chrome.setRootController('lens', () => { - // prefix the path in the hash with lens/ - const prefixedHashRoute = window.location.hash.replace(/^#\//, '#/lens/'); - - // redirect to the new lens url `app/kibana#/lens/...` - window.location.href = npSetup.core.http.basePath.prepend('/app/kibana' + prefixedHashRoute); -}); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/_index.scss b/x-pack/legacy/plugins/lens/public/xy_visualization/_index.scss deleted file mode 100644 index 794ed4aed82ec..0000000000000 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './_xy_expression'; diff --git a/x-pack/legacy/plugins/logstash/index.js b/x-pack/legacy/plugins/logstash/index.js index ae8571d1c19c3..29f01032f3413 100755 --- a/x-pack/legacy/plugins/logstash/index.js +++ b/x-pack/legacy/plugins/logstash/index.js @@ -5,12 +5,8 @@ */ import { resolve } from 'path'; -import { registerLogstashPipelinesRoutes } from './server/routes/api/pipelines'; -import { registerLogstashPipelineRoutes } from './server/routes/api/pipeline'; -import { registerLogstashUpgradeRoutes } from './server/routes/api/upgrade'; -import { registerLogstashClusterRoutes } from './server/routes/api/cluster'; import { registerLicenseChecker } from './server/lib/register_license_checker'; -import { PLUGIN } from './common/constants'; +import { PLUGIN } from '../../../plugins/logstash/common/constants'; export const logstash = kibana => new kibana.Plugin({ @@ -32,9 +28,5 @@ export const logstash = kibana => }, init: server => { registerLicenseChecker(server); - registerLogstashPipelinesRoutes(server); - registerLogstashPipelineRoutes(server); - registerLogstashUpgradeRoutes(server); - registerLogstashClusterRoutes(server); }, }); diff --git a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js b/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js index 43ca656e0827c..5e430ccbd8ceb 100644 --- a/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js +++ b/x-pack/legacy/plugins/logstash/public/components/pipeline_editor/pipeline_editor.js @@ -13,7 +13,7 @@ import 'brace/mode/plain_text'; import 'brace/theme/github'; import { isEmpty } from 'lodash'; -import { TOOLTIPS } from '../../../common/constants/tooltips'; +import { TOOLTIPS } from '../../../../../../plugins/logstash/common/constants/tooltips'; import { EuiButton, EuiButtonEmpty, diff --git a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts b/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts index e943656120d5e..2e1ee2afb9ce6 100644 --- a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts +++ b/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts @@ -10,7 +10,7 @@ import { npSetup } from 'ui/new_platform'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; import { FeatureCatalogueCategory } from '../../../../../../src/plugins/home/public'; // @ts-ignore -import { PLUGIN } from '../../common/constants'; +import { PLUGIN } from '../../../../../plugins/logstash/common/constants'; const { plugins: { home }, diff --git a/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js b/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js index 14900fdaa7cdc..06d01a05bac27 100755 --- a/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js +++ b/x-pack/legacy/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js @@ -8,7 +8,7 @@ import { pick, capitalize } from 'lodash'; import { getSearchValue } from 'plugins/logstash/lib/get_search_value'; import { getMoment } from 'plugins/logstash/../common/lib/get_moment'; -import { PIPELINE } from '../../../common/constants'; +import { PIPELINE } from '../../../../../../plugins/logstash/common/constants'; /** * Represents the model for listing pipelines in the UI diff --git a/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js b/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js index 4bad4f48cc61d..e89c2fe7d11bf 100755 --- a/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/cluster/cluster_service.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { ROUTES } from '../../../common/constants'; +import { ROUTES } from '../../../../../../plugins/logstash/common/constants'; import { Cluster } from 'plugins/logstash/models/cluster'; export class ClusterService { diff --git a/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js b/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js index 97b336ec0728b..69cc8614a6ae2 100755 --- a/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/license/logstash_license_service.js @@ -7,7 +7,7 @@ import React from 'react'; import { toastNotifications } from 'ui/notify'; import { MarkdownSimple } from '../../../../../../../src/plugins/kibana_react/public'; -import { PLUGIN } from '../../../common/constants'; +import { PLUGIN } from '../../../../../../plugins/logstash/common/constants'; export class LogstashLicenseService { constructor(xpackInfoService, kbnUrlService, $timeout) { diff --git a/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js b/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js index 8a267e38db738..6103e730c2171 100755 --- a/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/monitoring/monitoring_service.js @@ -6,7 +6,7 @@ import moment from 'moment'; import chrome from 'ui/chrome'; -import { ROUTES, MONITORING } from '../../../common/constants'; +import { ROUTES, MONITORING } from '../../../../../../plugins/logstash/common/constants'; import { PipelineListItem } from 'plugins/logstash/models/pipeline_list_item'; export class MonitoringService { diff --git a/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js b/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js index 0696bf9d83256..b5d0dbeb852d5 100755 --- a/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/pipeline/pipeline_service.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { ROUTES } from '../../../common/constants'; +import { ROUTES } from '../../../../../../plugins/logstash/common/constants'; import { Pipeline } from 'plugins/logstash/models/pipeline'; export class PipelineService { diff --git a/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js b/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js index 5a43cf07eba41..d70c8be06fde4 100755 --- a/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/pipelines/pipelines_service.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { ROUTES, MONITORING } from '../../../common/constants'; +import { ROUTES, MONITORING } from '../../../../../../plugins/logstash/common/constants'; import { PipelineListItem } from 'plugins/logstash/models/pipeline_list_item'; const RECENTLY_DELETED_PIPELINE_IDS_STORAGE_KEY = 'xpack.logstash.recentlyDeletedPipelines'; diff --git a/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js b/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js index 7870a495d07a3..2019bdc1bf1aa 100755 --- a/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js +++ b/x-pack/legacy/plugins/logstash/public/services/upgrade/upgrade_service.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { ROUTES } from '../../../common/constants'; +import { ROUTES } from '../../../../../../plugins/logstash/common/constants'; export class UpgradeService { constructor($http) { diff --git a/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/call_with_request_factory.js deleted file mode 100755 index 8dc09d394e973..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; - -const callWithRequest = once(server => { - const cluster = server.plugins.elasticsearch.createCluster('logstash'); - return cluster.callWithRequest; -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_custom_error.js deleted file mode 100755 index f9c102be7a1ff..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_custom_error.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapCustomError } from '../wrap_custom_error'; - -describe('wrap_custom_error', () => { - describe('#wrapCustomError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const statusCode = 404; - const wrappedError = wrapCustomError(originalError, statusCode); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.output.statusCode).to.equal(statusCode); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100755 index cab25cd0b1b10..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js +++ /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 expect from '@kbn/expect'; -import { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - }); - - it('should return a Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - - it('should return the correct Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); - }); - - it('should return invalid permissions message for 403 errors', () => { - const securityError = new Error('I am an error'); - securityError.statusCode = 403; - const wrappedError = wrapEsError(securityError); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.message).to.be( - 'Insufficient user permissions for managing Logstash pipelines' - ); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_unknown_error.js deleted file mode 100755 index 85e0b2b3033ad..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_unknown_error.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapUnknownError } from '../wrap_unknown_error'; - -describe('wrap_unknown_error', () => { - describe('#wrapUnknownError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const wrappedError = wrapUnknownError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/index.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/index.js deleted file mode 100755 index f275f15637091..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { wrapCustomError } from './wrap_custom_error'; -export { wrapEsError } from './wrap_es_error'; -export { wrapUnknownError } from './wrap_unknown_error'; diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_custom_error.js deleted file mode 100755 index 3295113d38ee5..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_custom_error.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps a custom error into a Boom error response and returns it - * - * @param err Object error - * @param statusCode Error status code - * @return Object Boom error response - */ -export function wrapCustomError(err, statusCode) { - return Boom.boomify(err, { statusCode }); -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_es_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_es_error.js deleted file mode 100755 index 41819179bde55..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_es_error.js +++ /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 Boom from 'boom'; -import { i18n } from '@kbn/i18n'; - -/** - * Wraps ES errors into a Boom error response and returns it - * This also handles the permissions issue gracefully - * - * @param err Object ES error - * @return Object Boom error response - */ -export function wrapEsError(err) { - const statusCode = err.statusCode; - if (statusCode === 403) { - return Boom.forbidden( - i18n.translate('xpack.logstash.insufficientUserPermissionsDescription', { - defaultMessage: 'Insufficient user permissions for managing Logstash pipelines', - }) - ); - } - return Boom.boomify(err, { statusCode: err.statusCode }); -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_unknown_error.js deleted file mode 100755 index ffd915c513362..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/error_wrappers/wrap_unknown_error.js +++ /dev/null @@ -1,17 +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 Boom from 'boom'; - -/** - * Wraps an unknown error into a Boom error response and returns it - * - * @param err Object Unknown error - * @return Object Boom error response - */ -export function wrapUnknownError(err) { - return Boom.boomify(err); -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js b/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js deleted file mode 100755 index b1593fb1ba355..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js +++ /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 expect from '@kbn/expect'; -import sinon from 'sinon'; -import { fetchAllFromScroll } from '../fetch_all_from_scroll'; -import { set } from 'lodash'; - -describe('fetch_all_from_scroll', () => { - let mockResponse; - let stubCallWithRequest; - - beforeEach(() => { - mockResponse = {}; - - stubCallWithRequest = sinon.stub(); - stubCallWithRequest.onCall(0).returns( - new Promise(resolve => { - const mockInnerResponse = { - hits: { - hits: ['newhit'], - }, - _scroll_id: 'newScrollId', - }; - return resolve(mockInnerResponse); - }) - ); - - stubCallWithRequest.onCall(1).returns( - new Promise(resolve => { - const mockInnerResponse = { - hits: { - hits: [], - }, - }; - return resolve(mockInnerResponse); - }) - ); - }); - - describe('#fetchAllFromScroll', () => { - describe('when the passed-in response has no hits', () => { - beforeEach(() => { - set(mockResponse, 'hits.hits', []); - }); - - it('should return an empty array of hits', () => { - return fetchAllFromScroll(mockResponse).then(hits => { - expect(hits).to.eql([]); - }); - }); - - it('should not call callWithRequest', () => { - return fetchAllFromScroll(mockResponse, stubCallWithRequest).then(() => { - expect(stubCallWithRequest.called).to.be(false); - }); - }); - }); - - describe('when the passed-in response has some hits', () => { - beforeEach(() => { - set(mockResponse, 'hits.hits', ['foo', 'bar']); - set(mockResponse, '_scroll_id', 'originalScrollId'); - }); - - it('should return the hits from the response', () => { - return fetchAllFromScroll(mockResponse, stubCallWithRequest).then(hits => { - expect(hits).to.eql(['foo', 'bar', 'newhit']); - }); - }); - - it('should call callWithRequest', () => { - return fetchAllFromScroll(mockResponse, stubCallWithRequest).then(() => { - expect(stubCallWithRequest.calledTwice).to.be(true); - - const firstCallWithRequestCallArgs = stubCallWithRequest.args[0]; - expect(firstCallWithRequestCallArgs[1].body.scroll_id).to.eql('originalScrollId'); - - const secondCallWithRequestCallArgs = stubCallWithRequest.args[1]; - expect(secondCallWithRequestCallArgs[1].body.scroll_id).to.eql('newScrollId'); - }); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js b/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js deleted file mode 100755 index 835ef0090a5d2..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash'; -import { ES_SCROLL_SETTINGS } from '../../../common/constants'; - -export function fetchAllFromScroll(response, callWithRequest, hits = []) { - const newHits = get(response, 'hits.hits', []); - const scrollId = get(response, '_scroll_id'); - - if (newHits.length > 0) { - hits.push(...newHits); - - return callWithRequest('scroll', { - body: { - scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - scroll_id: scrollId, - }, - }).then(innerResponse => { - return fetchAllFromScroll(innerResponse, callWithRequest, hits); - }); - } - - return Promise.resolve(hits); -} diff --git a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js deleted file mode 100755 index 1dc1df922acf7..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { licensePreRoutingFactory } from '../license_pre_routing_factory'; - -describe('license_pre_routing_factory', () => { - describe('#logstashFeaturePreRoutingFactory', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults, - }), - }, - }, - }, - }; - }); - - it('only instantiates one instance per server', () => { - const firstInstance = licensePreRoutingFactory(mockServer); - const secondInstance = licensePreRoutingFactory(mockServer); - - expect(firstInstance).to.be(secondInstance); - }); - - describe('isAvailable is false', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: false, - }; - }); - - it('replies with 403', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - expect(() => licensePreRouting(stubRequest)).to.throwException(response => { - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - }); - }); - }); - - describe('isAvailable is true', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: true, - }; - }); - - it('replies with nothing', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - const response = licensePreRouting(stubRequest); - expect(response).to.be(null); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100755 index 05402a56a52d8..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/license_pre_routing_factory.js +++ /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 { once } from 'lodash'; -import { wrapCustomError } from '../error_wrappers'; -import { PLUGIN } from '../../../common/constants'; - -export const licensePreRoutingFactory = once(server => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting() { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - if (!licenseCheckResults.isAvailable) { - const error = new Error(licenseCheckResults.message); - const statusCode = 403; - throw wrapCustomError(error, statusCode); - } - - return null; - } - - return licensePreRouting; -}); diff --git a/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js b/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js index 8a17fb2eea497..a0d06e77b410d 100755 --- a/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js +++ b/x-pack/legacy/plugins/logstash/server/lib/register_license_checker/register_license_checker.js @@ -6,7 +6,7 @@ import { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status'; import { checkLicense } from '../check_license'; -import { PLUGIN } from '../../../common/constants'; +import { PLUGIN } from '../../../../../../plugins/logstash/common/constants'; export function registerLicenseChecker(server) { const xpackMainPlugin = server.plugins.xpack_main; diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_load_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_load_route.js deleted file mode 100755 index 663b60cc8c1d1..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_load_route.js +++ /dev/null @@ -1,40 +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 Boom from 'boom'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { Cluster } from '../../../models/cluster'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function fetchCluster(callWithRequest) { - return callWithRequest('info'); -} - -export function registerLoadRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/cluster', - method: 'GET', - handler: (request, h) => { - const callWithRequest = callWithRequestFactory(server, request); - - return fetchCluster(callWithRequest) - .then(responseFromES => ({ - cluster: Cluster.fromUpstreamJSON(responseFromES).downstreamJSON, - })) - .catch(e => { - if (e.status === 403) { - return h.response(); - } - throw Boom.internal(e); - }); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/index.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/index.js deleted file mode 100755 index 643a405ced919..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerLogstashPipelineRoutes } from './register_pipeline_routes'; diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_delete_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_delete_route.js deleted file mode 100755 index 232ee4207541c..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_delete_route.js +++ /dev/null @@ -1,38 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { wrapEsError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function deletePipeline(callWithRequest, pipelineId) { - return callWithRequest('delete', { - index: INDEX_NAMES.PIPELINES, - id: pipelineId, - refresh: 'wait_for', - }); -} - -export function registerDeleteRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipeline/{id}', - method: 'DELETE', - handler: (request, h) => { - const callWithRequest = callWithRequestFactory(server, request); - const pipelineId = request.params.id; - - return deletePipeline(callWithRequest, pipelineId) - .then(() => h.response().code(204)) - .catch(e => wrapEsError(e)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_load_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_load_route.js deleted file mode 100755 index 796bf939d747f..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_load_route.js +++ /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 Boom from 'boom'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { Pipeline } from '../../../models/pipeline'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function fetchPipeline(callWithRequest, pipelineId) { - return callWithRequest('get', { - index: INDEX_NAMES.PIPELINES, - id: pipelineId, - _source: ['description', 'username', 'pipeline', 'pipeline_settings'], - ignore: [404], - }); -} - -export function registerLoadRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipeline/{id}', - method: 'GET', - handler: request => { - const callWithRequest = callWithRequestFactory(server, request); - const pipelineId = request.params.id; - - return fetchPipeline(callWithRequest, pipelineId) - .then(pipelineResponseFromES => { - if (!pipelineResponseFromES.found) { - throw Boom.notFound(); - } - - const pipeline = Pipeline.fromUpstreamJSON(pipelineResponseFromES); - return pipeline.downstreamJSON; - }) - .catch(e => Boom.boomify(e)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_pipeline_routes.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_pipeline_routes.js deleted file mode 100755 index 9966cd2ca2139..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_pipeline_routes.js +++ /dev/null @@ -1,15 +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 { registerLoadRoute } from './register_load_route'; -import { registerDeleteRoute } from './register_delete_route'; -import { registerSaveRoute } from './register_save_route'; - -export function registerLogstashPipelineRoutes(server) { - registerLoadRoute(server); - registerDeleteRoute(server); - registerSaveRoute(server); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_save_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_save_route.js deleted file mode 100755 index 50f62dc0a0ddd..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipeline/register_save_route.js +++ /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 { get } from 'lodash'; -import { wrapEsError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { Pipeline } from '../../../models/pipeline'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function savePipeline(callWithRequest, pipelineId, pipelineBody) { - return callWithRequest('index', { - index: INDEX_NAMES.PIPELINES, - id: pipelineId, - body: pipelineBody, - refresh: 'wait_for', - }); -} - -export function registerSaveRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipeline/{id}', - method: 'PUT', - handler: async (request, h) => { - let username; - if (server.plugins.security) { - const user = await server.plugins.security.getUser(request); - username = get(user, 'username'); - } - - const callWithRequest = callWithRequestFactory(server, request); - const pipelineId = request.params.id; - - const pipeline = Pipeline.fromDownstreamJSON(request.payload, pipelineId, username); - return savePipeline(callWithRequest, pipeline.id, pipeline.upstreamJSON) - .then(() => h.response().code(204)) - .catch(e => wrapEsError(e)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/index.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/index.js deleted file mode 100755 index db275b5a3ea79..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerLogstashPipelinesRoutes } from './register_pipelines_routes'; diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_delete_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_delete_route.js deleted file mode 100755 index 8ccd792d5a876..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_delete_route.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { wrapUnknownError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function deletePipelines(callWithRequest, pipelineIds) { - const deletePromises = pipelineIds.map(pipelineId => { - return callWithRequest('delete', { - index: INDEX_NAMES.PIPELINES, - id: pipelineId, - refresh: 'wait_for', - }) - .then(success => ({ success })) - .catch(error => ({ error })); - }); - - return Promise.all(deletePromises).then(results => { - const successes = results.filter(result => Boolean(result.success)); - const errors = results.filter(result => Boolean(result.error)); - - return { - numSuccesses: successes.length, - numErrors: errors.length, - }; - }); -} - -export function registerDeleteRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipelines/delete', - method: 'POST', - handler: request => { - const callWithRequest = callWithRequestFactory(server, request); - - return deletePipelines(callWithRequest, request.payload.pipelineIds) - .then(results => ({ results })) - .catch(err => wrapUnknownError(err)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_list_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_list_route.js deleted file mode 100755 index 43ce1c3e8f6f6..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_list_route.js +++ /dev/null @@ -1,52 +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 { wrapEsError } from '../../../lib/error_wrappers'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; -import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../common/constants'; -import { PipelineListItem } from '../../../models/pipeline_list_item'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function fetchPipelines(callWithRequest) { - const params = { - index: INDEX_NAMES.PIPELINES, - scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - body: { - size: ES_SCROLL_SETTINGS.PAGE_SIZE, - }, - ignore: [404], - }; - - return callWithRequest('search', params).then(response => - fetchAllFromScroll(response, callWithRequest) - ); -} - -export function registerListRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/pipelines', - method: 'GET', - handler: request => { - const callWithRequest = callWithRequestFactory(server, request); - - return fetchPipelines(callWithRequest) - .then((pipelinesHits = []) => { - const pipelines = pipelinesHits.map(pipeline => { - return PipelineListItem.fromUpstreamJSON(pipeline).downstreamJSON; - }); - - return { pipelines }; - }) - .catch(e => wrapEsError(e)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/index.js b/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/index.js deleted file mode 100755 index d616349dd6566..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerLogstashUpgradeRoutes } from './register_upgrade_routes'; diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_execute_route.js b/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_execute_route.js deleted file mode 100755 index 16f97930ae25e..0000000000000 --- a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_execute_route.js +++ /dev/null @@ -1,56 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { wrapUnknownError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function doesIndexExist(callWithRequest) { - return callWithRequest('indices.exists', { - index: INDEX_NAMES.PIPELINES, - }); -} - -async function executeUpgrade(callWithRequest) { - // If index doesn't exist yet, there is no mapping to upgrade - if (!(await doesIndexExist(callWithRequest))) { - return; - } - - return callWithRequest('indices.putMapping', { - index: INDEX_NAMES.PIPELINES, - body: { - properties: { - pipeline_settings: { - dynamic: false, - type: 'object', - }, - }, - }, - }); -} - -export function registerExecuteRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/logstash/upgrade', - method: 'POST', - handler: async request => { - const callWithRequest = callWithRequestFactory(server, request); - try { - await executeUpgrade(callWithRequest); - return { is_upgraded: true }; - } catch (err) { - throw wrapUnknownError(err); - } - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss index 249b6dfca5c76..f9ad412f7e48a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss @@ -1,3 +1,3 @@ .mapStyleSettings__fixedBox { - width: $euiSize * 7.5; -} \ No newline at end of file + width: $euiSize * 7.5; +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap index eb1c65c6a696d..4a7537166bd8a 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap @@ -161,7 +161,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1 "children":

=> { + return Promise.resolve(basicCase); +}; + +export const getCasesStatus = async (signal: AbortSignal): Promise => + Promise.resolve(casesStatus); + +export const getTags = async (signal: AbortSignal): Promise => Promise.resolve(tags); + +export const getReporters = async (signal: AbortSignal): Promise => + Promise.resolve(respReporters); + +export const getCaseUserActions = async ( + caseId: string, + signal: AbortSignal +): Promise => Promise.resolve(caseUserActions); + +export const getCases = async ({ + filterOptions = { + search: '', + reporters: [], + status: 'open', + tags: [], + }, + queryParams = { + page: 1, + perPage: 5, + sortField: SortFieldCase.createdAt, + sortOrder: 'desc', + }, + signal, +}: FetchCasesProps): Promise => Promise.resolve(allCases); + +export const postCase = async (newCase: CasePostRequest, signal: AbortSignal): Promise => + Promise.resolve(basicCasePost); + +export const patchCase = async ( + caseId: string, + updatedCase: Pick, + version: string, + signal: AbortSignal +): Promise => Promise.resolve([basicCase]); + +export const patchCasesStatus = async ( + cases: BulkUpdateStatus[], + signal: AbortSignal +): Promise => Promise.resolve(allCases.cases); + +export const postComment = async ( + newComment: CommentRequest, + caseId: string, + signal: AbortSignal +): Promise => Promise.resolve(basicCase); + +export const patchComment = async ( + caseId: string, + commentId: string, + commentUpdate: string, + version: string, + signal: AbortSignal +): Promise => Promise.resolve(basicCaseCommentPatch); + +export const deleteCases = async (caseIds: string[], signal: AbortSignal): Promise => + Promise.resolve(true); + +export const pushCase = async ( + caseId: string, + push: CaseExternalServiceRequest, + signal: AbortSignal +): Promise => Promise.resolve(pushedCase); + +export const pushToService = async ( + connectorId: string, + casePushParams: ServiceConnectorCaseParams, + signal: AbortSignal +): Promise => Promise.resolve(serviceConnector); + +export const getActionLicense = async (signal: AbortSignal): Promise => + Promise.resolve(actionLicenses); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx new file mode 100644 index 0000000000000..4f5655cc9f221 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/api.test.tsx @@ -0,0 +1,463 @@ +/* + * 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 { KibanaServices } from '../../lib/kibana'; +import { + deleteCases, + getActionLicense, + getCase, + getCases, + getCasesStatus, + getCaseUserActions, + getReporters, + getTags, + patchCase, + patchCasesStatus, + patchComment, + postCase, + postComment, + pushCase, + pushToService, +} from './api'; +import { + actionLicenses, + allCases, + basicCase, + allCasesSnake, + basicCaseSnake, + actionTypeExecutorResult, + pushedCaseSnake, + casesStatus, + casesSnake, + cases, + caseUserActions, + pushedCase, + pushSnake, + reporters, + respReporters, + serviceConnector, + casePushParams, + tags, + caseUserActionsSnake, + casesStatusSnake, +} from './mock'; +import { CASES_URL } from './constants'; +import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases'; +import * as i18n from './translations'; + +const abortCtrl = new AbortController(); +const mockKibanaServices = KibanaServices.get as jest.Mock; +jest.mock('../../lib/kibana'); + +const fetchMock = jest.fn(); +mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } }); + +describe('Case Configuration API', () => { + describe('deleteCases', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(''); + }); + const data = ['1', '2']; + + test('check url, method, signal', async () => { + await deleteCases(data, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}`, { + method: 'DELETE', + query: { ids: JSON.stringify(data) }, + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await deleteCases(data, abortCtrl.signal); + expect(resp).toEqual(''); + }); + }); + describe('getActionLicense', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(actionLicenses); + }); + test('check url, method, signal', async () => { + await getActionLicense(abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`/api/action/types`, { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await getActionLicense(abortCtrl.signal); + expect(resp).toEqual(actionLicenses); + }); + }); + describe('getCase', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(basicCaseSnake); + }); + const data = basicCase.id; + + test('check url, method, signal', async () => { + await getCase(data, true, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}`, { + method: 'GET', + query: { includeComments: true }, + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await getCase(data, true, abortCtrl.signal); + expect(resp).toEqual(basicCase); + }); + }); + describe('getCases', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(allCasesSnake); + }); + test('check url, method, signal', async () => { + await getCases({ + filterOptions: DEFAULT_FILTER_OPTIONS, + queryParams: DEFAULT_QUERY_PARAMS, + signal: abortCtrl.signal, + }); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/_find`, { + method: 'GET', + query: { + ...DEFAULT_QUERY_PARAMS, + reporters: [], + tags: [], + status: 'open', + }, + signal: abortCtrl.signal, + }); + }); + test('correctly applies filters', async () => { + await getCases({ + filterOptions: { + ...DEFAULT_FILTER_OPTIONS, + reporters: [...respReporters, { username: null, full_name: null, email: null }], + tags, + status: '', + search: 'hello', + }, + queryParams: DEFAULT_QUERY_PARAMS, + signal: abortCtrl.signal, + }); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/_find`, { + method: 'GET', + query: { + ...DEFAULT_QUERY_PARAMS, + reporters, + tags, + search: 'hello', + }, + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await getCases({ + filterOptions: DEFAULT_FILTER_OPTIONS, + queryParams: DEFAULT_QUERY_PARAMS, + signal: abortCtrl.signal, + }); + expect(resp).toEqual({ ...allCases }); + }); + }); + describe('getCasesStatus', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(casesStatusSnake); + }); + test('check url, method, signal', async () => { + await getCasesStatus(abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/status`, { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await getCasesStatus(abortCtrl.signal); + expect(resp).toEqual(casesStatus); + }); + }); + describe('getCaseUserActions', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(caseUserActionsSnake); + }); + + test('check url, method, signal', async () => { + await getCaseUserActions(basicCase.id, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}/user_actions`, { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await getCaseUserActions(basicCase.id, abortCtrl.signal); + expect(resp).toEqual(caseUserActions); + }); + }); + describe('getReporters', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(respReporters); + }); + + test('check url, method, signal', async () => { + await getReporters(abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/reporters`, { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await getReporters(abortCtrl.signal); + expect(resp).toEqual(respReporters); + }); + }); + describe('getTags', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(tags); + }); + + test('check url, method, signal', async () => { + await getTags(abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/tags`, { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await getTags(abortCtrl.signal); + expect(resp).toEqual(tags); + }); + }); + describe('patchCase', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue([basicCaseSnake]); + }); + const data = { description: 'updated description' }; + test('check url, method, signal', async () => { + await patchCase(basicCase.id, data, basicCase.version, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}`, { + method: 'PATCH', + body: JSON.stringify({ + cases: [{ ...data, id: basicCase.id, version: basicCase.version }], + }), + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await patchCase( + basicCase.id, + { description: 'updated description' }, + basicCase.version, + abortCtrl.signal + ); + expect(resp).toEqual({ ...[basicCase] }); + }); + }); + describe('patchCasesStatus', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(casesSnake); + }); + const data = [ + { + status: 'closed', + id: basicCase.id, + version: basicCase.version, + }, + ]; + + test('check url, method, signal', async () => { + await patchCasesStatus(data, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}`, { + method: 'PATCH', + body: JSON.stringify({ cases: data }), + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await patchCasesStatus(data, abortCtrl.signal); + expect(resp).toEqual({ ...cases }); + }); + }); + describe('patchComment', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(basicCaseSnake); + }); + + test('check url, method, signal', async () => { + await patchComment( + basicCase.id, + basicCase.comments[0].id, + 'updated comment', + basicCase.comments[0].version, + abortCtrl.signal + ); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}/comments`, { + method: 'PATCH', + body: JSON.stringify({ + comment: 'updated comment', + id: basicCase.comments[0].id, + version: basicCase.comments[0].version, + }), + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await patchComment( + basicCase.id, + basicCase.comments[0].id, + 'updated comment', + basicCase.comments[0].version, + abortCtrl.signal + ); + expect(resp).toEqual(basicCase); + }); + }); + describe('postCase', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(basicCaseSnake); + }); + const data = { + description: 'description', + tags: ['tag'], + title: 'title', + }; + + test('check url, method, signal', async () => { + await postCase(data, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}`, { + method: 'POST', + body: JSON.stringify(data), + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await postCase(data, abortCtrl.signal); + expect(resp).toEqual(basicCase); + }); + }); + describe('postComment', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(basicCaseSnake); + }); + const data = { + comment: 'comment', + }; + + test('check url, method, signal', async () => { + await postComment(data, basicCase.id, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}/comments`, { + method: 'POST', + body: JSON.stringify(data), + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await postComment(data, basicCase.id, abortCtrl.signal); + expect(resp).toEqual(basicCase); + }); + }); + describe('pushCase', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(pushedCaseSnake); + }); + + test('check url, method, signal', async () => { + await pushCase(basicCase.id, pushSnake, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`${CASES_URL}/${basicCase.id}/_push`, { + method: 'POST', + body: JSON.stringify(pushSnake), + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await pushCase(basicCase.id, pushSnake, abortCtrl.signal); + expect(resp).toEqual(pushedCase); + }); + }); + describe('pushToService', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(actionTypeExecutorResult); + }); + const connectorId = 'connectorId'; + test('check url, method, signal', async () => { + await pushToService(connectorId, casePushParams, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith(`/api/action/${connectorId}/_execute`, { + method: 'POST', + body: JSON.stringify({ params: casePushParams }), + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await pushToService(connectorId, casePushParams, abortCtrl.signal); + expect(resp).toEqual(serviceConnector); + }); + + test('unhappy path - serviceMessage', async () => { + const theError = 'the error'; + fetchMock.mockResolvedValue({ + ...actionTypeExecutorResult, + status: 'error', + serviceMessage: theError, + message: 'not it', + }); + await expect( + pushToService(connectorId, casePushParams, abortCtrl.signal) + ).rejects.toMatchObject({ message: theError }); + }); + + test('unhappy path - message', async () => { + const theError = 'the error'; + fetchMock.mockResolvedValue({ + ...actionTypeExecutorResult, + status: 'error', + message: theError, + }); + await expect( + pushToService(connectorId, casePushParams, abortCtrl.signal) + ).rejects.toMatchObject({ message: theError }); + }); + + test('unhappy path - no message', async () => { + const theError = i18n.ERROR_PUSH_TO_SERVICE; + fetchMock.mockResolvedValue({ + ...actionTypeExecutorResult, + status: 'error', + }); + await expect( + pushToService(connectorId, casePushParams, abortCtrl.signal) + ).rejects.toMatchObject({ message: theError }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/mock.ts b/x-pack/legacy/plugins/siem/public/containers/case/mock.ts new file mode 100644 index 0000000000000..0bda75e5bc9e0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/mock.ts @@ -0,0 +1,307 @@ +/* + * 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 { ActionLicense, AllCases, Case, CasesStatus, CaseUserActions, Comment } from './types'; + +import { + CommentResponse, + ServiceConnectorCaseResponse, + Status, + UserAction, + UserActionField, + CaseResponse, + CasesStatusResponse, + CaseUserActionsResponse, + CasesResponse, + CasesFindResponse, +} from '../../../../../../plugins/case/common/api/cases'; +import { UseGetCasesState, DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases'; + +export const basicCaseId = 'basic-case-id'; +const basicCommentId = 'basic-comment-id'; +const basicCreatedAt = '2020-02-19T23:06:33.798Z'; +const basicUpdatedAt = '2020-02-20T15:02:57.995Z'; +const laterTime = '2020-02-28T15:02:57.995Z'; +export const elasticUser = { + fullName: 'Leslie Knope', + username: 'lknope', + email: 'leslie.knope@elastic.co', +}; + +export const tags: string[] = ['coke', 'pepsi']; + +export const basicComment: Comment = { + comment: 'Solve this fast!', + id: basicCommentId, + createdAt: basicCreatedAt, + createdBy: elasticUser, + pushedAt: null, + pushedBy: null, + updatedAt: null, + updatedBy: null, + version: 'WzQ3LDFc', +}; + +export const basicCase: Case = { + closedAt: null, + closedBy: null, + id: basicCaseId, + comments: [basicComment], + createdAt: basicCreatedAt, + createdBy: elasticUser, + description: 'Security banana Issue', + externalService: null, + status: 'open', + tags, + title: 'Another horrible breach!!', + totalComment: 1, + updatedAt: basicUpdatedAt, + updatedBy: elasticUser, + version: 'WzQ3LDFd', +}; + +export const basicCasePost: Case = { + ...basicCase, + updatedAt: null, + updatedBy: null, +}; + +export const basicCommentPatch: Comment = { + ...basicComment, + updatedAt: basicUpdatedAt, + updatedBy: { + username: 'elastic', + }, +}; + +export const basicCaseCommentPatch = { + ...basicCase, + comments: [basicCommentPatch], +}; + +export const casesStatus: CasesStatus = { + countClosedCases: 130, + countOpenCases: 20, +}; + +const basicPush = { + connectorId: 'connector_id', + connectorName: 'connector name', + externalId: 'external_id', + externalTitle: 'external title', + externalUrl: 'basicPush.com', + pushedAt: basicUpdatedAt, + pushedBy: elasticUser, +}; + +export const pushedCase: Case = { + ...basicCase, + externalService: basicPush, +}; + +export const serviceConnector: ServiceConnectorCaseResponse = { + number: '123', + incidentId: '444', + pushedDate: basicUpdatedAt, + url: 'connector.com', + comments: [ + { + commentId: basicCommentId, + pushedDate: basicUpdatedAt, + }, + ], +}; + +const basicAction = { + actionAt: basicCreatedAt, + actionBy: elasticUser, + oldValue: null, + newValue: 'what a cool value', + caseId: basicCaseId, + commentId: null, +}; + +export const casePushParams = { + actionBy: elasticUser, + caseId: basicCaseId, + createdAt: basicCreatedAt, + createdBy: elasticUser, + incidentId: null, + title: 'what a cool value', + commentId: null, + updatedAt: basicCreatedAt, + updatedBy: elasticUser, + description: 'nice', +}; +export const actionTypeExecutorResult = { + actionId: 'string', + status: 'ok', + data: serviceConnector, +}; + +export const cases: Case[] = [ + basicCase, + { ...pushedCase, id: '1', totalComment: 0, comments: [] }, + { ...pushedCase, updatedAt: laterTime, id: '2', totalComment: 0, comments: [] }, + { ...basicCase, id: '3', totalComment: 0, comments: [] }, + { ...basicCase, id: '4', totalComment: 0, comments: [] }, +]; + +export const allCases: AllCases = { + cases, + page: 1, + perPage: 5, + total: 10, + ...casesStatus, +}; +export const actionLicenses: ActionLicense[] = [ + { + id: '.servicenow', + name: 'ServiceNow', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + }, +]; + +// Snake case for mock api responses +export const elasticUserSnake = { + full_name: 'Leslie Knope', + username: 'lknope', + email: 'leslie.knope@elastic.co', +}; +export const basicCommentSnake: CommentResponse = { + ...basicComment, + comment: 'Solve this fast!', + id: basicCommentId, + created_at: basicCreatedAt, + created_by: elasticUserSnake, + pushed_at: null, + pushed_by: null, + updated_at: null, + updated_by: null, +}; + +export const basicCaseSnake: CaseResponse = { + ...basicCase, + status: 'open' as Status, + closed_at: null, + closed_by: null, + comments: [basicCommentSnake], + created_at: basicCreatedAt, + created_by: elasticUserSnake, + external_service: null, + updated_at: basicUpdatedAt, + updated_by: elasticUserSnake, +}; + +export const casesStatusSnake: CasesStatusResponse = { + count_closed_cases: 130, + count_open_cases: 20, +}; + +export const pushSnake = { + connector_id: 'connector_id', + connector_name: 'connector name', + external_id: 'external_id', + external_title: 'external title', + external_url: 'basicPush.com', +}; +const basicPushSnake = { + ...pushSnake, + pushed_at: basicUpdatedAt, + pushed_by: elasticUserSnake, +}; +export const pushedCaseSnake = { + ...basicCaseSnake, + external_service: basicPushSnake, +}; + +export const reporters: string[] = ['alexis', 'kim', 'maria', 'steph']; +export const respReporters = [ + { username: 'alexis', full_name: null, email: null }, + { username: 'kim', full_name: null, email: null }, + { username: 'maria', full_name: null, email: null }, + { username: 'steph', full_name: null, email: null }, +]; +export const casesSnake: CasesResponse = [ + basicCaseSnake, + { ...pushedCaseSnake, id: '1', totalComment: 0, comments: [] }, + { ...pushedCaseSnake, updated_at: laterTime, id: '2', totalComment: 0, comments: [] }, + { ...basicCaseSnake, id: '3', totalComment: 0, comments: [] }, + { ...basicCaseSnake, id: '4', totalComment: 0, comments: [] }, +]; + +export const allCasesSnake: CasesFindResponse = { + cases: casesSnake, + page: 1, + per_page: 5, + total: 10, + ...casesStatusSnake, +}; + +const basicActionSnake = { + action_at: basicCreatedAt, + action_by: elasticUserSnake, + old_value: null, + new_value: 'what a cool value', + case_id: basicCaseId, + comment_id: null, +}; +export const getUserActionSnake = (af: UserActionField, a: UserAction) => ({ + ...basicActionSnake, + action_id: `${af[0]}-${a}`, + action_field: af, + action: a, + comment_id: af[0] === 'comment' ? basicCommentId : null, + new_value: + a === 'push-to-service' && af[0] === 'pushed' + ? JSON.stringify(basicPushSnake) + : basicAction.newValue, +}); + +export const caseUserActionsSnake: CaseUserActionsResponse = [ + getUserActionSnake(['description'], 'create'), + getUserActionSnake(['comment'], 'create'), + getUserActionSnake(['description'], 'update'), +]; + +// user actions + +export const getUserAction = (af: UserActionField, a: UserAction) => ({ + ...basicAction, + actionId: `${af[0]}-${a}`, + actionField: af, + action: a, + commentId: af[0] === 'comment' ? basicCommentId : null, + newValue: + a === 'push-to-service' && af[0] === 'pushed' + ? JSON.stringify(basicPushSnake) + : basicAction.newValue, +}); + +export const caseUserActions: CaseUserActions[] = [ + getUserAction(['description'], 'create'), + getUserAction(['comment'], 'create'), + getUserAction(['description'], 'update'), +]; + +// components tests +export const useGetCasesMockState: UseGetCasesState = { + data: allCases, + loading: [], + selectedCases: [], + isError: false, + queryParams: DEFAULT_QUERY_PARAMS, + filterOptions: DEFAULT_FILTER_OPTIONS, +}; + +export const basicCaseClosed: Case = { + ...basicCase, + closedAt: '2020-02-25T23:06:33.798Z', + closedBy: elasticUser, + status: 'closed', +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/types.ts index d2a58e9eeeff4..e552f22b55fa4 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/types.ts @@ -31,7 +31,7 @@ export interface CaseUserActions { export interface CaseExternalService { pushedAt: string; - pushedBy: string; + pushedBy: ElasticUser; connectorId: string; connectorName: string; externalId: string; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.test.tsx new file mode 100644 index 0000000000000..329fda10424a8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.test.tsx @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useUpdateCases, UseUpdateCases } from './use_bulk_update_case'; +import { basicCase } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useUpdateCases', () => { + const abortCtrl = new AbortController(); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCases() + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoading: false, + isError: false, + isUpdated: false, + updateBulkStatus: result.current.updateBulkStatus, + dispatchResetIsUpdated: result.current.dispatchResetIsUpdated, + }); + }); + }); + + it('calls patchCase with correct arguments', async () => { + const spyOnPatchCases = jest.spyOn(api, 'patchCasesStatus'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCases() + ); + await waitForNextUpdate(); + + result.current.updateBulkStatus([basicCase], 'closed'); + await waitForNextUpdate(); + expect(spyOnPatchCases).toBeCalledWith( + [ + { + status: 'closed', + id: basicCase.id, + version: basicCase.version, + }, + ], + abortCtrl.signal + ); + }); + }); + + it('patch cases', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCases() + ); + await waitForNextUpdate(); + result.current.updateBulkStatus([basicCase], 'closed'); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isUpdated: true, + isLoading: false, + isError: false, + updateBulkStatus: result.current.updateBulkStatus, + dispatchResetIsUpdated: result.current.dispatchResetIsUpdated, + }); + }); + }); + + it('set isLoading to true when posting case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCases() + ); + await waitForNextUpdate(); + result.current.updateBulkStatus([basicCase], 'closed'); + + expect(result.current.isLoading).toBe(true); + }); + }); + + it('dispatchResetIsUpdated resets is updated', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCases() + ); + + await waitForNextUpdate(); + result.current.updateBulkStatus([basicCase], 'closed'); + await waitForNextUpdate(); + expect(result.current.isUpdated).toBeTruthy(); + result.current.dispatchResetIsUpdated(); + expect(result.current.isUpdated).toBeFalsy(); + }); + }); + + it('unhappy path', async () => { + const spyOnPatchCases = jest.spyOn(api, 'patchCasesStatus'); + spyOnPatchCases.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCases() + ); + await waitForNextUpdate(); + result.current.updateBulkStatus([basicCase], 'closed'); + + expect(result.current).toEqual({ + isUpdated: false, + isLoading: false, + isError: true, + updateBulkStatus: result.current.updateBulkStatus, + dispatchResetIsUpdated: result.current.dispatchResetIsUpdated, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx index 7d040c49f1971..d0cc4d99f8f9f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx @@ -51,12 +51,12 @@ const dataFetchReducer = (state: UpdateState, action: Action): UpdateState => { return state; } }; -interface UseUpdateCase extends UpdateState { +export interface UseUpdateCases extends UpdateState { updateBulkStatus: (cases: Case[], status: string) => void; dispatchResetIsUpdated: () => void; } -export const useUpdateCases = (): UseUpdateCase => { +export const useUpdateCases = (): UseUpdateCases => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.test.tsx new file mode 100644 index 0000000000000..45ba392f3b5b4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.test.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useDeleteCases, UseDeleteCase } from './use_delete_cases'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useDeleteCases', () => { + const abortCtrl = new AbortController(); + const deleteObj = [{ id: '1' }, { id: '2' }, { id: '3' }]; + const deleteArr = ['1', '2', '3']; + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useDeleteCases() + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isDisplayConfirmDeleteModal: false, + isLoading: false, + isError: false, + isDeleted: false, + dispatchResetIsDeleted: result.current.dispatchResetIsDeleted, + handleOnDeleteConfirm: result.current.handleOnDeleteConfirm, + handleToggleModal: result.current.handleToggleModal, + }); + }); + }); + + it('calls deleteCases with correct arguments', async () => { + const spyOnDeleteCases = jest.spyOn(api, 'deleteCases'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useDeleteCases() + ); + await waitForNextUpdate(); + + result.current.handleOnDeleteConfirm(deleteObj); + await waitForNextUpdate(); + expect(spyOnDeleteCases).toBeCalledWith(deleteArr, abortCtrl.signal); + }); + }); + + it('deletes cases', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useDeleteCases() + ); + await waitForNextUpdate(); + result.current.handleToggleModal(); + result.current.handleOnDeleteConfirm(deleteObj); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isDisplayConfirmDeleteModal: false, + isLoading: false, + isError: false, + isDeleted: true, + dispatchResetIsDeleted: result.current.dispatchResetIsDeleted, + handleOnDeleteConfirm: result.current.handleOnDeleteConfirm, + handleToggleModal: result.current.handleToggleModal, + }); + }); + }); + + it('resets is deleting', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useDeleteCases() + ); + await waitForNextUpdate(); + result.current.handleToggleModal(); + result.current.handleOnDeleteConfirm(deleteObj); + await waitForNextUpdate(); + expect(result.current.isDeleted).toBeTruthy(); + result.current.handleToggleModal(); + result.current.dispatchResetIsDeleted(); + expect(result.current.isDeleted).toBeFalsy(); + }); + }); + + it('set isLoading to true when deleting cases', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useDeleteCases() + ); + await waitForNextUpdate(); + result.current.handleToggleModal(); + result.current.handleOnDeleteConfirm(deleteObj); + expect(result.current.isLoading).toBe(true); + }); + }); + + it('unhappy path', async () => { + const spyOnDeleteCases = jest.spyOn(api, 'deleteCases'); + spyOnDeleteCases.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useDeleteCases() + ); + await waitForNextUpdate(); + result.current.handleToggleModal(); + result.current.handleOnDeleteConfirm(deleteObj); + + expect(result.current).toEqual({ + isDisplayConfirmDeleteModal: false, + isLoading: false, + isError: true, + isDeleted: false, + dispatchResetIsDeleted: result.current.dispatchResetIsDeleted, + handleOnDeleteConfirm: result.current.handleOnDeleteConfirm, + handleToggleModal: result.current.handleToggleModal, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx index 07e3786758aeb..3c49be551c064 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx @@ -59,9 +59,9 @@ const dataFetchReducer = (state: DeleteState, action: Action): DeleteState => { } }; -interface UseDeleteCase extends DeleteState { +export interface UseDeleteCase extends DeleteState { dispatchResetIsDeleted: () => void; - handleOnDeleteConfirm: (caseIds: DeleteCase[]) => void; + handleOnDeleteConfirm: (cases: DeleteCase[]) => void; handleToggleModal: () => void; } @@ -117,8 +117,8 @@ export const useDeleteCases = (): UseDeleteCase => { }, [state.isDisplayConfirmDeleteModal]); const handleOnDeleteConfirm = useCallback( - caseIds => { - dispatchDeleteCases(caseIds); + (cases: DeleteCase[]) => { + dispatchDeleteCases(cases); dispatchToggleDeleteModal(); }, [state.isDisplayConfirmDeleteModal] diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.test.tsx new file mode 100644 index 0000000000000..23c9ff5e49586 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.test.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { initialData, useGetActionLicense, ActionLicenseState } from './use_get_action_license'; +import { actionLicenses } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useGetActionLicense', () => { + const abortCtrl = new AbortController(); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetActionLicense() + ); + await waitForNextUpdate(); + expect(result.current).toEqual(initialData); + }); + }); + + it('calls getActionLicense with correct arguments', async () => { + const spyOnGetActionLicense = jest.spyOn(api, 'getActionLicense'); + + await act(async () => { + const { waitForNextUpdate } = renderHook(() => + useGetActionLicense() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(spyOnGetActionLicense).toBeCalledWith(abortCtrl.signal); + }); + }); + + it('gets action license', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetActionLicense() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoading: false, + isError: false, + actionLicense: actionLicenses[0], + }); + }); + }); + + it('set isLoading to true when posting case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetActionLicense() + ); + await waitForNextUpdate(); + expect(result.current.isLoading).toBe(true); + }); + }); + + it('unhappy path', async () => { + const spyOnGetActionLicense = jest.spyOn(api, 'getActionLicense'); + spyOnGetActionLicense.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetActionLicense() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ + actionLicense: null, + isLoading: false, + isError: true, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx index 12f92b2db039b..0d28a1b20c61f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx @@ -11,13 +11,13 @@ import { getActionLicense } from './api'; import * as i18n from './translations'; import { ActionLicense } from './types'; -interface ActionLicenseState { +export interface ActionLicenseState { actionLicense: ActionLicense | null; isLoading: boolean; isError: boolean; } -const initialData: ActionLicenseState = { +export const initialData: ActionLicenseState = { actionLicense: null, isLoading: true, isError: false, diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.test.tsx new file mode 100644 index 0000000000000..10649da548d43 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.test.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { initialData, useGetCase, UseGetCase } from './use_get_case'; +import { basicCase } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useGetCase', () => { + const abortCtrl = new AbortController(); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCase(basicCase.id) + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + data: initialData, + isLoading: true, + isError: false, + fetchCase: result.current.fetchCase, + updateCase: result.current.updateCase, + }); + }); + }); + + it('calls getCase with correct arguments', async () => { + const spyOnGetCase = jest.spyOn(api, 'getCase'); + await act(async () => { + const { waitForNextUpdate } = renderHook(() => useGetCase(basicCase.id)); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(spyOnGetCase).toBeCalledWith(basicCase.id, true, abortCtrl.signal); + }); + }); + + it('fetch case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCase(basicCase.id) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + data: basicCase, + isLoading: false, + isError: false, + fetchCase: result.current.fetchCase, + updateCase: result.current.updateCase, + }); + }); + }); + + it('refetch case', async () => { + const spyOnGetCase = jest.spyOn(api, 'getCase'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCase(basicCase.id) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.fetchCase(); + expect(spyOnGetCase).toHaveBeenCalledTimes(2); + }); + }); + + it('set isLoading to true when refetching case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCase(basicCase.id) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.fetchCase(); + + expect(result.current.isLoading).toBe(true); + }); + }); + + it('unhappy path', async () => { + const spyOnGetCase = jest.spyOn(api, 'getCase'); + spyOnGetCase.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCase(basicCase.id) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ + data: initialData, + isLoading: false, + isError: true, + fetchCase: result.current.fetchCase, + updateCase: result.current.updateCase, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx index 835fb7153dc95..b2e3b6d0cacf6 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx @@ -53,7 +53,7 @@ const dataFetchReducer = (state: CaseState, action: Action): CaseState => { return state; } }; -const initialData: Case = { +export const initialData: Case = { id: '', closedAt: null, closedBy: null, @@ -73,7 +73,7 @@ const initialData: Case = { version: '', }; -interface UseGetCase extends CaseState { +export interface UseGetCase extends CaseState { fetchCase: () => void; updateCase: (newCase: Case) => void; } diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx new file mode 100644 index 0000000000000..cdd40b84f8724 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { + initialData, + useGetCaseUserActions, + UseGetCaseUserActions, +} from './use_get_case_user_actions'; +import { basicCaseId, caseUserActions, elasticUser } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useGetCaseUserActions', () => { + const abortCtrl = new AbortController(); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCaseUserActions(basicCaseId) + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + ...initialData, + fetchCaseUserActions: result.current.fetchCaseUserActions, + }); + }); + }); + + it('calls getCaseUserActions with correct arguments', async () => { + const spyOnPostCase = jest.spyOn(api, 'getCaseUserActions'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCaseUserActions(basicCaseId) + ); + await waitForNextUpdate(); + + result.current.fetchCaseUserActions(basicCaseId); + await waitForNextUpdate(); + expect(spyOnPostCase).toBeCalledWith(basicCaseId, abortCtrl.signal); + }); + }); + + it('retuns proper state on getCaseUserActions', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCaseUserActions(basicCaseId) + ); + await waitForNextUpdate(); + result.current.fetchCaseUserActions(basicCaseId); + await waitForNextUpdate(); + expect(result.current).toEqual({ + ...initialData, + caseUserActions: caseUserActions.slice(1), + fetchCaseUserActions: result.current.fetchCaseUserActions, + hasDataToPush: true, + isError: false, + isLoading: false, + participants: [elasticUser], + }); + }); + }); + + it('set isLoading to true when posting case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCaseUserActions(basicCaseId) + ); + await waitForNextUpdate(); + result.current.fetchCaseUserActions(basicCaseId); + + expect(result.current.isLoading).toBe(true); + }); + }); + + it('unhappy path', async () => { + const spyOnPostCase = jest.spyOn(api, 'getCaseUserActions'); + spyOnPostCase.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCaseUserActions(basicCaseId) + ); + await waitForNextUpdate(); + result.current.fetchCaseUserActions(basicCaseId); + + expect(result.current).toEqual({ + ...initialData, + isLoading: false, + isError: true, + fetchCaseUserActions: result.current.fetchCaseUserActions, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx index 4c278bc038134..6d9874a655e97 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx @@ -22,7 +22,7 @@ interface CaseUserActionsState { lastIndexPushToService: number; } -const initialData: CaseUserActionsState = { +export const initialData: CaseUserActionsState = { caseUserActions: [], firstIndexPushToService: -1, lastIndexPushToService: -1, @@ -32,7 +32,7 @@ const initialData: CaseUserActionsState = { participants: [], }; -interface UseGetCaseUserActions extends CaseUserActionsState { +export interface UseGetCaseUserActions extends CaseUserActionsState { fetchCaseUserActions: (caseId: string) => void; } @@ -80,6 +80,7 @@ export const useGetCaseUserActions = (caseId: string): UseGetCaseUserActions => const participants = !isEmpty(response) ? uniqBy('actionBy.username', response).map(cau => cau.actionBy) : []; + const caseUserActions = !isEmpty(response) ? response.slice(1) : []; setCaseUserActionsState({ caseUserActions, diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.test.tsx new file mode 100644 index 0000000000000..4e274e074b036 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.test.tsx @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { + DEFAULT_FILTER_OPTIONS, + DEFAULT_QUERY_PARAMS, + initialData, + useGetCases, + UseGetCases, +} from './use_get_cases'; +import { UpdateKey } from './use_update_case'; +import { allCases, basicCase } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useGetCases', () => { + const abortCtrl = new AbortController(); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + expect(result.current).toEqual({ + data: initialData, + dispatchUpdateCaseProperty: result.current.dispatchUpdateCaseProperty, + filterOptions: DEFAULT_FILTER_OPTIONS, + isError: false, + loading: [], + queryParams: DEFAULT_QUERY_PARAMS, + refetchCases: result.current.refetchCases, + selectedCases: [], + setFilters: result.current.setFilters, + setQueryParams: result.current.setQueryParams, + setSelectedCases: result.current.setSelectedCases, + }); + }); + }); + + it('calls getCases with correct arguments', async () => { + const spyOnGetCases = jest.spyOn(api, 'getCases'); + await act(async () => { + const { waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(spyOnGetCases).toBeCalledWith({ + filterOptions: DEFAULT_FILTER_OPTIONS, + queryParams: DEFAULT_QUERY_PARAMS, + signal: abortCtrl.signal, + }); + }); + }); + + it('fetch cases', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + data: allCases, + dispatchUpdateCaseProperty: result.current.dispatchUpdateCaseProperty, + filterOptions: DEFAULT_FILTER_OPTIONS, + isError: false, + loading: [], + queryParams: DEFAULT_QUERY_PARAMS, + refetchCases: result.current.refetchCases, + selectedCases: [], + setFilters: result.current.setFilters, + setQueryParams: result.current.setQueryParams, + setSelectedCases: result.current.setSelectedCases, + }); + }); + }); + it('dispatch update case property', async () => { + const spyOnPatchCase = jest.spyOn(api, 'patchCase'); + await act(async () => { + const updateCase = { + updateKey: 'description' as UpdateKey, + updateValue: 'description update', + caseId: basicCase.id, + refetchCasesStatus: jest.fn(), + version: '99999', + }; + const { result, waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.dispatchUpdateCaseProperty(updateCase); + expect(result.current.loading).toEqual(['caseUpdate']); + expect(spyOnPatchCase).toBeCalledWith( + basicCase.id, + { [updateCase.updateKey]: updateCase.updateValue }, + updateCase.version, + abortCtrl.signal + ); + }); + }); + + it('refetch cases', async () => { + const spyOnGetCases = jest.spyOn(api, 'getCases'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.refetchCases(); + expect(spyOnGetCases).toHaveBeenCalledTimes(2); + }); + }); + + it('set isLoading to true when refetching case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.refetchCases(); + + expect(result.current.loading).toEqual(['cases']); + }); + }); + + it('unhappy path', async () => { + const spyOnGetCases = jest.spyOn(api, 'getCases'); + spyOnGetCases.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ + data: initialData, + dispatchUpdateCaseProperty: result.current.dispatchUpdateCaseProperty, + filterOptions: DEFAULT_FILTER_OPTIONS, + isError: true, + loading: [], + queryParams: DEFAULT_QUERY_PARAMS, + refetchCases: result.current.refetchCases, + selectedCases: [], + setFilters: result.current.setFilters, + setQueryParams: result.current.setQueryParams, + setSelectedCases: result.current.setSelectedCases, + }); + }); + }); + it('set filters', async () => { + await act(async () => { + const spyOnGetCases = jest.spyOn(api, 'getCases'); + const newFilters = { + search: 'new', + tags: ['new'], + status: 'closed', + }; + const { result, waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.setFilters(newFilters); + await waitForNextUpdate(); + expect(spyOnGetCases.mock.calls[1][0]).toEqual({ + filterOptions: { ...DEFAULT_FILTER_OPTIONS, ...newFilters }, + queryParams: DEFAULT_QUERY_PARAMS, + signal: abortCtrl.signal, + }); + }); + }); + it('set query params', async () => { + await act(async () => { + const spyOnGetCases = jest.spyOn(api, 'getCases'); + const newQueryParams = { + page: 2, + }; + const { result, waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.setQueryParams(newQueryParams); + await waitForNextUpdate(); + expect(spyOnGetCases.mock.calls[1][0]).toEqual({ + filterOptions: DEFAULT_FILTER_OPTIONS, + queryParams: { ...DEFAULT_QUERY_PARAMS, ...newQueryParams }, + signal: abortCtrl.signal, + }); + }); + }); + it('set selected cases', async () => { + await act(async () => { + const selectedCases = [basicCase]; + const { result, waitForNextUpdate } = renderHook(() => useGetCases()); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.setSelectedCases(selectedCases); + expect(result.current.selectedCases).toEqual(selectedCases); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx index 1cbce5af6304b..465b50dbdc1bc 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx @@ -105,7 +105,7 @@ export const DEFAULT_QUERY_PARAMS: QueryParams = { sortOrder: 'desc', }; -const initialData: AllCases = { +export const initialData: AllCases = { cases: [], countClosedCases: null, countOpenCases: null, @@ -113,7 +113,7 @@ const initialData: AllCases = { perPage: 0, total: 0, }; -interface UseGetCases extends UseGetCasesState { +export interface UseGetCases extends UseGetCasesState { dispatchUpdateCaseProperty: ({ updateKey, updateValue, @@ -121,7 +121,7 @@ interface UseGetCases extends UseGetCasesState { version, refetchCasesStatus, }: UpdateCase) => void; - refetchCases: (filters: FilterOptions, queryParams: QueryParams) => void; + refetchCases: () => void; setFilters: (filters: Partial) => void; setQueryParams: (queryParams: Partial) => void; setSelectedCases: (mySelectedCases: Case[]) => void; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.test.tsx new file mode 100644 index 0000000000000..bfbcbd2525e3b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.test.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useGetCasesStatus, UseGetCasesStatus } from './use_get_cases_status'; +import { casesStatus } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useGetCasesStatus', () => { + const abortCtrl = new AbortController(); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCasesStatus() + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + countClosedCases: null, + countOpenCases: null, + isLoading: true, + isError: false, + fetchCasesStatus: result.current.fetchCasesStatus, + }); + }); + }); + + it('calls getCasesStatus api', async () => { + const spyOnGetCasesStatus = jest.spyOn(api, 'getCasesStatus'); + await act(async () => { + const { waitForNextUpdate } = renderHook(() => + useGetCasesStatus() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(spyOnGetCasesStatus).toBeCalledWith(abortCtrl.signal); + }); + }); + + it('fetch reporters', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCasesStatus() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + countClosedCases: casesStatus.countClosedCases, + countOpenCases: casesStatus.countOpenCases, + isLoading: false, + isError: false, + fetchCasesStatus: result.current.fetchCasesStatus, + }); + }); + }); + + it('unhappy path', async () => { + const spyOnGetCasesStatus = jest.spyOn(api, 'getCasesStatus'); + spyOnGetCasesStatus.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetCasesStatus() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ + countClosedCases: 0, + countOpenCases: 0, + isLoading: false, + isError: true, + fetchCasesStatus: result.current.fetchCasesStatus, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.tsx index 7f56d27ef160e..0788464602357 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases_status.tsx @@ -23,7 +23,7 @@ const initialData: CasesStatusState = { isError: false, }; -interface UseGetCasesStatus extends CasesStatusState { +export interface UseGetCasesStatus extends CasesStatusState { fetchCasesStatus: () => void; } diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx new file mode 100644 index 0000000000000..3629fbc60e4d3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useGetReporters, UseGetReporters } from './use_get_reporters'; +import { reporters, respReporters } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useGetReporters', () => { + const abortCtrl = new AbortController(); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetReporters() + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + reporters: [], + respReporters: [], + isLoading: true, + isError: false, + fetchReporters: result.current.fetchReporters, + }); + }); + }); + + it('calls getReporters api', async () => { + const spyOnGetReporters = jest.spyOn(api, 'getReporters'); + await act(async () => { + const { waitForNextUpdate } = renderHook(() => useGetReporters()); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(spyOnGetReporters).toBeCalledWith(abortCtrl.signal); + }); + }); + + it('fetch reporters', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetReporters() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + reporters, + respReporters, + isLoading: false, + isError: false, + fetchReporters: result.current.fetchReporters, + }); + }); + }); + + it('unhappy path', async () => { + const spyOnGetReporters = jest.spyOn(api, 'getReporters'); + spyOnGetReporters.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetReporters() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ + reporters: [], + respReporters: [], + isLoading: false, + isError: true, + fetchReporters: result.current.fetchReporters, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx index 2478172a3394b..2fc9b8294c8e0 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_reporters.tsx @@ -26,7 +26,7 @@ const initialData: ReportersState = { isError: false, }; -interface UseGetReporters extends ReportersState { +export interface UseGetReporters extends ReportersState { fetchReporters: () => void; } diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx new file mode 100644 index 0000000000000..3df83d1c8a596 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.test.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useGetTags, TagsState } from './use_get_tags'; +import { tags } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useGetTags', () => { + const abortCtrl = new AbortController(); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useGetTags()); + await waitForNextUpdate(); + expect(result.current).toEqual({ + tags: [], + isLoading: true, + isError: false, + }); + }); + }); + + it('calls getTags api', async () => { + const spyOnGetTags = jest.spyOn(api, 'getTags'); + await act(async () => { + const { waitForNextUpdate } = renderHook(() => useGetTags()); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(spyOnGetTags).toBeCalledWith(abortCtrl.signal); + }); + }); + + it('fetch tags', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useGetTags()); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + tags, + isLoading: false, + isError: false, + }); + }); + }); + + it('unhappy path', async () => { + const spyOnGetTags = jest.spyOn(api, 'getTags'); + spyOnGetTags.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useGetTags()); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ + tags: [], + isLoading: false, + isError: true, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx index b41d5aab5c07a..7c58316ac3fe9 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_tags.tsx @@ -10,7 +10,7 @@ import { errorToToaster, useStateToaster } from '../../components/toasters'; import { getTags } from './api'; import * as i18n from './translations'; -interface TagsState { +export interface TagsState { tags: string[]; isLoading: boolean; isError: boolean; @@ -49,7 +49,7 @@ const initialData: string[] = []; export const useGetTags = (): TagsState => { const [state, dispatch] = useReducer(dataFetchReducer, { - isLoading: false, + isLoading: true, isError: false, tags: initialData, }); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.test.tsx new file mode 100644 index 0000000000000..8b105fe041d27 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.test.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { usePostCase, UsePostCase } from './use_post_case'; +import { basicCasePost } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('usePostCase', () => { + const abortCtrl = new AbortController(); + const samplePost = { + description: 'description', + tags: ['tags'], + title: 'title', + }; + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => usePostCase()); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoading: false, + isError: false, + caseData: null, + postCase: result.current.postCase, + }); + }); + }); + + it('calls postCase with correct arguments', async () => { + const spyOnPostCase = jest.spyOn(api, 'postCase'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => usePostCase()); + await waitForNextUpdate(); + + result.current.postCase(samplePost); + await waitForNextUpdate(); + expect(spyOnPostCase).toBeCalledWith(samplePost, abortCtrl.signal); + }); + }); + + it('post case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => usePostCase()); + await waitForNextUpdate(); + result.current.postCase(samplePost); + await waitForNextUpdate(); + expect(result.current).toEqual({ + caseData: basicCasePost, + isLoading: false, + isError: false, + postCase: result.current.postCase, + }); + }); + }); + + it('set isLoading to true when posting case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => usePostCase()); + await waitForNextUpdate(); + result.current.postCase(samplePost); + + expect(result.current.isLoading).toBe(true); + }); + }); + + it('unhappy path', async () => { + const spyOnPostCase = jest.spyOn(api, 'postCase'); + spyOnPostCase.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => usePostCase()); + await waitForNextUpdate(); + result.current.postCase(samplePost); + + expect(result.current).toEqual({ + caseData: null, + isLoading: false, + isError: true, + postCase: result.current.postCase, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx index 0e01364721dc5..aeb50fc098eee 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx @@ -48,7 +48,7 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => } }; -interface UsePostCase extends NewCaseState { +export interface UsePostCase extends NewCaseState { postCase: (data: CasePostRequest) => void; } export const usePostCase = (): UsePostCase => { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.test.tsx new file mode 100644 index 0000000000000..d7d9cf9c557c9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { usePostComment, UsePostComment } from './use_post_comment'; +import { basicCaseId } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('usePostComment', () => { + const abortCtrl = new AbortController(); + const samplePost = { + comment: 'a comment', + }; + const updateCaseCallback = jest.fn(); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostComment(basicCaseId) + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoading: false, + isError: false, + postComment: result.current.postComment, + }); + }); + }); + + it('calls postComment with correct arguments', async () => { + const spyOnPostCase = jest.spyOn(api, 'postComment'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostComment(basicCaseId) + ); + await waitForNextUpdate(); + + result.current.postComment(samplePost, updateCaseCallback); + await waitForNextUpdate(); + expect(spyOnPostCase).toBeCalledWith(samplePost, basicCaseId, abortCtrl.signal); + }); + }); + + it('post case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostComment(basicCaseId) + ); + await waitForNextUpdate(); + result.current.postComment(samplePost, updateCaseCallback); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoading: false, + isError: false, + postComment: result.current.postComment, + }); + }); + }); + + it('set isLoading to true when posting case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostComment(basicCaseId) + ); + await waitForNextUpdate(); + result.current.postComment(samplePost, updateCaseCallback); + + expect(result.current.isLoading).toBe(true); + }); + }); + + it('unhappy path', async () => { + const spyOnPostCase = jest.spyOn(api, 'postComment'); + spyOnPostCase.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostComment(basicCaseId) + ); + await waitForNextUpdate(); + result.current.postComment(samplePost, updateCaseCallback); + + expect(result.current).toEqual({ + isLoading: false, + isError: true, + postComment: result.current.postComment, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx index 207b05814717f..c6d34b5449977 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx @@ -41,7 +41,7 @@ const dataFetchReducer = (state: NewCommentState, action: Action): NewCommentSta } }; -interface UsePostComment extends NewCommentState { +export interface UsePostComment extends NewCommentState { postComment: (data: CommentRequest, updateCase: (newCase: Case) => void) => void; } diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.test.tsx new file mode 100644 index 0000000000000..b07a346a8da46 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.test.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { + formatServiceRequestData, + usePostPushToService, + UsePostPushToService, +} from './use_post_push_to_service'; +import { basicCase, pushedCase, serviceConnector } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('usePostPushToService', () => { + const abortCtrl = new AbortController(); + const updateCase = jest.fn(); + const samplePush = { + caseId: pushedCase.id, + connectorName: 'sample', + connectorId: '22', + updateCase, + }; + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostPushToService() + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + serviceData: null, + pushedCaseData: null, + isLoading: false, + isError: false, + postPushToService: result.current.postPushToService, + }); + }); + }); + + it('calls pushCase with correct arguments', async () => { + const spyOnPushCase = jest.spyOn(api, 'pushCase'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostPushToService() + ); + await waitForNextUpdate(); + result.current.postPushToService(samplePush); + await waitForNextUpdate(); + expect(spyOnPushCase).toBeCalledWith( + samplePush.caseId, + { + connector_id: samplePush.connectorId, + connector_name: samplePush.connectorName, + external_id: serviceConnector.incidentId, + external_title: serviceConnector.number, + external_url: serviceConnector.url, + }, + abortCtrl.signal + ); + }); + }); + + it('calls pushToService with correct arguments', async () => { + const spyOnPushToService = jest.spyOn(api, 'pushToService'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostPushToService() + ); + await waitForNextUpdate(); + result.current.postPushToService(samplePush); + await waitForNextUpdate(); + expect(spyOnPushToService).toBeCalledWith( + samplePush.connectorId, + formatServiceRequestData(basicCase), + abortCtrl.signal + ); + }); + }); + + it('post push to service', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostPushToService() + ); + await waitForNextUpdate(); + result.current.postPushToService(samplePush); + await waitForNextUpdate(); + expect(result.current).toEqual({ + serviceData: serviceConnector, + pushedCaseData: pushedCase, + isLoading: false, + isError: false, + postPushToService: result.current.postPushToService, + }); + }); + }); + + it('set isLoading to true when deleting cases', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostPushToService() + ); + await waitForNextUpdate(); + result.current.postPushToService(samplePush); + expect(result.current.isLoading).toBe(true); + }); + }); + + it('unhappy path', async () => { + const spyOnPushToService = jest.spyOn(api, 'pushToService'); + spyOnPushToService.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostPushToService() + ); + await waitForNextUpdate(); + result.current.postPushToService(samplePush); + await waitForNextUpdate(); + + expect(result.current).toEqual({ + serviceData: null, + pushedCaseData: null, + isLoading: false, + isError: true, + postPushToService: result.current.postPushToService, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx index d9a32f26f7fe7..89e7e18cf0688 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx @@ -68,7 +68,7 @@ interface PushToServiceRequest { updateCase: (newCase: Case) => void; } -interface UsePostPushToService extends PushToServiceState { +export interface UsePostPushToService extends PushToServiceState { postPushToService: ({ caseId, connectorId, updateCase }: PushToServiceRequest) => void; } @@ -131,7 +131,7 @@ export const usePostPushToService = (): UsePostPushToService => { return { ...state, postPushToService }; }; -const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => { +export const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => { const { id: caseId, createdAt, diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.test.tsx new file mode 100644 index 0000000000000..86cfc3459c595 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.test.tsx @@ -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 { renderHook, act } from '@testing-library/react-hooks'; +import { useUpdateCase, UseUpdateCase, UpdateKey } from './use_update_case'; +import { basicCase } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useUpdateCase', () => { + const abortCtrl = new AbortController(); + const fetchCaseUserActions = jest.fn(); + const updateCase = jest.fn(); + const updateKey: UpdateKey = 'description'; + const sampleUpdate = { + fetchCaseUserActions, + updateKey, + updateValue: 'updated description', + updateCase, + version: basicCase.version, + }; + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCase({ caseId: basicCase.id }) + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoading: false, + isError: false, + updateKey: null, + updateCaseProperty: result.current.updateCaseProperty, + }); + }); + }); + + it('calls patchCase with correct arguments', async () => { + const spyOnPatchCase = jest.spyOn(api, 'patchCase'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCase({ caseId: basicCase.id }) + ); + await waitForNextUpdate(); + + result.current.updateCaseProperty(sampleUpdate); + await waitForNextUpdate(); + expect(spyOnPatchCase).toBeCalledWith( + basicCase.id, + { description: 'updated description' }, + basicCase.version, + abortCtrl.signal + ); + }); + }); + + it('patch case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCase({ caseId: basicCase.id }) + ); + await waitForNextUpdate(); + result.current.updateCaseProperty(sampleUpdate); + await waitForNextUpdate(); + expect(result.current).toEqual({ + updateKey: null, + isLoading: false, + isError: false, + updateCaseProperty: result.current.updateCaseProperty, + }); + expect(fetchCaseUserActions).toBeCalledWith(basicCase.id); + expect(updateCase).toBeCalledWith(basicCase); + }); + }); + + it('set isLoading to true when posting case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCase({ caseId: basicCase.id }) + ); + await waitForNextUpdate(); + result.current.updateCaseProperty(sampleUpdate); + + expect(result.current.isLoading).toBe(true); + expect(result.current.updateKey).toBe(updateKey); + }); + }); + + it('unhappy path', async () => { + const spyOnPatchCase = jest.spyOn(api, 'patchCase'); + spyOnPatchCase.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCase({ caseId: basicCase.id }) + ); + await waitForNextUpdate(); + result.current.updateCaseProperty(sampleUpdate); + + expect(result.current).toEqual({ + updateKey: null, + isLoading: false, + isError: true, + updateCaseProperty: result.current.updateCaseProperty, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx index 4973deef4d91a..7ebbbba076c12 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx @@ -12,7 +12,7 @@ import { patchCase } from './api'; import * as i18n from './translations'; import { Case } from './types'; -type UpdateKey = keyof Pick; +export type UpdateKey = keyof Pick; interface NewCaseState { isLoading: boolean; @@ -62,7 +62,7 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => } }; -interface UseUpdateCase extends NewCaseState { +export interface UseUpdateCase extends NewCaseState { updateCaseProperty: (updates: UpdateByKey) => void; } export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase => { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.test.tsx new file mode 100644 index 0000000000000..5772ff4246866 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.test.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useUpdateComment, UseUpdateComment } from './use_update_comment'; +import { basicCase, basicCaseCommentPatch } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useUpdateComment', () => { + const abortCtrl = new AbortController(); + const fetchUserActions = jest.fn(); + const updateCase = jest.fn(); + const sampleUpdate = { + caseId: basicCase.id, + commentId: basicCase.comments[0].id, + commentUpdate: 'updated comment', + fetchUserActions, + updateCase, + version: basicCase.comments[0].version, + }; + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateComment() + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoadingIds: [], + isError: false, + patchComment: result.current.patchComment, + }); + }); + }); + + it('calls patchComment with correct arguments', async () => { + const spyOnPatchComment = jest.spyOn(api, 'patchComment'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateComment() + ); + await waitForNextUpdate(); + + result.current.patchComment(sampleUpdate); + await waitForNextUpdate(); + expect(spyOnPatchComment).toBeCalledWith( + basicCase.id, + basicCase.comments[0].id, + 'updated comment', + basicCase.comments[0].version, + abortCtrl.signal + ); + }); + }); + + it('patch comment', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateComment() + ); + await waitForNextUpdate(); + result.current.patchComment(sampleUpdate); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoadingIds: [], + isError: false, + patchComment: result.current.patchComment, + }); + expect(fetchUserActions).toBeCalled(); + expect(updateCase).toBeCalledWith(basicCaseCommentPatch); + }); + }); + + it('set isLoading to true when posting case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateComment() + ); + await waitForNextUpdate(); + result.current.patchComment(sampleUpdate); + + expect(result.current.isLoadingIds).toEqual([basicCase.comments[0].id]); + }); + }); + + it('unhappy path', async () => { + const spyOnPatchComment = jest.spyOn(api, 'patchComment'); + spyOnPatchComment.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateComment() + ); + await waitForNextUpdate(); + result.current.patchComment(sampleUpdate); + + expect(result.current).toEqual({ + isLoadingIds: [], + isError: true, + patchComment: result.current.patchComment, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx index faf9649a705c5..ffc5cffee7a55 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx @@ -60,7 +60,7 @@ interface UpdateComment { version: string; } -interface UseUpdateComment extends CommentUpdateState { +export interface UseUpdateComment extends CommentUpdateState { patchComment: ({ caseId, commentId, commentUpdate, fetchUserActions }: UpdateComment) => void; } 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 c7692755c1330..952f7f51b63f2 100644 --- a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx @@ -20,6 +20,7 @@ import { ThemeProvider } from 'styled-components'; import { createStore, State } from '../store'; import { mockGlobalState } from './global_state'; import { createKibanaContextProviderMock } from './kibana_react'; +import { FieldHook, useForm } from '../shared_imports'; jest.mock('ui/new_platform'); @@ -91,3 +92,29 @@ const TestProviderWithoutDragAndDropComponent: React.FC = ({ ); export const TestProviderWithoutDragAndDrop = React.memo(TestProviderWithoutDragAndDropComponent); + +export const useFormFieldMock = (options?: Partial): FieldHook => { + const { form } = useForm(); + + return { + path: 'path', + type: 'type', + value: [], + isPristine: false, + isValidating: false, + isValidated: false, + isChangingValue: false, + form, + errors: [], + isValid: true, + getErrorsMessages: jest.fn(), + onChange: jest.fn(), + setValue: jest.fn(), + setErrors: jest.fn(), + clearErrors: jest.fn(), + validate: jest.fn(), + reset: jest.fn(), + __serializeOutput: jest.fn(), + ...options, + }; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx index aefb0a93366b8..2b613f6692df1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx @@ -26,7 +26,6 @@ export const CasesPage = React.memo(() => { message={savedObjectReadOnly.description} /> )} - diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx deleted file mode 100644 index 64c6276fc1be2..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx +++ /dev/null @@ -1,226 +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 { CaseProps } from '../case_view'; -import { Case, Comment, SortFieldCase } from '../../../../containers/case/types'; -import { UseGetCasesState } from '../../../../containers/case/use_get_cases'; -import { UserAction, UserActionField } from '../../../../../../../../plugins/case/common/api/cases'; - -const updateCase = jest.fn(); -const fetchCase = jest.fn(); - -const basicCaseId = 'basic-case-id'; -const basicCommentId = 'basic-comment-id'; -const basicCreatedAt = '2020-02-20T23:06:33.798Z'; -const elasticUser = { - fullName: 'Leslie Knope', - username: 'lknope', - email: 'leslie.knope@elastic.co', -}; - -export const basicComment: Comment = { - comment: 'Solve this fast!', - id: basicCommentId, - createdAt: basicCreatedAt, - createdBy: elasticUser, - pushedAt: null, - pushedBy: null, - updatedAt: '2020-02-20T23:06:33.798Z', - updatedBy: { - username: 'elastic', - }, - version: 'WzQ3LDFc', -}; - -export const basicCase: Case = { - closedAt: null, - closedBy: null, - id: basicCaseId, - comments: [basicComment], - createdAt: '2020-02-13T19:44:23.627Z', - createdBy: elasticUser, - description: 'Security banana Issue', - externalService: null, - status: 'open', - tags: ['defacement'], - title: 'Another horrible breach!!', - totalComment: 1, - updatedAt: '2020-02-19T15:02:57.995Z', - updatedBy: { - username: 'elastic', - }, - version: 'WzQ3LDFd', -}; - -export const caseProps: CaseProps = { - caseId: basicCaseId, - userCanCrud: true, - caseData: basicCase, - fetchCase, - updateCase, -}; - -export const caseClosedProps: CaseProps = { - ...caseProps, - caseData: { - ...caseProps.caseData, - closedAt: '2020-02-20T23:06:33.798Z', - closedBy: { - username: 'elastic', - }, - status: 'closed', - }, -}; - -export const basicCaseClosed: Case = { - ...caseClosedProps.caseData, -}; - -const basicAction = { - actionAt: basicCreatedAt, - actionBy: elasticUser, - oldValue: null, - newValue: 'what a cool value', - caseId: basicCaseId, - commentId: null, -}; -export const caseUserActions = [ - { - ...basicAction, - actionBy: elasticUser, - actionField: ['comment'], - action: 'create', - actionId: 'tt', - }, -]; - -export const useGetCasesMockState: UseGetCasesState = { - data: { - countClosedCases: 0, - countOpenCases: 5, - cases: [ - basicCase, - { - closedAt: null, - closedBy: null, - id: '362a5c10-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:13.328Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: { - pushedAt: '2020-02-13T19:45:01.901Z', - pushedBy: 'elastic', - connectorId: 'string', - connectorName: 'string', - externalId: 'string', - externalTitle: 'string', - externalUrl: 'string', - }, - status: 'open', - tags: ['phishing'], - title: 'Bad email', - totalComment: 0, - updatedAt: '2020-02-13T15:45:01.901Z', - updatedBy: { username: 'elastic' }, - version: 'WzQ3LDFd', - }, - { - closedAt: null, - closedBy: null, - id: '34f8b9e0-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:11.328Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: { - pushedAt: '2020-02-13T19:45:01.901Z', - pushedBy: 'elastic', - connectorId: 'string', - connectorName: 'string', - externalId: 'string', - externalTitle: 'string', - externalUrl: 'string', - }, - status: 'open', - tags: ['phishing'], - title: 'Bad email', - totalComment: 0, - updatedAt: '2020-02-14T19:45:01.901Z', - updatedBy: { username: 'elastic' }, - version: 'WzQ3LDFd', - }, - { - closedAt: '2020-02-13T19:44:13.328Z', - closedBy: { username: 'elastic' }, - id: '31890e90-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:05.563Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: null, - status: 'closed', - tags: ['phishing'], - title: 'Uh oh', - totalComment: 0, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFd', - }, - { - closedAt: null, - closedBy: null, - id: '2f5b3210-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:01.901Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: null, - status: 'open', - tags: ['phishing'], - title: 'Uh oh', - totalComment: 0, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFd', - }, - ], - page: 1, - perPage: 5, - total: 10, - }, - loading: [], - selectedCases: [], - isError: false, - queryParams: { - page: 1, - perPage: 5, - sortField: SortFieldCase.createdAt, - sortOrder: 'desc', - }, - filterOptions: { search: '', reporters: [], tags: [], status: 'open' }, -}; - -const basicPush = { - connector_id: 'connector_id', - connector_name: 'connector name', - external_id: 'external_id', - external_title: 'external title', - external_url: 'basicPush.com', - pushed_at: basicCreatedAt, - pushed_by: elasticUser, -}; -export const getUserAction = (af: UserActionField, a: UserAction) => ({ - ...basicAction, - actionId: `${af[0]}-${a}`, - actionField: af, - action: a, - commentId: af[0] === 'comment' ? basicCommentId : null, - newValue: - a === 'push-to-service' && af[0] === 'pushed' - ? JSON.stringify(basicPush) - : basicAction.newValue, -}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx index e008b94ab9e16..31c795c05edd5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx @@ -9,7 +9,7 @@ import { mount } from 'enzyme'; import { ServiceNowColumn } from './columns'; -import { useGetCasesMockState } from '../__mock__/case_data'; +import { useGetCasesMockState } from '../../../../containers/case/mock'; describe('ServiceNowColumn ', () => { it('Not pushed render', () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx index f65736e7cd109..58d0c1b0faaf3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx @@ -9,7 +9,7 @@ import { mount } from 'enzyme'; import moment from 'moment-timezone'; import { AllCases } from './'; import { TestProviders } from '../../../../mock'; -import { useGetCasesMockState } from '../__mock__/case_data'; +import { useGetCasesMockState } from '../../../../containers/case/mock'; import * as i18n from './translations'; import { getEmptyTagValue } from '../../../../components/empty_value'; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index b0ff3dbada6c9..c50b7d8c17abc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -131,11 +131,11 @@ export const AllCases = React.memo(({ userCanCrud }) => { const [deleteBulk, setDeleteBulk] = useState([]); const refreshCases = useCallback(() => { - refetchCases(filterOptions, queryParams); + refetchCases(); fetchCasesStatus(); setSelectedCases([]); setDeleteBulk([]); - }, [filterOptions, queryParams]); + }, []); useEffect(() => { if (isDeleted) { diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx index 8a25a2121104d..8b6ee76dd783d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx @@ -9,7 +9,7 @@ import { mount } from 'enzyme'; import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; import { TestProviders } from '../../../../mock'; -import { basicCase } from '../__mock__/case_data'; +import { basicCase } from '../../../../containers/case/mock'; import { CaseViewActions } from './actions'; jest.mock('../../../../containers/case/use_delete_cases'); const useDeleteCasesMock = useDeleteCases as jest.Mock; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx index 3721a5a727ca5..7ce9d7b8533e4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx @@ -8,13 +8,8 @@ import React from 'react'; import { mount } from 'enzyme'; import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; -import { CaseComponent, CaseView } from './'; -import { - basicCaseClosed, - caseClosedProps, - caseProps, - caseUserActions, -} from '../__mock__/case_data'; +import { CaseComponent, CaseProps, CaseView } from './'; +import { basicCase, basicCaseClosed, caseUserActions } from '../../../../containers/case/mock'; import { TestProviders } from '../../../../mock'; import { useUpdateCase } from '../../../../containers/case/use_update_case'; import { useGetCase } from '../../../../containers/case/use_get_case'; @@ -29,6 +24,19 @@ const useUpdateCaseMock = useUpdateCase as jest.Mock; const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock; const usePushToServiceMock = usePushToService as jest.Mock; +export const caseProps: CaseProps = { + caseId: basicCase.id, + userCanCrud: true, + caseData: basicCase, + fetchCase: jest.fn(), + updateCase: jest.fn(), +}; + +export const caseClosedProps: CaseProps = { + ...caseProps, + caseData: basicCaseClosed, +}; + describe('CaseView ', () => { const updateCaseProperty = jest.fn(); const fetchCaseUserActions = jest.fn(); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts index 17132b9610754..70b8035db5c16 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts @@ -118,3 +118,6 @@ export const EMAIL_BODY = (caseUrl: string) => values: { caseUrl }, defaultMessage: 'Case reference: {caseUrl}', }); +export const UNKNOWN = i18n.translate('xpack.siem.case.caseView.unknown', { + defaultMessage: 'Unknown', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx index 5c342538f0feb..e34981286bc81 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { getUserAction } from '../__mock__/case_data'; +import { getUserAction } from '../../../../containers/case/mock'; import { getLabelTitle } from './helpers'; import * as i18n from '../case_view/translations'; import { mount } from 'enzyme'; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx index 0d8cd729b4a1d..1c71260422d4b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx @@ -10,7 +10,7 @@ import { mount } from 'enzyme'; import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; import { getFormMock } from '../__mock__/form'; import { useUpdateComment } from '../../../../containers/case/use_update_comment'; -import { basicCase, getUserAction } from '../__mock__/case_data'; +import { basicCase, getUserAction } from '../../../../containers/case/mock'; import { UserActionTree } from './'; import { TestProviders } from '../../../../mock'; import { useFormMock } from '../create/index.test'; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx index f8f3f0651fa3c..d1e8eb3f6306b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx @@ -179,7 +179,7 @@ export const UserActionTree = React.memo( markdown={MarkdownDescription} onEdit={handleManageMarkdownEditId.bind(null, DESCRIPTION_ID)} onQuote={handleManageQuote.bind(null, caseData.description)} - username={caseData.createdBy.username ?? 'Unknown'} + username={caseData.createdBy.username ?? i18n.UNKNOWN} /> {caseUserActions.map((action, index) => { diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx index e2189367068ca..8a1e8a80f664d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; import copy from 'copy-to-clipboard'; import { Router, routeData, mockHistory } from '../__mock__/router'; -import { caseUserActions as basicUserActions } from '../__mock__/case_data'; +import { caseUserActions as basicUserActions } from '../../../../containers/case/mock'; import { UserActionTitle } from './user_action_title'; import { TestProviders } from '../../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx index a1edbab7e1fa2..fc2a74466dedc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx @@ -43,7 +43,7 @@ interface UserActionTitleProps { linkId?: string | null; fullName?: string | null; updatedAt?: string | null; - username: string; + username?: string | null; onEdit?: (id: string) => void; onQuote?: (id: string) => void; outlineComment?: (id: string) => void; @@ -63,7 +63,7 @@ export const UserActionTitle = ({ onQuote, outlineComment, updatedAt, - username, + username = i18n.UNKNOWN, }: UserActionTitleProps) => { const { detailName: caseId } = useParams(); const urlSearch = useGetUrlSearch(navTabs.case); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.test.tsx new file mode 100644 index 0000000000000..c5a4057b64ea7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { ActivityMonitor } from './index'; + +describe('activity_monitor', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('[title="Activity monitor"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/index.test.tsx new file mode 100644 index 0000000000000..a2685017f86d6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { DetectionEngineHeaderPage } from './index'; + +describe('detection_engine_header_page', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('[title="Title"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/index.test.tsx new file mode 100644 index 0000000000000..0e2589150e858 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_api_integration_callout/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { NoApiIntegrationKeyCallOut } from './index'; + +describe('no_api_integration_callout', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/index.test.tsx new file mode 100644 index 0000000000000..2e6890e60fc61 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { NoWriteSignalsCallOut } from './index'; + +describe('no_write_signals_callout', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.test.tsx new file mode 100644 index 0000000000000..b66a9fc881045 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.test.tsx @@ -0,0 +1,43 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { SignalsTableComponent } from './index'; + +describe('SignalsTableComponent', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[title="Signals"]')).toBeTruthy(); + }); +}); 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 index 6cdb2f326901e..ce8ae2054b2c7 100644 --- 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 @@ -61,7 +61,7 @@ interface OwnProps { type SignalsTableComponentProps = OwnProps & PropsFromRedux; -const SignalsTableComponent: React.FC = ({ +export const SignalsTableComponent: React.FC = ({ canUserCRUD, clearEventsDeleted, clearEventsLoading, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.test.tsx new file mode 100644 index 0000000000000..dd30bb1b0a74d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SignalsTableFilterGroup } from './index'; + +describe('SignalsTableFilterGroup', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiFilterButton')).toBeTruthy(); + }); +}); 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 deleted file mode 100644 index bb45ff68cb01d..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.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 { 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'; - -interface GetBatchItems { - areEventsLoading: boolean; - allEventsSelected: boolean; - selectedEventIds: Readonly>; - updateSignalsStatus: UpdateSignalsStatus; - sendSignalsToTimeline: SendSignalsToTimeline; - closePopover: () => void; - isFilteredToOpen: boolean; -} -/** - * 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, - allEventsSelected, - selectedEventIds, - updateSignalsStatus, - sendSignalsToTimeline, - closePopover, - isFilteredToOpen, -}: GetBatchItems) => { - 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.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.test.tsx new file mode 100644 index 0000000000000..6cab43b5285b5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SignalsUtilityBar } from './index'; + +jest.mock('../../../../../lib/kibana'); + +describe('SignalsUtilityBar', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[dataTestSubj="openCloseSignal"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.test.tsx new file mode 100644 index 0000000000000..6921c49d8a8b4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SignalsHistogramPanel } from './index'; + +jest.mock('../../../../lib/kibana'); +jest.mock('../../../../components/navigation/use_get_url_search'); + +describe('SignalsHistogramPanel', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[id="detections-histogram"]')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.test.tsx new file mode 100644 index 0000000000000..5eb9beaaaf76a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.test.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SignalsHistogram } from './signals_histogram'; + +jest.mock('../../../../lib/kibana'); + +describe('SignalsHistogram', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('Chart')).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.test.tsx new file mode 100644 index 0000000000000..b3d710de5e94e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useUserInfo } from './index'; + +import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user'; +import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index'; +import { useKibana } from '../../../../lib/kibana'; +jest.mock('../../../../containers/detection_engine/signals/use_privilege_user'); +jest.mock('../../../../containers/detection_engine/signals/use_signal_index'); +jest.mock('../../../../lib/kibana'); + +describe('useUserInfo', () => { + beforeAll(() => { + (usePrivilegeUser as jest.Mock).mockReturnValue({}); + (useSignalIndex as jest.Mock).mockReturnValue({}); + (useKibana as jest.Mock).mockReturnValue({ + services: { + application: { + capabilities: { + siem: { + crud: true, + }, + }, + }, + }, + }); + }); + it('returns default state', () => { + const { result } = renderHook(() => useUserInfo()); + + expect(result).toEqual({ + current: { + canUserCRUD: null, + hasEncryptionKey: null, + hasIndexManage: null, + hasIndexWrite: null, + isAuthenticated: null, + isSignalIndexExists: null, + loading: true, + signalIndexName: null, + }, + error: undefined, + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.test.tsx new file mode 100644 index 0000000000000..779e9a4557f2a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.test.tsx @@ -0,0 +1,43 @@ +/* + * 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 { shallow } from 'enzyme'; +import { useParams } from 'react-router-dom'; + +import '../../mock/match_media'; +import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; +import { DetectionEnginePageComponent } from './detection_engine'; +import { useUserInfo } from './components/user_info'; + +jest.mock('./components/user_info'); +jest.mock('../../lib/kibana'); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useParams: jest.fn(), + }; +}); + +describe('DetectionEnginePageComponent', () => { + beforeAll(() => { + (useParams as jest.Mock).mockReturnValue({}); + (useUserInfo as jest.Mock).mockReturnValue({}); + }); + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('WithSource')).toHaveLength(1); + }); +}); 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 1bd7ab2c4f1ae..a26d7f5672106 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 @@ -59,7 +59,7 @@ const detectionsTabs: Record = { }, }; -const DetectionEnginePageComponent: React.FC = ({ +export const DetectionEnginePageComponent: React.FC = ({ filters, query, setAbsoluteRangeDatePicker, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.test.tsx new file mode 100644 index 0000000000000..f64526fd2f7c4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; +jest.mock('../../lib/kibana'); + +describe('DetectionEngineEmptyPage', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EmptyPage')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.test.tsx new file mode 100644 index 0000000000000..e9f05f7aafe3c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; +jest.mock('../../lib/kibana'); + +describe('DetectionEngineNoIndex', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EmptyPage')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.test.tsx new file mode 100644 index 0000000000000..e71f4de2b010b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; +jest.mock('../../lib/kibana'); + +describe('DetectionEngineUserUnauthenticated', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EmptyPage')).toHaveLength(1); + }); +}); 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 deleted file mode 100644 index 1399df0fcf6d1..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/helpers.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -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.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.test.tsx new file mode 100644 index 0000000000000..6c4980f1d1500 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import '../../mock/match_media'; +import { DetectionEngineContainer } from './index'; + +describe('DetectionEngineContainer', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('ManageUserInfo')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.test.tsx new file mode 100644 index 0000000000000..f4955c2a93b8d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { AllRules } from './index'; + +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useHistory: jest.fn(), + }; +}); + +describe('AllRules', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[title="All rules"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx new file mode 100644 index 0000000000000..92f69d79110d2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { RulesTableFilters } from './rules_table_filters'; + +describe('RulesTableFilters', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="show-elastic-rules-filter-button"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx new file mode 100644 index 0000000000000..e31b8394e07d6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { TagsFilterPopover } from './tags_filter_popover'; + +describe('TagsFilterPopover', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('EuiPopover')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.test.tsx new file mode 100644 index 0000000000000..9202da3336565 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { AccordionTitle } from './index'; + +describe('AccordionTitle', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('h6').text()).toContain('title'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.test.tsx new file mode 100644 index 0000000000000..eafa89a33f596 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { AddItem } from './index'; +import { useFormFieldMock } from '../../../../../../public/mock/test_providers'; + +describe('AddItem', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[iconType="plusInCircle"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/all_rules_tables/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/all_rules_tables/index.test.tsx new file mode 100644 index 0000000000000..3dab83bca2946 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/all_rules_tables/index.test.tsx @@ -0,0 +1,46 @@ +/* + * 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, { useRef } from 'react'; +import { shallow } from 'enzyme'; + +import { AllRulesTables } from './index'; + +describe('AllRulesTables', () => { + it('renders correctly', () => { + const Component = () => { + const ref = useRef(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="rules-table"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.test.tsx new file mode 100644 index 0000000000000..c0e957d94261f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.test.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { AnomalyThresholdSlider } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('AnomalyThresholdSlider', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('EuiRange')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx index 19d1c698cbd9b..01fddf98b97d8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/anomaly_threshold_slider/index.tsx @@ -16,10 +16,10 @@ interface AnomalyThresholdSliderProps { type Event = React.ChangeEvent; type EventArg = Event | React.MouseEvent; -export const AnomalyThresholdSlider: React.FC = ({ +export const AnomalyThresholdSlider = ({ describedByIds = [], field, -}) => { +}: AnomalyThresholdSliderProps) => { const threshold = field.value as number; const onThresholdChange = useCallback( (event: EventArg) => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.test.tsx new file mode 100644 index 0000000000000..59231c31d15bb --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { MlJobDescription, AuditIcon, JobStatusBadge } from './ml_job_description'; +jest.mock('../../../../../lib/kibana'); + +const job = { + moduleId: 'moduleId', + defaultIndexPattern: 'defaultIndexPattern', + isCompatible: true, + isInstalled: true, + isElasticJob: true, + datafeedId: 'datafeedId', + datafeedIndices: [], + datafeedState: 'datafeedState', + description: 'description', + groups: [], + hasDatafeed: true, + id: 'id', + isSingleMetricViewerJob: false, + jobState: 'jobState', + memory_status: 'memory_status', + processed_record_count: 0, +}; + +describe('MlJobDescription', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="machineLearningJobId"]')).toHaveLength(1); + }); +}); + +describe('AuditIcon', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiToolTip')).toHaveLength(0); + }); +}); + +describe('JobStatusBadge', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiBadge')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx index 5a9593f1a6de2..1664ea320bc1e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx @@ -20,7 +20,7 @@ enum MessageLevels { error = 'error', } -const AuditIcon: React.FC<{ +const AuditIconComponent: React.FC<{ message: SiemJob['auditMessage']; }> = ({ message }) => { if (!message) { @@ -45,7 +45,9 @@ const AuditIcon: React.FC<{ ); }; -export const JobStatusBadge: React.FC<{ job: SiemJob }> = ({ job }) => { +export const AuditIcon = React.memo(AuditIconComponent); + +const JobStatusBadgeComponent: React.FC<{ job: SiemJob }> = ({ job }) => { const isStarted = isJobStarted(job.jobState, job.datafeedState); const color = isStarted ? 'secondary' : 'danger'; const text = isStarted ? ML_JOB_STARTED : ML_JOB_STOPPED; @@ -57,6 +59,8 @@ export const JobStatusBadge: React.FC<{ job: SiemJob }> = ({ job }) => { ); }; +export const JobStatusBadge = React.memo(JobStatusBadgeComponent); + const JobLink = styled(EuiLink)` margin-right: ${({ theme }) => theme.eui.euiSizeS}; `; @@ -65,7 +69,7 @@ const Wrapper = styled.div` overflow: hidden; `; -export const MlJobDescription: React.FC<{ job: SiemJob }> = ({ job }) => { +const MlJobDescriptionComponent: React.FC<{ job: SiemJob }> = ({ job }) => { const jobUrl = useKibana().services.application.getUrlForApp( `ml#/jobs?mlManagement=(jobId:${encodeURI(job.id)})` ); @@ -83,6 +87,8 @@ export const MlJobDescription: React.FC<{ job: SiemJob }> = ({ job }) => { ); }; +export const MlJobDescription = React.memo(MlJobDescriptionComponent); + export const buildMlJobDescription = ( jobId: string, label: string, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.test.tsx new file mode 100644 index 0000000000000..dc201eb21c911 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.test.tsx @@ -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 { isMitreAttackInvalid } from './helpers'; + +describe('isMitreAttackInvalid', () => { + it('returns true if tacticName is empty', () => { + expect(isMitreAttackInvalid('', undefined)).toBe(true); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.test.tsx new file mode 100644 index 0000000000000..3e8d542682456 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.test.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 React from 'react'; +import { shallow } from 'enzyme'; + +import { AddMitreThreat } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('AddMitreThreat', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="addMitre"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.test.tsx new file mode 100644 index 0000000000000..dea27d8d04536 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.test.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 React from 'react'; +import { shallow } from 'enzyme'; + +import { MlJobSelect } from './index'; +import { useSiemJobs } from '../../../../../components/ml_popover/hooks/use_siem_jobs'; +import { useFormFieldMock } from '../../../../../mock'; +jest.mock('../../../../../components/ml_popover/hooks/use_siem_jobs'); +jest.mock('../../../../../lib/kibana'); + +describe('MlJobSelect', () => { + beforeAll(() => { + (useSiemJobs as jest.Mock).mockReturnValue([false, []]); + }); + + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="mlJobSelect"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.test.tsx new file mode 100644 index 0000000000000..789f12f290a34 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.test.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallow } from 'enzyme'; + +import { OptionalFieldLabel } from './index'; + +describe('OptionalFieldLabel', () => { + it('renders correctly', () => { + const wrapper = shallow(OptionalFieldLabel); + + expect(wrapper.find('EuiTextColor')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.test.tsx new file mode 100644 index 0000000000000..fefc9697176c4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.test.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 React from 'react'; +import { shallow } from 'enzyme'; + +import { PickTimeline } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('PickTimeline', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="pick-timeline"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.test.tsx new file mode 100644 index 0000000000000..8ace42fc5c3f9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.test.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { PrePackagedRulesPrompt } from './load_empty_prompt'; + +describe('PrePackagedRulesPrompt', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('EmptyPrompt')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx index 1cff4751e8188..5d136265ef1f2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx @@ -15,6 +15,8 @@ const EmptyPrompt = styled(EuiEmptyPrompt)` align-self: center; /* Corrects horizontal centering in IE11 */ `; +EmptyPrompt.displayName = 'EmptyPrompt'; + interface PrePackagedRulesPromptProps { createPrePackagedRules: () => void; loading: boolean; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.test.tsx new file mode 100644 index 0000000000000..807da79fb7a1a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { UpdatePrePackagedRulesCallOut } from './update_callout'; +import { useKibana } from '../../../../../lib/kibana'; +jest.mock('../../../../../lib/kibana'); + +describe('UpdatePrePackagedRulesCallOut', () => { + beforeAll(() => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + docLinks: { + ELASTIC_WEBSITE_URL: '', + DOC_LINK_VERSION: '', + }, + }, + }); + }); + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.test.tsx new file mode 100644 index 0000000000000..cdd06ad58bb4b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { QueryBarDefineRule } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +jest.mock('../../../../../lib/kibana'); + +describe('QueryBarDefineRule', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="query-bar-define-rule"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/index.test.tsx new file mode 100644 index 0000000000000..e761cb3323b2c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { ReadOnlyCallOut } from './index'; + +describe('ReadOnlyCallOut', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx new file mode 100644 index 0000000000000..4cfad36b2933f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { RuleActionsField } from './index'; +import { useKibana } from '../../../../../lib/kibana'; +import { useFormFieldMock } from '../../../../../mock'; +jest.mock('../../../../../lib/kibana'); + +describe('RuleActionsField', () => { + it('should not render ActionForm is no actions are supported', () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + triggers_actions_ui: { + actionTypeRegistry: {}, + }, + }, + }); + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('ActionForm')).toHaveLength(0); + }); +}); diff --git a/x-pack/plugins/infra/public/store/local/epic.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.test.tsx similarity index 53% rename from x-pack/plugins/infra/public/store/local/epic.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.test.tsx index e1a051355576f..aba30e4b7f3ca 100644 --- a/x-pack/plugins/infra/public/store/local/epic.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.test.tsx @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineEpics } from 'redux-observable'; +import { getStatusColor } from './helpers'; -import { createWaffleTimeEpic } from './waffle_time'; - -export const createLocalEpic = () => combineEpics(createWaffleTimeEpic()); +describe('rule_status helpers', () => { + it('getStatusColor returns subdued if null was provided', () => { + expect(getStatusColor(null)).toBe('subdued'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.test.tsx new file mode 100644 index 0000000000000..6e230de11c4f3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { RuleStatus } from './index'; + +describe('RuleStatus', () => { + it('renders loader correctly', () => { + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="rule-status-loader"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.test.tsx new file mode 100644 index 0000000000000..3829af02ca4f1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.test.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 React from 'react'; +import { shallow } from 'enzyme'; + +import { ScheduleItem } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('ScheduleItem', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="schedule-item"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.test.tsx new file mode 100644 index 0000000000000..3d832d61abb28 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.test.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SelectRuleType } from './index'; +import { useFormFieldMock } from '../../../../../mock'; +jest.mock('../../../../../lib/kibana'); + +describe('SelectRuleType', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="selectRuleType"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.test.tsx new file mode 100644 index 0000000000000..a9dddfedc2bab --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SeverityBadge } from './index'; + +describe('SeverityBadge', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiHealth')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.test.tsx new file mode 100644 index 0000000000000..89b8a56e79054 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.test.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { TestProviders } from '../../../../../mock'; +import { RuleStatusIcon } from './index'; +jest.mock('../../../../../lib/kibana'); + +describe('RuleStatusIcon', () => { + it('renders correctly', () => { + const wrapper = shallow(, { + wrappingComponent: TestProviders, + }); + + expect(wrapper.find('EuiAvatar')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.test.tsx new file mode 100644 index 0000000000000..af0547ea03261 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { StepContentWrapper } from './index'; + +describe('StepContentWrapper', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('div')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx index b04a321dab05b..a7343a87a7ef8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx @@ -16,3 +16,5 @@ StyledDiv.defaultProps = { }; export const StepContentWrapper = React.memo(StyledDiv); + +StepContentWrapper.displayName = 'StepContentWrapper'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.test.tsx new file mode 100644 index 0000000000000..ebef6348d477e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { StepDefineRule } from './index'; + +jest.mock('../../../../../lib/kibana'); + +describe('StepDefineRule', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('Form[data-test-subj="stepDefineRule"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.test.tsx new file mode 100644 index 0000000000000..ce01d6995a2d8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.test.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { StepPanel } from './index'; + +describe('StepPanel', () => { + it('renders correctly', () => { + const wrapper = shallow( + +

+ + ); + + expect(wrapper.find('MyPanel')).toHaveLength(1); + }); +}); 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 index 88cecadb8b137..1923ed09252dd 100644 --- 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 @@ -20,6 +20,8 @@ const MyPanel = styled(EuiPanel)` position: relative; `; +MyPanel.displayName = 'MyPanel'; + const StepPanelComponent: React.FC = ({ children, loading, title }) => ( {loading && } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx new file mode 100644 index 0000000000000..69d118ba9f28e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { StepRuleActions } from './index'; + +jest.mock('../../../../../lib/kibana'); + +describe('StepRuleActions', () => { + it('renders correctly', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="stepRuleActions"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx index bc3b0dfe720bc..1b27d0e0fcc0e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* istanbul ignore file */ + import { i18n } from '@kbn/i18n'; import { FormSchema } from '../../../../../shared_imports'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.test.tsx new file mode 100644 index 0000000000000..98de933590d60 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.test.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow, mount } from 'enzyme'; + +import { TestProviders } from '../../../../../mock'; +import { StepScheduleRule } from './index'; + +describe('StepScheduleRule', () => { + it('renders correctly', () => { + const wrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(wrapper.find('Form[data-test-subj="stepScheduleRule"]')).toHaveLength(1); + }); + + it('renders correctly if isReadOnlyView', () => { + const wrapper = shallow(); + + expect(wrapper.find('StepContentWrapper')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx index 8fbfdf5f25a51..e79aec2be6e15 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* istanbul ignore file */ + import { i18n } from '@kbn/i18n'; import { OptionalFieldLabel } from '../optional_field_label'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.test.tsx new file mode 100644 index 0000000000000..0ab19b671494e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.test.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { ThrottleSelectField } from './index'; +import { useFormFieldMock } from '../../../../../mock'; + +describe('ThrottleSelectField', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('SelectField')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.test.tsx new file mode 100644 index 0000000000000..db32be652d0f7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.test.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { TestProviders } from '../../../../mock'; +import { CreateRulePage } from './index'; +import { useUserInfo } from '../../components/user_info'; + +jest.mock('../../components/user_info'); + +describe('CreateRulePage', () => { + it('renders correctly', () => { + (useUserInfo as jest.Mock).mockReturnValue({}); + const wrapper = shallow(, { wrappingComponent: TestProviders }); + + expect(wrapper.find('[title="Create new rule"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.test.tsx new file mode 100644 index 0000000000000..a83ff4c54b076 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.test.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { TestProviders } from '../../../../mock'; +import { FailureHistory } from './failure_history'; +import { useRuleStatus } from '../../../../containers/detection_engine/rules'; +jest.mock('../../../../containers/detection_engine/rules'); + +describe('FailureHistory', () => { + beforeAll(() => { + (useRuleStatus as jest.Mock).mockReturnValue([false, null]); + }); + + it('renders correctly', () => { + const wrapper = shallow(, { + wrappingComponent: TestProviders, + }); + + expect(wrapper.find('EuiBasicTable')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.test.tsx new file mode 100644 index 0000000000000..19c6f39a9bc7e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.test.tsx @@ -0,0 +1,47 @@ +/* + * 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 { shallow } from 'enzyme'; + +import '../../../../mock/match_media'; +import { TestProviders } from '../../../../mock'; +import { RuleDetailsPageComponent } from './index'; +import { setAbsoluteRangeDatePicker } from '../../../../store/inputs/actions'; +import { useUserInfo } from '../../components/user_info'; +import { useParams } from 'react-router-dom'; + +jest.mock('../../components/user_info'); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useParams: jest.fn(), + }; +}); + +describe('RuleDetailsPageComponent', () => { + beforeAll(() => { + (useUserInfo as jest.Mock).mockReturnValue({}); + (useParams as jest.Mock).mockReturnValue({}); + }); + + it('renders correctly', () => { + const wrapper = shallow( + , + { + wrappingComponent: TestProviders, + } + ); + + expect(wrapper.find('WithSource')).toHaveLength(1); + }); +}); 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 index 2b648a3b3f825..14e5f2b90882e 100644 --- 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 @@ -88,7 +88,7 @@ const ruleDetailTabs = [ }, ]; -const RuleDetailsPageComponent: FC = ({ +export const RuleDetailsPageComponent: FC = ({ filters, query, setAbsoluteRangeDatePicker, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/status_failed_callout.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/status_failed_callout.test.tsx new file mode 100644 index 0000000000000..3394b0fc8c5c0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/status_failed_callout.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { RuleStatusFailedCallOut } from './status_failed_callout'; + +describe('RuleStatusFailedCallOut', () => { + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.test.tsx new file mode 100644 index 0000000000000..d22bc12abf9fa --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { TestProviders } from '../../../../mock'; +import { EditRulePage } from './index'; +import { useUserInfo } from '../../components/user_info'; +import { useParams } from 'react-router-dom'; + +jest.mock('../../components/user_info'); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useParams: jest.fn(), + }; +}); + +describe('EditRulePage', () => { + it('renders correctly', () => { + (useUserInfo as jest.Mock).mockReturnValue({}); + (useParams as jest.Mock).mockReturnValue({}); + const wrapper = shallow(, { wrappingComponent: TestProviders }); + + expect(wrapper.find('[title="Edit rule settings"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx index 443dbd2c93a35..1c01a19573cd6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.test.tsx @@ -302,11 +302,25 @@ describe('rule helpers', () => { test('returns expected ActionsStepRule rule object', () => { const mockedRule = { ...mockRule('test-id'), - actions: [], + actions: [ + { + id: 'id', + group: 'group', + params: {}, + action_type_id: 'action_type_id', + }, + ], }; const result: ActionsStepRule = getActionsStepsData(mockedRule); const expected = { - actions: [], + actions: [ + { + id: 'id', + group: 'group', + params: {}, + actionTypeId: 'action_type_id', + }, + ], enabled: mockedRule.enabled, isNew: false, throttle: 'no_actions', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.test.tsx new file mode 100644 index 0000000000000..3fa81ca3ced08 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.test.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { RulesPage } from './index'; +import { useUserInfo } from '../components/user_info'; +import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; + +jest.mock('../components/user_info'); +jest.mock('../../../containers/detection_engine/rules'); + +describe('RulesPage', () => { + beforeAll(() => { + (useUserInfo as jest.Mock).mockReturnValue({}); + (usePrePackagedRules as jest.Mock).mockReturnValue({}); + }); + it('renders correctly', () => { + const wrapper = shallow(); + + expect(wrapper.find('AllRules')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.test.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.test.ts new file mode 100644 index 0000000000000..34a521ed32b12 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getBreadcrumbs } from './utils'; + +describe('getBreadcrumbs', () => { + it('returns default value for incorrect params', () => { + expect( + getBreadcrumbs( + { + pageName: 'pageName', + detailName: 'detailName', + tabName: undefined, + search: '', + pathName: 'pathName', + }, + [] + ) + ).toEqual([{ href: '#/link-to/detections', text: 'Detections' }]); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/common/graphql/types.ts b/x-pack/legacy/plugins/uptime/common/graphql/types.ts index c8beb91d807d5..506966ec6b5c9 100644 --- a/x-pack/legacy/plugins/uptime/common/graphql/types.ts +++ b/x-pack/legacy/plugins/uptime/common/graphql/types.ts @@ -15,294 +15,15 @@ export type UnsignedInteger = any; // ==================================================== export interface Query { - /** Get a list of all recorded pings for all monitors */ - allPings: PingResults; - /** Fetches the current state of Uptime monitors for the given parameters. */ getMonitorStates?: MonitorSummaryResult | null; } -export interface PingResults { - /** Total number of matching pings */ - total: UnsignedInteger; - /** Unique list of all locations the query matched */ - locations: string[]; - /** List of pings */ - pings: Ping[]; -} -/** A request sent from a monitor to a host */ -export interface Ping { - /** unique ID for this ping */ - id: string; - /** The timestamp of the ping's creation */ - timestamp: string; - /** The agent that recorded the ping */ - beat?: Beat | null; - - container?: Container | null; - - docker?: Docker | null; - - ecs?: Ecs | null; - - error?: Error | null; - - host?: Host | null; - - http?: Http | null; - - icmp?: Icmp | null; - - kubernetes?: Kubernetes | null; - - meta?: Meta | null; - - monitor?: Monitor | null; - - observer?: Observer | null; - - resolve?: Resolve | null; - - socks5?: Socks5 | null; - - summary?: Summary | null; - - tags?: string | null; - - tcp?: Tcp | null; - - tls?: PingTls | null; - - url?: Url | null; -} -/** An agent for recording a beat */ -export interface Beat { - hostname?: string | null; - - name?: string | null; - - timezone?: string | null; - - type?: string | null; -} - -export interface Container { - id?: string | null; - - image?: ContainerImage | null; - - name?: string | null; - - runtime?: string | null; -} - -export interface ContainerImage { - name?: string | null; - - tag?: string | null; -} - -export interface Docker { - id?: string | null; - - image?: string | null; - - name?: string | null; -} - -export interface Ecs { - version?: string | null; -} - -export interface Error { - code?: number | null; - - message?: string | null; - - type?: string | null; -} - -export interface Host { - architecture?: string | null; - - id?: string | null; - - hostname?: string | null; - - ip?: string | null; - - mac?: string | null; - - name?: string | null; - - os?: Os | null; -} - -export interface Os { - family?: string | null; - - kernel?: string | null; - - platform?: string | null; - - version?: string | null; - - name?: string | null; - - build?: string | null; -} - -export interface Http { - response?: HttpResponse | null; - - rtt?: HttpRtt | null; - - url?: string | null; -} - -export interface HttpResponse { - status_code?: UnsignedInteger | null; - - body?: HttpBody | null; -} - -export interface HttpBody { - /** Size of HTTP response body in bytes */ - bytes?: UnsignedInteger | null; - /** Hash of the HTTP response body */ - hash?: string | null; - /** Response body of the HTTP Response. May be truncated based on client settings. */ - content?: string | null; - /** Byte length of the content string, taking into account multibyte chars. */ - content_bytes?: UnsignedInteger | null; -} - -export interface HttpRtt { - content?: Duration | null; - - response_header?: Duration | null; - - total?: Duration | null; - - validate?: Duration | null; - - validate_body?: Duration | null; - - write_request?: Duration | null; -} /** The monitor's status for a ping */ export interface Duration { us?: UnsignedInteger | null; } -export interface Icmp { - requests?: number | null; - - rtt?: number | null; -} - -export interface Kubernetes { - container?: KubernetesContainer | null; - - namespace?: string | null; - - node?: KubernetesNode | null; - - pod?: KubernetesPod | null; -} - -export interface KubernetesContainer { - image?: string | null; - - name?: string | null; -} - -export interface KubernetesNode { - name?: string | null; -} - -export interface KubernetesPod { - name?: string | null; - - uid?: string | null; -} - -export interface Meta { - cloud?: MetaCloud | null; -} - -export interface MetaCloud { - availability_zone?: string | null; - - instance_id?: string | null; - - instance_name?: string | null; - - machine_type?: string | null; - - project_id?: string | null; - - provider?: string | null; - - region?: string | null; -} - -export interface Monitor { - duration?: Duration | null; - - host?: string | null; - /** The id of the monitor */ - id?: string | null; - /** The IP pinged by the monitor */ - ip?: string | null; - /** The name of the protocol being monitored */ - name?: string | null; - /** The protocol scheme of the monitored host */ - scheme?: string | null; - /** The status of the monitored host */ - status?: string | null; - /** The type of host being monitored */ - type?: string | null; - - check_group?: string | null; -} -/** Metadata added by a proccessor, which is specified in its configuration. */ -export interface Observer { - /** Geolocation data for the agent. */ - geo?: Geo | null; -} -/** Geolocation data added via processors to enrich events. */ -export interface Geo { - /** Name of the city in which the agent is running. */ - city_name?: string | null; - /** The name of the continent on which the agent is running. */ - continent_name?: string | null; - /** ISO designation for the agent's country. */ - country_iso_code?: string | null; - /** The name of the agent's country. */ - country_name?: string | null; - /** The lat/long of the agent. */ - location?: string | null; - /** A name for the host's location, e.g. 'us-east-1' or 'LAX'. */ - name?: string | null; - /** ISO designation of the agent's region. */ - region_iso_code?: string | null; - /** Name of the region hosting the agent. */ - region_name?: string | null; -} - -export interface Resolve { - host?: string | null; - - ip?: string | null; - - rtt?: Duration | null; -} - -export interface Socks5 { - rtt?: Rtt | null; -} - export interface Rtt { connect?: Duration | null; @@ -331,53 +52,10 @@ export interface Location { lon?: number | null; } -export interface Tcp { - port?: number | null; - - rtt?: Rtt | null; -} -/** Contains monitor transmission encryption information. */ -export interface PingTls { - /** The date and time after which the certificate is invalid. */ - certificate_not_valid_after?: string | null; - - certificate_not_valid_before?: string | null; - - certificates?: string | null; - - rtt?: Rtt | null; -} - -export interface Url { - full?: string | null; - - scheme?: string | null; - - domain?: string | null; - - port?: number | null; - - path?: string | null; - - query?: string | null; -} - export interface DocCount { count: UnsignedInteger; } -export interface Snapshot { - counts: SnapshotCount; -} - -export interface SnapshotCount { - up: number; - - down: number; - - total: number; -} - /** The primary object returned for monitor states. */ export interface MonitorSummaryResult { /** Used to go to the next page of results */ @@ -521,24 +199,6 @@ export interface SummaryHistogramPoint { down: number; } -export interface AllPingsQueryArgs { - /** Optional: the direction to sort by. Accepts 'asc' and 'desc'. Defaults to 'desc'. */ - sort?: string | null; - /** Optional: the number of results to return. */ - size?: number | null; - /** Optional: the monitor ID filter. */ - monitorId?: string | null; - /** Optional: the check status to filter by. */ - status?: string | null; - /** The lower limit of the date range. */ - dateRangeStart: string; - /** The upper limit of the date range. */ - dateRangeEnd: string; - /** Optional: agent location to filter by. */ - location?: string | null; - page?: number; -} - export interface GetMonitorStatesQueryArgs { dateRangeStart: string; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts index 37101b5b46fd2..9018f4acaa320 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts @@ -27,7 +27,13 @@ export const StatesIndexStatusType = t.type({ docCount: t.number, }); +export const DateRangeType = t.type({ + from: t.string, + to: t.string, +}); + export type Summary = t.TypeOf; export type CheckGeo = t.TypeOf; export type Location = t.TypeOf; export type StatesIndexStatus = t.TypeOf; +export type DateRange = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts index 5e3fb2326bdb9..652d60cbe304d 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts @@ -8,5 +8,6 @@ export * from './alerts'; export * from './common'; export * from './monitor'; export * from './overview_filters'; +export * from './ping'; export * from './snapshot'; export * from './dynamic_settings'; diff --git a/x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/histogram.ts similarity index 77% rename from x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts rename to x-pack/legacy/plugins/uptime/common/runtime_types/ping/histogram.ts index 3ae32e15ca55c..2c3b52051be0f 100644 --- a/x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/histogram.ts @@ -28,3 +28,15 @@ export interface HistogramResult { histogram: HistogramDataPoint[]; interval: string; } + +export interface HistogramQueryResult { + key: number; + key_as_string: string; + doc_count: number; + down: { + doc_count: number; + }; + up: { + doc_count: number; + }; +} diff --git a/x-pack/legacy/plugins/uptime/public/queries/index.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/index.ts similarity index 79% rename from x-pack/legacy/plugins/uptime/public/queries/index.ts rename to x-pack/legacy/plugins/uptime/common/runtime_types/ping/index.ts index 283382ec1b7ba..a2fc7c1b243ba 100644 --- a/x-pack/legacy/plugins/uptime/public/queries/index.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { pingsQuery, pingsQueryString } from './pings_query'; +export * from './histogram'; +export * from './ping'; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/ping.ts new file mode 100644 index 0000000000000..ee14b298f3810 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/ping/ping.ts @@ -0,0 +1,181 @@ +/* + * 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 t from 'io-ts'; +import { DateRangeType } from '../common'; + +export const HttpResponseBodyType = t.partial({ + bytes: t.number, + content: t.string, + content_bytes: t.number, + hash: t.string, +}); + +export type HttpResponseBody = t.TypeOf; + +export const TlsType = t.partial({ + certificate_not_valid_after: t.string, + certificate_not_valid_before: t.string, +}); + +export type Tls = t.TypeOf; + +export const MonitorType = t.intersection([ + t.type({ + duration: t.type({ + us: t.number, + }), + id: t.string, + status: t.string, + type: t.string, + }), + t.partial({ + check_group: t.string, + ip: t.string, + name: t.string, + timespan: t.partial({ + gte: t.string, + lte: t.string, + }), + }), +]); + +export type Monitor = t.TypeOf; + +export const PingType = t.intersection([ + t.type({ + timestamp: t.string, + monitor: MonitorType, + docId: t.string, + }), + t.partial({ + agent: t.intersection([ + t.type({ + ephemeral_id: t.string, + hostname: t.string, + id: t.string, + type: t.string, + version: t.string, + }), + t.partial({ + name: t.string, + }), + ]), + container: t.partial({ + id: t.string, + image: t.partial({ + name: t.string, + tag: t.string, + }), + name: t.string, + runtime: t.string, + }), + ecs: t.partial({ + version: t.string, + }), + error: t.intersection([ + t.partial({ + code: t.string, + id: t.string, + stack_trace: t.string, + type: t.string, + }), + t.type({ + // this is _always_ on the error field + message: t.string, + }), + ]), + http: t.partial({ + request: t.partial({ + body: t.partial({ + bytes: t.number, + content: t.partial({ + text: t.string, + }), + }), + bytes: t.number, + method: t.string, + referrer: t.string, + }), + response: t.partial({ + body: HttpResponseBodyType, + bytes: t.number, + redirects: t.string, + status_code: t.number, + }), + version: t.string, + }), + icmp: t.partial({ + requests: t.number, + rtt: t.partial({ + us: t.number, + }), + }), + kubernetes: t.partial({ + pod: t.partial({ + name: t.string, + uid: t.string, + }), + }), + observer: t.partial({ + geo: t.partial({ + name: t.string, + }), + }), + resolve: t.partial({ + ip: t.string, + rtt: t.partial({ + us: t.number, + }), + }), + summary: t.partial({ + down: t.number, + up: t.number, + }), + tags: t.array(t.string), + tcp: t.partial({ + rtt: t.partial({ + connect: t.partial({ + us: t.number, + }), + }), + }), + tls: TlsType, + // should this be partial? + url: t.partial({ + domain: t.string, + full: t.string, + port: t.number, + scheme: t.string, + }), + }), +]); + +export type Ping = t.TypeOf; + +export const PingsResponseType = t.type({ + total: t.number, + locations: t.array(t.string), + pings: t.array(PingType), +}); + +export type PingsResponse = t.TypeOf; + +export const GetPingsParamsType = t.intersection([ + t.type({ + dateRange: DateRangeType, + }), + t.partial({ + index: t.number, + size: t.number, + location: t.string, + monitorId: t.string, + sort: t.string, + status: t.string, + }), +]); + +export type GetPingsParams = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/types/index.ts b/x-pack/legacy/plugins/uptime/common/types/index.ts index 2c39f2a3b7314..1d0003addd761 100644 --- a/x-pack/legacy/plugins/uptime/common/types/index.ts +++ b/x-pack/legacy/plugins/uptime/common/types/index.ts @@ -41,5 +41,3 @@ export interface MonitorDurationResult { /** The maximum duration value in this chart. */ durationMaxValue: number; } - -export * from './ping/histogram'; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx index 6428ddfd10f8c..cf35dbf4e5206 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx @@ -14,7 +14,7 @@ import { import { getPingHistogram } from '../../../state/actions'; import { selectPingHistogram } from '../../../state/selectors'; import { withResponsiveWrapper, ResponsiveWrapperProps } from '../../higher_order'; -import { GetPingHistogramParams, HistogramResult } from '../../../../common/types'; +import { GetPingHistogramParams, HistogramResult } from '../../../../common/runtime_types'; import { useGetUrlParams } from '../../../hooks'; type Props = ResponsiveWrapperProps & diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/index.ts b/x-pack/legacy/plugins/uptime/public/components/connected/index.ts index 7e442cbe850ba..be3cc5b65aa0e 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/connected/index.ts @@ -13,5 +13,6 @@ export { MonitorStatusDetails } from './monitor/status_details_container'; export { MonitorStatusBar } from './monitor/status_bar_container'; export { MonitorListDrawer } from './monitor/list_drawer_container'; export { MonitorListActionsPopover } from './monitor/drawer_popover_container'; +export { PingList, PingListProps } from './pings'; export { DurationChart } from './charts/monitor_duration'; export { EmptyState } from './empty_state/empty_state'; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx index 00cfd02d8dc32..3a96aa7c0275b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx @@ -12,7 +12,7 @@ import { monitorLocationsSelector, monitorStatusSelector } from '../../../state/ import { MonitorStatusBarComponent } from '../../functional/monitor_status_details/monitor_status_bar'; import { getMonitorStatusAction } from '../../../state/actions'; import { useGetUrlParams } from '../../../hooks'; -import { Ping } from '../../../../common/graphql/types'; +import { Ping } from '../../../../common/runtime_types'; import { MonitorLocations } from '../../../../common/runtime_types/monitor'; import { UptimeRefreshContext } from '../../../contexts'; diff --git a/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/index.js b/x-pack/legacy/plugins/uptime/public/components/connected/pings/index.ts old mode 100755 new mode 100644 similarity index 77% rename from x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/index.js rename to x-pack/legacy/plugins/uptime/public/components/connected/pings/index.ts index 787814d87dff9..95ced104e5188 --- a/x-pack/legacy/plugins/logstash/server/lib/call_with_request_factory/index.js +++ b/x-pack/legacy/plugins/uptime/public/components/connected/pings/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { callWithRequestFactory } from './call_with_request_factory'; +export { PingList, PingListProps } from './ping_list'; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx new file mode 100644 index 0000000000000..5b32a623495f1 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx @@ -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 { useSelector, useDispatch } from 'react-redux'; +import React, { useContext, useCallback } from 'react'; +import { selectPingList } from '../../../state/selectors'; +import { getPings } from '../../../state/actions'; +import { GetPingsParams } from '../../../../common/runtime_types'; +import { UptimeSettingsContext } from '../../../contexts'; +import { PingListComponent } from '../../functional'; + +export interface PingListProps { + monitorId: string; +} + +export const PingList = (props: PingListProps) => { + const { + lastRefresh, + pings: { + error, + loading, + pingList: { locations, pings, total }, + }, + } = useSelector(selectPingList); + + const { dateRangeStart: drs, dateRangeEnd: dre } = useContext(UptimeSettingsContext); + + const dispatch = useDispatch(); + const getPingsCallback = useCallback((params: GetPingsParams) => dispatch(getPings(params)), [ + dispatch, + ]); + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx index f988afc7fc84d..66e86d6731236 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx @@ -13,7 +13,7 @@ import moment from 'moment'; import { getChartDateLabel } from '../../../lib/helper'; import { ChartWrapper } from './chart_wrapper'; import { UptimeThemeContext } from '../../../contexts'; -import { HistogramResult } from '../../../../common/types'; +import { HistogramResult } from '../../../../common/runtime_types'; import { useUrlParams } from '../../../hooks'; import { ChartEmptyState } from './chart_empty_state'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/index.ts b/x-pack/legacy/plugins/uptime/public/components/functional/index.ts index 8d0352e01d40e..d82912a6216e8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/index.ts @@ -15,6 +15,6 @@ export { KueryBarComponent } from './kuery_bar/kuery_bar'; export { MonitorCharts } from './monitor_charts'; export { MonitorList } from './monitor_list'; export { OverviewPageParsingErrorCallout } from './overview_page_parsing_error_callout'; -export { PingList } from './ping_list'; +export { PingListComponent } from './ping_list'; export { PingHistogramComponent } from './charts'; export { StatusPanel } from './status_panel'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx index 2eae14301fd4d..57ed09cc30ef1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx @@ -9,11 +9,11 @@ import moment from 'moment'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { EuiBadge } from '@elastic/eui'; import { renderWithIntl } from 'test_utils/enzyme_helpers'; -import { PingTls } from '../../../../../common/graphql/types'; +import { Tls } from '../../../../../common/runtime_types'; import { MonitorSSLCertificate } from '../monitor_status_bar'; describe('MonitorStatusBar component', () => { - let monitorTls: PingTls; + let monitorTls: Tls; beforeEach(() => { const dateInTwoMonths = moment() diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx index 0a53eeb89d793..5fd32c808da42 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx @@ -8,7 +8,7 @@ import moment from 'moment'; import React from 'react'; import { renderWithIntl } from 'test_utils/enzyme_helpers'; import { MonitorStatusBarComponent } from '../monitor_status_bar'; -import { Ping } from '../../../../../common/graphql/types'; +import { Ping } from '../../../../../common/runtime_types'; describe('MonitorStatusBar component', () => { let monitorStatus: Ping; @@ -16,7 +16,7 @@ describe('MonitorStatusBar component', () => { beforeEach(() => { monitorStatus = { - id: 'id1', + docId: 'few213kl', timestamp: moment(new Date()) .subtract(15, 'm') .toString(), @@ -24,7 +24,9 @@ describe('MonitorStatusBar component', () => { duration: { us: 1234567, }, + id: 'id1', status: 'up', + type: 'http', }, url: { full: 'https://www.example.com/', diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx index c57348c4ab4cd..d92534aecd175 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx @@ -9,14 +9,13 @@ import moment from 'moment'; import { EuiSpacer, EuiText, EuiBadge } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; - -import { PingTls } from '../../../../../common/graphql/types'; +import { Tls } from '../../../../../common/runtime_types'; interface Props { /** * TLS information coming from monitor in ES heartbeat index */ - tls: PingTls | null | undefined; + tls: Tls | null | undefined; } export const MonitorSSLCertificate = ({ tls }: Props) => { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx index 22e4377944be1..ac3cedc517995 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx @@ -17,7 +17,7 @@ import { import { MonitorSSLCertificate } from './monitor_ssl_certificate'; import * as labels from './translations'; import { StatusByLocations } from './status_by_location'; -import { Ping } from '../../../../../common/graphql/types'; +import { Ping } from '../../../../../common/runtime_types'; import { MonitorLocations } from '../../../../../common/runtime_types'; interface MonitorStatusBarProps { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap index 2e59ec5e57337..154ab6399452d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap @@ -1,384 +1,349 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PingList component renders sorted list without errors 1`] = ` - - - + +

+ +

+
+ + + -

- + -

-
- - - + + + - - - - - - - - - - - - - - - - - - - - + + + + + + Response code + , + "render": [Function], + }, + Object { + "align": "right", + "isExpander": true, + "render": [Function], + "width": "24px", + }, + ] + } + hasActions={true} + isExpandable={true} + itemId="docId" + itemIdToExpandedRowMap={Object {}} + items={ + Array [ + Object { + "docId": "fewjio21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - Object { - "align": "right", - "dataType": "number", - "field": "monitor.ip", - "name": "IP", + "monitor": Object { + "duration": Object { + "us": 1430, + }, + "id": "auto-tcp-0X81440A68E839814F", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "align": "right", - "field": "monitor.duration.us", - "name": "Duration", - "render": [Function], + "timestamp": "2019-01-28T17:47:08.078Z", + }, + Object { + "docId": "fewjoo21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - Object { - "align": "right", - "field": "error.type", - "name": "Error type", - "render": [Function], + "monitor": Object { + "duration": Object { + "us": 1370, + }, + "id": "auto-tcp-0X81440A68E839814D", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "align": "right", - "field": "http.response.status_code", - "name": - Response code - , - "render": [Function], + "timestamp": "2019-01-28T17:47:09.075Z", + }, + Object { + "docId": "fejjio21", + "monitor": Object { + "duration": Object { + "us": 1452, + }, + "id": "auto-tcp-0X81440A68E839814D", + "ip": "127.0.0.1", + "name": "", + "status": "up", + "type": "tcp", }, - Object { - "align": "right", - "isExpander": true, - "render": [Function], - "width": "24px", + "timestamp": "2019-01-28T17:47:06.077Z", + }, + Object { + "docId": "fewzio21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - ] - } - hasActions={true} - isExpandable={true} - itemId="id" - itemIdToExpandedRowMap={Object {}} - items={ - Array [ - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", + "monitor": Object { + "duration": Object { + "us": 1094, }, - "http": null, - "id": "id1", - "monitor": Object { - "duration": Object { - "us": 1430, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", - }, - "timestamp": "2019-01-28T17:47:08.078Z", + "id": "auto-tcp-0X81440A68E839814E", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id2", - "monitor": Object { - "duration": Object { - "us": 1370, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", - }, - "timestamp": "2019-01-28T17:47:09.075Z", + "timestamp": "2019-01-28T17:47:07.075Z", + }, + Object { + "docId": "fewpi321", + "error": Object { + "message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused", + "type": "io", }, - Object { - "error": null, - "http": null, - "id": "id3", - "monitor": Object { - "duration": Object { - "us": 1452, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "tcp", + "monitor": Object { + "duration": Object { + "us": 1597, }, - "timestamp": "2019-01-28T17:47:06.077Z", + "id": "auto-http-0X3675F89EF061209G", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "http", }, - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id4", - "monitor": Object { - "duration": Object { - "us": 1094, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", - }, - "timestamp": "2019-01-28T17:47:07.075Z", + "timestamp": "2019-01-28T17:47:07.074Z", + }, + Object { + "docId": "0ewjio21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - Object { - "error": Object { - "message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused", - "type": "io", + "monitor": Object { + "duration": Object { + "us": 1699, }, - "http": null, - "id": "id5", - "monitor": Object { - "duration": Object { - "us": 1597, - }, - "id": "auto-http-0X3675F89EF0612091", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "http", - }, - "timestamp": "2019-01-28T17:47:07.074Z", + "id": "auto-tcp-0X81440A68E839814H", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id6", - "monitor": Object { - "duration": Object { - "us": 1699, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", - }, - "timestamp": "2019-01-28T17:47:18.080Z", + "timestamp": "2019-01-28T17:47:18.080Z", + }, + Object { + "docId": "3ewjio21", + "error": Object { + "message": "dial tcp 127.0.0.1:9200: connect: connection refused", + "type": "io", }, - Object { - "error": Object { - "message": "dial tcp 127.0.0.1:9200: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id7", - "monitor": Object { - "duration": Object { - "us": 5384, - }, - "id": "auto-tcp-0X81440A68E839814C", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "tcp", + "monitor": Object { + "duration": Object { + "us": 5384, }, - "timestamp": "2019-01-28T17:47:19.076Z", + "id": "auto-tcp-0X81440A68E839814I", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "tcp", }, - Object { - "error": Object { - "message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused", - "type": "io", - }, - "http": null, - "id": "id8", - "monitor": Object { - "duration": Object { - "us": 5397, - }, - "id": "auto-http-0X3675F89EF0612091", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "http", + "timestamp": "2019-01-28T17:47:19.076Z", + }, + Object { + "docId": "fewjip21", + "error": Object { + "message": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused", + "type": "io", + }, + "monitor": Object { + "duration": Object { + "us": 5397, }, - "timestamp": "2019-01-28T17:47:19.076Z", + "id": "auto-http-0X3675F89EF061209J", + "ip": "127.0.0.1", + "name": "", + "status": "down", + "type": "http", }, - Object { - "error": null, - "http": Object { - "response": Object { - "status_code": 200, - }, + "timestamp": "2019-01-28T17:47:19.076Z", + }, + Object { + "docId": "fewjio21", + "http": Object { + "response": Object { + "status_code": 200, }, - "id": "id9", - "monitor": Object { - "duration": Object { - "us": 127511, - }, - "id": "auto-http-0X131221E73F825974", - "ip": "172.217.7.4", - "name": "", - "scheme": null, - "status": "up", - "type": "http", + }, + "monitor": Object { + "duration": Object { + "us": 127511, }, - "timestamp": "2019-01-28T17:47:19.077Z", + "id": "auto-tcp-0X81440A68E839814C", + "ip": "172.217.7.4", + "name": "", + "status": "up", + "type": "http", }, - Object { - "error": null, - "http": Object { - "response": Object { - "status_code": 200, - }, + "timestamp": "2019-01-28T17:47:19.077Z", + }, + Object { + "docId": "fewjik81", + "http": Object { + "response": Object { + "status_code": 200, }, - "id": "id10", - "monitor": Object { - "duration": Object { - "us": 287543, - }, - "id": "auto-http-0X9CB71300ABD5A2A8", - "ip": "192.30.253.112", - "name": "", - "scheme": null, - "status": "up", - "type": "http", + }, + "monitor": Object { + "duration": Object { + "us": 287543, }, - "timestamp": "2019-01-28T17:47:19.077Z", + "id": "auto-http-0X131221E73F825974", + "ip": "192.30.253.112", + "name": "", + "status": "up", + "type": "http", }, - ] - } - loading={false} - noItemsMessage="No items found" - onChange={[Function]} - pagination={ - Object { - "initialPageSize": 25, - "pageIndex": 0, - "pageSize": 10, - "pageSizeOptions": Array [ - 10, - 25, - 50, - 100, - ], - "totalItemCount": 9231, - } + "timestamp": "2019-01-28T17:47:19.077Z", + }, + ] + } + loading={false} + noItemsMessage="No items found" + onChange={[Function]} + pagination={ + Object { + "initialPageSize": 10, + "pageIndex": 0, + "pageSize": 10, + "pageSizeOptions": Array [ + 10, + 25, + 50, + 100, + ], + "totalItemCount": 10, } - responsive={true} - tableLayout="fixed" - /> -
-
+ } + responsive={true} + tableLayout="fixed" + /> + `; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx index 9dbe48ec5553a..2c1434cfd64bd 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx @@ -7,15 +7,23 @@ import { mountWithIntl, renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { PingListExpandedRowComponent } from '../expanded_row'; -import { Ping } from '../../../../../common/graphql/types'; +import { Ping } from '../../../../../common/runtime_types'; import { DocLinkForBody } from '../doc_link_body'; describe('PingListExpandedRow', () => { let ping: Ping; beforeEach(() => { ping = { - id: '123', + docId: 'fdeio12', timestamp: '19290310', + monitor: { + duration: { + us: 12345, + }, + id: '123', + status: 'down', + type: 'http', + }, http: { response: { body: { @@ -34,7 +42,7 @@ describe('PingListExpandedRow', () => { it('renders error information when an error field is present', () => { ping.error = { - code: 403, + code: '403', message: 'Forbidden', }; expect(shallowWithIntl()).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx index 68d285bd0baf1..ec256a886aa16 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx @@ -6,211 +6,188 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { PingResults, Ping } from '../../../../../common/graphql/types'; -import { PingListComponent, AllLocationOption, toggleDetails } from '../ping_list'; +import { PingListComponent, toggleDetails } from '../ping_list'; import { ExpandedRowMap } from '../../monitor_list/types'; +import { Ping, PingsResponse } from '../../../../../common/runtime_types'; describe('PingList component', () => { - let pingList: { allPings: PingResults }; + let response: PingsResponse; beforeEach(() => { - pingList = { - allPings: { - total: 9231, - pings: [ - { - id: 'id1', - timestamp: '2019-01-28T17:47:08.078Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1430 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id2', - timestamp: '2019-01-28T17:47:09.075Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1370 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id3', - timestamp: '2019-01-28T17:47:06.077Z', - http: null, - error: null, - monitor: { - duration: { us: 1452 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'up', - type: 'tcp', - }, - }, - { - id: 'id4', - timestamp: '2019-01-28T17:47:07.075Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1094 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id5', - timestamp: '2019-01-28T17:47:07.074Z', - http: null, - error: { - message: - 'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1597 }, - id: 'auto-http-0X3675F89EF0612091', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'http', - }, - }, - { - id: 'id6', - timestamp: '2019-01-28T17:47:18.080Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 1699 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id7', - timestamp: '2019-01-28T17:47:19.076Z', - http: null, - error: { - message: 'dial tcp 127.0.0.1:9200: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 5384 }, - id: 'auto-tcp-0X81440A68E839814C', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'tcp', - }, - }, - { - id: 'id8', - timestamp: '2019-01-28T17:47:19.076Z', - http: null, - error: { - message: - 'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused', - type: 'io', - }, - monitor: { - duration: { us: 5397 }, - id: 'auto-http-0X3675F89EF0612091', - ip: '127.0.0.1', - name: '', - scheme: null, - status: 'down', - type: 'http', - }, - }, - { - id: 'id9', - timestamp: '2019-01-28T17:47:19.077Z', - http: { response: { status_code: 200 } }, - error: null, - monitor: { - duration: { us: 127511 }, - id: 'auto-http-0X131221E73F825974', - ip: '172.217.7.4', - name: '', - scheme: null, - status: 'up', - type: 'http', - }, - }, - { - id: 'id10', - timestamp: '2019-01-28T17:47:19.077Z', - http: { response: { status_code: 200 } }, - error: null, - monitor: { - duration: { us: 287543 }, - id: 'auto-http-0X9CB71300ABD5A2A8', - ip: '192.30.253.112', - name: '', - scheme: null, - status: 'up', - type: 'http', - }, - }, - ], - locations: ['nyc'], - }, + response = { + total: 9231, + locations: ['nyc'], + pings: [ + { + docId: 'fewjio21', + timestamp: '2019-01-28T17:47:08.078Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1430 }, + id: 'auto-tcp-0X81440A68E839814F', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: 'fewjoo21', + timestamp: '2019-01-28T17:47:09.075Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1370 }, + id: 'auto-tcp-0X81440A68E839814D', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: 'fejjio21', + timestamp: '2019-01-28T17:47:06.077Z', + monitor: { + duration: { us: 1452 }, + id: 'auto-tcp-0X81440A68E839814D', + ip: '127.0.0.1', + name: '', + status: 'up', + type: 'tcp', + }, + }, + { + docId: 'fewzio21', + timestamp: '2019-01-28T17:47:07.075Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1094 }, + id: 'auto-tcp-0X81440A68E839814E', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: 'fewpi321', + timestamp: '2019-01-28T17:47:07.074Z', + error: { + message: + 'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1597 }, + id: 'auto-http-0X3675F89EF061209G', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'http', + }, + }, + { + docId: '0ewjio21', + timestamp: '2019-01-28T17:47:18.080Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 1699 }, + id: 'auto-tcp-0X81440A68E839814H', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: '3ewjio21', + timestamp: '2019-01-28T17:47:19.076Z', + error: { + message: 'dial tcp 127.0.0.1:9200: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 5384 }, + id: 'auto-tcp-0X81440A68E839814I', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'tcp', + }, + }, + { + docId: 'fewjip21', + timestamp: '2019-01-28T17:47:19.076Z', + error: { + message: + 'Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused', + type: 'io', + }, + monitor: { + duration: { us: 5397 }, + id: 'auto-http-0X3675F89EF061209J', + ip: '127.0.0.1', + name: '', + status: 'down', + type: 'http', + }, + }, + { + docId: 'fewjio21', + timestamp: '2019-01-28T17:47:19.077Z', + http: { response: { status_code: 200 } }, + monitor: { + duration: { us: 127511 }, + id: 'auto-tcp-0X81440A68E839814C', + ip: '172.217.7.4', + name: '', + status: 'up', + type: 'http', + }, + }, + { + docId: 'fewjik81', + timestamp: '2019-01-28T17:47:19.077Z', + http: { response: { status_code: 200 } }, + monitor: { + duration: { us: 287543 }, + id: 'auto-http-0X131221E73F825974', + ip: '192.30.253.112', + name: '', + status: 'up', + type: 'http', + }, + }, + ], }; }); it('renders sorted list without errors', () => { - const { allPings } = pingList; const component = shallowWithIntl( {}} - onSelectedStatusChange={jest.fn()} - pageIndex={0} - pageSize={10} - selectedOption="down" - selectedLocation={AllLocationOption.value} + locations={[]} + monitorId="foo" + pings={response.pings} + total={10} /> ); expect(component).toMatchSnapshot(); @@ -224,13 +201,38 @@ describe('PingList component', () => { beforeEach(() => { itemIdToExpandedRowMap = {}; - pings = pingList.allPings.pings; + pings = response.pings; }); it('should expand an item if empty', () => { const ping = pings[0]; toggleDetails(ping, itemIdToExpandedRowMap, setItemIdToExpandedRowMap); - expect(itemIdToExpandedRowMap).toHaveProperty(ping.id); + expect(itemIdToExpandedRowMap).toMatchInlineSnapshot(` + Object { + "fewjio21": , + } + `); }); it('should un-expand an item if clicked again', () => { @@ -245,7 +247,31 @@ describe('PingList component', () => { const pingB = pings[1]; toggleDetails(pingA, itemIdToExpandedRowMap, setItemIdToExpandedRowMap); toggleDetails(pingB, itemIdToExpandedRowMap, setItemIdToExpandedRowMap); - expect(itemIdToExpandedRowMap).toHaveProperty(pingB.id); + expect(pingA.docId).not.toEqual(pingB.docId); + expect(itemIdToExpandedRowMap[pingB.docId]).toMatchInlineSnapshot(` + + `); }); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx index c684235122e34..28b96fcb1bf7b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx @@ -16,14 +16,14 @@ import { } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { Ping, HttpBody } from '../../../../common/graphql/types'; +import { Ping, HttpResponseBody } from '../../../../common/runtime_types'; import { DocLinkForBody } from './doc_link_body'; interface Props { ping: Ping; } -const BodyDescription = ({ body }: { body: HttpBody }) => { +const BodyDescription = ({ body }: { body: HttpResponseBody }) => { const contentBytes = body.content_bytes || 0; const bodyBytes = body.bytes || 0; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx index e57b229dfd973..808f3f90ef015 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './ping_list'; +export { PingListComponent } from './ping_list'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx index 19768c7104e91..934dfd961f9e0 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx @@ -20,109 +20,127 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { get } from 'lodash'; import moment from 'moment'; -import React, { Fragment, useState } from 'react'; +import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; -import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table'; -import { Ping, PingResults } from '../../../../common/graphql/types'; +import { Ping, GetPingsParams, DateRange } from '../../../../common/runtime_types'; import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper'; -import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../../higher_order'; -import { pingsQuery } from '../../../queries'; import { LocationName } from './location_name'; import { Pagination } from './../monitor_list'; import { PingListExpandedRowComponent } from './expanded_row'; +import { PingListProps } from '../../connected/pings'; -interface PingListQueryResult { - allPings?: PingResults; -} - -interface PingListProps { - onSelectedStatusChange: (status: string | undefined) => void; - onSelectedLocationChange: (location: any) => void; - onPageCountChange: (itemCount: number) => void; - onPageIndexChange: (index: number) => void; - pageSize: number; - pageIndex: number; - selectedOption: string; - selectedLocation: string | undefined; -} - -type Props = UptimeGraphQLQueryProps & PingListProps; -interface ExpandedRowMap { - [key: string]: JSX.Element; -} - -export const AllLocationOption = { text: 'All', value: '' }; +export const AllLocationOption = { + 'data-test-subj': 'xpack.uptime.pingList.locationOptions.all', + text: 'All', + value: '', +}; export const toggleDetails = ( ping: Ping, - itemIdToExpandedRowMap: ExpandedRowMap, - setItemIdToExpandedRowMap: (update: ExpandedRowMap) => any + expandedRows: Record, + setExpandedRows: (update: Record) => any ) => { - // If the user has clicked on the expanded map, close all expanded rows. - if (itemIdToExpandedRowMap[ping.id]) { - setItemIdToExpandedRowMap({}); + // If already expanded, collapse + if (expandedRows[ping.docId]) { + delete expandedRows[ping.docId]; + setExpandedRows({ ...expandedRows }); return; } // Otherwise expand this row - const newItemIdToExpandedRowMap: ExpandedRowMap = {}; - newItemIdToExpandedRowMap[ping.id] = ; - setItemIdToExpandedRowMap(newItemIdToExpandedRowMap); + setExpandedRows({ + ...expandedRows, + [ping.docId]: , + }); }; const SpanWithMargin = styled.span` margin-right: 16px; `; -export const PingListComponent = ({ - data, - loading, - onPageCountChange, - onPageIndexChange, - onSelectedLocationChange, - onSelectedStatusChange, - pageIndex, - pageSize, - selectedOption, - selectedLocation, -}: Props) => { - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({}); +interface Props extends PingListProps { + dateRange: DateRange; + error?: Error; + getPings: (props: GetPingsParams) => void; + lastRefresh: number; + loading: boolean; + locations: string[]; + pings: Ping[]; + total: number; +} + +const DEFAULT_PAGE_SIZE = 10; + +const statusOptions = [ + { + 'data-test-subj': 'xpack.uptime.pingList.statusOptions.all', + text: i18n.translate('xpack.uptime.pingList.statusOptions.allStatusOptionLabel', { + defaultMessage: 'All', + }), + value: '', + }, + { + 'data-test-subj': 'xpack.uptime.pingList.statusOptions.up', + text: i18n.translate('xpack.uptime.pingList.statusOptions.upStatusOptionLabel', { + defaultMessage: 'Up', + }), + value: 'up', + }, + { + 'data-test-subj': 'xpack.uptime.pingList.statusOptions.down', + text: i18n.translate('xpack.uptime.pingList.statusOptions.downStatusOptionLabel', { + defaultMessage: 'Down', + }), + value: 'down', + }, +]; + +export const PingListComponent = (props: Props) => { + const [selectedLocation, setSelectedLocation] = useState(''); + const [status, setStatus] = useState(''); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + const [pageIndex, setPageIndex] = useState(0); + const { + dateRange: { from, to }, + error, + getPings, + lastRefresh, + loading, + locations, + monitorId, + pings, + total, + } = props; + + useEffect(() => { + getPings({ + dateRange: { + from, + to, + }, + location: selectedLocation, + monitorId, + index: pageIndex, + size: pageSize, + status: status !== 'all' ? status : '', + }); + }, [from, to, getPings, monitorId, lastRefresh, selectedLocation, pageIndex, pageSize, status]); + + const [expandedRows, setExpandedRows] = useState>({}); - const statusOptions = [ - { - text: i18n.translate('xpack.uptime.pingList.statusOptions.allStatusOptionLabel', { - defaultMessage: 'All', - }), - value: '', - }, - { - text: i18n.translate('xpack.uptime.pingList.statusOptions.upStatusOptionLabel', { - defaultMessage: 'Up', - }), - value: 'up', - }, - { - text: i18n.translate('xpack.uptime.pingList.statusOptions.downStatusOptionLabel', { - defaultMessage: 'Down', - }), - value: 'down', - }, - ]; - const locations = get(data, 'allPings.locations'); const locationOptions = !locations ? [AllLocationOption] : [AllLocationOption].concat( - locations.map(name => { - return { text: name, value: name }; - }) + locations.map(name => ({ + text: name, + 'data-test-subj': `xpack.uptime.pingList.locationOptions.${name}`, + value: name, + })) ); - const pings: Ping[] = data?.allPings?.pings ?? []; - const hasStatus: boolean = pings.reduce( - (hasHttpStatus: boolean, currentPing: Ping) => + (hasHttpStatus: boolean, currentPing) => hasHttpStatus || !!currentPing.http?.response?.status_code, false ); @@ -134,7 +152,7 @@ export const PingListComponent = ({ defaultMessage: 'Status', }), render: (pingStatus: string, item: Ping) => ( -
+
{pingStatus === 'up' ? i18n.translate('xpack.uptime.pingList.statusColumnHealthUpLabel', { @@ -189,7 +207,7 @@ export const PingListComponent = ({ name: i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', { defaultMessage: 'Error type', }), - render: (error: string) => error ?? '-', + render: (errorType: string) => errorType ?? '-', }, // Only add this column is there is any status present in list ...(hasStatus @@ -219,16 +237,16 @@ export const PingListComponent = ({ render: (item: Ping) => { return ( toggleDetails(item, itemIdToExpandedRowMap, setItemIdToExpandedRowMap)} - disabled={!item.error && !(item.http?.response?.body?.bytes > 0)} + onClick={() => toggleDetails(item, expandedRows, setExpandedRows)} + disabled={!item.error && !(item.http?.response?.body?.bytes ?? 0 > 0)} aria-label={ - itemIdToExpandedRowMap[item.id] + expandedRows[item.docId] ? i18n.translate('xpack.uptime.pingList.collapseRow', { defaultMessage: 'Collapse', }) : i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' }) } - iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} + iconType={expandedRows[item.docId] ? 'arrowUp' : 'arrowDown'} /> ); }, @@ -236,104 +254,83 @@ export const PingListComponent = ({ ]; const pagination: Pagination = { - initialPageSize: 25, + initialPageSize: DEFAULT_PAGE_SIZE, pageIndex, pageSize, pageSizeOptions: [10, 25, 50, 100], - totalItemCount: data?.allPings?.total ?? pageSize, + /** + * we're not currently supporting pagination in this component + * so the first page is the only page + */ + totalItemCount: total, }; return ( - - - -

- + +

+ +

+
+ + + + + { + setStatus(selected.target.value); + }} /> -

-
- - - - - - - - - { - if (typeof selected.target.value === 'string') { - onSelectedStatusChange( - selected.target && selected.target.value !== '' - ? selected.target.value - : undefined - ); - } - }} - /> - - - - - { - onSelectedLocationChange( - selected.target && selected.target.value !== '' - ? selected.target.value - : null - ); - }} - /> - - - - - - - - - ) => { - onPageCountChange(criteria.page!.size); - onPageIndexChange(criteria.page!.index); - }} - /> -
-
+ + + + + { + setSelectedLocation(selected.target.value); + }} + /> + + + + + { + setPageSize(criteria.page!.size); + setPageIndex(criteria.page!.index); + }} + /> + ); }; - -export const PingList = withUptimeGraphQL( - PingListComponent, - pingsQuery -); diff --git a/x-pack/legacy/plugins/uptime/public/hooks/index.ts b/x-pack/legacy/plugins/uptime/public/hooks/index.ts index e022248df407a..1f50e995eda49 100644 --- a/x-pack/legacy/plugins/uptime/public/hooks/index.ts +++ b/x-pack/legacy/plugins/uptime/public/hooks/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './use_monitor'; export * from './use_url_params'; export * from './use_telemetry'; export * from './update_kuery_string'; diff --git a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx index 5016ccebbab54..683af81239c99 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx @@ -5,39 +5,21 @@ */ import { EuiSpacer } from '@elastic/eui'; -import React, { useContext, useState } from 'react'; +import React from 'react'; import { useSelector } from 'react-redux'; -import { MonitorCharts, PingList } from '../components/functional'; -import { UptimeRefreshContext } from '../contexts'; -import { useUptimeTelemetry, useUrlParams, UptimePage } from '../hooks'; import { useTrackPageview } from '../../../../../plugins/observability/public'; -import { MonitorStatusDetails } from '../components/connected'; +import { MonitorStatusDetails, PingList } from '../components/connected'; import { monitorStatusSelector } from '../state/selectors'; import { PageHeader } from './page_header'; +import { MonitorCharts } from '../components/functional'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; -import { useMonitorId } from '../hooks/use_monitor'; +import { useMonitorId, useUptimeTelemetry, UptimePage } from '../hooks'; export const MonitorPage: React.FC = () => { const monitorId = useMonitorId(); - const [pingListPageCount, setPingListPageCount] = useState(10); - const { refreshApp } = useContext(UptimeRefreshContext); - const [getUrlParams, updateUrlParams] = useUrlParams(); - const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = getUrlParams(); - const { dateRangeStart, dateRangeEnd, selectedPingStatus } = params; - - const [selectedLocation, setSelectedLocation] = useState(undefined); - const [pingListIndex, setPingListIndex] = useState(0); - const selectedMonitor = useSelector(monitorStatusSelector); - const sharedVariables = { - dateRangeStart, - dateRangeEnd, - monitorId, - location: selectedLocation, - }; - useUptimeTelemetry(UptimePage.Monitor); useTrackPageview({ app: 'uptime', path: 'monitor' }); @@ -53,25 +35,7 @@ export const MonitorPage: React.FC = () => { - { - updateUrlParams({ selectedPingStatus: selectedStatus || '' }); - refreshApp(); - }} - onPageIndexChange={(index: number) => setPingListIndex(index)} - pageIndex={pingListIndex} - pageSize={pingListPageCount} - selectedOption={selectedPingStatus} - selectedLocation={selectedLocation} - variables={{ - ...sharedVariables, - page: pingListIndex, - size: pingListPageCount, - status: selectedPingStatus, - }} - /> + ); }; diff --git a/x-pack/legacy/plugins/uptime/public/queries/pings_query.ts b/x-pack/legacy/plugins/uptime/public/queries/pings_query.ts deleted file mode 100644 index ed20fe8eb2931..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/queries/pings_query.ts +++ /dev/null @@ -1,73 +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'; - -export const pingsQueryString = ` -query PingList( - $dateRangeStart: String! - $dateRangeEnd: String! - $monitorId: String - $status: String - $sort: String - $size: Int - $location: String - $page: Int -) { - allPings( - dateRangeStart: $dateRangeStart - dateRangeEnd: $dateRangeEnd - monitorId: $monitorId - status: $status - sort: $sort - size: $size - location: $location - page: $page - ) { - total - locations - pings { - id - timestamp - http { - response { - status_code - body { - bytes - hash - content - content_bytes - } - } - } - error { - message - type - } - monitor { - duration { - us - } - id - ip - name - scheme - status - type - } - observer { - geo { - name - } - } - } - } - } -`; - -export const pingsQuery = gql` - ${pingsQueryString} -`; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts index a8f37d38ebae6..e8c862e209739 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts @@ -5,7 +5,7 @@ */ import { createAction } from 'redux-actions'; import { QueryParams } from './types'; -import { Ping } from '../../../common/graphql/types'; +import { Ping } from '../../../common/runtime_types'; export const getMonitorStatusAction = createAction('GET_MONITOR_STATUS'); export const getMonitorStatusActionSuccess = createAction('GET_MONITOR_STATUS_SUCCESS'); diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/ping.ts b/x-pack/legacy/plugins/uptime/public/state/actions/ping.ts index bb7258d9a54b2..70918a4cc70e5 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/ping.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/ping.ts @@ -5,8 +5,17 @@ */ import { createAction } from 'redux-actions'; -import { GetPingHistogramParams, HistogramResult } from '../../../common/types'; +import { + GetPingHistogramParams, + HistogramResult, + PingsResponse, + GetPingsParams, +} from '../../../common/runtime_types'; export const getPingHistogram = createAction('GET_PING_HISTOGRAM'); export const getPingHistogramSuccess = createAction('GET_PING_HISTOGRAM_SUCCESS'); export const getPingHistogramFail = createAction('GET_PING_HISTOGRAM_FAIL'); + +export const getPings = createAction('GET PINGS'); +export const getPingsSuccess = createAction('GET PINGS SUCCESS'); +export const getPingsFail = createAction('GET PINGS FAIL'); diff --git a/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts index f9e171adda334..7c8ab3518b5a0 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts @@ -5,9 +5,9 @@ */ import { QueryParams } from '../actions/types'; -import { Ping } from '../../../common/graphql/types'; -import { apiService } from './utils'; +import { Ping } from '../../../common/runtime_types'; import { API_URLS } from '../../../common/constants'; +import { apiService } from './utils'; export const fetchMonitorStatus = async ({ monitorId, diff --git a/x-pack/legacy/plugins/uptime/public/state/api/ping.ts b/x-pack/legacy/plugins/uptime/public/state/api/ping.ts index df71cc8d67bd0..bbd3ddf399dcc 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/ping.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/ping.ts @@ -5,10 +5,21 @@ */ import { APIFn } from './types'; -import { GetPingHistogramParams, HistogramResult } from '../../../common/types'; +import { + PingsResponseType, + PingsResponse, + GetPingsParams, + GetPingHistogramParams, + HistogramResult, +} from '../../../common/runtime_types'; import { apiService } from './utils'; import { API_URLS } from '../../../common/constants/rest_api'; +export const fetchPings: APIFn = async ({ + dateRange: { from, to }, + ...optional +}) => await apiService.get(API_URLS.PINGS, { from, to, ...optional }, PingsResponseType); + export const fetchPingHistogram: APIFn = async ({ monitorId, dateStart, @@ -19,9 +30,9 @@ export const fetchPingHistogram: APIFn const queryParams = { dateStart, dateEnd, - ...(monitorId && { monitorId }), - ...(statusFilter && { statusFilter }), - ...(filters && { filters }), + monitorId, + statusFilter, + filters, }; return await apiService.get(API_URLS.PING_HISTOGRAM, queryParams); diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts b/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts index 49e497952ea44..b0734cb5ccabb 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts @@ -16,7 +16,7 @@ import { IHttpFetchError } from '../../../../../../../target/types/core/public/h * @param fail creates a failure action * @template T the action type expected by the fetch action * @template R the type that the API request should return on success - * @template S tye type of the success action + * @template S the type of the success action * @template F the type of the failure action */ export function fetchEffectFactory( diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/index.ts b/x-pack/legacy/plugins/uptime/public/state/effects/index.ts index 8d457be1d1c78..8df11312b6455 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/index.ts @@ -11,7 +11,7 @@ import { fetchSnapshotCountEffect } from './snapshot'; import { fetchMonitorStatusEffect } from './monitor_status'; import { fetchDynamicSettingsEffect, setDynamicSettingsEffect } from './dynamic_settings'; import { fetchIndexPatternEffect } from './index_pattern'; -import { fetchPingHistogramEffect } from './ping'; +import { fetchPingsEffect, fetchPingHistogramEffect } from './ping'; import { fetchMonitorDurationEffect } from './monitor_duration'; import { fetchMLJobEffect } from './ml_anomaly'; import { fetchIndexStatusEffect } from './index_status'; @@ -24,6 +24,7 @@ export function* rootEffect() { yield fork(fetchDynamicSettingsEffect); yield fork(setDynamicSettingsEffect); yield fork(fetchIndexPatternEffect); + yield fork(fetchPingsEffect); yield fork(fetchPingHistogramEffect); yield fork(fetchMLJobEffect); yield fork(fetchMonitorDurationEffect); diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/ping.ts b/x-pack/legacy/plugins/uptime/public/state/effects/ping.ts index acb9b31915fa9..dec67ed8cf979 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/ping.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/ping.ts @@ -5,10 +5,21 @@ */ import { takeLatest } from 'redux-saga/effects'; -import { getPingHistogram, getPingHistogramSuccess, getPingHistogramFail } from '../actions'; -import { fetchPingHistogram } from '../api'; +import { + getPingHistogram, + getPingHistogramSuccess, + getPingHistogramFail, + getPings, + getPingsSuccess, + getPingsFail, +} from '../actions'; +import { fetchPingHistogram, fetchPings } from '../api'; import { fetchEffectFactory } from './fetch_effect'; +export function* fetchPingsEffect() { + yield takeLatest(String(getPings), fetchEffectFactory(fetchPings, getPingsSuccess, getPingsFail)); +} + export function* fetchPingHistogramEffect() { yield takeLatest( String(getPingHistogram), diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts index 7bf8af5dd4d03..0a3ff35df0599 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts @@ -13,6 +13,7 @@ import { monitorStatusReducer } from './monitor_status'; import { dynamicSettingsReducer } from './dynamic_settings'; import { indexPatternReducer } from './index_pattern'; import { pingReducer } from './ping'; +import { pingListReducer } from './ping_list'; import { monitorDurationReducer } from './monitor_duration'; import { indexStatusReducer } from './index_status'; import { mlJobsReducer } from './ml_anomaly'; @@ -26,6 +27,7 @@ export const rootReducer = combineReducers({ dynamicSettings: dynamicSettingsReducer, indexPattern: indexPatternReducer, ping: pingReducer, + pingList: pingListReducer, ml: mlJobsReducer, monitorDuration: monitorDurationReducer, indexStatus: indexStatusReducer, diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts index 6cfaa9f8f59c1..fa3e377b5ebf4 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts @@ -9,7 +9,7 @@ import { getMonitorStatusActionSuccess, getMonitorStatusActionFail, } from '../actions'; -import { Ping } from '../../../common/graphql/types'; +import { Ping } from '../../../common/runtime_types'; import { QueryParams } from '../actions/types'; export interface MonitorStatusState { diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/ping.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/ping.ts index 76775e6a0a355..4c8715038ce36 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/ping.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/ping.ts @@ -6,7 +6,7 @@ import { handleActions, Action } from 'redux-actions'; import { getPingHistogram, getPingHistogramSuccess, getPingHistogramFail } from '../actions'; -import { HistogramResult } from '../../../common/types'; +import { HistogramResult } from '../../../common/runtime_types'; export interface PingState { pingHistogram: HistogramResult | null; diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/ping_list.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/ping_list.ts new file mode 100644 index 0000000000000..e3ccb1e663eda --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/ping_list.ts @@ -0,0 +1,48 @@ +/* + * 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 { handleActions, Action } from 'redux-actions'; +import { PingsResponse } from '../../../common/runtime_types'; +import { getPings, getPingsSuccess, getPingsFail } from '../actions'; + +export interface PingListState { + pingList: PingsResponse; + error?: Error; + loading: boolean; +} + +const initialState: PingListState = { + pingList: { + total: 0, + locations: [], + pings: [], + }, + loading: false, +}; + +type PingListPayload = PingsResponse & Error; + +export const pingListReducer = handleActions( + { + [String(getPings)]: state => ({ + ...state, + loading: true, + }), + + [String(getPingsSuccess)]: (state, action: Action) => ({ + ...state, + pingList: { ...action.payload }, + loading: false, + }), + + [String(getPingsFail)]: (state, action: Action) => ({ + ...state, + error: action.payload, + loading: false, + }), + }, + initialState +); diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts index 3b4547514a11e..77902f347f6f9 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -58,6 +58,14 @@ describe('state selectors', () => { loading: false, errors: [], }, + pingList: { + loading: false, + pingList: { + total: 0, + locations: [], + pings: [], + }, + }, monitorDuration: { durationLines: null, loading: false, diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts index 0fc3c7151cb3b..37695e4274306 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts @@ -41,6 +41,11 @@ export const selectPingHistogram = ({ ping, ui }: AppState) => { }; }; +export const selectPingList = ({ pingList, ui: { lastRefresh } }: AppState) => ({ + pings: pingList, + lastRefresh, +}); + const mlCapabilitiesSelector = (state: AppState) => state.ml.mlCapabilities.data; export const hasMLFeatureAvailable = createSelector( diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts index 68c3967359ff4..6bdd30848e4b7 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -23,6 +23,7 @@ describe('execute()', () => { actionTypeRegistry: actionTypeRegistryMock.create(), getScopedSavedObjectsClient: jest.fn().mockReturnValueOnce(savedObjectsClient), isESOUsingEphemeralEncryptionKey: false, + preconfiguredActions: [], }); savedObjectsClient.get.mockResolvedValueOnce({ id: '123', @@ -68,6 +69,68 @@ describe('execute()', () => { }); }); + test('schedules the action with all given parameters with a preconfigured action', async () => { + const executeFn = createExecuteFunction({ + getBasePath, + taskManager: mockTaskManager, + actionTypeRegistry: actionTypeRegistryMock.create(), + getScopedSavedObjectsClient: jest.fn().mockReturnValueOnce(savedObjectsClient), + isESOUsingEphemeralEncryptionKey: false, + preconfiguredActions: [ + { + id: '123', + actionTypeId: 'mock-action-preconfigured', + config: {}, + isPreconfigured: true, + name: 'x', + secrets: {}, + }, + ], + }); + savedObjectsClient.get.mockResolvedValueOnce({ + id: '123', + type: 'action', + attributes: { + actionTypeId: 'mock-action', + }, + references: [], + }); + savedObjectsClient.create.mockResolvedValueOnce({ + id: '234', + type: 'action_task_params', + attributes: {}, + references: [], + }); + await executeFn({ + id: '123', + params: { baz: false }, + spaceId: 'default', + apiKey: Buffer.from('123:abc').toString('base64'), + }); + expect(mockTaskManager.schedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.schedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "params": Object { + "actionTaskParamsId": "234", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:mock-action-preconfigured", + }, + ] + `); + expect(savedObjectsClient.get).not.toHaveBeenCalled(); + expect(savedObjectsClient.create).toHaveBeenCalledWith('action_task_params', { + actionId: '123', + params: { baz: false }, + apiKey: Buffer.from('123:abc').toString('base64'), + }); + }); + test('uses API key when provided', async () => { const getScopedSavedObjectsClient = jest.fn().mockReturnValueOnce(savedObjectsClient); const executeFn = createExecuteFunction({ @@ -76,6 +139,7 @@ describe('execute()', () => { getScopedSavedObjectsClient, isESOUsingEphemeralEncryptionKey: false, actionTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredActions: [], }); savedObjectsClient.get.mockResolvedValueOnce({ id: '123', @@ -125,6 +189,7 @@ describe('execute()', () => { getScopedSavedObjectsClient, isESOUsingEphemeralEncryptionKey: false, actionTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredActions: [], }); savedObjectsClient.get.mockResolvedValueOnce({ id: '123', @@ -171,6 +236,7 @@ describe('execute()', () => { getScopedSavedObjectsClient, isESOUsingEphemeralEncryptionKey: true, actionTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredActions: [], }); await expect( executeFn({ @@ -193,6 +259,7 @@ describe('execute()', () => { getScopedSavedObjectsClient, isESOUsingEphemeralEncryptionKey: false, actionTypeRegistry: mockedActionTypeRegistry, + preconfiguredActions: [], }); mockedActionTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => { throw new Error('Fail'); diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index 4bbcda4cba7fc..4a9ddf412b7cc 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -6,7 +6,12 @@ import { SavedObjectsClientContract } from '../../../../src/core/server'; import { TaskManagerStartContract } from '../../task_manager/server'; -import { GetBasePathFunction, RawAction, ActionTypeRegistryContract } from './types'; +import { + GetBasePathFunction, + RawAction, + ActionTypeRegistryContract, + PreConfiguredAction, +} from './types'; interface CreateExecuteFunctionOptions { taskManager: TaskManagerStartContract; @@ -14,6 +19,7 @@ interface CreateExecuteFunctionOptions { getBasePath: GetBasePathFunction; isESOUsingEphemeralEncryptionKey: boolean; actionTypeRegistry: ActionTypeRegistryContract; + preconfiguredActions: PreConfiguredAction[]; } export interface ExecuteOptions { @@ -29,6 +35,7 @@ export function createExecuteFunction({ actionTypeRegistry, getScopedSavedObjectsClient, isESOUsingEphemeralEncryptionKey, + preconfiguredActions, }: CreateExecuteFunctionOptions) { return async function execute({ id, params, spaceId, apiKey }: ExecuteOptions) { if (isESOUsingEphemeralEncryptionKey === true) { @@ -61,9 +68,9 @@ export function createExecuteFunction({ }; const savedObjectsClient = getScopedSavedObjectsClient(fakeRequest); - const actionSavedObject = await savedObjectsClient.get('action', id); + const actionTypeId = await getActionTypeId(id); - actionTypeRegistry.ensureActionTypeEnabled(actionSavedObject.attributes.actionTypeId); + actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); const actionTaskParamsRecord = await savedObjectsClient.create('action_task_params', { actionId: id, @@ -72,7 +79,7 @@ export function createExecuteFunction({ }); await taskManager.schedule({ - taskType: `actions:${actionSavedObject.attributes.actionTypeId}`, + taskType: `actions:${actionTypeId}`, params: { spaceId, actionTaskParamsId: actionTaskParamsRecord.id, @@ -80,5 +87,15 @@ export function createExecuteFunction({ state: {}, scope: ['actions'], }); + + async function getActionTypeId(actionId: string): Promise { + const pcAction = preconfiguredActions.find(action => action.id === actionId); + if (pcAction) { + return pcAction.actionTypeId; + } + + const actionSO = await savedObjectsClient.get('action', actionId); + return actionSO.attributes.actionTypeId; + } }; } diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index bbcb0457fc1d1..124e5951c714b 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -43,6 +43,7 @@ actionExecutor.initialize({ actionTypeRegistry, encryptedSavedObjectsPlugin, eventLogger: eventLoggerMock.create(), + preconfiguredActions: [], }); beforeEach(() => { @@ -232,6 +233,7 @@ test('throws an error when passing isESOUsingEphemeralEncryptionKey with value o actionTypeRegistry, encryptedSavedObjectsPlugin, eventLogger: eventLoggerMock.create(), + preconfiguredActions: [], }); await expect( customActionExecutor.execute(executeParams) diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index af0353247d99f..a33fb8830a930 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -11,6 +11,8 @@ import { ActionTypeRegistryContract, GetServicesFunction, RawAction, + PreConfiguredAction, + Services, } from '../types'; import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server'; import { SpacesServiceSetup } from '../../../spaces/server'; @@ -24,6 +26,7 @@ export interface ActionExecutorContext { encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; actionTypeRegistry: ActionTypeRegistryContract; eventLogger: IEventLogger; + preconfiguredActions: PreConfiguredAction[]; } export interface ExecuteOptions { @@ -72,29 +75,22 @@ export class ActionExecutor { encryptedSavedObjectsPlugin, actionTypeRegistry, eventLogger, + preconfiguredActions, } = this.actionExecutorContext!; const services = getServices(request); - const namespace = spaces && spaces.getSpaceId(request); + const spaceId = spaces && spaces.getSpaceId(request); + const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {}; - // Ensure user can read the action before processing - const { - attributes: { actionTypeId, config, name }, - } = await services.savedObjectsClient.get('action', actionId); - - actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); - - // Only get encrypted attributes here, the remaining attributes can be fetched in - // the savedObjectsClient call - const { - attributes: { secrets }, - } = await encryptedSavedObjectsPlugin.getDecryptedAsInternalUser( - 'action', + const { actionTypeId, name, config, secrets } = await getActionInfo( + services, + encryptedSavedObjectsPlugin, + preconfiguredActions, actionId, - { - namespace: namespace === 'default' ? undefined : namespace, - } + namespace.namespace ); + + actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); const actionType = actionTypeRegistry.get(actionTypeId); let validatedParams: Record; @@ -112,7 +108,7 @@ export class ActionExecutor { const actionLabel = `${actionTypeId}:${actionId}: ${name}`; const event: IEvent = { event: { action: EVENT_LOG_ACTIONS.execute }, - kibana: { namespace, saved_objects: [{ type: 'action', id: actionId }] }, + kibana: { saved_objects: [{ type: 'action', id: actionId, ...namespace }] }, }; eventLogger.startTiming(event); @@ -174,3 +170,50 @@ function actionErrorToMessage(result: ActionTypeExecutorResult): string { return message; } + +interface ActionInfo { + actionTypeId: string; + name: string; + config: any; + secrets: any; +} + +async function getActionInfo( + services: Services, + encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart, + preconfiguredActions: PreConfiguredAction[], + actionId: string, + namespace: string | undefined +): Promise { + // check to see if it's a pre-configured action first + const pcAction = preconfiguredActions.find( + preconfiguredAction => preconfiguredAction.id === actionId + ); + if (pcAction) { + return { + actionTypeId: pcAction.actionTypeId, + name: pcAction.name, + config: pcAction.config, + secrets: pcAction.secrets, + }; + } + + // if not pre-configured action, should be a saved object + // ensure user can read the action before processing + const { + attributes: { actionTypeId, config, name }, + } = await services.savedObjectsClient.get('action', actionId); + + const { + attributes: { secrets }, + } = await encryptedSavedObjectsPlugin.getDecryptedAsInternalUser('action', actionId, { + namespace: namespace === 'default' ? undefined : namespace, + }); + + return { + actionTypeId, + name, + config, + secrets, + }; +} diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 43882cef21170..f070f714ee508 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -61,6 +61,7 @@ const actionExecutorInitializerParams = { actionTypeRegistry, encryptedSavedObjectsPlugin: mockedEncryptedSavedObjectsPlugin, eventLogger: eventLoggerMock.create(), + preconfiguredActions: [], }; const taskRunnerFactoryInitializerParams = { spaceIdToNamespace, diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 6215b08df81d4..fa5b2f9399a4d 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -99,6 +99,9 @@ describe('Actions Plugin', () => { savedObjects: { client: {}, }, + elasticsearch: { + adminClient: jest.fn(), + }, }, } as any, httpServerMock.createKibanaRequest(), diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 34c9e7aa9e8b8..a8ab3bbb2fad2 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -11,13 +11,13 @@ import { Plugin, CoreSetup, CoreStart, - IClusterClient, KibanaRequest, Logger, SharedGlobalConfig, RequestHandler, IContextProvider, SavedObjectsServiceStart, + ElasticsearchServiceStart, } from '../../../../src/core/server'; import { @@ -89,7 +89,6 @@ export class ActionsPlugin implements Plugin, Plugi private readonly logger: Logger; private serverBasePath?: string; - private adminClient?: IClusterClient; private taskRunnerFactory?: TaskRunnerFactory; private actionTypeRegistry?: ActionTypeRegistry; private actionExecutor?: ActionExecutor; @@ -173,7 +172,6 @@ export class ActionsPlugin implements Plugin, Plugi this.actionTypeRegistry = actionTypeRegistry; this.serverBasePath = core.http.basePath.serverBasePath; this.actionExecutor = actionExecutor; - this.adminClient = core.elasticsearch.adminClient; this.spaces = plugins.spaces?.spacesService; registerBuiltInActionTypes({ @@ -233,7 +231,6 @@ export class ActionsPlugin implements Plugin, Plugi actionTypeRegistry, taskRunnerFactory, kibanaIndex, - adminClient, isESOUsingEphemeralEncryptionKey, preconfiguredActions, } = this; @@ -242,9 +239,10 @@ export class ActionsPlugin implements Plugin, Plugi logger, eventLogger: this.eventLogger!, spaces: this.spaces, - getServices: this.getServicesFactory(core.savedObjects), + getServices: this.getServicesFactory(core.savedObjects, core.elasticsearch), encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, actionTypeRegistry: actionTypeRegistry!, + preconfiguredActions, }); taskRunnerFactory!.initialize({ @@ -265,6 +263,7 @@ export class ActionsPlugin implements Plugin, Plugi getScopedSavedObjectsClient: core.savedObjects.getScopedClient, getBasePath: this.getBasePath, isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!, + preconfiguredActions, }), isActionTypeEnabled: id => { return this.actionTypeRegistry!.isActionTypeEnabled(id); @@ -280,7 +279,7 @@ export class ActionsPlugin implements Plugin, Plugi savedObjectsClient: core.savedObjects.getScopedClient(request), actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex: await kibanaIndex, - scopedClusterClient: adminClient!.asScoped(request), + scopedClusterClient: core.elasticsearch.legacy.client.asScoped(request), preconfiguredActions, }); }, @@ -289,11 +288,11 @@ export class ActionsPlugin implements Plugin, Plugi } private getServicesFactory( - savedObjects: SavedObjectsServiceStart + savedObjects: SavedObjectsServiceStart, + elasticsearch: ElasticsearchServiceStart ): (request: KibanaRequest) => Services { - const { adminClient } = this; return request => ({ - callCluster: adminClient!.asScoped(request).callAsCurrentUser, + callCluster: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser, savedObjectsClient: savedObjects.getScopedClient(request), }); } @@ -301,12 +300,8 @@ export class ActionsPlugin implements Plugin, Plugi private createRouteHandlerContext = ( defaultKibanaIndex: string ): IContextProvider, 'actions'> => { - const { - actionTypeRegistry, - adminClient, - isESOUsingEphemeralEncryptionKey, - preconfiguredActions, - } = this; + const { actionTypeRegistry, isESOUsingEphemeralEncryptionKey, preconfiguredActions } = this; + return async function actionsRouteHandlerContext(context, request) { return { getActionsClient: () => { @@ -319,7 +314,7 @@ export class ActionsPlugin implements Plugin, Plugi savedObjectsClient: context.core.savedObjects.client, actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex, - scopedClusterClient: adminClient!.asScoped(request), + scopedClusterClient: context.core.elasticsearch.adminClient, preconfiguredActions, }); }, diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts index a07a2aa8f1c70..ed0d876ed0208 100644 --- a/x-pack/plugins/actions/server/usage/task.ts +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, CoreSetup } from 'kibana/server'; +import { Logger, CoreSetup, APICaller } from 'kibana/server'; import moment from 'moment'; import { RunContext, @@ -62,7 +62,11 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex: string) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; - const callCluster = core.elasticsearch.adminClient.callAsInternalUser; + const callCluster = (...args: Parameters) => { + return core.getStartServices().then(([{ elasticsearch: { legacy: { client } } }]) => + client.callAsInternalUser(...args) + ); + }; return { async run() { return Promise.all([ diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index 6f8478df58a53..3e4c26d3444c9 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -34,6 +34,7 @@ import { import { EncryptedSavedObjectsPluginStart } from '../../../plugins/encrypted_saved_objects/server'; import { TaskManagerStartContract } from '../../../plugins/task_manager/server'; import { taskInstanceToAlertTaskInstance } from './task_runner/alert_task_instance'; +import { deleteTaskIfItExists } from './lib/delete_task_if_it_exists'; type NormalizedAlertAction = Omit; export type CreateAPIKeyResult = @@ -268,7 +269,7 @@ export class AlertsClient { const removeResult = await this.savedObjectsClient.delete('alert', id); await Promise.all([ - taskIdToRemove ? this.taskManager.remove(taskIdToRemove) : null, + taskIdToRemove ? deleteTaskIfItExists(this.taskManager, taskIdToRemove) : null, apiKeyToInvalidate ? this.invalidateApiKey({ apiKey: apiKeyToInvalidate }) : null, ]); @@ -510,7 +511,9 @@ export class AlertsClient { ); await Promise.all([ - attributes.scheduledTaskId ? this.taskManager.remove(attributes.scheduledTaskId) : null, + attributes.scheduledTaskId + ? deleteTaskIfItExists(this.taskManager, attributes.scheduledTaskId) + : null, apiKeyToInvalidate ? this.invalidateApiKey({ apiKey: apiKeyToInvalidate }) : null, ]); } diff --git a/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts b/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts new file mode 100644 index 0000000000000..84a1743387c9c --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts @@ -0,0 +1,44 @@ +/* + * 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 uuid from 'uuid'; +import { taskManagerMock } from '../../../task_manager/server/mocks'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; +import { deleteTaskIfItExists } from './delete_task_if_it_exists'; + +describe('deleteTaskIfItExists', () => { + test('removes the task by its ID', async () => { + const tm = taskManagerMock.createStart(); + const id = uuid.v4(); + + expect(await deleteTaskIfItExists(tm, id)).toBe(undefined); + + expect(tm.remove).toHaveBeenCalledWith(id); + }); + + test('handles 404 errors caused by the task not existing', async () => { + const tm = taskManagerMock.createStart(); + const id = uuid.v4(); + + tm.remove.mockRejectedValue(SavedObjectsErrorHelpers.createGenericNotFoundError('task', id)); + + expect(await deleteTaskIfItExists(tm, id)).toBe(undefined); + + expect(tm.remove).toHaveBeenCalledWith(id); + }); + + test('throws if any other errro is caused by task removal', async () => { + const tm = taskManagerMock.createStart(); + const id = uuid.v4(); + + const error = SavedObjectsErrorHelpers.createInvalidVersionError(uuid.v4()); + tm.remove.mockRejectedValue(error); + + expect(deleteTaskIfItExists(tm, id)).rejects.toBe(error); + + expect(tm.remove).toHaveBeenCalledWith(id); + }); +}); diff --git a/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts b/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts new file mode 100644 index 0000000000000..53bb1b5cb5d53 --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { TaskManagerStartContract } from '../../../task_manager/server'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; + +export async function deleteTaskIfItExists(taskManager: TaskManagerStartContract, taskId: string) { + try { + await taskManager.remove(taskId); + } catch (err) { + if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { + throw err; + } + } +} diff --git a/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts b/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts new file mode 100644 index 0000000000000..46ceee3ce420b --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.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 { isAlertSavedObjectNotFoundError } from './is_alert_not_found_error'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; +import uuid from 'uuid'; + +describe('isAlertSavedObjectNotFoundError', () => { + test('identifies SavedObjects Not Found errors', () => { + const id = uuid.v4(); + // ensure the error created by SO parses as a string with the format we expect + expect( + `${SavedObjectsErrorHelpers.createGenericNotFoundError('alert', id)}`.includes(`alert/${id}`) + ).toBe(true); + + const errorBySavedObjectsHelper = SavedObjectsErrorHelpers.createGenericNotFoundError( + 'alert', + id + ); + + expect(isAlertSavedObjectNotFoundError(errorBySavedObjectsHelper, id)).toBe(true); + }); + + test('identifies generic errors', () => { + const id = uuid.v4(); + expect(isAlertSavedObjectNotFoundError(new Error(`not found`), id)).toBe(false); + }); +}); diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_pipelines_routes.js b/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts old mode 100755 new mode 100644 similarity index 50% rename from x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_pipelines_routes.js rename to x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts index 6d25f3acb9bf9..0aa83ad0e883c --- a/x-pack/legacy/plugins/logstash/server/routes/api/pipelines/register_pipelines_routes.js +++ b/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerListRoute } from './register_list_route'; -import { registerDeleteRoute } from './register_delete_route'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -export function registerLogstashPipelinesRoutes(server) { - registerListRoute(server); - registerDeleteRoute(server); +export function isAlertSavedObjectNotFoundError(err: Error, alertId: string) { + return SavedObjectsErrorHelpers.isNotFoundError(err) && `${err}`.includes(alertId); } diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index fdca6c0a9b503..ad39d09bd6d3d 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -19,7 +19,6 @@ import { TaskRunnerFactory } from './task_runner'; import { AlertsClientFactory } from './alerts_client_factory'; import { LicenseState } from './lib/license_state'; import { - IClusterClient, KibanaRequest, Logger, PluginInitializerContext, @@ -29,6 +28,7 @@ import { IContextProvider, RequestHandler, SharedGlobalConfig, + ElasticsearchServiceStart, } from '../../../../src/core/server'; import { @@ -94,7 +94,6 @@ export class AlertingPlugin { private readonly logger: Logger; private alertTypeRegistry?: AlertTypeRegistry; private readonly taskRunnerFactory: TaskRunnerFactory; - private adminClient?: IClusterClient; private serverBasePath?: string; private licenseState: LicenseState | null = null; private isESOUsingEphemeralEncryptionKey?: boolean; @@ -119,7 +118,6 @@ export class AlertingPlugin { } public async setup(core: CoreSetup, plugins: AlertingPluginsSetup): Promise { - this.adminClient = core.elasticsearch.adminClient; this.licenseState = new LicenseState(plugins.licensing.license$); this.spaces = plugins.spaces?.spacesService; this.security = plugins.security; @@ -223,7 +221,7 @@ export class AlertingPlugin { taskRunnerFactory.initialize({ logger, - getServices: this.getServicesFactory(core.savedObjects), + getServices: this.getServicesFactory(core.savedObjects, core.elasticsearch), spaceIdToNamespace: this.spaceIdToNamespace, actionsPlugin: plugins.actions, encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, @@ -263,11 +261,11 @@ export class AlertingPlugin { }; private getServicesFactory( - savedObjects: SavedObjectsServiceStart + savedObjects: SavedObjectsServiceStart, + elasticsearch: ElasticsearchServiceStart ): (request: KibanaRequest) => Services { - const { adminClient } = this; return request => ({ - callCluster: adminClient!.asScoped(request).callAsCurrentUser, + callCluster: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser, savedObjectsClient: savedObjects.getScopedClient(request), }); } diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts index 8d037a1ecee91..756080baba626 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts @@ -91,7 +91,6 @@ test('calls actionsPlugin.execute per selected action', async () => { "alerting": Object { "instance_id": "2", }, - "namespace": "default", "saved_objects": Array [ Object { "id": "1", diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts index de06c8bbb374a..72f9e70905dc2 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -87,16 +87,17 @@ export function createExecutionHandler({ apiKey, }); + const namespace = spaceId === 'default' ? {} : { namespace: spaceId }; + const event: IEvent = { event: { action: EVENT_LOG_ACTIONS.executeAction }, kibana: { alerting: { instance_id: alertInstanceId, }, - namespace: spaceId, saved_objects: [ - { type: 'alert', id: alertId }, - { type: 'action', id: action.id }, + { type: 'alert', id: alertId, ...namespace }, + { type: 'action', id: action.id, ...namespace }, ], }, }; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 520f8d5c99b16..31cc893f785cb 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -16,6 +16,7 @@ import { PluginStartContract as ActionsPluginStart } from '../../../actions/serv import { actionsMock } from '../../../actions/server/mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; import { IEventLogger } from '../../../event_log/server'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; const alertType = { id: 'test', @@ -169,10 +170,10 @@ describe('Task Runner', () => { "action": "execute", }, "kibana": Object { - "namespace": undefined, "saved_objects": Array [ Object { "id": "1", + "namespace": undefined, "type": "alert", }, ], @@ -229,10 +230,10 @@ describe('Task Runner', () => { "action": "execute", }, "kibana": Object { - "namespace": undefined, "saved_objects": Array [ Object { "id": "1", + "namespace": undefined, "type": "alert", }, ], @@ -249,10 +250,10 @@ describe('Task Runner', () => { "alerting": Object { "instance_id": "1", }, - "namespace": undefined, "saved_objects": Array [ Object { "id": "1", + "namespace": undefined, "type": "alert", }, ], @@ -269,14 +270,15 @@ describe('Task Runner', () => { "alerting": Object { "instance_id": "1", }, - "namespace": undefined, "saved_objects": Array [ Object { "id": "1", + "namespace": undefined, "type": "alert", }, Object { "id": "1", + "namespace": undefined, "type": "action", }, ], @@ -344,10 +346,10 @@ describe('Task Runner', () => { "action": "execute", }, "kibana": Object { - "namespace": undefined, "saved_objects": Array [ Object { "id": "1", + "namespace": undefined, "type": "alert", }, ], @@ -364,10 +366,10 @@ describe('Task Runner', () => { "alerting": Object { "instance_id": "2", }, - "namespace": undefined, "saved_objects": Array [ Object { "id": "1", + "namespace": undefined, "type": "alert", }, ], @@ -560,10 +562,10 @@ describe('Task Runner', () => { "action": "execute", }, "kibana": Object { - "namespace": undefined, "saved_objects": Array [ Object { "id": "1", + "namespace": undefined, "type": "alert", }, ], @@ -664,4 +666,36 @@ describe('Task Runner', () => { } `); }); + + test('avoids rescheduling a failed Alert Task Runner when it throws due to failing to fetch the alert', async () => { + savedObjectsClient.get.mockImplementation(() => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', '1'); + }); + + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toMatchInlineSnapshot(` + Object { + "runAt": undefined, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 2ba56396279ea..1d4b12e96bc76 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -26,12 +26,13 @@ import { taskInstanceToAlertTaskInstance } from './alert_task_instance'; import { AlertInstances } from '../alert_instance/alert_instance'; import { EVENT_LOG_ACTIONS } from '../plugin'; import { IEvent, IEventLogger } from '../../../event_log/server'; +import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error'; const FALLBACK_RETRY_INTERVAL: IntervalSchedule = { interval: '5m' }; interface AlertTaskRunResult { state: AlertTaskState; - runAt: Date; + runAt: Date | undefined; } interface AlertTaskInstance extends ConcreteTaskInstance { @@ -173,7 +174,7 @@ export class TaskRunner { const alertLabel = `${this.alertType.id}:${alertId}: '${name}'`; const event: IEvent = { event: { action: EVENT_LOG_ACTIONS.execute }, - kibana: { namespace, saved_objects: [{ type: 'alert', id: alertId }] }, + kibana: { saved_objects: [{ type: 'alert', id: alertId, namespace }] }, }; eventLogger.startTiming(event); @@ -328,22 +329,29 @@ export class TaskRunner { }; }, (err: Error) => { - this.logger.error(`Executing Alert "${alertId}" has resulted in Error: ${err.message}`); + const message = `Executing Alert "${alertId}" has resulted in Error: ${err.message}`; + if (isAlertSavedObjectNotFoundError(err, alertId)) { + this.logger.debug(message); + } else { + this.logger.error(message); + } return { ...originalState, previousStartedAt, }; } ), - runAt: resolveErr(runAt, () => - getNextRunAt( - new Date(), - // if we fail at this point we wish to recover but don't have access to the Alert's - // attributes, so we'll use a default interval to prevent the underlying task from - // falling into a failed state - FALLBACK_RETRY_INTERVAL - ) - ), + runAt: resolveErr(runAt, err => { + return isAlertSavedObjectNotFoundError(err, alertId) + ? undefined + : getNextRunAt( + new Date(), + // if we fail at this point we wish to recover but don't have access to the Alert's + // attributes, so we'll use a default interval to prevent the underlying task from + // falling into a failed state + FALLBACK_RETRY_INTERVAL + ); + }), }; } } @@ -378,11 +386,10 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst action, }, kibana: { - namespace: params.namespace, alerting: { instance_id: id, }, - saved_objects: [{ type: 'alert', id: params.alertId }], + saved_objects: [{ type: 'alert', id: params.alertId, namespace: params.namespace }], }, message, }; diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index 3da60aef301e2..ab62d81d44f8a 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, CoreSetup } from 'kibana/server'; +import { Logger, CoreSetup, APICaller } from 'kibana/server'; import moment from 'moment'; import { RunContext, @@ -65,7 +65,12 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex: string) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; - const callCluster = core.elasticsearch.adminClient.callAsInternalUser; + const callCluster = (...args: Parameters) => { + return core.getStartServices().then(([{ elasticsearch: { legacy: { client } } }]) => + client.callAsInternalUser(...args) + ); + }; + return { async run() { return Promise.all([ diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index e18b6d33ca419..b434d41982f4c 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -3,7 +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 { PluginInitializerContext, Plugin, CoreSetup } from 'src/core/server'; +import { + PluginInitializerContext, + Plugin, + CoreSetup, + CoreStart, + Logger +} from 'src/core/server'; import { Observable, combineLatest, AsyncSubject } from 'rxjs'; import { map, take } from 'rxjs/operators'; import { Server } from 'hapi'; @@ -37,6 +43,8 @@ export interface APMPluginContract { } export class APMPlugin implements Plugin { + private currentConfig?: APMConfig; + private logger?: Logger; legacySetup$: AsyncSubject; constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; @@ -56,7 +64,7 @@ export class APMPlugin implements Plugin { actions?: ActionsPlugin['setup']; } ) { - const logger = this.initContext.logger.get(); + this.logger = this.initContext.logger.get(); const config$ = this.initContext.config.create(); const mergedConfig$ = combineLatest(plugins.apm_oss.config$, config$).pipe( map(([apmOssConfig, apmConfig]) => mergeConfigs(apmOssConfig, apmConfig)) @@ -71,49 +79,40 @@ export class APMPlugin implements Plugin { } this.legacySetup$.subscribe(__LEGACY => { - createApmApi().init(core, { config$: mergedConfig$, logger, __LEGACY }); + createApmApi().init(core, { + config$: mergedConfig$, + logger: this.logger!, + __LEGACY + }); }); - const currentConfig = await mergedConfig$.pipe(take(1)).toPromise(); + this.currentConfig = await mergedConfig$.pipe(take(1)).toPromise(); if ( plugins.taskManager && plugins.usageCollection && - currentConfig['xpack.apm.telemetryCollectionEnabled'] + this.currentConfig['xpack.apm.telemetryCollectionEnabled'] ) { createApmTelemetry({ core, config$: mergedConfig$, usageCollector: plugins.usageCollection, taskManager: plugins.taskManager, - logger + logger: this.logger }); } - // create agent configuration index without blocking setup lifecycle - createApmAgentConfigurationIndex({ - esClient: core.elasticsearch.dataClient, - config: currentConfig, - logger - }); - // create custom action index without blocking setup lifecycle - createApmCustomLinkIndex({ - esClient: core.elasticsearch.dataClient, - config: currentConfig, - logger - }); - plugins.home.tutorials.registerTutorial( tutorialProvider({ - isEnabled: currentConfig['xpack.apm.ui.enabled'], - indexPatternTitle: currentConfig['apm_oss.indexPattern'], + isEnabled: this.currentConfig['xpack.apm.ui.enabled'], + indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], cloud: plugins.cloud, indices: { - errorIndices: currentConfig['apm_oss.errorIndices'], - metricsIndices: currentConfig['apm_oss.metricsIndices'], - onboardingIndices: currentConfig['apm_oss.onboardingIndices'], - sourcemapIndices: currentConfig['apm_oss.sourcemapIndices'], - transactionIndices: currentConfig['apm_oss.transactionIndices'] + errorIndices: this.currentConfig['apm_oss.errorIndices'], + metricsIndices: this.currentConfig['apm_oss.metricsIndices'], + onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], + sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], + transactionIndices: this.currentConfig['apm_oss.transactionIndices'] } }) ); @@ -127,12 +126,29 @@ export class APMPlugin implements Plugin { getApmIndices: async () => getApmIndices({ savedObjectsClient: await getInternalSavedObjectsClient(core), - config: currentConfig + config: await mergedConfig$.pipe(take(1)).toPromise() }) }; } - public async start() {} + public start(core: CoreStart) { + if (this.currentConfig == null || this.logger == null) { + throw new Error('APMPlugin needs to be setup before calling start()'); + } + + // create agent configuration index without blocking start lifecycle + createApmAgentConfigurationIndex({ + esClient: core.elasticsearch.legacy.client, + config: this.currentConfig, + logger: this.logger + }); + // create custom action index without blocking start lifecycle + createApmCustomLinkIndex({ + esClient: core.elasticsearch.legacy.client, + config: this.currentConfig, + logger: this.logger + }); + } public stop() {} } diff --git a/x-pack/plugins/case/common/api/cases/case.ts b/x-pack/plugins/case/common/api/cases/case.ts index 3c5d3405f395e..1f08a41024905 100644 --- a/x-pack/plugins/case/common/api/cases/case.ts +++ b/x-pack/plugins/case/common/api/cases/case.ts @@ -167,6 +167,7 @@ export type CasesResponse = rt.TypeOf; export type CasesFindResponse = rt.TypeOf; export type CasePatchRequest = rt.TypeOf; export type CasesPatchRequest = rt.TypeOf; +export type Status = rt.TypeOf; export type CaseExternalServiceRequest = rt.TypeOf; export type ServiceConnectorCaseParams = rt.TypeOf; export type ServiceConnectorCaseResponse = rt.TypeOf; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts index 95cd66a9c51a2..e83dafc68ee69 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts @@ -11,14 +11,20 @@ import { SavedObjectsBulkUpdateObject, } from 'src/core/server'; -import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../../saved_object_types'; +import { + CASE_COMMENT_SAVED_OBJECT, + CASE_SAVED_OBJECT, + CASE_CONFIGURE_SAVED_OBJECT, +} from '../../../saved_object_types'; export const createMockSavedObjectsRepository = ({ caseSavedObject = [], caseCommentSavedObject = [], + caseConfigureSavedObject = [], }: { caseSavedObject?: any[]; caseCommentSavedObject?: any[]; + caseConfigureSavedObject?: any[]; }) => { const mockSavedObjectsClientContract = ({ bulkGet: jest.fn((objects: SavedObjectsBulkGetObject[]) => { @@ -70,6 +76,7 @@ export const createMockSavedObjectsRepository = ({ } return result[0]; } + const result = caseSavedObject.filter(s => s.id === id); if (!result.length) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); @@ -81,6 +88,23 @@ export const createMockSavedObjectsRepository = ({ throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); } + if ( + findArgs.type === CASE_CONFIGURE_SAVED_OBJECT && + caseConfigureSavedObject[0] && + caseConfigureSavedObject[0].id === 'throw-error-find' + ) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('Error thrown for testing'); + } + + if (findArgs.type === CASE_CONFIGURE_SAVED_OBJECT) { + return { + page: 1, + per_page: 5, + total: caseConfigureSavedObject.length, + saved_objects: caseConfigureSavedObject, + }; + } + if (findArgs.type === CASE_COMMENT_SAVED_OBJECT) { return { page: 1, @@ -101,6 +125,13 @@ export const createMockSavedObjectsRepository = ({ throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); } + if ( + type === CASE_CONFIGURE_SAVED_OBJECT && + attributes.connector_id === 'throw-error-create' + ) { + throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); + } + if (type === CASE_COMMENT_SAVED_OBJECT) { const newCommentObj = { type, @@ -113,6 +144,20 @@ export const createMockSavedObjectsRepository = ({ caseCommentSavedObject = [...caseCommentSavedObject, newCommentObj]; return newCommentObj; } + + if (type === CASE_CONFIGURE_SAVED_OBJECT) { + const newConfiguration = { + type, + id: 'mock-configuration', + attributes, + updated_at: '2020-04-09T09:43:51.778Z', + version: attributes.connector_id === 'no-version' ? undefined : 'WzksMV0=', + }; + + caseConfigureSavedObject = [newConfiguration]; + return newConfiguration; + } + return { type, id: 'mock-it', @@ -143,6 +188,16 @@ export const createMockSavedObjectsRepository = ({ } } + if (type === CASE_CONFIGURE_SAVED_OBJECT) { + return { + id, + type, + updated_at: '2019-11-22T22:50:55.191Z', + attributes, + version: attributes.connector_id === 'no-version' ? undefined : 'WzE3LDFd', + }; + } + return { id, type, @@ -153,16 +208,29 @@ export const createMockSavedObjectsRepository = ({ }), delete: jest.fn((type: string, id: string) => { let result = caseSavedObject.filter(s => s.id === id); + if (type === CASE_COMMENT_SAVED_OBJECT) { result = caseCommentSavedObject.filter(s => s.id === id); } + + if (type === CASE_CONFIGURE_SAVED_OBJECT) { + result = caseConfigureSavedObject.filter(s => s.id === id); + } + if (type === CASE_COMMENT_SAVED_OBJECT && id === 'bad-guy') { throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); } + if (!result.length) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } + if ( + type === CASE_CONFIGURE_SAVED_OBJECT && + caseConfigureSavedObject[0].id === 'throw-error-delete' + ) { + throw new Error('Error thrown for testing'); + } return {}; }), deleteByNamespace: jest.fn(), diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts index 03da50f886fd5..75e793a80272f 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -5,7 +5,11 @@ */ import { SavedObject } from 'kibana/server'; -import { CaseAttributes, CommentAttributes } from '../../../../common/api'; +import { + CaseAttributes, + CommentAttributes, + CasesConfigureAttributes, +} from '../../../../common/api'; export const mockCases: Array> = [ { @@ -225,7 +229,33 @@ export const mockCaseComments: Array> = [ }, ], updated_at: '2019-11-25T22:32:30.608Z', + version: 'WzYsMV0=', + }, +]; +export const mockCaseConfigure: Array> = [ + { + type: 'cases-configure', + id: 'mock-configuration-1', + attributes: { + connector_id: '123', + connector_name: 'My connector', + closure_type: 'close-by-user', + created_at: '2020-04-09T09:43:51.778Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + updated_at: '2020-04-09T09:43:51.778Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + }, + references: [], + updated_at: '2020-04-09T09:43:51.778Z', version: 'WzYsMV0=', }, ]; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts index b1881e394e796..d947ffbaf181d 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts @@ -5,13 +5,19 @@ */ import { RequestHandlerContext } from 'src/core/server'; +import { actionsClientMock } from '../../../../../actions/server/mocks'; +import { getActions } from '../__mocks__/request_responses'; export const createRouteContext = (client: any) => { + const actionsMock = actionsClientMock.create(); + actionsMock.getAll.mockImplementation(() => Promise.resolve(getActions())); + return ({ core: { savedObjects: { client, }, }, + actions: { getActionsClient: () => actionsMock }, } as unknown) as RequestHandlerContext; }; diff --git a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts new file mode 100644 index 0000000000000..846013674986e --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts @@ -0,0 +1,64 @@ +/* + * 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 { CasePostRequest, CasesConfigureRequest } from '../../../../common/api'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FindActionResult } from '../../../../../actions/server/types'; + +export const newCase: CasePostRequest = { + title: 'My new case', + description: 'A description', + tags: ['new', 'case'], +}; + +export const getActions = (): FindActionResult[] => [ + { + id: 'e90075a5-c386-41e3-ae21-ba4e61510695', + actionTypeId: '.webhook', + name: 'Test', + config: { + method: 'post', + url: 'https://example.com', + headers: null, + }, + isPreconfigured: false, + referencedByCount: 0, + }, + { + id: 'd611af27-3532-4da9-8034-271fee81d634', + actionTypeId: '.servicenow', + name: 'ServiceNow', + config: { + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + apiUrl: 'https://dev102283.service-now.com', + }, + isPreconfigured: false, + referencedByCount: 0, + }, +]; + +export const newConfiguration: CasesConfigureRequest = { + connector_id: '456', + connector_name: 'My connector 2', + closure_type: 'close-by-pushing', +}; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts new file mode 100644 index 0000000000000..66d39c3f11d28 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, +} from '../../__fixtures__'; + +import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; +import { initGetCaseConfigure } from './get_configure'; + +describe('GET configuration', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initGetCaseConfigure, 'get'); + }); + + it('returns the configuration', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'get', + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(200); + expect(res.payload).toEqual({ + ...mockCaseConfigure[0].attributes, + version: mockCaseConfigure[0].version, + }); + }); + + it('handles undefined version correctly', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'get', + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: [{ ...mockCaseConfigure[0], version: undefined }], + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(200); + expect(res.payload).toEqual({ + connector_id: '123', + connector_name: 'My connector', + closure_type: 'close-by-user', + created_at: '2020-04-09T09:43:51.778Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + updated_at: '2020-04-09T09:43:51.778Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + version: '', + }); + }); + + it('returns an empty object when there is no configuration', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'get', + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: [], + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(200); + expect(res.payload).toEqual({}); + }); + + it('returns an error if find throws an error', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'get', + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: [{ ...mockCaseConfigure[0], id: 'throw-error-find' }], + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(404); + expect(res.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts new file mode 100644 index 0000000000000..62edaa0a4792a --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.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 { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, +} from '../../__fixtures__'; + +import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; +import { initCaseConfigureGetActionConnector } from './get_connectors'; +import { getActions } from '../../__mocks__/request_responses'; + +describe('GET connectors', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initCaseConfigureGetActionConnector, 'get'); + }); + + it('returns the connectors', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure/connectors/_find', + method: 'get', + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(200); + expect(res.payload).toEqual( + getActions().filter(action => action.actionTypeId === '.servicenow') + ); + }); + + it('it throws an error when actions client is null', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure/connectors/_find', + method: 'get', + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + context.actions = undefined; + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(404); + expect(res.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts new file mode 100644 index 0000000000000..5b3d68a258664 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts @@ -0,0 +1,156 @@ +/* + * 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 { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, +} from '../../__fixtures__'; + +import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; +import { initPatchCaseConfigure } from './patch_configure'; + +describe('PATCH configuration', () => { + let routeHandler: RequestHandler; + + beforeAll(async () => { + routeHandler = await createRoute(initPatchCaseConfigure, 'patch'); + const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; + spyOnDate.mockImplementation(() => ({ + toISOString: jest.fn().mockReturnValue('2020-04-09T09:43:51.778Z'), + })); + }); + + it('patch configuration', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'patch', + body: { + closure_type: 'close-by-pushing', + version: mockCaseConfigure[0].version, + }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + + expect(res.status).toEqual(200); + expect(res.payload).toEqual( + expect.objectContaining({ + ...mockCaseConfigure[0].attributes, + closure_type: 'close-by-pushing', + updated_at: '2020-04-09T09:43:51.778Z', + updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, + version: 'WzE3LDFd', + }) + ); + }); + + it('patch configuration without authentication', async () => { + routeHandler = await createRoute(initPatchCaseConfigure, 'patch', true); + + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'patch', + body: { + closure_type: 'close-by-pushing', + version: mockCaseConfigure[0].version, + }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + + expect(res.status).toEqual(200); + expect(res.payload).toEqual( + expect.objectContaining({ + ...mockCaseConfigure[0].attributes, + closure_type: 'close-by-pushing', + updated_at: '2020-04-09T09:43:51.778Z', + updated_by: { email: null, full_name: null, username: null }, + version: 'WzE3LDFd', + }) + ); + }); + + it('throw error when configuration have not being created', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'patch', + body: { + closure_type: 'close-by-pushing', + version: mockCaseConfigure[0].version, + }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: [], + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + + expect(res.status).toEqual(409); + expect(res.payload.isBoom).toEqual(true); + }); + + it('throw error when the versions are different', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'patch', + body: { + closure_type: 'close-by-pushing', + version: 'different-version', + }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + + expect(res.status).toEqual(409); + expect(res.payload.isBoom).toEqual(true); + }); + + it('handles undefined version correctly', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'patch', + body: { connector_id: 'no-version', version: mockCaseConfigure[0].version }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.payload).toEqual( + expect.objectContaining({ + version: '', + }) + ); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts new file mode 100644 index 0000000000000..7e40cad5b1298 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts @@ -0,0 +1,294 @@ +/* + * 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 { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, +} from '../../__fixtures__'; + +import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; +import { initPostCaseConfigure } from './post_configure'; +import { newConfiguration } from '../../__mocks__/request_responses'; + +describe('POST configuration', () => { + let routeHandler: RequestHandler; + + beforeAll(async () => { + routeHandler = await createRoute(initPostCaseConfigure, 'post'); + const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; + spyOnDate.mockImplementation(() => ({ + toISOString: jest.fn().mockReturnValue('2020-04-09T09:43:51.778Z'), + })); + }); + + it('create configuration', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: newConfiguration, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + + expect(res.status).toEqual(200); + expect(res.payload).toEqual( + expect.objectContaining({ + connector_id: '456', + connector_name: 'My connector 2', + closure_type: 'close-by-pushing', + created_at: '2020-04-09T09:43:51.778Z', + created_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, + updated_at: null, + updated_by: null, + }) + ); + }); + + it('create configuration without authentication', async () => { + routeHandler = await createRoute(initPostCaseConfigure, 'post', true); + + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: newConfiguration, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + + expect(res.status).toEqual(200); + expect(res.payload).toEqual( + expect.objectContaining({ + connector_id: '456', + connector_name: 'My connector 2', + closure_type: 'close-by-pushing', + created_at: '2020-04-09T09:43:51.778Z', + created_by: { email: null, full_name: null, username: null }, + updated_at: null, + updated_by: null, + }) + ); + }); + + it('throws when missing connector_id', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: { + connector_name: 'My connector 2', + closure_type: 'close-by-pushing', + }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(400); + expect(res.payload.isBoom).toEqual(true); + }); + + it('throws when missing connector_name', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: { + connector_id: '456', + closure_type: 'close-by-pushing', + }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(400); + expect(res.payload.isBoom).toEqual(true); + }); + + it('throws when missing closure_type', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: { + connector_id: '456', + connector_name: 'My connector 2', + }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(400); + expect(res.payload.isBoom).toEqual(true); + }); + + it('it deletes the previous configuration', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: newConfiguration, + }); + + const savedObjectRepository = createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }); + + const context = createRouteContext(savedObjectRepository); + + const res = await routeHandler(context, req, kibanaResponseFactory); + + expect(res.status).toEqual(200); + expect(savedObjectRepository.delete.mock.calls[0][1]).toBe(mockCaseConfigure[0].id); + }); + + it('it does NOT delete when not found', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: newConfiguration, + }); + + const savedObjectRepository = createMockSavedObjectsRepository({ + caseConfigureSavedObject: [], + }); + + const context = createRouteContext(savedObjectRepository); + + const res = await routeHandler(context, req, kibanaResponseFactory); + + expect(res.status).toEqual(200); + expect(savedObjectRepository.delete).not.toHaveBeenCalled(); + }); + + it('it deletes all configuration', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: newConfiguration, + }); + + const savedObjectRepository = createMockSavedObjectsRepository({ + caseConfigureSavedObject: [ + mockCaseConfigure[0], + { ...mockCaseConfigure[0], id: 'mock-configuration-2' }, + ], + }); + + const context = createRouteContext(savedObjectRepository); + + const res = await routeHandler(context, req, kibanaResponseFactory); + + expect(res.status).toEqual(200); + expect(savedObjectRepository.delete.mock.calls[0][1]).toBe(mockCaseConfigure[0].id); + expect(savedObjectRepository.delete.mock.calls[1][1]).toBe('mock-configuration-2'); + }); + + it('returns an error if find throws an error', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: newConfiguration, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: [{ ...mockCaseConfigure[0], id: 'throw-error-find' }], + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(404); + expect(res.payload.isBoom).toEqual(true); + }); + + it('returns an error if delete throws an error', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: newConfiguration, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: [{ ...mockCaseConfigure[0], id: 'throw-error-delete' }], + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(500); + expect(res.payload.isBoom).toEqual(true); + }); + + it('returns an error if post throws an error', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: { + connector_id: 'throw-error-create', + connector_name: 'My connector 2', + closure_type: 'close-by-pushing', + }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(400); + expect(res.payload.isBoom).toEqual(true); + }); + + it('handles undefined version correctly', async () => { + const req = httpServerMock.createKibanaRequest({ + path: '/api/cases/configure', + method: 'post', + body: { ...newConfiguration, connector_id: 'no-version' }, + }); + + const context = createRouteContext( + createMockSavedObjectsRepository({ + caseConfigureSavedObject: mockCaseConfigure, + }) + ); + + const res = await routeHandler(context, req, kibanaResponseFactory); + expect(res.status).toEqual(200); + expect(res.payload).toEqual( + expect.objectContaining({ + version: '', + }) + ); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/utils.test.ts b/x-pack/plugins/case/server/routes/api/utils.test.ts new file mode 100644 index 0000000000000..a22f4db30bf8d --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/utils.test.ts @@ -0,0 +1,418 @@ +/* + * 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 { + transformNewCase, + transformNewComment, + wrapError, + transformCases, + flattenCaseSavedObjects, + flattenCaseSavedObject, + flattenCommentSavedObjects, + transformComments, + flattenCommentSavedObject, + sortToSnake, +} from './utils'; +import { newCase } from './__mocks__/request_responses'; +import { isBoom, boomify } from 'boom'; +import { mockCases, mockCaseComments } from './__fixtures__/mock_saved_objects'; + +describe('Utils', () => { + describe('transformNewCase', () => { + it('transform correctly', () => { + const myCase = { + newCase, + createdDate: '2020-04-09T09:43:51.778Z', + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }; + + const res = transformNewCase(myCase); + + expect(res).toEqual({ + ...myCase.newCase, + closed_at: null, + closed_by: null, + created_at: '2020-04-09T09:43:51.778Z', + created_by: { email: 'elastic@elastic.co', full_name: 'Elastic', username: 'elastic' }, + external_service: null, + status: 'open', + updated_at: null, + updated_by: null, + }); + }); + + it('transform correctly without optional fields', () => { + const myCase = { + newCase, + createdDate: '2020-04-09T09:43:51.778Z', + }; + + const res = transformNewCase(myCase); + + expect(res).toEqual({ + ...myCase.newCase, + closed_at: null, + closed_by: null, + created_at: '2020-04-09T09:43:51.778Z', + created_by: { email: undefined, full_name: undefined, username: undefined }, + external_service: null, + status: 'open', + updated_at: null, + updated_by: null, + }); + }); + + it('transform correctly with optional fields as null', () => { + const myCase = { + newCase, + createdDate: '2020-04-09T09:43:51.778Z', + email: null, + full_name: null, + username: null, + }; + + const res = transformNewCase(myCase); + + expect(res).toEqual({ + ...myCase.newCase, + closed_at: null, + closed_by: null, + created_at: '2020-04-09T09:43:51.778Z', + created_by: { email: null, full_name: null, username: null }, + external_service: null, + status: 'open', + updated_at: null, + updated_by: null, + }); + }); + }); + + describe('transformNewComment', () => { + it('transforms correctly', () => { + const comment = { + comment: 'A comment', + createdDate: '2020-04-09T09:43:51.778Z', + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }; + + const res = transformNewComment(comment); + expect(res).toEqual({ + comment: 'A comment', + created_at: '2020-04-09T09:43:51.778Z', + created_by: { email: 'elastic@elastic.co', full_name: 'Elastic', username: 'elastic' }, + pushed_at: null, + pushed_by: null, + updated_at: null, + updated_by: null, + }); + }); + + it('transform correctly without optional fields', () => { + const comment = { + comment: 'A comment', + createdDate: '2020-04-09T09:43:51.778Z', + }; + + const res = transformNewComment(comment); + + expect(res).toEqual({ + comment: 'A comment', + created_at: '2020-04-09T09:43:51.778Z', + created_by: { email: undefined, full_name: undefined, username: undefined }, + pushed_at: null, + pushed_by: null, + updated_at: null, + updated_by: null, + }); + }); + + it('transform correctly with optional fields as null', () => { + const comment = { + comment: 'A comment', + createdDate: '2020-04-09T09:43:51.778Z', + email: null, + full_name: null, + username: null, + }; + + const res = transformNewComment(comment); + + expect(res).toEqual({ + comment: 'A comment', + created_at: '2020-04-09T09:43:51.778Z', + created_by: { email: null, full_name: null, username: null }, + pushed_at: null, + pushed_by: null, + updated_at: null, + updated_by: null, + }); + }); + }); + + describe('wrapError', () => { + it('wraps an error', () => { + const error = new Error('Something happened'); + const res = wrapError(error); + + expect(isBoom(res.body as Error)).toBe(true); + }); + + it('it set statusCode to 500', () => { + const error = new Error('Something happened'); + const res = wrapError(error); + + expect(res.statusCode).toBe(500); + }); + + it('it set statusCode to errors status code', () => { + const error = new Error('Something happened') as any; + error.statusCode = 404; + const res = wrapError(error); + + expect(res.statusCode).toBe(404); + }); + + it('it accepts a boom error', () => { + const error = boomify(new Error('Something happened')); + const res = wrapError(error); + + // Utils returns the same boom error as body + expect(res.body).toBe(error); + }); + + it('it accepts a boom error with status code', () => { + const error = boomify(new Error('Something happened'), { statusCode: 404 }); + const res = wrapError(error); + + expect(res.statusCode).toBe(404); + }); + + it('it returns empty headers', () => { + const error = new Error('Something happened'); + const res = wrapError(error); + + expect(res.headers).toEqual({}); + }); + }); + + describe('transformCases', () => { + it('transforms correctly', () => { + const totalCommentsByCase = [ + { caseId: mockCases[0].id, totalComments: 2 }, + { caseId: mockCases[1].id, totalComments: 2 }, + { caseId: mockCases[2].id, totalComments: 2 }, + { caseId: mockCases[3].id, totalComments: 2 }, + ]; + + const res = transformCases( + { saved_objects: mockCases, total: mockCases.length, per_page: 10, page: 1 }, + 2, + 2, + totalCommentsByCase + ); + expect(res).toEqual({ + page: 1, + per_page: 10, + total: mockCases.length, + cases: flattenCaseSavedObjects(mockCases, totalCommentsByCase), + count_open_cases: 2, + count_closed_cases: 2, + }); + }); + }); + + describe('flattenCaseSavedObjects', () => { + it('flattens correctly', () => { + const totalCommentsByCase = [{ caseId: mockCases[0].id, totalComments: 2 }]; + + const res = flattenCaseSavedObjects([mockCases[0]], totalCommentsByCase); + expect(res).toEqual([ + { + id: 'mock-id-1', + closed_at: null, + closed_by: null, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + description: 'This is a brand new case of a bad meanie defacing data', + external_service: null, + title: 'Super Bad Security Issue', + status: 'open', + tags: ['defacement'], + updated_at: '2019-11-25T21:54:48.952Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + comments: [], + totalComment: 2, + version: 'WzAsMV0=', + }, + ]); + }); + + it('it handles total comments correctly', () => { + const totalCommentsByCase = [{ caseId: 'not-exist', totalComments: 2 }]; + + const res = flattenCaseSavedObjects([mockCases[0]], totalCommentsByCase); + + expect(res).toEqual([ + { + id: 'mock-id-1', + closed_at: null, + closed_by: null, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + description: 'This is a brand new case of a bad meanie defacing data', + external_service: null, + title: 'Super Bad Security Issue', + status: 'open', + tags: ['defacement'], + updated_at: '2019-11-25T21:54:48.952Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + comments: [], + totalComment: 0, + version: 'WzAsMV0=', + }, + ]); + }); + }); + + describe('flattenCaseSavedObject', () => { + it('flattens correctly', () => { + const myCase = { ...mockCases[0] }; + const res = flattenCaseSavedObject(myCase, [], 2); + expect(res).toEqual({ + id: myCase.id, + version: myCase.version, + comments: [], + totalComment: 2, + ...myCase.attributes, + }); + }); + + it('flattens correctly without version', () => { + const myCase = { ...mockCases[0] }; + myCase.version = undefined; + const res = flattenCaseSavedObject(myCase, [], 2); + expect(res).toEqual({ + id: myCase.id, + version: '0', + comments: [], + totalComment: 2, + ...myCase.attributes, + }); + }); + + it('flattens correctly with comments', () => { + const myCase = { ...mockCases[0] }; + const comments = [{ ...mockCaseComments[0] }]; + const res = flattenCaseSavedObject(myCase, comments, 2); + expect(res).toEqual({ + id: myCase.id, + version: myCase.version, + comments: flattenCommentSavedObjects(comments), + totalComment: 2, + ...myCase.attributes, + }); + }); + }); + + describe('transformComments', () => { + it('transforms correctly', () => { + const comments = { + saved_objects: mockCaseComments, + total: mockCaseComments.length, + per_page: 10, + page: 1, + }; + + const res = transformComments(comments); + expect(res).toEqual({ + page: 1, + per_page: 10, + total: mockCaseComments.length, + comments: flattenCommentSavedObjects(comments.saved_objects), + }); + }); + }); + + describe('flattenCommentSavedObjects', () => { + it('flattens correctly', () => { + const comments = [{ ...mockCaseComments[0] }, { ...mockCaseComments[1] }]; + const res = flattenCommentSavedObjects(comments); + expect(res).toEqual([ + flattenCommentSavedObject(comments[0]), + flattenCommentSavedObject(comments[1]), + ]); + }); + }); + + describe('flattenCommentSavedObject', () => { + it('flattens correctly', () => { + const comment = { ...mockCaseComments[0] }; + const res = flattenCommentSavedObject(comment); + expect(res).toEqual({ + id: comment.id, + version: comment.version, + ...comment.attributes, + }); + }); + + it('flattens correctly without version', () => { + const comment = { ...mockCaseComments[0] }; + comment.version = undefined; + const res = flattenCommentSavedObject(comment); + expect(res).toEqual({ + id: comment.id, + version: '0', + ...comment.attributes, + }); + }); + }); + + describe('sortToSnake', () => { + it('it transforms status correctly', () => { + expect(sortToSnake('status')).toBe('status'); + }); + + it('it transforms createdAt correctly', () => { + expect(sortToSnake('createdAt')).toBe('created_at'); + }); + + it('it transforms created_at correctly', () => { + expect(sortToSnake('created_at')).toBe('created_at'); + }); + + it('it transforms closedAt correctly', () => { + expect(sortToSnake('closedAt')).toBe('closed_at'); + }); + + it('it transforms closed_at correctly', () => { + expect(sortToSnake('closed_at')).toBe('closed_at'); + }); + + it('it transforms default correctly', () => { + expect(sortToSnake('not-exist')).toBe('created_at'); + }); + }); +}); diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts index 3f783d90e577d..1978d780f54f5 100644 --- a/x-pack/plugins/endpoint/common/generate_data.ts +++ b/x-pack/plugins/endpoint/common/generate_data.ts @@ -6,11 +6,8 @@ import uuid from 'uuid'; import seedrandom from 'seedrandom'; -import { AlertEvent, EndpointEvent, HostMetadata, OSFields, HostFields } from './types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { PolicyData } from '../public/applications/endpoint/types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { generatePolicy } from '../public/applications/endpoint/models/policy'; +import { AlertEvent, EndpointEvent, HostMetadata, OSFields, HostFields, PolicyData } from './types'; +import { factory as policyFactory } from './models/policy_config'; export type Event = AlertEvent | EndpointEvent; @@ -474,7 +471,7 @@ export class EndpointDocGenerator { streams: [], config: { policy: { - value: generatePolicy(), + value: policyFactory(), }, }, }, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts b/x-pack/plugins/endpoint/common/models/policy_config.ts similarity index 89% rename from x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts rename to x-pack/plugins/endpoint/common/models/policy_config.ts index 30f45e54c2005..199b8a91e4307 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts +++ b/x-pack/plugins/endpoint/common/models/policy_config.ts @@ -7,16 +7,19 @@ import { PolicyConfig, ProtectionModes } from '../types'; /** - * Generate a new Policy model. - * NOTE: in the near future, this will likely be removed and an API call to EPM will be used to retrieve - * the latest from the Endpoint package + * Return a new default `PolicyConfig`. */ -export const generatePolicy = (): PolicyConfig => { +export const factory = (): PolicyConfig => { return { windows: { events: { - process: true, + dll_and_driver_load: true, + dns: true, + file: true, network: true, + process: true, + registry: true, + security: true, }, malware: { mode: ProtectionModes.prevent, diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index 403ca9832e191..7143f07d8c702 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -7,12 +7,15 @@ import { SearchResponse } from 'elasticsearch'; import { TypeOf } from '@kbn/config-schema'; import { alertingIndexGetQuerySchema } from './schema/alert_index'; +import { Datasource, NewDatasource } from '../../ingest_manager/common'; /** * A deep readonly type that will make all children of a given object readonly recursively */ export type Immutable = T extends undefined | null | boolean | string | number ? T + : unknown extends T + ? unknown : T extends Array ? ImmutableArray : T extends Map @@ -442,3 +445,125 @@ export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf< * Result of the validated query params when handling alert index requests. */ export type AlertingIndexGetQueryResult = TypeOf; + +/** + * Endpoint Policy configuration + */ +export interface PolicyConfig { + windows: { + events: { + dll_and_driver_load: boolean; + dns: boolean; + file: boolean; + network: boolean; + process: boolean; + registry: boolean; + security: boolean; + }; + malware: MalwareFields; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; + mac: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; + malware: MalwareFields; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; + linux: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; +} + +/** + * Windows-specific policy configuration that is supported via the UI + */ +type WindowsPolicyConfig = Pick; + +/** + * Mac-specific policy configuration that is supported via the UI + */ +type MacPolicyConfig = Pick; + +/** + * Linux-specific policy configuration that is supported via the UI + */ +type LinuxPolicyConfig = Pick; + +/** + * The set of Policy configuration settings that are show/edited via the UI + */ +export interface UIPolicyConfig { + windows: WindowsPolicyConfig; + mac: MacPolicyConfig; + linux: LinuxPolicyConfig; +} + +interface PolicyConfigAdvancedOptions { + elasticsearch: { + indices: { + control: string; + event: string; + logging: string; + }; + kernel: { + connect: boolean; + process: boolean; + }; + }; +} + +/** Policy: Malware protection fields */ +export interface MalwareFields { + mode: ProtectionModes; +} + +/** Policy protection mode options */ +export enum ProtectionModes { + detect = 'detect', + prevent = 'prevent', + preventNotify = 'preventNotify', + off = 'off', +} + +/** + * Endpoint Policy data, which extends Ingest's `Datasource` type + */ +export type PolicyData = Datasource & NewPolicyData; + +/** + * New policy data. Used when updating the policy record via ingest APIs + */ +export type NewPolicyData = NewDatasource & { + inputs: [ + { + type: 'endpoint'; + enabled: boolean; + streams: []; + config: { + policy: { + value: PolicyConfig; + }; + }; + } + ]; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.ts new file mode 100644 index 0000000000000..0cae054432f96 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.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 { all } from 'deepmerge'; +import { Immutable } from '../../../../common/types'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; + +/** + * Model for the `IIndexPattern` interface exported by the `data` plugin. + */ +export function clone(value: IIndexPattern | Immutable): IIndexPattern { + return all([value]) as IIndexPattern; +} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts index bf96942e83a91..3e56b1ff14d65 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UIPolicyConfig } from '../types'; +import { UIPolicyConfig } from '../../../../common/types'; /** * A typed Object.entries() function where the keys and values are typed based on the given object diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts index 79e9de9c67352..feac8944f476b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts @@ -14,16 +14,17 @@ import { coreMock } from 'src/core/public/mocks'; import { DepsStartMock, depsStartMock } from '../../mocks'; import { createBrowserHistory } from 'history'; import { mockAlertResultList } from './mock_alert_result_list'; +import { Immutable } from '../../../../../common/types'; describe('alert details tests', () => { - let store: Store; + let store: Store, Immutable>; let coreStart: ReturnType; let depsStart: DepsStartMock; let history: History; /** * A function that waits until a selector returns true. */ - let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise; + let selectorIsTrue: (selector: (state: Immutable) => boolean) => Promise; beforeEach(() => { coreStart = coreMock.createStart(); depsStart = depsStartMock(); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts index b1cc2d46f614a..84281813312e0 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts @@ -12,20 +12,20 @@ import { alertMiddlewareFactory } from './middleware'; import { AppAction } from '../action'; import { coreMock } from 'src/core/public/mocks'; import { DepsStartMock, depsStartMock } from '../../mocks'; -import { AlertResultList } from '../../../../../common/types'; +import { AlertResultList, Immutable } from '../../../../../common/types'; import { isOnAlertPage } from './selectors'; import { createBrowserHistory } from 'history'; import { mockAlertResultList } from './mock_alert_result_list'; describe('alert list tests', () => { - let store: Store; + let store: Store, Immutable>; let coreStart: ReturnType; let depsStart: DepsStartMock; let history: History; /** * A function that waits until a selector returns true. */ - let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise; + let selectorIsTrue: (selector: (state: Immutable) => boolean) => Promise; beforeEach(() => { coreStart = coreMock.createStart(); depsStart = depsStartMock(); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts index bb5893f14287b..4cc86e9c0449c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts @@ -15,9 +15,10 @@ import { DepsStartMock, depsStartMock } from '../../mocks'; import { createBrowserHistory } from 'history'; import { uiQueryParams } from './selectors'; import { urlFromQueryParams } from '../../view/alerts/url_from_query_params'; +import { Immutable } from '../../../../../common/types'; describe('alert list pagination', () => { - let store: Store; + let store: Store, Immutable>; let coreStart: ReturnType; let depsStart: DepsStartMock; let history: History; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts index 4430a4d39cf4a..52b91dcae7d70 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer } from 'redux'; -import { AlertListState } from '../../types'; +import { AlertListState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; +import { Immutable } from '../../../../../common/types'; -const initialState = (): AlertListState => { +const initialState = (): Immutable => { return { alerts: [], alertDetails: undefined, @@ -22,7 +22,7 @@ const initialState = (): AlertListState => { }; }; -export const alertListReducer: Reducer = ( +export const alertListReducer: ImmutableReducer = ( state = initialState(), action ) => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts index 5e9b08c09c2c7..cc362c3701956 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts @@ -19,23 +19,23 @@ const createStructuredSelector: CreateStructuredSelector = createStructuredSelec /** * Returns the Alert Data array from state */ -export const alertListData = (state: AlertListState) => state.alerts; +export const alertListData = (state: Immutable) => state.alerts; -export const selectedAlertDetailsData = (state: AlertListState) => state.alertDetails; +export const selectedAlertDetailsData = (state: Immutable) => state.alertDetails; /** * Returns the alert list pagination data from state */ export const alertListPagination = createStructuredSelector({ - pageIndex: (state: AlertListState) => state.pageIndex, - pageSize: (state: AlertListState) => state.pageSize, - total: (state: AlertListState) => state.total, + pageIndex: (state: Immutable) => state.pageIndex, + pageSize: (state: Immutable) => state.pageSize, + total: (state: Immutable) => state.total, }); /** * Returns a boolean based on whether or not the user is on the alerts page */ -export const isOnAlertPage = (state: AlertListState): boolean => { +export const isOnAlertPage = (state: Immutable): boolean => { return state.location ? state.location.pathname === '/alerts' : false; }; @@ -44,10 +44,10 @@ export const isOnAlertPage = (state: AlertListState): boolean => { * Used to calculate urls for links and such. */ export const uiQueryParams: ( - state: AlertListState + state: Immutable ) => Immutable = createSelector( - (state: AlertListState) => state.location, - (location: AlertListState['location']) => { + state => state.location, + (location: Immutable['location']) => { const data: AlertingIndexUIQueryParams = {}; if (location) { // Removes the `?` from the beginning of query string if it exists @@ -82,7 +82,7 @@ export const uiQueryParams: ( * Parses the ui query params and returns a object that represents the query used by the SearchBar component. * If the query url param is undefined, a default is returned. */ -export const searchBarQuery: (state: AlertListState) => Query = createSelector( +export const searchBarQuery: (state: Immutable) => Query = createSelector( uiQueryParams, ({ query }) => { if (query !== undefined) { @@ -97,21 +97,20 @@ export const searchBarQuery: (state: AlertListState) => Query = createSelector( * Parses the ui query params and returns a rison encoded string that represents the search bar's date range. * A default is provided if 'date_range' is not present in the url params. */ -export const encodedSearchBarDateRange: (state: AlertListState) => string = createSelector( - uiQueryParams, - ({ date_range: dateRange }) => { - if (dateRange === undefined) { - return encode({ from: 'now-24h', to: 'now' }); - } else { - return dateRange; - } +export const encodedSearchBarDateRange: ( + state: Immutable +) => string = createSelector(uiQueryParams, ({ date_range: dateRange }) => { + if (dateRange === undefined) { + return encode({ from: 'now-24h', to: 'now' }); + } else { + return dateRange; } -); +}); /** * Parses the ui query params and returns a object that represents the dateRange used by the SearchBar component. */ -export const searchBarDateRange: (state: AlertListState) => TimeRange = createSelector( +export const searchBarDateRange: (state: Immutable) => TimeRange = createSelector( encodedSearchBarDateRange, encodedDateRange => { return (decode(encodedDateRange) as unknown) as TimeRange; @@ -122,7 +121,7 @@ export const searchBarDateRange: (state: AlertListState) => TimeRange = createSe * Parses the ui query params and returns an array of filters used by the SearchBar component. * If the 'filters' param is not present, a default is returned. */ -export const searchBarFilters: (state: AlertListState) => Filter[] = createSelector( +export const searchBarFilters: (state: Immutable) => Filter[] = createSelector( uiQueryParams, ({ filters }) => { if (filters !== undefined) { @@ -136,13 +135,14 @@ export const searchBarFilters: (state: AlertListState) => Filter[] = createSelec /** * Returns the indexPatterns used by the SearchBar component */ -export const searchBarIndexPatterns = (state: AlertListState) => state.searchBar.patterns; +export const searchBarIndexPatterns = (state: Immutable) => + state.searchBar.patterns; /** * query params to use when requesting alert data. */ export const apiQueryParams: ( - state: AlertListState + state: Immutable ) => Immutable = createSelector( uiQueryParams, encodedSearchBarDateRange, @@ -161,7 +161,7 @@ export const apiQueryParams: ( * True if the user has selected an alert to see details about. * Populated via the browsers query params. */ -export const hasSelectedAlert: (state: AlertListState) => boolean = createSelector( +export const hasSelectedAlert: (state: Immutable) => boolean = createSelector( uiQueryParams, ({ selected_alert: selectedAlert }) => selectedAlert !== undefined ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts index 4dafa68ddb647..21871ec8ca849 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts @@ -27,14 +27,8 @@ interface UserPaginatedHostList { payload: HostListPagination; } -// Why is FakeActionWithNoPayload here, see: https://github.com/elastic/endpoint-app-team/issues/273 -interface FakeActionWithNoPayload { - type: 'fakeActionWithNoPayLoad'; -} - export type HostAction = | ServerReturnedHostList | ServerReturnedHostDetails | ServerFailedToReturnHostDetails - | UserPaginatedHostList - | FakeActionWithNoPayload; + | UserPaginatedHostList; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts index 8c8578426aa29..8f39baddda00e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { CoreStart, HttpSetup } from 'kibana/public'; -import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; +import { applyMiddleware, createStore, Store } from 'redux'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { History, createBrowserHistory } from 'history'; import { hostListReducer, hostMiddlewareFactory } from './index'; -import { HostResultList } from '../../../../../common/types'; +import { HostResultList, Immutable } from '../../../../../common/types'; import { HostListState } from '../../types'; import { AppAction } from '../action'; import { listData } from './selectors'; @@ -20,9 +20,10 @@ describe('host list middleware', () => { let fakeCoreStart: jest.Mocked; let depsStart: DepsStartMock; let fakeHttpServices: jest.Mocked; - let store: Store; - let getState: typeof store['getState']; - let dispatch: Dispatch; + type HostListStore = Store, Immutable>; + let store: HostListStore; + let getState: HostListStore['getState']; + let dispatch: HostListStore['dispatch']; let history: History; const getEndpointListApiResponse = (): HostResultList => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index ad6741dab7be7..298e819645dbe 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer } from 'redux'; -import { HostListState } from '../../types'; +import { HostListState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; const initialState = (): HostListState => { @@ -21,7 +20,7 @@ const initialState = (): HostListState => { }; }; -export const hostListReducer: Reducer = ( +export const hostListReducer: ImmutableReducer = ( state = initialState(), action ) => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index ebe310cb51190..35bf5d0616878 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -8,30 +8,30 @@ import { createSelector } from 'reselect'; import { Immutable } from '../../../../../common/types'; import { HostListState, HostIndexUIQueryParams } from '../../types'; -export const listData = (state: HostListState) => state.hosts; +export const listData = (state: Immutable) => state.hosts; -export const pageIndex = (state: HostListState) => state.pageIndex; +export const pageIndex = (state: Immutable) => state.pageIndex; -export const pageSize = (state: HostListState) => state.pageSize; +export const pageSize = (state: Immutable) => state.pageSize; -export const totalHits = (state: HostListState) => state.total; +export const totalHits = (state: Immutable) => state.total; -export const isLoading = (state: HostListState) => state.loading; +export const isLoading = (state: Immutable) => state.loading; -export const detailsError = (state: HostListState) => state.detailsError; +export const detailsError = (state: Immutable) => state.detailsError; -export const detailsData = (state: HostListState) => { +export const detailsData = (state: Immutable) => { return state.details; }; -export const isOnHostPage = (state: HostListState) => +export const isOnHostPage = (state: Immutable) => state.location ? state.location.pathname === '/hosts' : false; export const uiQueryParams: ( - state: HostListState + state: Immutable ) => Immutable = createSelector( - (state: HostListState) => state.location, - (location: HostListState['location']) => { + (state: Immutable) => state.location, + (location: Immutable['location']) => { const data: HostIndexUIQueryParams = {}; if (location) { // Removes the `?` from the beginning of query string if it exists @@ -52,7 +52,7 @@ export const uiQueryParams: ( } ); -export const hasSelectedHost: (state: HostListState) => boolean = createSelector( +export const hasSelectedHost: (state: Immutable) => boolean = createSelector( uiQueryParams, ({ selected_host: selectedHost }) => { return selectedHost !== undefined; diff --git a/x-pack/plugins/infra/public/store/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts similarity index 51% rename from x-pack/plugins/infra/public/store/reducer.ts rename to x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts index 2536ddbee401b..6895f0106fb5d 100644 --- a/x-pack/plugins/infra/public/store/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts @@ -5,17 +5,9 @@ */ import { combineReducers } from 'redux'; +import { ImmutableCombineReducers } from '../types'; -import { initialLocalState, localReducer, LocalState } from './local'; - -export interface State { - local: LocalState; -} - -export const initialState: State = { - local: initialLocalState, -}; - -export const reducer = combineReducers({ - local: localReducer, -}); +/** + * Works the same as `combineReducers` from `redux`, but uses the `ImmutableCombineReducers` type. + */ +export const immutableCombineReducers: ImmutableCombineReducers = combineReducers; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts index 9905145048a8a..4de3dac02a8ec 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyData, PolicyDetailsState, ServerApiError, UIPolicyConfig } from '../../types'; +import { PolicyDetailsState, ServerApiError } from '../../types'; import { GetAgentStatusResponse } from '../../../../../../ingest_manager/common/types/rest_spec'; +import { PolicyData, UIPolicyConfig } from '../../../../../common/types'; interface ServerReturnedPolicyDetailsData { type: 'serverReturnedPolicyDetailsData'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts index e09a62b235e35..a24687ebbcbbc 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts @@ -9,7 +9,7 @@ import { createStore, Dispatch, Store } from 'redux'; import { policyDetailsReducer, PolicyDetailsAction } from './index'; import { policyConfig } from './selectors'; import { clone } from '../../models/policy_details_config'; -import { generatePolicy } from '../../models/policy'; +import { factory as policyConfigFactory } from '../../../../../common/models/policy_config'; describe('policy details: ', () => { let store: Store; @@ -38,7 +38,7 @@ describe('policy details: ', () => { streams: [], config: { policy: { - value: generatePolicy(), + value: policyConfigFactory(), }, }, }, @@ -98,4 +98,26 @@ describe('policy details: ', () => { expect(config!.mac.events.file).toEqual(true); }); }); + + describe('when the user has enabled linux process events', () => { + beforeEach(() => { + const config = policyConfig(getState()); + if (!config) { + throw new Error(); + } + + const newPayload1 = clone(config); + newPayload1.linux.events.file = true; + + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload1 }, + }); + }); + + it('linux file events is enabled', () => { + const config = policyConfig(getState()); + expect(config!.linux.events.file).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts index a00ce255cbac4..7a3fbe7f23b9b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts @@ -4,19 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - MiddlewareFactory, - PolicyData, - PolicyDetailsState, - UpdateDatasourceResponse, -} from '../../types'; +import { MiddlewareFactory, PolicyDetailsState, UpdatePolicyResponse } from '../../types'; import { policyIdFromParams, isOnPolicyDetailsPage, policyDetails } from './selectors'; -import { generatePolicy } from '../../models/policy'; import { sendGetDatasource, sendGetFleetAgentStatusForConfig, sendPutDatasource, } from '../policy_list/services/ingest'; +import { PolicyData } from '../../../../../common/types'; +import { factory as policyConfigFactory } from '../../../../../common/models/policy_config'; export const policyDetailsMiddlewareFactory: MiddlewareFactory = coreStart => { const http = coreStart.http; @@ -49,7 +45,7 @@ export const policyDetailsMiddlewareFactory: MiddlewareFactory { return { @@ -23,7 +23,7 @@ const initialPolicyDetailsState = (): PolicyDetailsState => { }; }; -export const policyDetailsReducer: Reducer = ( +export const policyDetailsReducer: ImmutableReducer = ( state = initialPolicyDetailsState(), action ) => { @@ -70,7 +70,7 @@ export const policyDetailsReducer: Reducer = ( } if (action.type === 'userChangedUrl') { - const newState = { + const newState: Immutable = { ...state, location: action.payload, }; @@ -79,8 +79,10 @@ export const policyDetailsReducer: Reducer = ( // Did user just enter the Detail page? if so, then set the loading indicator and return new state if (isCurrentlyOnDetailsPage && !wasPreviouslyOnDetailsPage) { - newState.isLoading = true; - return newState; + return { + ...newState, + isLoading: true, + }; } return { ...initialPolicyDetailsState(), @@ -93,10 +95,19 @@ export const policyDetailsReducer: Reducer = ( return state; } const newState = { ...state, policyItem: { ...state.policyItem } }; - const newPolicy: any = { ...fullPolicy(state) }; + const newPolicy: PolicyConfig = { ...fullPolicy(state) }; + + /** + * This is directly changing redux state because `policyItem.inputs` was copied over and not cloned. + */ + // @ts-ignore newState.policyItem.inputs[0].config.policy.value = newPolicy; Object.entries(action.payload.policyConfig).forEach(([section, newSettings]) => { + /** + * this is not safe because `action.payload.policyConfig` may have excess keys + */ + // @ts-ignore newPolicy[section as keyof UIPolicyConfig] = { ...newPolicy[section as keyof UIPolicyConfig], ...newSettings, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts index 4b4dc9d9bee43..98e129132e1cb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts @@ -5,14 +5,15 @@ */ import { createSelector } from 'reselect'; -import { PolicyConfig, PolicyDetailsState, UIPolicyConfig } from '../../types'; -import { generatePolicy } from '../../models/policy'; +import { PolicyDetailsState } from '../../types'; +import { Immutable, PolicyConfig, UIPolicyConfig } from '../../../../../common/types'; +import { factory as policyConfigFactory } from '../../../../../common/models/policy_config'; /** Returns the policy details */ -export const policyDetails = (state: PolicyDetailsState) => state.policyItem; +export const policyDetails = (state: Immutable) => state.policyItem; /** Returns a boolean of whether the user is on the policy details page or not */ -export const isOnPolicyDetailsPage = (state: PolicyDetailsState) => { +export const isOnPolicyDetailsPage = (state: Immutable) => { if (state.location) { const pathnameParts = state.location.pathname.split('/'); return pathnameParts[1] === 'policy' && pathnameParts[2]; @@ -32,14 +33,16 @@ export const policyIdFromParams: (state: PolicyDetailsState) => string = createS } ); +const defaultFullPolicy: Immutable = policyConfigFactory(); + /** * Returns the full Endpoint Policy, which will include private settings not shown on the UI. * Note: this will return a default full policy if the `policyItem` is `undefined` */ -export const fullPolicy: (s: PolicyDetailsState) => PolicyConfig = createSelector( +export const fullPolicy: (s: Immutable) => PolicyConfig = createSelector( policyDetails, policyData => { - return policyData?.inputs[0]?.config?.policy?.value ?? generatePolicy(); + return policyData?.inputs[0]?.config?.policy?.value ?? defaultFullPolicy; } ); @@ -119,6 +122,26 @@ export const selectedMacEvents = (state: PolicyDetailsState): number => { return 0; }; +/** Returns the total number of possible linux eventing configurations */ +export const totalLinuxEvents = (state: PolicyDetailsState): number => { + const config = policyConfig(state); + if (config) { + return Object.keys(config.linux.events).length; + } + return 0; +}; + +/** Returns the number of selected liinux eventing configurations */ +export const selectedLinuxEvents = (state: PolicyDetailsState): number => { + const config = policyConfig(state); + if (config) { + return Object.values(config.linux.events).reduce((count, event) => { + return event === true ? count + 1 : count; + }, 0); + } + return 0; +}; + /** is there an api call in flight */ export const isLoading = (state: PolicyDetailsState) => state.isLoading; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts index 3db224f049c05..4c379b7426461 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyData, ServerApiError } from '../../types'; +import { ServerApiError } from '../../types'; +import { PolicyData } from '../../../../../common/types'; interface ServerReturnedPolicyListData { type: 'serverReturnedPolicyListData'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts index 4d153b5e03cd2..97a2b65fb65f8 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts @@ -5,7 +5,7 @@ */ import { EndpointAppLocation, PolicyListState } from '../../types'; -import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; +import { applyMiddleware, createStore, Store } from 'redux'; import { AppAction } from '../action'; import { policyListReducer } from './reducer'; import { policyListMiddlewareFactory } from './middleware'; @@ -18,13 +18,15 @@ import { setPolicyListApiMockImplementation, } from './test_mock_utils'; import { INGEST_API_DATASOURCES } from './services/ingest'; +import { Immutable } from '../../../../../common/types'; describe('policy list store concerns', () => { let fakeCoreStart: ReturnType; let depsStart: DepsStartMock; - let store: Store; - let getState: typeof store['getState']; - let dispatch: Dispatch; + type PolicyListStore = Store, Immutable>; + let store: PolicyListStore; + let getState: PolicyListStore['getState']; + let dispatch: PolicyListStore['dispatch']; let waitForAction: MiddlewareActionSpyHelper['waitForAction']; beforeEach(() => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/middleware.ts index c073d26a676f0..cc771a4619e78 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/middleware.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MiddlewareFactory, PolicyListState, GetDatasourcesResponse } from '../../types'; +import { GetPolicyListResponse, MiddlewareFactory, PolicyListState } from '../../types'; import { sendGetEndpointSpecificDatasources } from './services/ingest'; import { isOnPolicyListPage, urlSearchParams } from './selectors'; @@ -18,7 +18,7 @@ export const policyListMiddlewareFactory: MiddlewareFactory = c if (action.type === 'userChangedUrl' && isOnPolicyListPage(state)) { const { page_index: pageIndex, page_size: pageSize } = urlSearchParams(state); - let response: GetDatasourcesResponse; + let response: GetPolicyListResponse; try { response = await sendGetEndpointSpecificDatasources(http, { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts index 30c1deac7f5e1..ccd3f84dd060c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer } from 'redux'; -import { PolicyListState } from '../../types'; +import { PolicyListState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; import { isOnPolicyListPage } from './selectors'; +import { Immutable } from '../../../../../common/types'; const initialPolicyListState = (): PolicyListState => { return { @@ -21,7 +21,7 @@ const initialPolicyListState = (): PolicyListState => { }; }; -export const policyListReducer: Reducer = ( +export const policyListReducer: ImmutableReducer = ( state = initialPolicyListState(), action ) => { @@ -42,7 +42,7 @@ export const policyListReducer: Reducer = ( } if (action.type === 'userChangedUrl') { - const newState = { + const newState: Immutable = { ...state, location: action.payload, }; @@ -53,14 +53,15 @@ export const policyListReducer: Reducer = ( // Also adjust some state if user is just entering the policy list view if (isCurrentlyOnListPage) { if (!wasPreviouslyOnListPage) { - newState.apiError = undefined; - newState.isLoading = true; + return { + ...newState, + apiError: undefined, + isLoading: true, + }; } return newState; } - return { - ...initialPolicyListState(), - }; + return initialPolicyListState(); } return state; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts index ce13d89b2b8c2..6d2e952fa07bb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts @@ -7,32 +7,33 @@ import { createSelector } from 'reselect'; import { parse } from 'query-string'; import { PolicyListState, PolicyListUrlSearchParams } from '../../types'; +import { Immutable } from '../../../../../common/types'; const PAGE_SIZES = Object.freeze([10, 20, 50]); -export const selectPolicyItems = (state: PolicyListState) => state.policyItems; +export const selectPolicyItems = (state: Immutable) => state.policyItems; -export const selectPageIndex = (state: PolicyListState) => state.pageIndex; +export const selectPageIndex = (state: Immutable) => state.pageIndex; -export const selectPageSize = (state: PolicyListState) => state.pageSize; +export const selectPageSize = (state: Immutable) => state.pageSize; -export const selectTotal = (state: PolicyListState) => state.total; +export const selectTotal = (state: Immutable) => state.total; -export const selectIsLoading = (state: PolicyListState) => state.isLoading; +export const selectIsLoading = (state: Immutable) => state.isLoading; -export const selectApiError = (state: PolicyListState) => state.apiError; +export const selectApiError = (state: Immutable) => state.apiError; -export const isOnPolicyListPage = (state: PolicyListState) => { +export const isOnPolicyListPage = (state: Immutable) => { return state.location?.pathname === '/policy'; }; -const routeLocation = (state: PolicyListState) => state.location; +const routeLocation = (state: Immutable) => state.location; /** * Returns the supported URL search params, populated with defaults if none where present in the URL */ export const urlSearchParams: ( - state: PolicyListState + state: Immutable ) => PolicyListUrlSearchParams = createSelector(routeLocation, location => { const searchParams = { page_index: 0, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts index bfbb5f94e8950..4356517e43c2c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts @@ -9,12 +9,8 @@ import { GetDatasourcesRequest, GetAgentStatusResponse, } from '../../../../../../../ingest_manager/common'; -import { - NewPolicyData, - GetDatasourcesResponse, - GetDatasourceResponse, - UpdateDatasourceResponse, -} from '../../../types'; +import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../../types'; +import { NewPolicyData } from '../../../../../../common/types'; const INGEST_API_ROOT = `/api/ingest_manager`; export const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`; @@ -30,8 +26,8 @@ const INGEST_API_FLEET_AGENT_STATUS = `${INGEST_API_FLEET}/agent-status`; export const sendGetEndpointSpecificDatasources = ( http: HttpStart, options: HttpFetchOptions & Partial = {} -): Promise => { - return http.get(INGEST_API_DATASOURCES, { +): Promise => { + return http.get(INGEST_API_DATASOURCES, { ...options, query: { ...options.query, @@ -53,7 +49,7 @@ export const sendGetDatasource = ( datasourceId: string, options?: HttpFetchOptions ) => { - return http.get(`${INGEST_API_DATASOURCES}/${datasourceId}`, options); + return http.get(`${INGEST_API_DATASOURCES}/${datasourceId}`, options); }; /** @@ -69,7 +65,7 @@ export const sendPutDatasource = ( datasourceId: string, datasource: NewPolicyData, options: Exclude = {} -): Promise => { +): Promise => { return http.put(`${INGEST_API_DATASOURCES}/${datasourceId}`, { ...options, body: JSON.stringify(datasource), diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/test_mock_utils.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/test_mock_utils.ts index 0d41ae0d76da4..20d5a637182d2 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/test_mock_utils.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/test_mock_utils.ts @@ -8,7 +8,7 @@ import { HttpStart } from 'kibana/public'; import { Dispatch } from 'redux'; import { INGEST_API_DATASOURCES } from './services/ingest'; import { EndpointDocGenerator } from '../../../../../common/generate_data'; -import { AppAction, GetDatasourcesResponse, GlobalState, MiddlewareFactory } from '../../types'; +import { AppAction, GetPolicyListResponse, GlobalState, MiddlewareFactory } from '../../types'; const generator = new EndpointDocGenerator('policy-list'); @@ -19,13 +19,13 @@ const generator = new EndpointDocGenerator('policy-list'); */ export const setPolicyListApiMockImplementation = ( mockedHttpService: jest.Mocked, - responseItems: GetDatasourcesResponse['items'] = [generator.generatePolicyDatasource()] + responseItems: GetPolicyListResponse['items'] = [generator.generatePolicyDatasource()] ): void => { mockedHttpService.get.mockImplementation((...args) => { const [path] = args; if (typeof path === 'string') { if (path === INGEST_API_DATASOURCES) { - return Promise.resolve({ + return Promise.resolve({ items: responseItems, total: 10, page: 1, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts index c8b2d08676724..2f77c380d9387 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts @@ -3,15 +3,16 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { combineReducers, Reducer } from 'redux'; + import { hostListReducer } from './hosts'; import { AppAction } from './action'; import { alertListReducer } from './alerts'; -import { GlobalState } from '../types'; +import { GlobalState, ImmutableReducer } from '../types'; import { policyListReducer } from './policy_list'; import { policyDetailsReducer } from './policy_details'; +import { immutableCombineReducers } from './immutable_combine_reducers'; -export const appReducer: Reducer = combineReducers({ +export const appReducer: ImmutableReducer = immutableCombineReducers({ hostList: hostListReducer, alertList: alertListReducer, policyList: policyListReducer, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index a625c49bf7d5a..7aca94d3e9c7c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Dispatch, MiddlewareAPI } from 'redux'; +import { Dispatch, MiddlewareAPI, Action as ReduxAction, AnyAction as ReduxAnyAction } from 'redux'; import { IIndexPattern } from 'src/plugins/data/public'; import { HostMetadata, @@ -13,15 +13,19 @@ import { Immutable, ImmutableArray, AlertDetails, + MalwareFields, + UIPolicyConfig, + PolicyData, } from '../../../common/types'; import { EndpointPluginStartDependencies } from '../../plugin'; import { AppAction } from './store/action'; import { CoreStart } from '../../../../../../src/core/public'; -import { Datasource, NewDatasource } from '../../../../ingest_manager/common/types/models'; import { GetAgentStatusResponse, - CreateDatasourceResponse, -} from '../../../../ingest_manager/common/types/rest_spec'; + GetDatasourcesResponse, + GetOneDatasourceResponse, + UpdateDatasourceResponse, +} from '../../../../ingest_manager/common'; export { AppAction }; export type MiddlewareFactory = ( @@ -56,29 +60,6 @@ export interface ServerApiError { message: string; } -/** - * New policy data. Used when updating the policy record via ingest APIs - */ -export type NewPolicyData = NewDatasource & { - inputs: [ - { - type: 'endpoint'; - enabled: boolean; - streams: []; - config: { - policy: { - value: PolicyConfig; - }; - }; - } - ]; -}; - -/** - * Endpoint Policy data, which extends Ingest's `Datasource` type - */ -export type PolicyData = Datasource & NewPolicyData; - /** * Policy list store state */ @@ -131,21 +112,42 @@ export interface PolicyListUrlSearchParams { * Endpoint Policy configuration */ export interface PolicyConfig { - windows: UIPolicyConfig['windows'] & { + windows: { + events: { + dll_and_driver_load: boolean; + dns: boolean; + file: boolean; + network: boolean; + process: boolean; + registry: boolean; + security: boolean; + }; + malware: MalwareFields; logging: { stdout: string; file: string; }; advanced: PolicyConfigAdvancedOptions; }; - mac: UIPolicyConfig['mac'] & { + mac: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; + malware: MalwareFields; logging: { stdout: string; file: string; }; advanced: PolicyConfigAdvancedOptions; }; - linux: UIPolicyConfig['linux'] & { + linux: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; logging: { stdout: string; file: string; @@ -168,40 +170,6 @@ interface PolicyConfigAdvancedOptions { }; } -/** - * The set of Policy configuration settings that are show/edited via the UI - */ -/* eslint-disable @typescript-eslint/consistent-type-definitions */ -export type UIPolicyConfig = { - windows: { - events: { - process: boolean; - network: boolean; - }; - /** malware mode can be off, detect, prevent or prevent and notify user */ - malware: MalwareFields; - }; - mac: { - events: { - file: boolean; - process: boolean; - network: boolean; - }; - malware: MalwareFields; - }; - - /** - * Linux-specific policy configuration that is supported via the UI - */ - linux: { - events: { - file: boolean; - process: boolean; - network: boolean; - }; - }; -}; - /** OS used in Policy */ export enum OS { windows = 'windows', @@ -209,13 +177,6 @@ export enum OS { linux = 'linux', } -/** Used in Policy */ -export enum EventingFields { - process = 'process', - network = 'network', - file = 'file', -} - /** * Returns the keys of an object whose values meet a criteria. * Ex) interface largeNestedObject = { @@ -239,20 +200,7 @@ export type KeysByValueCriteria = { }[keyof O]; /** Returns an array of the policy OSes that have a malware protection field */ - export type MalwareProtectionOSes = KeysByValueCriteria; -/** Policy: Malware protection fields */ -export interface MalwareFields { - mode: ProtectionModes; -} - -/** Policy protection mode options */ -export enum ProtectionModes { - detect = 'detect', - prevent = 'prevent', - preventNotify = 'preventNotify', - off = 'off', -} export interface GlobalState { readonly hostList: HostListState; @@ -331,24 +279,39 @@ export interface AlertingIndexUIQueryParams { filters?: string; } -export interface GetDatasourcesResponse { +export interface GetPolicyListResponse extends GetDatasourcesResponse { items: PolicyData[]; - total: number; - page: number; - perPage: number; - success: boolean; } -export interface GetDatasourceResponse { +export interface GetPolicyResponse extends GetOneDatasourceResponse { item: PolicyData; - success: boolean; } -export type UpdateDatasourceResponse = CreateDatasourceResponse & { +export interface UpdatePolicyResponse extends UpdateDatasourceResponse { item: PolicyData; -}; +} + +/** + * Like `Reducer` from `redux` but it accepts immutable versions of `state` and `action`. + * Use this type for all Reducers in order to help enforce our pattern of immutable state. + */ +export type ImmutableReducer = ( + state: Immutable | undefined, + action: Immutable +) => State | Immutable; /** - * The PageId type is used for the payload when firing userNavigatedToPage actions + * A alternate interface for `redux`'s `combineReducers`. Will work with the same underlying implementation, + * but will enforce that `Immutable` versions of `state` and `action` are received. */ -export type PageId = 'alertsPage' | 'managementPage' | 'policyListPage'; +export type ImmutableCombineReducers = ( + reducers: ImmutableReducersMapObject +) => ImmutableReducer; + +/** + * Like `redux`'s `ReducersMapObject` (which is used by `combineReducers`) but enforces that + * the `state` and `action` received are `Immutable` versions. + */ +type ImmutableReducersMapObject = { + [K in keyof S]: ImmutableReducer; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx index 5b872962a5dc0..1ede06c086517 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { memo, useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { encode, RisonValue } from 'rison-node'; @@ -14,11 +14,18 @@ import { urlFromQueryParams } from './url_from_query_params'; import { useAlertListSelector } from './hooks/use_alerts_selector'; import * as selectors from '../../store/alerts/selectors'; import { EndpointPluginServices } from '../../../../plugin'; +import { clone } from '../../models/index_pattern'; export const AlertIndexSearchBar = memo(() => { const history = useHistory(); const queryParams = useAlertListSelector(selectors.uiQueryParams); const searchBarIndexPatterns = useAlertListSelector(selectors.searchBarIndexPatterns); + + // Deeply clone the search bar index patterns as the receiving component may mutate them + const clonedSearchBarIndexPatterns = useMemo( + () => searchBarIndexPatterns.map(pattern => clone(pattern)), + [searchBarIndexPatterns] + ); const searchBarQuery = useAlertListSelector(selectors.searchBarQuery); const searchBarDateRange = useAlertListSelector(selectors.searchBarDateRange); const searchBarFilters = useAlertListSelector(selectors.searchBarFilters); @@ -68,7 +75,7 @@ export const AlertIndexSearchBar = memo(() => { dataTestSubj="alertsSearchBar" appName="endpoint" isLoading={false} - indexPatterns={searchBarIndexPatterns} + indexPatterns={clonedSearchBarIndexPatterns} query={searchBarQuery} dateRangeFrom={searchBarDateRange.from} dateRangeTo={searchBarDateRange.to} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 94625b8c66191..1d81d6e8a16db 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -23,12 +23,14 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { createStructuredSelector } from 'reselect'; +import { EuiBasicTableColumn } from '@elastic/eui'; import { HostDetailsFlyout } from './details'; import * as selectors from '../../store/hosts/selectors'; import { HostAction } from '../../store/hosts/action'; import { useHostListSelector } from './hooks'; import { CreateStructuredSelector } from '../../types'; import { urlFromQueryParams } from './url_from_query_params'; +import { HostMetadata, Immutable } from '../../../../../common/types'; const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const HostList = () => { @@ -65,7 +67,7 @@ export const HostList = () => { [dispatch] ); - const columns = useMemo(() => { + const columns: Array>> = useMemo(() => { return [ { field: '', @@ -174,7 +176,7 @@ export const HostList = () => { [...listData], [listData])} columns={columns} loading={isLoading} pagination={paginationSetup} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx index 267077da6598c..076de7b57b44b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx @@ -34,7 +34,7 @@ import { AppAction } from '../../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { AgentsSummary } from './agents_summary'; import { VerticalDivider } from './vertical_divider'; -import { WindowsEvents, MacEvents } from './policy_forms/events'; +import { WindowsEvents, MacEvents, LinuxEvents } from './policy_forms/events'; import { MalwareProtections } from './policy_forms/protections/malware'; export const PolicyDetails = React.memo(() => { @@ -208,6 +208,8 @@ export const PolicyDetails = React.memo(() => { + + ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx index 8b6c32c3277ed..341086c7cf75c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiCard, EuiFlexGroup, @@ -25,14 +25,27 @@ const PolicyDetailCard = styled.div` } `; export const ConfigForm: React.FC<{ + /** + * A subtitle for this component. + **/ type: string; - supportedOss: string[]; + /** + * Types of supported operating systems. + */ + supportedOss: React.ReactNode; children: React.ReactNode; - id: string; - /** Takes a react component to be put on the right corner of the card */ + /** + * A description for the component. + */ + description: string; + /** + * The `data-test-subj` attribute to append to a certain child element. + */ + dataTestSubj: string; + /** React Node to be put on the right corner of the card */ rightCorner: React.ReactNode; -}> = React.memo(({ type, supportedOss, children, id, rightCorner }) => { - const typeTitle = () => { +}> = React.memo(({ type, supportedOss, children, dataTestSubj, rightCorner, description }) => { + const typeTitle = useMemo(() => { return ( @@ -59,28 +72,25 @@ export const ConfigForm: React.FC<{ - {supportedOss.join(', ')} + {supportedOss} {rightCorner} ); - }; + }, [rightCorner, supportedOss, type]); return ( - - {children} - - } - /> + title={typeTitle} + > + + {children} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx index bec6b33b85c7f..74322ac8b993b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx @@ -11,7 +11,7 @@ import { htmlIdGenerator } from '@elastic/eui'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { policyConfig } from '../../../../store/policy_details/selectors'; import { PolicyDetailsAction } from '../../../../store/policy_details'; -import { UIPolicyConfig } from '../../../../types'; +import { UIPolicyConfig } from '../../../../../../../common/types'; export const EventsCheckbox = React.memo(function({ name, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/index.tsx index 44716d8183041..927456fb671d8 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/index.tsx @@ -6,3 +6,4 @@ export { WindowsEvents } from './windows'; export { MacEvents } from './mac'; +export { LinuxEvents } from './linux'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx new file mode 100644 index 0000000000000..c3d6bdba7c852 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx @@ -0,0 +1,107 @@ +/* + * 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, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; +import { EventsCheckbox } from './checkbox'; +import { OS } from '../../../../types'; +import { usePolicyDetailsSelector } from '../../policy_hooks'; +import { selectedLinuxEvents, totalLinuxEvents } from '../../../../store/policy_details/selectors'; +import { ConfigForm } from '../config_form'; +import { getIn, setIn } from '../../../../models/policy_details_config'; +import { UIPolicyConfig } from '../../../../../../../common/types'; + +export const LinuxEvents = React.memo(() => { + const selected = usePolicyDetailsSelector(selectedLinuxEvents); + const total = usePolicyDetailsSelector(totalLinuxEvents); + + const checkboxes = useMemo(() => { + const items: Array<{ + name: string; + os: 'linux'; + protectionField: keyof UIPolicyConfig['linux']['events']; + }> = [ + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.linux.events.file', { + defaultMessage: 'File', + }), + os: OS.linux, + protectionField: 'file', + }, + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.linux.events.process', { + defaultMessage: 'Process', + }), + os: OS.linux, + protectionField: 'process', + }, + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.linux.events.network', { + defaultMessage: 'Network', + }), + os: OS.linux, + protectionField: 'network', + }, + ]; + return ( + <> + +
+ +
+
+ + {items.map((item, index) => { + return ( + + setIn(config)(item.os)('events')(item.protectionField)(checked) + } + getter={config => getIn(config)(item.os)('events')(item.protectionField)} + /> + ); + })} + + ); + }, []); + + const collectionsEnabled = useMemo(() => { + return ( + + + + ); + }, [selected, total]); + + return ( + + {checkboxes} + + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx index 3b69c21d2b150..40b80b9af0f65 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx @@ -9,22 +9,23 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; import { EventsCheckbox } from './checkbox'; -import { OS, UIPolicyConfig } from '../../../../types'; +import { OS } from '../../../../types'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { selectedMacEvents, totalMacEvents } from '../../../../store/policy_details/selectors'; import { ConfigForm } from '../config_form'; import { getIn, setIn } from '../../../../models/policy_details_config'; +import { UIPolicyConfig } from '../../../../../../../common/types'; export const MacEvents = React.memo(() => { const selected = usePolicyDetailsSelector(selectedMacEvents); const total = usePolicyDetailsSelector(totalMacEvents); - const checkboxes: Array<{ - name: string; - os: 'mac'; - protectionField: keyof UIPolicyConfig['mac']['events']; - }> = useMemo( - () => [ + const checkboxes = useMemo(() => { + const items: Array<{ + name: string; + os: 'mac'; + protectionField: keyof UIPolicyConfig['mac']['events']; + }> = [ { name: i18n.translate('xpack.endpoint.policyDetailsConfig.mac.events.file', { defaultMessage: 'File', @@ -46,11 +47,7 @@ export const MacEvents = React.memo(() => { os: OS.mac, protectionField: 'network', }, - ], - [] - ); - - const renderCheckboxes = () => { + ]; return ( <> @@ -62,7 +59,7 @@ export const MacEvents = React.memo(() => { - {checkboxes.map((item, index) => { + {items.map((item, index) => { return ( { })} ); - }; + }, []); - const collectionsEnabled = () => { + const collectionsEnabled = useMemo(() => { return ( { /> ); - }; + }, [selected, total]); return ( + description={i18n.translate('xpack.endpoint.policy.details.eventCollectionLabel', { + defaultMessage: 'Event Collection', + })} + supportedOss={i18n.translate('xpack.endpoint.policy.details.mac', { defaultMessage: 'Mac' })} + dataTestSubj="macEventingForm" + rightCorner={collectionsEnabled} + > + {checkboxes} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx index 63a140912437d..7f946de9614ca 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; import { EventsCheckbox } from './checkbox'; -import { OS, UIPolicyConfig } from '../../../../types'; +import { OS } from '../../../../types'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { selectedWindowsEvents, @@ -17,23 +17,38 @@ import { } from '../../../../store/policy_details/selectors'; import { ConfigForm } from '../config_form'; import { setIn, getIn } from '../../../../models/policy_details_config'; +import { UIPolicyConfig, ImmutableArray } from '../../../../../../../common/types'; export const WindowsEvents = React.memo(() => { const selected = usePolicyDetailsSelector(selectedWindowsEvents); const total = usePolicyDetailsSelector(totalWindowsEvents); - const checkboxes: Array<{ - name: string; - os: 'windows'; - protectionField: keyof UIPolicyConfig['windows']['events']; - }> = useMemo( - () => [ + const checkboxes = useMemo(() => { + const items: ImmutableArray<{ + name: string; + os: 'windows'; + protectionField: keyof UIPolicyConfig['windows']['events']; + }> = [ { - name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.process', { - defaultMessage: 'Process', + name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.dllDriverLoad', { + defaultMessage: 'DLL and Driver Load', }), os: OS.windows, - protectionField: 'process', + protectionField: 'dll_and_driver_load', + }, + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.dns', { + defaultMessage: 'DNS', + }), + os: OS.windows, + protectionField: 'dns', + }, + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.file', { + defaultMessage: 'File', + }), + os: OS.windows, + protectionField: 'file', }, { name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.network', { @@ -42,11 +57,28 @@ export const WindowsEvents = React.memo(() => { os: OS.windows, protectionField: 'network', }, - ], - [] - ); - - const renderCheckboxes = useMemo(() => { + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.process', { + defaultMessage: 'Process', + }), + os: OS.windows, + protectionField: 'process', + }, + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.registry', { + defaultMessage: 'Registry', + }), + os: OS.windows, + protectionField: 'registry', + }, + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.security', { + defaultMessage: 'Security', + }), + os: OS.windows, + protectionField: 'security', + }, + ]; return ( <> @@ -58,7 +90,7 @@ export const WindowsEvents = React.memo(() => { - {checkboxes.map((item, index) => { + {items.map((item, index) => { return ( { })} ); - }, [checkboxes]); + }, []); const collectionsEnabled = useMemo(() => { return ( @@ -91,15 +123,16 @@ export const WindowsEvents = React.memo(() => { type={i18n.translate('xpack.endpoint.policy.details.eventCollection', { defaultMessage: 'Event Collection', })} - supportedOss={useMemo( - () => [ - i18n.translate('xpack.endpoint.policy.details.windows', { defaultMessage: 'Windows' }), - ], - [] - )} - id="windowsEventingForm" + description={i18n.translate('xpack.endpoint.policy.details.windowsLabel', { + defaultMessage: 'Windows', + })} + supportedOss={i18n.translate('xpack.endpoint.policy.details.windows', { + defaultMessage: 'Windows', + })} + dataTestSubj="windowsEventingForm" rightCorner={collectionsEnabled} - children={renderCheckboxes} - /> + > + {checkboxes} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx index 66b22178607b9..14871c71ec038 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx @@ -11,8 +11,8 @@ import { EuiRadio, EuiSwitch, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { htmlIdGenerator } from '@elastic/eui'; -import { Immutable } from '../../../../../../../common/types'; -import { OS, ProtectionModes, MalwareProtectionOSes } from '../../../../types'; +import { Immutable, ProtectionModes, ImmutableArray } from '../../../../../../../common/types'; +import { OS, MalwareProtectionOSes } from '../../../../types'; import { ConfigForm } from '../config_form'; import { policyConfig } from '../../../../store/policy_details/selectors'; import { usePolicyDetailsSelector } from '../../policy_hooks'; @@ -73,7 +73,7 @@ export const MalwareProtections = React.memo(() => { // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; - const radios: Array<{ + const radios: ImmutableArray<{ id: ProtectionModes; label: string; protection: 'malware'; @@ -123,7 +123,7 @@ export const MalwareProtections = React.memo(() => { [dispatch, policyDetailsConfig] ); - const RadioButtons = () => { + const radioButtons = useMemo(() => { return ( <> @@ -148,9 +148,9 @@ export const MalwareProtections = React.memo(() => { ); - }; + }, [radios]); - const ProtectionSwitch = () => { + const protectionSwitch = useMemo(() => { return ( { onChange={handleSwitchChange} /> ); - }; + }, [handleSwitchChange, selected]); return ( + supportedOss={i18n.translate('xpack.endpoint.policy.details.windowsAndMac', { + defaultMessage: 'Windows, Mac', + })} + dataTestSubj="malwareProtectionsForm" + description={i18n.translate('xpack.endpoint.policy.details.malwareLabel', { + defaultMessage: 'Malware', + })} + rightCorner={protectionSwitch} + > + {radioButtons} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx index 295312fff01dd..062c7afb6706d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx @@ -20,10 +20,10 @@ import { } from '../../store/policy_list/selectors'; import { usePolicyListSelector } from './policy_hooks'; import { PolicyListAction } from '../../store/policy_list'; -import { PolicyData } from '../../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { PageView } from '../components/page_view'; import { LinkToApp } from '../components/link_to_app'; +import { Immutable, PolicyData } from '../../../../../common/types'; interface TableChangeCallbackArguments { page: { index: number; size: number }; @@ -44,8 +44,8 @@ const PolicyLink: React.FC<{ name: string; route: string }> = ({ name, route }) ); }; -const renderPolicyNameLink = (value: string, _item: PolicyData) => { - return ; +const renderPolicyNameLink = (value: string, item: Immutable) => { + return ; }; export const PolicyList = React.memo(() => { @@ -88,7 +88,7 @@ export const PolicyList = React.memo(() => { [history, location.pathname] ); - const columns: Array> = useMemo( + const columns: Array>> = useMemo( () => [ { field: 'name', @@ -160,7 +160,7 @@ export const PolicyList = React.memo(() => { } > [...policyItems], [policyItems])} columns={columns} loading={loading} pagination={paginationSetup} diff --git a/x-pack/plugins/endpoint/scripts/alert_mapping.json b/x-pack/plugins/endpoint/scripts/alert_mapping.json index a21e48b4bc95f..2e0041d0af986 100644 --- a/x-pack/plugins/endpoint/scripts/alert_mapping.json +++ b/x-pack/plugins/endpoint/scripts/alert_mapping.json @@ -394,7 +394,8 @@ "type": "nested" }, "file_extension": { - "type": "long" + "ignore_above": 1024, + "type": "keyword" }, "project_file": { "properties": { diff --git a/x-pack/plugins/endpoint/scripts/event_mapping.json b/x-pack/plugins/endpoint/scripts/event_mapping.json index 59d1ed17852b1..f410edc7abe5e 100644 --- a/x-pack/plugins/endpoint/scripts/event_mapping.json +++ b/x-pack/plugins/endpoint/scripts/event_mapping.json @@ -386,7 +386,8 @@ "type": "nested" }, "file_extension": { - "type": "long" + "ignore_above": 1024, + "type": "keyword" }, "project_file": { "properties": { diff --git a/x-pack/plugins/event_log/README.md b/x-pack/plugins/event_log/README.md index 027bbc694801f..38364033cb70b 100644 --- a/x-pack/plugins/event_log/README.md +++ b/x-pack/plugins/event_log/README.md @@ -125,7 +125,6 @@ Here's the event written to the event log index: "duration": 1000000 }, "kibana": { - "namespace": "default", "saved_objects": [ { "type": "action", diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index ab1b4096d17f2..9c1dff60f9727 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -72,10 +72,6 @@ "type": "keyword", "ignore_above": 1024 }, - "namespace": { - "type": "keyword", - "ignore_above": 1024 - }, "alerting": { "properties": { "instance_id": { @@ -86,7 +82,7 @@ }, "saved_objects": { "properties": { - "store": { + "namespace": { "type": "keyword", "ignore_above": 1024 }, diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index b731093b33b06..5e93f320c009f 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -56,7 +56,6 @@ export const EventSchema = schema.maybe( kibana: schema.maybe( schema.object({ server_uuid: ecsString(), - namespace: ecsString(), alerting: schema.maybe( schema.object({ instance_id: ecsString(), @@ -65,7 +64,7 @@ export const EventSchema = schema.maybe( saved_objects: schema.maybe( schema.arrayOf( schema.object({ - store: ecsString(), + namespace: ecsString(), id: ecsString(), type: ecsString(), }) diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index 9e721b06ec335..de3c9d631fbca 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -20,17 +20,12 @@ exports.EcsKibanaExtensionsMappings = { }, }, }, - // relevant kibana space - namespace: { - type: 'keyword', - ignore_above: 1024, - }, // array of saved object references, for "linking" via search saved_objects: { type: 'nested', properties: { - // 'kibana' for typical saved object, 'task_manager' for TM, etc - store: { + // relevant kibana space + namespace: { type: 'keyword', ignore_above: 1024, }, @@ -61,9 +56,8 @@ exports.EcsEventLogProperties = [ 'error.message', 'user.name', 'kibana.server_uuid', - 'kibana.namespace', 'kibana.alerting.instance_id', - 'kibana.saved_objects.store', + 'kibana.saved_objects.namespace', 'kibana.saved_objects.id', 'kibana.saved_objects.name', 'kibana.saved_objects.type', diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index ae26d7a7ece07..986486902c3fa 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -21,7 +21,7 @@ beforeEach(() => { clusterClient = elasticsearchServiceMock.createClusterClient(); clusterClientAdapter = new ClusterClientAdapter({ logger, - clusterClient, + clusterClientPromise: Promise.resolve(clusterClient), }); }); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index 36bc94edfca4e..409bb2d00e161 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -14,7 +14,7 @@ export type IClusterClientAdapter = PublicMethodsOf; export interface ConstructorOpts { logger: Logger; - clusterClient: EsClusterClient; + clusterClientPromise: Promise; } export interface QueryEventsBySavedObjectResult { @@ -26,11 +26,11 @@ export interface QueryEventsBySavedObjectResult { export class ClusterClientAdapter { private readonly logger: Logger; - private readonly clusterClient: EsClusterClient; + private readonly clusterClientPromise: Promise; constructor(opts: ConstructorOpts) { this.logger = opts.logger; - this.clusterClient = opts.clusterClient; + this.clusterClientPromise = opts.clusterClientPromise; } public async indexDocument(doc: any): Promise { @@ -201,7 +201,8 @@ export class ClusterClientAdapter { private async callEs(operation: string, body?: any): Promise { try { this.debug(`callEs(${operation}) calls:`, body); - const result = await this.clusterClient.callAsInternalUser(operation, body); + const clusterClient = await this.clusterClientPromise; + const result = await clusterClient.callAsInternalUser(operation, body); this.debug(`callEs(${operation}) result:`, result); return result; } catch (err) { diff --git a/x-pack/plugins/event_log/server/es/context.ts b/x-pack/plugins/event_log/server/es/context.ts index 144f44ac8e5ea..b8e351367b695 100644 --- a/x-pack/plugins/event_log/server/es/context.ts +++ b/x-pack/plugins/event_log/server/es/context.ts @@ -32,7 +32,7 @@ export function createEsContext(params: EsContextCtorParams): EsContext { export interface EsContextCtorParams { logger: Logger; - clusterClient: EsClusterClient; + clusterClientPromise: Promise; indexNameRoot: string; } @@ -50,7 +50,7 @@ class EsContextImpl implements EsContext { this.initialized = false; this.esAdapter = new ClusterClientAdapter({ logger: params.logger, - clusterClient: params.clusterClient, + clusterClientPromise: params.clusterClientPromise, }); } diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index 2cc41354b4fbc..e5034f599f118 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -66,7 +66,9 @@ export class Plugin implements CorePlugin elasticsearch.legacy.client), }); this.eventLogService = new EventLogService({ diff --git a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts index 6640683bf6005..19933649277aa 100644 --- a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts @@ -50,9 +50,9 @@ export function fakeEvent(overrides = {}) { duration: 1000000, }, kibana: { - namespace: 'default', saved_objects: [ { + namespace: 'default', type: 'action', id: '968f1b82-0414-4a10-becc-56b6473e4a29', }, diff --git a/x-pack/plugins/infra/common/http_api/metadata_api.ts b/x-pack/plugins/infra/common/http_api/metadata_api.ts index 7fc3c3e876f08..5ee96b479be8e 100644 --- a/x-pack/plugins/infra/common/http_api/metadata_api.ts +++ b/x-pack/plugins/infra/common/http_api/metadata_api.ts @@ -11,6 +11,10 @@ export const InfraMetadataRequestRT = rt.type({ nodeId: rt.string, nodeType: ItemTypeRT, sourceId: rt.string, + timeRange: rt.type({ + from: rt.number, + to: rt.number, + }), }); export const InfraMetadataFeatureRT = rt.type({ diff --git a/x-pack/plugins/infra/common/inventory_models/aws_ec2/layout.tsx b/x-pack/plugins/infra/common/inventory_models/aws_ec2/layout.tsx index c8e0680287526..68bfe41fd538e 100644 --- a/x-pack/plugins/infra/common/inventory_models/aws_ec2/layout.tsx +++ b/x-pack/plugins/infra/common/inventory_models/aws_ec2/layout.tsx @@ -20,7 +20,7 @@ import { withTheme } from '../../../../observability/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { MetadataDetails } from '../../../public/pages/metrics/components/metadata_details'; -export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( +export const Layout = withTheme(({ metrics, theme, onChangeRangeTime }: LayoutPropsWithTheme) => ( ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => ( @@ -40,6 +40,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => ( ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( />
- - + +
)); diff --git a/x-pack/plugins/infra/common/inventory_models/pod/layout.tsx b/x-pack/plugins/infra/common/inventory_models/pod/layout.tsx index 43b95d73f6d95..8bc2f3ee8b4b3 100644 --- a/x-pack/plugins/infra/common/inventory_models/pod/layout.tsx +++ b/x-pack/plugins/infra/common/inventory_models/pod/layout.tsx @@ -23,7 +23,7 @@ import { MetadataDetails } from '../../../public/pages/metrics/components/metada // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { LayoutContent } from '../../../public/pages/metrics/components/layout_content'; -export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => ( @@ -38,6 +38,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( />
- +
)); diff --git a/x-pack/plugins/infra/common/inventory_models/shared/layouts/aws.tsx b/x-pack/plugins/infra/common/inventory_models/shared/layouts/aws.tsx index fba48c4224e6b..7a0b898d406ce 100644 --- a/x-pack/plugins/infra/common/inventory_models/shared/layouts/aws.tsx +++ b/x-pack/plugins/infra/common/inventory_models/shared/layouts/aws.tsx @@ -18,7 +18,7 @@ import { ChartSectionVis } from '../../../../public/pages/metrics/components/cha // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { withTheme } from '../../../../../observability/public'; -export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => ( -
+
{ const [darkMode] = useUiSetting$('theme:darkMode'); @@ -56,19 +45,15 @@ export async function startApp( - - - - - - - - - - - - - + + + + + + + + + diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 2e43ede2480ce..7de52840777f9 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -17,6 +17,9 @@ import { import { IFieldType } from 'src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { EuiExpression } from '@elastic/eui'; +import { EuiCallOut } from '@elastic/eui'; +import { EuiLink } from '@elastic/eui'; import { MetricExpressionParams, Comparator, @@ -208,6 +211,37 @@ export const Expressions: React.FC = props => { } }, [alertsContext.metadata, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps + // INFO: If there is metadata, you're in the metrics explorer context + const canAddConditions = !!alertsContext.metadata; + + if (!canAddConditions && !alertParams.criteria) { + return ( + <> + + + {' '} + + + + . + + } + color="warning" + iconType="help" + /> + + + ); + } + return ( <> @@ -224,6 +258,7 @@ export const Expressions: React.FC = props => { alertParams.criteria.map((e, idx) => { return ( 1} fields={derivedIndexPattern.fields} remove={removeExpression} @@ -246,61 +281,65 @@ export const Expressions: React.FC = props => { />
- - - + {canAddConditions && ( + + + + )}
- - - - - - {alertsContext.metadata && ( - - - + <> + + + + + + + + + )} ); @@ -308,6 +347,7 @@ export const Expressions: React.FC = props => { interface ExpressionRowProps { fields: IFieldType[]; + canEditAggField: boolean; expressionId: number; expression: MetricExpression; errors: IErrorObject; @@ -359,7 +399,9 @@ export const ExpressionRow: React.FC = props => { const updateThreshold = useCallback( t => { - setAlertParams(expressionId, { ...expression, threshold: t }); + if (t.join() !== expression.threshold.join()) { + setAlertParams(expressionId, { ...expression, threshold: t }); + } }, [expressionId, expression, setAlertParams] ); @@ -378,17 +420,20 @@ export const ExpressionRow: React.FC = props => { {aggType !== 'count' && ( - ({ - normalizedType: f.type, - name: f.name, - }))} - aggType={aggType} - errors={errors} - onChangeSelectedAggField={updateMetric} - /> + {!props.canEditAggField && } + {props.canEditAggField && ( + ({ + normalizedType: f.type, + name: f.name, + }))} + aggType={aggType} + errors={errors} + onChangeSelectedAggField={updateMetric} + /> + )} )} @@ -420,6 +465,19 @@ export const ExpressionRow: React.FC = props => { ); }; +export const DisabledAggField = ({ text }: { text: string }) => { + return ( + + ); +}; + export const aggregationType: { [key: string]: any } = { avg: { text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.avg', { diff --git a/x-pack/plugins/infra/public/components/inventory/layout.tsx b/x-pack/plugins/infra/public/components/inventory/layout.tsx index 4dd9803c7bfce..3c91f9fa5946f 100644 --- a/x-pack/plugins/infra/public/components/inventory/layout.tsx +++ b/x-pack/plugins/infra/public/components/inventory/layout.tsx @@ -5,64 +5,89 @@ */ import React from 'react'; -import { InfraWaffleMapOptions, InfraWaffleMapBounds } from '../../lib/lib'; -import { KueryFilterQuery } from '../../store/local/waffle_filter'; +import { useInterval } from 'react-use'; +import { euiPaletteColorBlind } from '@elastic/eui'; import { NodesOverview } from '../nodes_overview'; import { Toolbar } from './toolbars/toolbar'; import { PageContent } from '../page'; import { useSnapshot } from '../../containers/waffle/use_snaphot'; import { useInventoryMeta } from '../../containers/inventory_metadata/use_inventory_meta'; -import { SnapshotMetricInput, SnapshotGroupBy } from '../../../common/http_api/snapshot_api'; -import { InventoryItemType } from '../../../common/inventory_models/types'; +import { useWaffleTimeContext } from '../../pages/inventory_view/hooks/use_waffle_time'; +import { useWaffleFiltersContext } from '../../pages/inventory_view/hooks/use_waffle_filters'; +import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options'; +import { useSourceContext } from '../../containers/source'; +import { InfraFormatterType, InfraWaffleMapGradientLegend } from '../../lib/lib'; -export interface LayoutProps { - options: InfraWaffleMapOptions; - nodeType: InventoryItemType; - onDrilldown: (filter: KueryFilterQuery) => void; - currentTime: number; - onViewChange: (view: string) => void; - view: string; - boundsOverride: InfraWaffleMapBounds; - autoBounds: boolean; +const euiVisColorPalette = euiPaletteColorBlind(); - filterQuery: string | null | undefined; - metric: SnapshotMetricInput; - groupBy: SnapshotGroupBy; - sourceId: string; - accountId: string; - region: string; -} - -export const Layout = (props: LayoutProps) => { - const { accounts, regions } = useInventoryMeta(props.sourceId, props.nodeType); +export const Layout = () => { + const { sourceId, source } = useSourceContext(); + const { + metric, + groupBy, + nodeType, + accountId, + region, + changeView, + view, + autoBounds, + boundsOverride, + } = useWaffleOptionsContext(); + const { accounts, regions } = useInventoryMeta(sourceId, nodeType); + const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext(); + const { filterQueryAsJson, applyFilterQuery } = useWaffleFiltersContext(); const { loading, nodes, reload, interval } = useSnapshot( - props.filterQuery, - props.metric, - props.groupBy, - props.nodeType, - props.sourceId, - props.currentTime, - props.accountId, - props.region + filterQueryAsJson, + metric, + groupBy, + nodeType, + sourceId, + currentTime, + accountId, + region + ); + + const options = { + formatter: InfraFormatterType.percent, + formatTemplate: '{{value}}', + legend: { + type: 'gradient', + rules: [ + { value: 0, color: '#D3DAE6' }, + { value: 1, color: euiVisColorPalette[1] }, + ], + } as InfraWaffleMapGradientLegend, + metric, + fields: source?.configuration?.fields, + groupBy, + }; + + useInterval( + () => { + if (!loading) { + jumpToTime(Date.now()); + } + }, + isAutoReloading ? 5000 : null ); return ( <> - + diff --git a/x-pack/plugins/infra/public/components/inventory/toolbars/save_views.tsx b/x-pack/plugins/infra/public/components/inventory/toolbars/save_views.tsx new file mode 100644 index 0000000000000..cb315d3e17b03 --- /dev/null +++ b/x-pack/plugins/infra/public/components/inventory/toolbars/save_views.tsx @@ -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 { SavedViewsToolbarControls } from '../../saved_views/toolbar_control'; +import { inventoryViewSavedObjectType } from '../../../../common/saved_objects/inventory_view'; +import { useWaffleViewState } from '../../../pages/inventory_view/hooks/use_waffle_view_state'; + +export const SavedViews = () => { + const { viewState, defaultViewState, onViewChange } = useWaffleViewState(); + return ( + + ); +}; diff --git a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx index c59ab994a018c..63ab6d2f4465a 100644 --- a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx +++ b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx @@ -5,7 +5,6 @@ */ import React, { FunctionComponent } from 'react'; -import { Action } from 'typescript-fsa'; import { EuiFlexItem } from '@elastic/eui'; import { SnapshotMetricInput, @@ -16,33 +15,23 @@ import { InventoryCloudAccount } from '../../../../common/http_api/inventory_met import { findToolbar } from '../../../../common/inventory_models/toolbars'; import { ToolbarWrapper } from './toolbar_wrapper'; -import { waffleOptionsSelectors } from '../../../store'; import { InfraGroupByOptions } from '../../../lib/lib'; -import { WithWaffleViewState } from '../../../containers/waffle/with_waffle_view_state'; -import { SavedViewsToolbarControls } from '../../saved_views/toolbar_control'; -import { inventoryViewSavedObjectType } from '../../../../common/saved_objects/inventory_view'; import { IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { InventoryItemType } from '../../../../common/inventory_models/types'; +import { WaffleOptionsState } from '../../../pages/inventory_view/hooks/use_waffle_options'; +import { SavedViews } from './save_views'; -export interface ToolbarProps { +export interface ToolbarProps + extends Omit { createDerivedIndexPattern: (type: 'logs' | 'metrics' | 'both') => IIndexPattern; - changeMetric: (payload: SnapshotMetricInput) => Action; - changeGroupBy: (payload: SnapshotGroupBy) => Action; - changeCustomOptions: (payload: InfraGroupByOptions[]) => Action; - changeAccount: (id: string) => Action; - changeRegion: (name: string) => Action; - customOptions: ReturnType; - groupBy: ReturnType; - metric: ReturnType; - nodeType: ReturnType; - accountId: ReturnType; - region: ReturnType; + changeMetric: (payload: SnapshotMetricInput) => void; + changeGroupBy: (payload: SnapshotGroupBy) => void; + changeCustomOptions: (payload: InfraGroupByOptions[]) => void; + changeAccount: (id: string) => void; + changeRegion: (name: string) => void; accounts: InventoryCloudAccount[]; regions: string[]; - customMetrics: ReturnType; - changeCustomMetrics: ( - payload: SnapshotCustomMetricInput[] - ) => Action; + changeCustomMetrics: (payload: SnapshotCustomMetricInput[]) => void; } const wrapToolbarItems = ( @@ -57,16 +46,7 @@ const wrapToolbarItems = ( - - {({ defaultViewState, viewState, onViewChange }) => ( - - )} - + )} diff --git a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx index 735539d063b01..fefda94372cfb 100644 --- a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx +++ b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx @@ -8,58 +8,52 @@ import React from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SnapshotMetricType } from '../../../../common/inventory_models/types'; -import { WithSource } from '../../../containers/with_source'; -import { WithWaffleOptions } from '../../../containers/waffle/with_waffle_options'; import { Toolbar } from '../../eui/toolbar'; import { ToolbarProps } from './toolbar'; import { fieldToName } from '../../waffle/lib/field_to_display_name'; +import { useSourceContext } from '../../../containers/source'; +import { useWaffleOptionsContext } from '../../../pages/inventory_view/hooks/use_waffle_options'; interface Props { children: (props: Omit) => React.ReactElement; } export const ToolbarWrapper = (props: Props) => { + const { + changeMetric, + changeGroupBy, + changeCustomOptions, + changeAccount, + changeRegion, + customOptions, + groupBy, + metric, + nodeType, + accountId, + region, + customMetrics, + changeCustomMetrics, + } = useWaffleOptionsContext(); + const { createDerivedIndexPattern } = useSourceContext(); return ( - - {({ createDerivedIndexPattern }) => ( - - {({ - changeMetric, - changeGroupBy, - changeCustomOptions, - changeAccount, - changeRegion, - customOptions, - groupBy, - metric, - nodeType, - accountId, - region, - customMetrics, - changeCustomMetrics, - }) => - props.children({ - createDerivedIndexPattern, - changeMetric, - changeGroupBy, - changeAccount, - changeRegion, - changeCustomOptions, - customOptions, - groupBy, - metric, - nodeType, - region, - accountId, - customMetrics, - changeCustomMetrics, - }) - } - - )} - + {props.children({ + createDerivedIndexPattern, + changeMetric, + changeGroupBy, + changeAccount, + changeRegion, + changeCustomOptions, + customOptions, + groupBy, + metric, + nodeType, + region, + accountId, + customMetrics, + changeCustomMetrics, + })} ); diff --git a/x-pack/plugins/infra/public/components/nodes_overview/index.tsx b/x-pack/plugins/infra/public/components/nodes_overview/index.tsx index 4d61568a63b9f..ef22e0486f892 100644 --- a/x-pack/plugins/infra/public/components/nodes_overview/index.tsx +++ b/x-pack/plugins/infra/public/components/nodes_overview/index.tsx @@ -12,7 +12,6 @@ import React from 'react'; import { euiStyled } from '../../../../observability/public'; import { InfraFormatterType, InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../lib/lib'; -import { KueryFilterQuery } from '../../store/local/waffle_filter'; import { createFormatter } from '../../utils/formatters'; import { NoData } from '../empty_states'; import { InfraLoadingPanel } from '../loading'; @@ -24,6 +23,11 @@ import { convertIntervalToString } from '../../utils/convert_interval_to_string' import { InventoryItemType } from '../../../common/inventory_models/types'; import { createFormatterForMetric } from '../metrics_explorer/helpers/create_formatter_for_metric'; +export interface KueryFilterQuery { + kind: 'kuery'; + expression: string; +} + interface Props { options: InfraWaffleMapOptions; nodeType: InventoryItemType; diff --git a/x-pack/plugins/infra/public/components/waffle/legend.tsx b/x-pack/plugins/infra/public/components/waffle/legend.tsx index de070efb35a1f..13e533b225d4d 100644 --- a/x-pack/plugins/infra/public/components/waffle/legend.tsx +++ b/x-pack/plugins/infra/public/components/waffle/legend.tsx @@ -6,12 +6,12 @@ import React from 'react'; import { euiStyled } from '../../../../observability/public'; -import { WithWaffleOptions } from '../../containers/waffle/with_waffle_options'; import { InfraFormatter, InfraWaffleMapBounds, InfraWaffleMapLegend } from '../../lib/lib'; import { GradientLegend } from './gradient_legend'; import { LegendControls } from './legend_controls'; import { isInfraWaffleMapGradientLegend, isInfraWaffleMapStepLegend } from './lib/type_guards'; import { StepLegend } from './steps_legend'; +import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options'; interface Props { legend: InfraWaffleMapLegend; bounds: InfraWaffleMapBounds; @@ -25,22 +25,24 @@ interface LegendControlOptions { } export const Legend: React.FC = ({ dataBounds, legend, bounds, formatter }) => { + const { + changeBoundsOverride, + changeAutoBounds, + autoBounds, + boundsOverride, + } = useWaffleOptionsContext(); return ( - - {({ changeBoundsOverride, changeAutoBounds, autoBounds, boundsOverride }) => ( - { - changeBoundsOverride(options.bounds); - changeAutoBounds(options.auto); - }} - /> - )} - + { + changeBoundsOverride(options.bounds); + changeAutoBounds(options.auto); + }} + /> {isInfraWaffleMapGradientLegend(legend) && ( )} diff --git a/x-pack/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts b/x-pack/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts index 18e5838a15b56..902969c83ba39 100644 --- a/x-pack/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts +++ b/x-pack/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts @@ -5,11 +5,7 @@ */ import { createUptimeLink } from './create_uptime_link'; -import { - InfraWaffleMapOptions, - InfraWaffleMapLegendMode, - InfraFormatterType, -} from '../../../lib/lib'; +import { InfraWaffleMapOptions, InfraFormatterType } from '../../../lib/lib'; import { SnapshotMetricType } from '../../../../common/inventory_models/types'; const options: InfraWaffleMapOptions = { @@ -26,7 +22,7 @@ const options: InfraWaffleMapOptions = { metric: { type: 'cpu' }, groupBy: [], legend: { - type: InfraWaffleMapLegendMode.gradient, + type: 'gradient', rules: [], }, }; diff --git a/x-pack/plugins/infra/public/components/waffle/lib/type_guards.ts b/x-pack/plugins/infra/public/components/waffle/lib/type_guards.ts index aff16374ae262..f793afee1b948 100644 --- a/x-pack/plugins/infra/public/components/waffle/lib/type_guards.ts +++ b/x-pack/plugins/infra/public/components/waffle/lib/type_guards.ts @@ -4,16 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - InfraWaffleMapGradientLegend, - InfraWaffleMapLegendMode, - InfraWaffleMapStepLegend, -} from '../../../lib/lib'; +import { InfraWaffleMapGradientLegend, InfraWaffleMapStepLegend } from '../../../lib/lib'; + export function isInfraWaffleMapStepLegend(subject: any): subject is InfraWaffleMapStepLegend { - return subject.type && subject.type === InfraWaffleMapLegendMode.step; + return subject.type && subject.type === 'step'; } + export function isInfraWaffleMapGradientLegend( subject: any ): subject is InfraWaffleMapGradientLegend { - return subject.type && subject.type === InfraWaffleMapLegendMode.gradient; + return subject.type && subject.type === 'gradient'; } diff --git a/x-pack/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx b/x-pack/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx index d265b418f010d..21da10a0a7650 100644 --- a/x-pack/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx +++ b/x-pack/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx @@ -16,36 +16,23 @@ import React, { useCallback, useState, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { findInventoryModel } from '../../../common/inventory_models'; import { InventoryItemType } from '../../../common/inventory_models/types'; -import { - SnapshotMetricInput, - SnapshotGroupBy, - SnapshotCustomMetricInput, -} from '../../../common/http_api/snapshot_api'; - -interface WaffleInventorySwitcherProps { - nodeType: InventoryItemType; - changeNodeType: (nodeType: InventoryItemType) => void; - changeGroupBy: (groupBy: SnapshotGroupBy) => void; - changeMetric: (metric: SnapshotMetricInput) => void; - changeCustomMetrics: (metrics: SnapshotCustomMetricInput[]) => void; - changeAccount: (id: string) => void; - changeRegion: (name: string) => void; -} +import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options'; const getDisplayNameForType = (type: InventoryItemType) => { const inventoryModel = findInventoryModel(type); return inventoryModel.displayName; }; -export const WaffleInventorySwitcher: React.FC = ({ - changeNodeType, - changeGroupBy, - changeMetric, - changeAccount, - changeRegion, - changeCustomMetrics, - nodeType, -}) => { +export const WaffleInventorySwitcher: React.FC = () => { + const { + changeNodeType, + changeGroupBy, + changeMetric, + changeAccount, + changeRegion, + changeCustomMetrics, + nodeType, + } = useWaffleOptionsContext(); const [isOpen, setIsOpen] = useState(false); const closePopover = useCallback(() => setIsOpen(false), []); const openPopover = useCallback(() => setIsOpen(true), []); diff --git a/x-pack/plugins/infra/public/components/waffle/waffle_time_controls.tsx b/x-pack/plugins/infra/public/components/waffle/waffle_time_controls.tsx index 4f840336de8c3..458bb674afade 100644 --- a/x-pack/plugins/infra/public/components/waffle/waffle_time_controls.tsx +++ b/x-pack/plugins/infra/public/components/waffle/waffle_time_controls.tsx @@ -7,84 +7,60 @@ import { EuiButtonEmpty, EuiDatePicker, EuiFormControlLayout } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import moment, { Moment } from 'moment'; -import React from 'react'; -import { Action } from 'typescript-fsa'; +import React, { useCallback } from 'react'; +import { useWaffleTimeContext } from '../../pages/inventory_view/hooks/use_waffle_time'; -interface WaffleTimeControlsProps { - currentTime: number; - isLiveStreaming?: boolean; - onChangeTime?: (time: number) => void; - startLiveStreaming?: (payload: void) => Action; - stopLiveStreaming?: (payload: void) => Action; -} +export const WaffleTimeControls = () => { + const { + currentTime, + isAutoReloading, + startAutoReload, + stopAutoReload, + jumpToTime, + } = useWaffleTimeContext(); -export class WaffleTimeControls extends React.Component { - public render() { - const { currentTime, isLiveStreaming } = this.props; + const currentMoment = moment(currentTime); - const currentMoment = moment(currentTime); + const liveStreamingButton = isAutoReloading ? ( + + + + ) : ( + + + + ); - const liveStreamingButton = isLiveStreaming ? ( - - - - ) : ( - - - - ); + const handleChangeDate = useCallback( + (time: Moment | null) => { + if (time) { + jumpToTime(time.valueOf()); + } + }, + [jumpToTime] + ); - return ( - - - - ); - } - - private handleChangeDate = (time: Moment | null) => { - const { onChangeTime } = this.props; - - if (onChangeTime && time) { - onChangeTime(time.valueOf()); - } - }; - - private startLiveStreaming = () => { - const { startLiveStreaming } = this.props; - - if (startLiveStreaming) { - startLiveStreaming(); - } - }; - - private stopLiveStreaming = () => { - const { stopLiveStreaming } = this.props; - - if (stopLiveStreaming) { - stopLiveStreaming(); - } - }; -} + return ( + + + + ); +}; diff --git a/x-pack/plugins/infra/public/containers/metadata/use_metadata.ts b/x-pack/plugins/infra/public/containers/metadata/use_metadata.ts index 52c522ce8efd4..1ba016195bef4 100644 --- a/x-pack/plugins/infra/public/containers/metadata/use_metadata.ts +++ b/x-pack/plugins/infra/public/containers/metadata/use_metadata.ts @@ -13,17 +13,18 @@ import { useHTTPRequest } from '../../hooks/use_http_request'; import { throwErrors, createPlainError } from '../../../common/runtime_types'; import { InventoryMetric, InventoryItemType } from '../../../common/inventory_models/types'; import { getFilteredMetrics } from './lib/get_filtered_metrics'; +import { MetricsTimeInput } from '../../pages/metrics/hooks/use_metrics_time'; export function useMetadata( nodeId: string, nodeType: InventoryItemType, requiredMetrics: InventoryMetric[], - sourceId: string + sourceId: string, + timeRange: MetricsTimeInput ) { const decodeResponse = (response: any) => { return pipe(InfraMetadataRT.decode(response), fold(throwErrors(createPlainError), identity)); }; - const { error, loading, response, makeRequest } = useHTTPRequest( '/api/infra/metadata', 'POST', @@ -31,6 +32,7 @@ export function useMetadata( nodeId, nodeType, sourceId, + timeRange: { from: timeRange.from, to: timeRange.to }, }), decodeResponse ); diff --git a/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts b/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts deleted file mode 100644 index 1ca6bc8c397e5..0000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const waffleNodesQuery = gql` - query WaffleNodesQuery( - $sourceId: ID! - $timerange: InfraTimerangeInput! - $filterQuery: String - $metric: InfraSnapshotMetricInput! - $groupBy: [InfraSnapshotGroupbyInput!]! - $type: InfraNodeType! - ) { - source(id: $sourceId) { - id - snapshot(timerange: $timerange, filterQuery: $filterQuery) { - nodes(groupBy: $groupBy, metric: $metric, type: $type) { - path { - value - label - ip - } - metric { - name - value - avg - max - } - } - } - } - } -`; diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_filters.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_filters.tsx deleted file mode 100644 index 0214237ef52d8..0000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_filters.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { connect } from 'react-redux'; - -import { IIndexPattern } from 'src/plugins/data/public'; -import { State, waffleFilterActions, waffleFilterSelectors } from '../../store'; -import { FilterQuery } from '../../store/local/waffle_filter'; -import { convertKueryToElasticSearchQuery } from '../../utils/kuery'; -import { asChildFunctionRenderer } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; -import { UrlStateContainer } from '../../utils/url_state'; - -interface WithWaffleFilterProps { - indexPattern: IIndexPattern; -} - -export const withWaffleFilter = connect( - (state: State) => ({ - filterQuery: waffleFilterSelectors.selectWaffleFilterQuery(state), - filterQueryDraft: waffleFilterSelectors.selectWaffleFilterQueryDraft(state), - filterQueryAsJson: waffleFilterSelectors.selectWaffleFilterQueryAsJson(state), - isFilterQueryDraftValid: waffleFilterSelectors.selectIsWaffleFilterQueryDraftValid(state), - }), - (dispatch, ownProps: WithWaffleFilterProps) => - bindPlainActionCreators({ - applyFilterQuery: (query: FilterQuery) => - waffleFilterActions.applyWaffleFilterQuery({ - query, - serializedQuery: convertKueryToElasticSearchQuery( - query.expression, - ownProps.indexPattern - ), - }), - applyFilterQueryFromKueryExpression: (expression: string) => - waffleFilterActions.applyWaffleFilterQuery({ - query: { - kind: 'kuery', - expression, - }, - serializedQuery: convertKueryToElasticSearchQuery(expression, ownProps.indexPattern), - }), - setFilterQueryDraft: waffleFilterActions.setWaffleFilterQueryDraft, - setFilterQueryDraftFromKueryExpression: (expression: string) => - waffleFilterActions.setWaffleFilterQueryDraft({ - kind: 'kuery', - expression, - }), - }) -); - -export const WithWaffleFilter = asChildFunctionRenderer(withWaffleFilter); - -/** - * Url State - */ - -type WaffleFilterUrlState = ReturnType; - -type WithWaffleFilterUrlStateProps = WithWaffleFilterProps; - -export const WithWaffleFilterUrlState: React.FC = ({ - indexPattern, -}) => ( - - {({ applyFilterQuery, filterQuery }) => ( - { - if (urlState) { - applyFilterQuery(urlState); - } - }} - onInitialize={urlState => { - if (urlState) { - applyFilterQuery(urlState); - } - }} - /> - )} - -); - -const mapToUrlState = (value: any): WaffleFilterUrlState | undefined => - value && value.kind === 'kuery' && typeof value.expression === 'string' - ? { - kind: value.kind, - expression: value.expression, - } - : undefined; diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx deleted file mode 100644 index 47dd6a5a73a73..0000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; - -import { isBoolean, isNumber } from 'lodash'; -import { InfraGroupByOptions } from '../../lib/lib'; -import { State, waffleOptionsActions, waffleOptionsSelectors } from '../../store'; -import { asChildFunctionRenderer } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; -import { UrlStateContainer } from '../../utils/url_state'; -import { - SnapshotMetricInput, - SnapshotGroupBy, - SnapshotCustomMetricInputRT, -} from '../../../common/http_api/snapshot_api'; -import { - SnapshotMetricTypeRT, - InventoryItemType, - ItemTypeRT, -} from '../../../common/inventory_models/types'; - -const selectOptionsUrlState = createSelector( - waffleOptionsSelectors.selectMetric, - waffleOptionsSelectors.selectView, - waffleOptionsSelectors.selectGroupBy, - waffleOptionsSelectors.selectNodeType, - waffleOptionsSelectors.selectCustomOptions, - waffleOptionsSelectors.selectBoundsOverride, - waffleOptionsSelectors.selectAutoBounds, - waffleOptionsSelectors.selectAccountId, - waffleOptionsSelectors.selectRegion, - waffleOptionsSelectors.selectCustomMetrics, - ( - metric, - view, - groupBy, - nodeType, - customOptions, - boundsOverride, - autoBounds, - accountId, - region, - customMetrics - ) => ({ - metric, - groupBy, - nodeType, - view, - customOptions, - boundsOverride, - autoBounds, - accountId, - region, - customMetrics, - }) -); - -export const withWaffleOptions = connect( - (state: State) => ({ - metric: waffleOptionsSelectors.selectMetric(state), - groupBy: waffleOptionsSelectors.selectGroupBy(state), - nodeType: waffleOptionsSelectors.selectNodeType(state), - view: waffleOptionsSelectors.selectView(state), - customOptions: waffleOptionsSelectors.selectCustomOptions(state), - boundsOverride: waffleOptionsSelectors.selectBoundsOverride(state), - autoBounds: waffleOptionsSelectors.selectAutoBounds(state), - accountId: waffleOptionsSelectors.selectAccountId(state), - region: waffleOptionsSelectors.selectRegion(state), - urlState: selectOptionsUrlState(state), - customMetrics: waffleOptionsSelectors.selectCustomMetrics(state), - }), - bindPlainActionCreators({ - changeMetric: waffleOptionsActions.changeMetric, - changeGroupBy: waffleOptionsActions.changeGroupBy, - changeNodeType: waffleOptionsActions.changeNodeType, - changeView: waffleOptionsActions.changeView, - changeCustomOptions: waffleOptionsActions.changeCustomOptions, - changeBoundsOverride: waffleOptionsActions.changeBoundsOverride, - changeAutoBounds: waffleOptionsActions.changeAutoBounds, - changeAccount: waffleOptionsActions.changeAccount, - changeRegion: waffleOptionsActions.changeRegion, - changeCustomMetrics: waffleOptionsActions.changeCustomMetrics, - }) -); - -export const WithWaffleOptions = asChildFunctionRenderer(withWaffleOptions); - -/** - * Url State - */ - -interface WaffleOptionsUrlState { - metric?: ReturnType; - groupBy?: ReturnType; - nodeType?: ReturnType; - view?: ReturnType; - customOptions?: ReturnType; - bounds?: ReturnType; - auto?: ReturnType; - accountId?: ReturnType; - region?: ReturnType; - customMetrics?: ReturnType; -} - -export const WithWaffleOptionsUrlState = () => ( - - {({ - changeMetric, - urlState, - changeGroupBy, - changeNodeType, - changeView, - changeCustomOptions, - changeAutoBounds, - changeBoundsOverride, - changeAccount, - changeRegion, - changeCustomMetrics, - }) => ( - - urlState={urlState} - urlStateKey="waffleOptions" - mapToUrlState={mapToUrlState} - onChange={newUrlState => { - if (newUrlState && newUrlState.metric) { - changeMetric(newUrlState.metric); - } - if (newUrlState && newUrlState.groupBy) { - changeGroupBy(newUrlState.groupBy); - } - if (newUrlState && newUrlState.nodeType) { - changeNodeType(newUrlState.nodeType); - } - if (newUrlState && newUrlState.view) { - changeView(newUrlState.view); - } - if (newUrlState && newUrlState.customOptions) { - changeCustomOptions(newUrlState.customOptions); - } - if (newUrlState && newUrlState.bounds) { - changeBoundsOverride(newUrlState.bounds); - } - if (newUrlState && newUrlState.auto) { - changeAutoBounds(newUrlState.auto); - } - if (newUrlState && newUrlState.accountId) { - changeAccount(newUrlState.accountId); - } - if (newUrlState && newUrlState.region) { - changeRegion(newUrlState.region); - } - if (newUrlState && newUrlState.customMetrics) { - changeCustomMetrics(newUrlState.customMetrics); - } - }} - onInitialize={initialUrlState => { - if (initialUrlState && initialUrlState.metric) { - changeMetric(initialUrlState.metric); - } - if (initialUrlState && initialUrlState.groupBy) { - changeGroupBy(initialUrlState.groupBy); - } - if (initialUrlState && initialUrlState.nodeType) { - changeNodeType(initialUrlState.nodeType); - } - if (initialUrlState && initialUrlState.view) { - changeView(initialUrlState.view); - } - if (initialUrlState && initialUrlState.customOptions) { - changeCustomOptions(initialUrlState.customOptions); - } - if (initialUrlState && initialUrlState.bounds) { - changeBoundsOverride(initialUrlState.bounds); - } - if (initialUrlState && initialUrlState.auto) { - changeAutoBounds(initialUrlState.auto); - } - if (initialUrlState && initialUrlState.accountId) { - changeAccount(initialUrlState.accountId); - } - if (initialUrlState && initialUrlState.region) { - changeRegion(initialUrlState.region); - } - if (initialUrlState && initialUrlState.customMetrics) { - changeCustomMetrics(initialUrlState.customMetrics); - } - }} - /> - )} - -); - -const mapToUrlState = (value: any): WaffleOptionsUrlState | undefined => - value - ? { - metric: mapToMetricUrlState(value.metric), - groupBy: mapToGroupByUrlState(value.groupBy), - nodeType: mapToNodeTypeUrlState(value.nodeType), - view: mapToViewUrlState(value.view), - customOptions: mapToCustomOptionsUrlState(value.customOptions), - bounds: mapToBoundsOverideUrlState(value.boundsOverride), - auto: mapToAutoBoundsUrlState(value.autoBounds), - accountId: value.accountId, - region: value.region, - customMetrics: mapToCustomMetricsUrlState(value.customMetrics), - } - : undefined; - -const isInfraNodeType = (value: any): value is InventoryItemType => value in ItemTypeRT; - -const isInfraSnapshotMetricInput = (subject: any): subject is SnapshotMetricInput => { - return subject != null && subject.type in SnapshotMetricTypeRT; -}; - -const isInfraSnapshotGroupbyInput = (subject: any): subject is SnapshotGroupBy => { - return subject != null && subject.type != null; -}; - -const isInfraGroupByOption = (subject: any): subject is InfraGroupByOptions => { - return subject != null && subject.text != null && subject.field != null; -}; - -const mapToMetricUrlState = (subject: any) => { - return subject && isInfraSnapshotMetricInput(subject) ? subject : undefined; -}; - -const mapToGroupByUrlState = (subject: any) => { - return subject && Array.isArray(subject) && subject.every(isInfraSnapshotGroupbyInput) - ? subject - : undefined; -}; - -const mapToNodeTypeUrlState = (subject: any) => { - return isInfraNodeType(subject) ? subject : undefined; -}; - -const mapToViewUrlState = (subject: any) => { - return subject && ['map', 'table'].includes(subject) ? subject : undefined; -}; - -const mapToCustomOptionsUrlState = (subject: any) => { - return subject && Array.isArray(subject) && subject.every(isInfraGroupByOption) - ? subject - : undefined; -}; - -const mapToCustomMetricsUrlState = (subject: any) => { - return subject && Array.isArray(subject) && subject.every(s => SnapshotCustomMetricInputRT.is(s)) - ? subject - : []; -}; - -const mapToBoundsOverideUrlState = (subject: any) => { - return subject != null && isNumber(subject.max) && isNumber(subject.min) ? subject : undefined; -}; - -const mapToAutoBoundsUrlState = (subject: any) => { - return subject != null && isBoolean(subject) ? subject : undefined; -}; diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_time.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_time.tsx deleted file mode 100644 index 293f6184af21b..0000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_time.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; - -import { State, waffleTimeActions, waffleTimeSelectors } from '../../store'; -import { asChildFunctionRenderer } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; -import { UrlStateContainer } from '../../utils/url_state'; - -export const withWaffleTime = connect( - (state: State) => ({ - currentTime: waffleTimeSelectors.selectCurrentTime(state), - currentTimeRange: waffleTimeSelectors.selectCurrentTimeRange(state), - isAutoReloading: waffleTimeSelectors.selectIsAutoReloading(state), - urlState: selectTimeUrlState(state), - }), - bindPlainActionCreators({ - jumpToTime: waffleTimeActions.jumpToTime, - startAutoReload: waffleTimeActions.startAutoReload, - stopAutoReload: waffleTimeActions.stopAutoReload, - }) -); - -export const WithWaffleTime = asChildFunctionRenderer(withWaffleTime, { - onCleanup: ({ stopAutoReload }) => stopAutoReload(), -}); - -/** - * Url State - */ - -interface WaffleTimeUrlState { - time?: ReturnType; - autoReload?: ReturnType; -} - -export const WithWaffleTimeUrlState = () => ( - - {({ jumpToTime, startAutoReload, stopAutoReload, urlState }) => ( - { - if (newUrlState && newUrlState.time) { - jumpToTime(newUrlState.time); - } - if (newUrlState && newUrlState.autoReload) { - startAutoReload(); - } else if ( - newUrlState && - typeof newUrlState.autoReload !== 'undefined' && - !newUrlState.autoReload - ) { - stopAutoReload(); - } - }} - onInitialize={initialUrlState => { - if (initialUrlState) { - jumpToTime(initialUrlState.time ? initialUrlState.time : Date.now()); - } - if (initialUrlState && initialUrlState.autoReload) { - startAutoReload(); - } - }} - /> - )} - -); - -const selectTimeUrlState = createSelector( - waffleTimeSelectors.selectCurrentTime, - waffleTimeSelectors.selectIsAutoReloading, - (time, autoReload) => ({ - time, - autoReload, - }) -); - -const mapToUrlState = (value: any): WaffleTimeUrlState | undefined => - value - ? { - time: mapToTimeUrlState(value.time), - autoReload: mapToAutoReloadUrlState(value.autoReload), - } - : undefined; - -const mapToTimeUrlState = (value: any) => (value && typeof value === 'number' ? value : undefined); - -const mapToAutoReloadUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined); diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx deleted file mode 100644 index 421c506166d04..0000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx +++ /dev/null @@ -1,145 +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 { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { IIndexPattern } from 'src/plugins/data/public'; -import { - State, - waffleOptionsActions, - waffleOptionsSelectors, - waffleTimeSelectors, - waffleTimeActions, - waffleFilterActions, - waffleFilterSelectors, - initialState, -} from '../../store'; -import { asChildFunctionRenderer } from '../../utils/typed_react'; -import { convertKueryToElasticSearchQuery } from '../../utils/kuery'; - -const selectViewState = createSelector( - waffleOptionsSelectors.selectMetric, - waffleOptionsSelectors.selectView, - waffleOptionsSelectors.selectGroupBy, - waffleOptionsSelectors.selectNodeType, - waffleOptionsSelectors.selectCustomOptions, - waffleOptionsSelectors.selectBoundsOverride, - waffleOptionsSelectors.selectAutoBounds, - waffleTimeSelectors.selectCurrentTime, - waffleTimeSelectors.selectIsAutoReloading, - waffleFilterSelectors.selectWaffleFilterQuery, - waffleOptionsSelectors.selectCustomMetrics, - ( - metric, - view, - groupBy, - nodeType, - customOptions, - boundsOverride, - autoBounds, - time, - autoReload, - filterQuery, - customMetrics - ) => ({ - time, - autoReload, - metric, - groupBy, - nodeType, - view, - customOptions, - boundsOverride, - autoBounds, - filterQuery, - customMetrics, - }) -); - -interface Props { - indexPattern: IIndexPattern; -} - -export const withWaffleViewState = connect( - (state: State) => ({ - viewState: selectViewState(state), - defaultViewState: selectViewState(initialState), - }), - (dispatch, ownProps: Props) => { - return { - onViewChange: (viewState: WaffleViewState) => { - if (viewState.time) { - dispatch(waffleTimeActions.jumpToTime(viewState.time)); - } - if (viewState.autoReload) { - dispatch(waffleTimeActions.startAutoReload()); - } else if (typeof viewState.autoReload !== 'undefined' && !viewState.autoReload) { - dispatch(waffleTimeActions.stopAutoReload()); - } - if (viewState.metric) { - dispatch(waffleOptionsActions.changeMetric(viewState.metric)); - } - if (viewState.groupBy) { - dispatch(waffleOptionsActions.changeGroupBy(viewState.groupBy)); - } - if (viewState.nodeType) { - dispatch(waffleOptionsActions.changeNodeType(viewState.nodeType)); - } - if (viewState.view) { - dispatch(waffleOptionsActions.changeView(viewState.view)); - } - if (viewState.customOptions) { - dispatch(waffleOptionsActions.changeCustomOptions(viewState.customOptions)); - } - if (viewState.customMetrics) { - dispatch(waffleOptionsActions.changeCustomMetrics(viewState.customMetrics)); - } - if (viewState.boundsOverride) { - dispatch(waffleOptionsActions.changeBoundsOverride(viewState.boundsOverride)); - } - if (viewState.autoBounds) { - dispatch(waffleOptionsActions.changeAutoBounds(viewState.autoBounds)); - } - if (viewState.filterQuery) { - dispatch( - waffleFilterActions.applyWaffleFilterQuery({ - query: viewState.filterQuery, - serializedQuery: convertKueryToElasticSearchQuery( - viewState.filterQuery.expression, - ownProps.indexPattern - ), - }) - ); - } else { - dispatch( - waffleFilterActions.applyWaffleFilterQuery({ - query: null, - serializedQuery: null, - }) - ); - } - }, - }; - } -); - -export const WithWaffleViewState = asChildFunctionRenderer(withWaffleViewState); - -/** - * View State - */ -export interface WaffleViewState { - metric?: ReturnType; - groupBy?: ReturnType; - nodeType?: ReturnType; - view?: ReturnType; - customOptions?: ReturnType; - customMetrics?: ReturnType; - boundsOverride?: ReturnType; - autoBounds?: ReturnType; - time?: ReturnType; - autoReload?: ReturnType; - filterQuery?: ReturnType; -} diff --git a/x-pack/plugins/infra/public/containers/with_options.tsx b/x-pack/plugins/infra/public/containers/with_options.tsx index 972722890ffef..e18fc85a68d60 100644 --- a/x-pack/plugins/infra/public/containers/with_options.tsx +++ b/x-pack/plugins/infra/public/containers/with_options.tsx @@ -8,7 +8,7 @@ import moment from 'moment'; import React from 'react'; import { euiPaletteColorBlind } from '@elastic/eui'; -import { InfraFormatterType, InfraOptions, InfraWaffleMapLegendMode } from '../lib/lib'; +import { InfraFormatterType, InfraOptions } from '../lib/lib'; import { RendererFunction } from '../utils/typed_react'; const euiVisColorPalette = euiPaletteColorBlind(); @@ -29,7 +29,7 @@ const initialState = { metric: { type: 'cpu' }, groupBy: [], legend: { - type: InfraWaffleMapLegendMode.gradient, + type: 'gradient', rules: [ { value: 0, diff --git a/x-pack/plugins/infra/public/lib/lib.ts b/x-pack/plugins/infra/public/lib/lib.ts index 9f851e185018b..e4de0caf9bb8b 100644 --- a/x-pack/plugins/infra/public/lib/lib.ts +++ b/x-pack/plugins/infra/public/lib/lib.ts @@ -136,18 +136,13 @@ export interface InfraWaffleMapGradientRule { color: string; } -export enum InfraWaffleMapLegendMode { - step = 'step', - gradient = 'gradient', -} - export interface InfraWaffleMapStepLegend { - type: InfraWaffleMapLegendMode.step; + type: 'step'; rules: InfraWaffleMapStepRule[]; } export interface InfraWaffleMapGradientLegend { - type: InfraWaffleMapLegendMode.gradient; + type: 'gradient'; rules: InfraWaffleMapGradientRule[]; } diff --git a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx index 422eb53148fe6..d592ae3480fc9 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx @@ -25,6 +25,9 @@ import { MetricsSettingsPage } from './settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; import { SourceLoadingPage } from '../../components/source_loading_page'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { WaffleOptionsProvider } from '../inventory_view/hooks/use_waffle_options'; +import { WaffleTimeProvider } from '../inventory_view/hooks/use_waffle_time'; +import { WaffleFiltersProvider } from '../inventory_view/hooks/use_waffle_filters'; import { AlertDropdown } from '../../components/alerting/metrics/alert_dropdown'; export const InfrastructurePage = ({ match }: RouteComponentProps) => { @@ -32,96 +35,101 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { return ( - - - - + + + + + -
+ - - - - - - - - - - + + + + + + + + + + - - - ( - - {({ configuration, createDerivedIndexPattern }) => ( - - - {configuration ? ( - - ) : ( - - )} - - )} - - )} - /> - - - + + + ( + + {({ configuration, createDerivedIndexPattern }) => ( + + + {configuration ? ( + + ) : ( + + )} + + )} + + )} + /> + + + + + + ); }; diff --git a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/index.tsx b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/index.tsx index dbb8b2d8e2952..48cc56388c0f2 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/index.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/index.tsx @@ -8,7 +8,6 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; -import { SnapshotPageContent } from './page_content'; import { SnapshotToolbar } from './toolbar'; import { DocumentTitle } from '../../../components/document_title'; @@ -19,17 +18,14 @@ import { SourceErrorPage } from '../../../components/source_error_page'; import { SourceLoadingPage } from '../../../components/source_loading_page'; import { ViewSourceConfigurationButton } from '../../../components/source_configuration'; 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 { useTrackPageview } from '../../../../../observability/public'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { Layout } from '../../../components/inventory/layout'; import { useLinkProps } from '../../../hooks/use_link_props'; export const SnapshotPage = () => { const uiCapabilities = useKibana().services.application?.capabilities; const { - createDerivedIndexPattern, hasFailedLoadingSource, isLoading, loadSourceFailureMessage, @@ -60,11 +56,8 @@ export const SnapshotPage = () => { ) : metricIndicesExist ? ( <> - - - - + ) : hasFailedLoadingSource ? ( diff --git a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/page_content.tsx b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/page_content.tsx deleted file mode 100644 index 83a4c8d3a497f..0000000000000 --- a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/page_content.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; - -import { WithWaffleFilter } from '../../../containers/waffle/with_waffle_filters'; -import { WithWaffleOptions } from '../../../containers/waffle/with_waffle_options'; -import { WithWaffleTime } from '../../../containers/waffle/with_waffle_time'; -import { WithOptions } from '../../../containers/with_options'; -import { WithSource } from '../../../containers/with_source'; -import { Layout } from '../../../components/inventory/layout'; - -export const SnapshotPageContent: React.FC = () => ( - - {({ configuration, createDerivedIndexPattern, sourceId }) => ( - - {({ wafflemap }) => ( - - {({ filterQueryAsJson, applyFilterQuery }) => ( - - {({ currentTime }) => ( - - {({ - metric, - groupBy, - nodeType, - view, - changeView, - autoBounds, - boundsOverride, - accountId, - region, - }) => ( - - )} - - )} - - )} - - )} - - )} - -); diff --git a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx index 3606580e86504..ccdaa5e8dc785 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx @@ -5,92 +5,24 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React from 'react'; -import { AutocompleteField } from '../../../components/autocomplete_field'; import { Toolbar } from '../../../components/eui/toolbar'; import { WaffleTimeControls } from '../../../components/waffle/waffle_time_controls'; -import { WithWaffleFilter } from '../../../containers/waffle/with_waffle_filters'; -import { WithWaffleTime } from '../../../containers/waffle/with_waffle_time'; -import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion'; -import { WithSource } from '../../../containers/with_source'; -import { WithWaffleOptions } from '../../../containers/waffle/with_waffle_options'; import { WaffleInventorySwitcher } from '../../../components/waffle/waffle_inventory_switcher'; +import { SearchBar } from '../../inventory_view/compontents/search_bar'; export const SnapshotToolbar = () => ( - - {({ - changeMetric, - changeNodeType, - changeGroupBy, - changeAccount, - changeRegion, - changeCustomMetrics, - nodeType, - }) => ( - - )} - + - - {({ createDerivedIndexPattern }) => ( - - {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( - - {({ - applyFilterQueryFromKueryExpression, - filterQueryDraft, - isFilterQueryDraftValid, - setFilterQueryDraftFromKueryExpression, - }) => ( - - )} - - )} - - )} - + - - {({ currentTime, isAutoReloading, jumpToTime, startAutoReload, stopAutoReload }) => ( - - )} - + diff --git a/x-pack/plugins/infra/public/pages/inventory_view/compontents/search_bar.tsx b/x-pack/plugins/infra/public/pages/inventory_view/compontents/search_bar.tsx new file mode 100644 index 0000000000000..f4fde46d434f8 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/compontents/search_bar.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useContext } from 'react'; +import { i18n } from '@kbn/i18n'; +import { Source } from '../../../containers/source'; +import { AutocompleteField } from '../../../components/autocomplete_field'; +import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion'; +import { useWaffleFiltersContext } from '../hooks/use_waffle_filters'; + +export const SearchBar = () => { + const { createDerivedIndexPattern } = useContext(Source.Context); + const { + applyFilterQueryFromKueryExpression, + filterQueryDraft, + isFilterQueryDraftValid, + setFilterQueryDraftFromKueryExpression, + } = useWaffleFiltersContext(); + return ( + + {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( + + )} + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_filters.ts b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_filters.ts new file mode 100644 index 0000000000000..02c079dcaddc4 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_filters.ts @@ -0,0 +1,93 @@ +/* + * 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 { useState, useMemo, useCallback, useEffect } from 'react'; +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import createContainter from 'constate'; +import { useUrlState } from '../../../utils/use_url_state'; +import { useSourceContext } from '../../../containers/source'; +import { convertKueryToElasticSearchQuery } from '../../../utils/kuery'; +import { esKuery } from '../../../../../../../src/plugins/data/public'; + +const validateKuery = (expression: string) => { + try { + esKuery.fromKueryExpression(expression); + } catch (err) { + return false; + } + return true; +}; + +export const DEFAULT_WAFFLE_FILTERS_STATE: WaffleFiltersState = { kind: 'kuery', expression: '' }; + +export const useWaffleFilters = () => { + const { createDerivedIndexPattern } = useSourceContext(); + const indexPattern = createDerivedIndexPattern('metrics'); + + const [urlState, setUrlState] = useUrlState({ + defaultState: DEFAULT_WAFFLE_FILTERS_STATE, + decodeUrlState, + encodeUrlState, + urlStateKey: 'waffleFilter', + }); + + const [state, setState] = useState(urlState); + + useEffect(() => setUrlState(state), [setUrlState, state]); + + const [filterQueryDraft, setFilterQueryDraft] = useState(urlState.expression); + + const filterQueryAsJson = useMemo( + () => convertKueryToElasticSearchQuery(urlState.expression, indexPattern), + [indexPattern, urlState.expression] + ); + + const applyFilterQueryFromKueryExpression = useCallback( + (expression: string) => { + setState(previous => ({ + ...previous, + kind: 'kuery', + expression, + })); + }, + [setState] + ); + + const applyFilterQuery = useCallback((filterQuery: WaffleFiltersState) => { + setState(filterQuery); + setFilterQueryDraft(filterQuery.expression); + }, []); + + const isFilterQueryDraftValid = useMemo(() => validateKuery(filterQueryDraft), [ + filterQueryDraft, + ]); + + return { + filterQuery: urlState, + filterQueryDraft, + filterQueryAsJson, + applyFilterQuery, + setFilterQueryDraftFromKueryExpression: setFilterQueryDraft, + applyFilterQueryFromKueryExpression, + isFilterQueryDraftValid, + setWaffleFiltersState: applyFilterQuery, + }; +}; + +export const WaffleFiltersStateRT = rt.type({ + kind: rt.literal('kuery'), + expression: rt.string, +}); + +export type WaffleFiltersState = rt.TypeOf; +const encodeUrlState = WaffleFiltersStateRT.encode; +const decodeUrlState = (value: unknown) => + pipe(WaffleFiltersStateRT.decode(value), fold(constant(undefined), identity)); +export const WaffleFilters = createContainter(useWaffleFilters); +export const [WaffleFiltersProvider, useWaffleFiltersContext] = WaffleFilters; diff --git a/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_options.ts b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_options.ts new file mode 100644 index 0000000000000..2853917d5f683 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_options.ts @@ -0,0 +1,147 @@ +/* + * 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, useState, useEffect } from 'react'; +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import createContainer from 'constate'; +import { + SnapshotMetricInput, + SnapshotGroupBy, + SnapshotCustomMetricInput, + SnapshotMetricInputRT, + SnapshotGroupByRT, + SnapshotCustomMetricInputRT, +} from '../../../../common/http_api/snapshot_api'; +import { useUrlState } from '../../../utils/use_url_state'; +import { InventoryItemType, ItemTypeRT } from '../../../../common/inventory_models/types'; + +export const DEFAULT_WAFFLE_OPTIONS_STATE: WaffleOptionsState = { + metric: { type: 'cpu' }, + groupBy: [], + nodeType: 'host', + view: 'map', + customOptions: [], + boundsOverride: { max: 1, min: 0 }, + autoBounds: true, + accountId: '', + region: '', + customMetrics: [], +}; + +export const useWaffleOptions = () => { + const [urlState, setUrlState] = useUrlState({ + defaultState: DEFAULT_WAFFLE_OPTIONS_STATE, + decodeUrlState, + encodeUrlState, + urlStateKey: 'waffleOptions', + }); + + const [state, setState] = useState(urlState); + + useEffect(() => setUrlState(state), [setUrlState, state]); + + const changeMetric = useCallback( + (metric: SnapshotMetricInput) => setState(previous => ({ ...previous, metric })), + [setState] + ); + + const changeGroupBy = useCallback( + (groupBy: SnapshotGroupBy) => setState(previous => ({ ...previous, groupBy })), + [setState] + ); + + const changeNodeType = useCallback( + (nodeType: InventoryItemType) => setState(previous => ({ ...previous, nodeType })), + [setState] + ); + + const changeView = useCallback((view: string) => setState(previous => ({ ...previous, view })), [ + setState, + ]); + + const changeCustomOptions = useCallback( + (customOptions: Array<{ text: string; field: string }>) => + setState(previous => ({ ...previous, customOptions })), + [setState] + ); + + const changeAutoBounds = useCallback( + (autoBounds: boolean) => setState(previous => ({ ...previous, autoBounds })), + [setState] + ); + + const changeBoundsOverride = useCallback( + (boundsOverride: { min: number; max: number }) => + setState(previous => ({ ...previous, boundsOverride })), + [setState] + ); + + const changeAccount = useCallback( + (accountId: string) => setState(previous => ({ ...previous, accountId })), + [setState] + ); + + const changeRegion = useCallback( + (region: string) => setState(previous => ({ ...previous, region })), + [setState] + ); + + const changeCustomMetrics = useCallback( + (customMetrics: SnapshotCustomMetricInput[]) => { + setState(previous => ({ ...previous, customMetrics })); + }, + [setState] + ); + + return { + ...state, + changeMetric, + changeGroupBy, + changeNodeType, + changeView, + changeCustomOptions, + changeAutoBounds, + changeBoundsOverride, + changeAccount, + changeRegion, + changeCustomMetrics, + setWaffleOptionsState: setState, + }; +}; + +export const WaffleOptionsStateRT = rt.type({ + metric: SnapshotMetricInputRT, + groupBy: SnapshotGroupByRT, + nodeType: ItemTypeRT, + view: rt.string, + customOptions: rt.array( + rt.type({ + text: rt.string, + field: rt.string, + }) + ), + boundsOverride: rt.type({ + min: rt.number, + max: rt.number, + }), + autoBounds: rt.boolean, + accountId: rt.string, + region: rt.string, + customMetrics: rt.array(SnapshotCustomMetricInputRT), +}); + +export type WaffleOptionsState = rt.TypeOf; +const encodeUrlState = (state: WaffleOptionsState) => { + return WaffleOptionsStateRT.encode(state); +}; +const decodeUrlState = (value: unknown) => + pipe(WaffleOptionsStateRT.decode(value), fold(constant(undefined), identity)); + +export const WaffleOptions = createContainer(useWaffleOptions); +export const [WaffleOptionsProvider, useWaffleOptionsContext] = WaffleOptions; diff --git a/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_time.ts b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_time.ts new file mode 100644 index 0000000000000..051b5e598cb75 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_time.ts @@ -0,0 +1,76 @@ +/* + * 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, useState, useEffect } from 'react'; +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import createContainer from 'constate'; +import { useUrlState } from '../../../utils/use_url_state'; + +export const DEFAULT_WAFFLE_TIME_STATE: WaffleTimeState = { + currentTime: Date.now(), + isAutoReloading: false, +}; + +export const useWaffleTime = () => { + const [urlState, setUrlState] = useUrlState({ + defaultState: DEFAULT_WAFFLE_TIME_STATE, + decodeUrlState, + encodeUrlState, + urlStateKey: 'waffleTime', + }); + + const [state, setState] = useState(urlState); + + useEffect(() => setUrlState(state), [setUrlState, state]); + + const { currentTime, isAutoReloading } = urlState; + + const startAutoReload = useCallback(() => { + setState(previous => ({ ...previous, isAutoReloading: true })); + }, [setState]); + + const stopAutoReload = useCallback(() => { + setState(previous => ({ ...previous, isAutoReloading: false })); + }, [setState]); + + const jumpToTime = useCallback( + (time: number) => { + setState(previous => ({ ...previous, currentTime: time })); + }, + [setState] + ); + + const currentTimeRange = { + from: currentTime - 1000 * 60 * 5, + interval: '1m', + to: currentTime, + }; + + return { + currentTime, + currentTimeRange, + isAutoReloading, + startAutoReload, + stopAutoReload, + jumpToTime, + setWaffleTimeState: setState, + }; +}; + +export const WaffleTimeStateRT = rt.type({ + currentTime: rt.number, + isAutoReloading: rt.boolean, +}); + +export type WaffleTimeState = rt.TypeOf; +const encodeUrlState = WaffleTimeStateRT.encode; +const decodeUrlState = (value: unknown) => + pipe(WaffleTimeStateRT.decode(value), fold(constant(undefined), identity)); + +export const WaffleTime = createContainer(useWaffleTime); +export const [WaffleTimeProvider, useWaffleTimeContext] = WaffleTime; diff --git a/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_view_state.ts b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_view_state.ts new file mode 100644 index 0000000000000..869560b2b8709 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_view_state.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 { useCallback } from 'react'; +import { + useWaffleOptionsContext, + DEFAULT_WAFFLE_OPTIONS_STATE, + WaffleOptionsState, +} from './use_waffle_options'; +import { useWaffleTimeContext, DEFAULT_WAFFLE_TIME_STATE } from './use_waffle_time'; +import { + useWaffleFiltersContext, + DEFAULT_WAFFLE_FILTERS_STATE, + WaffleFiltersState, +} from './use_waffle_filters'; + +export const useWaffleViewState = () => { + const { + metric, + groupBy, + nodeType, + view, + customOptions, + customMetrics, + boundsOverride, + autoBounds, + accountId, + region, + setWaffleOptionsState, + } = useWaffleOptionsContext(); + const { currentTime, isAutoReloading, setWaffleTimeState } = useWaffleTimeContext(); + const { filterQuery, setWaffleFiltersState } = useWaffleFiltersContext(); + + const viewState: WaffleViewState = { + metric, + groupBy, + nodeType, + view, + customOptions, + customMetrics, + boundsOverride, + autoBounds, + accountId, + region, + time: currentTime, + autoReload: isAutoReloading, + filterQuery, + }; + + const defaultViewState: WaffleViewState = { + ...DEFAULT_WAFFLE_OPTIONS_STATE, + filterQuery: DEFAULT_WAFFLE_FILTERS_STATE, + time: DEFAULT_WAFFLE_TIME_STATE.currentTime, + autoReload: DEFAULT_WAFFLE_TIME_STATE.isAutoReloading, + }; + + const onViewChange = useCallback( + (newState: WaffleViewState) => { + setWaffleOptionsState({ + metric: newState.metric, + groupBy: newState.groupBy, + nodeType: newState.nodeType, + view: newState.view, + customOptions: newState.customOptions, + customMetrics: newState.customMetrics, + boundsOverride: newState.boundsOverride, + autoBounds: newState.autoBounds, + accountId: newState.accountId, + region: newState.region, + }); + if (newState.time) { + setWaffleTimeState({ + currentTime: newState.time, + isAutoReloading: newState.autoReload, + }); + } + setWaffleFiltersState(newState.filterQuery); + }, + [setWaffleOptionsState, setWaffleTimeState, setWaffleFiltersState] + ); + + return { + viewState, + defaultViewState, + onViewChange, + }; +}; + +export type WaffleViewState = WaffleOptionsState & { + time: number; + autoReload: boolean; + filterQuery: WaffleFiltersState; +}; diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx index 01b02f1acbbf2..b1dab3bd3f673 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import { replaceMetricTimeInQueryString } from '../metrics/containers/with_metrics_time'; +import { replaceMetricTimeInQueryString } from '../metrics/hooks/use_metrics_time'; import { useHostIpToName } from './use_host_ip_to_name'; import { getFromFromLocation, getToFromLocation } from './query_params'; import { LoadingPage } from '../../components/loading_page'; diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx index 9eae632756a3f..72a41f5264244 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; -import { replaceMetricTimeInQueryString } from '../metrics/containers/with_metrics_time'; +import { replaceMetricTimeInQueryString } from '../metrics/hooks/use_metrics_time'; import { getFromFromLocation, getToFromLocation } from './query_params'; import { InventoryItemType } from '../../../common/inventory_models/types'; import { LinkDescriptor } from '../../hooks/use_link_props'; diff --git a/x-pack/plugins/infra/public/pages/metrics/components/node_details_page.tsx b/x-pack/plugins/infra/public/pages/metrics/components/node_details_page.tsx index ea91c53faf675..dd2a5f2bdb39e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/node_details_page.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/node_details_page.tsx @@ -22,7 +22,7 @@ import { MetricsTimeControls } from './time_controls'; import { SideNavContext, NavItem } from '../lib/side_nav_context'; import { PageBody } from './page_body'; import { euiStyled } from '../../../../../observability/public'; -import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { MetricsTimeInput } from '../hooks/use_metrics_time'; import { InfraMetadata } from '../../../../common/http_api/metadata_api'; import { PageError } from './page_error'; import { MetadataContext } from '../../../pages/metrics/containers/metadata_context'; @@ -94,7 +94,7 @@ export const NodeDetailsPage = (props: Props) => { setRefreshInterval={props.setRefreshInterval} onChangeTimeRange={props.setTimeRange} setAutoReload={props.setAutoReload} - onRefresh={props.triggerRefresh} + onRefresh={refetch} /> diff --git a/x-pack/plugins/infra/public/pages/metrics/components/page_body.tsx b/x-pack/plugins/infra/public/pages/metrics/components/page_body.tsx index 414b9c60adee3..e651d6b92d981 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/page_body.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/page_body.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { findLayout } from '../../../../common/inventory_models/layouts'; import { InventoryItemType } from '../../../../common/inventory_models/types'; -import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { MetricsTimeInput } from '../hooks/use_metrics_time'; import { InfraLoadingPanel } from '../../../components/loading'; import { NoData } from '../../../components/empty_states'; import { NodeDetailsMetricData } from '../../../../common/http_api/node_details_api'; @@ -19,9 +19,9 @@ interface Props { refetch: () => void; type: InventoryItemType; metrics: NodeDetailsMetricData[]; - onChangeRangeTime?: (time: MetricsTimeInput) => void; - isLiveStreaming?: boolean; - stopLiveStreaming?: () => void; + onChangeRangeTime: (time: MetricsTimeInput) => void; + isLiveStreaming: boolean; + stopLiveStreaming: () => void; } export const PageBody = ({ diff --git a/x-pack/plugins/infra/public/pages/metrics/components/section.tsx b/x-pack/plugins/infra/public/pages/metrics/components/section.tsx index 2f9ed9f54df82..68003737a1f14 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/section.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/section.tsx @@ -41,6 +41,9 @@ export const Section: FunctionComponent = ({ if (metric === null) { return accumulatedChildren; } + if (!child.props.label) { + return accumulatedChildren; + } return [ ...accumulatedChildren, { diff --git a/x-pack/plugins/infra/public/pages/metrics/components/time_controls.test.tsx b/x-pack/plugins/infra/public/pages/metrics/components/time_controls.test.tsx index 91e25fd8ef585..02ba506e8abe1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/time_controls.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/time_controls.test.tsx @@ -19,7 +19,7 @@ jest.mock('../../../utils/use_kibana_ui_setting', () => ({ import React from 'react'; import { MetricsTimeControls } from './time_controls'; import { mount } from 'enzyme'; -import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { MetricsTimeInput } from '../hooks/use_metrics_time'; describe('MetricsTimeControls', () => { it('should set a valid from and to value for Today', () => { diff --git a/x-pack/plugins/infra/public/pages/metrics/components/time_controls.tsx b/x-pack/plugins/infra/public/pages/metrics/components/time_controls.tsx index b1daaa0320fab..cdbdc9bb7ecdb 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/time_controls.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/time_controls.tsx @@ -7,7 +7,7 @@ import { EuiSuperDatePicker, OnRefreshChangeProps, OnTimeChangeProps } from '@elastic/eui'; import React, { useCallback } from 'react'; import { euiStyled } from '../../../../../observability/public'; -import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { MetricsTimeInput } from '../hooks/use_metrics_time'; import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting'; import { mapKibanaQuickRangesToDatePickerRanges } from '../../../utils/map_timepicker_quickranges_to_datepicker_ranges'; @@ -61,8 +61,8 @@ export const MetricsTimeControls = (props: MetricsTimeControlsProps) => { return ( void; - refreshInterval: number; - setRefreshInterval: (refreshInterval: number) => void; - isAutoReloading: boolean; - setAutoReload: (isAutoReloading: boolean) => void; - lastRefresh: number; - triggerRefresh: () => void; -} - -const parseRange = (range: MetricsTimeInput) => { - const parsedFrom = dateMath.parse(range.from); - const parsedTo = dateMath.parse(range.to, { roundUp: true }); - return { - ...range, - from: - (parsedFrom && parsedFrom.valueOf()) || - moment() - .subtract(1, 'hour') - .valueOf(), - to: (parsedTo && parsedTo.valueOf()) || moment().valueOf(), - }; -}; - -export const useMetricsTime = () => { - const defaultRange = { - from: 'now-1h', - to: 'now', - interval: '>=1m', - }; - const [isAutoReloading, setAutoReload] = useState(false); - const [refreshInterval, setRefreshInterval] = useState(5000); - const [lastRefresh, setLastRefresh] = useState(moment().valueOf()); - const [timeRange, setTimeRange] = useState(defaultRange); - - const [parsedTimeRange, setParsedTimeRange] = useState(parseRange(defaultRange)); - - const updateTimeRange = useCallback((range: MetricsTimeInput) => { - setTimeRange(range); - setParsedTimeRange(parseRange(range)); - }, []); - - return { - timeRange, - setTimeRange: updateTimeRange, - parsedTimeRange, - refreshInterval, - setRefreshInterval, - isAutoReloading, - setAutoReload, - lastRefresh, - triggerRefresh: useCallback(() => setLastRefresh(moment().valueOf()), [setLastRefresh]), - }; -}; - -export const MetricsTimeContainer = createContainer(useMetricsTime); - -interface WithMetricsTimeProps { - children: (args: MetricsTimeState) => React.ReactElement; -} -export const WithMetricsTime: React.FunctionComponent = ({ - children, -}: WithMetricsTimeProps) => { - const metricsTimeState = useContext(MetricsTimeContainer.Context); - return children({ ...metricsTimeState }); -}; - -/** - * Url State - */ - -interface MetricsTimeUrlState { - time?: MetricsTimeState['timeRange']; - autoReload?: boolean; - refreshInterval?: number; -} - -export const WithMetricsTimeUrlState = () => ( - - {({ - timeRange, - setTimeRange, - refreshInterval, - setRefreshInterval, - isAutoReloading, - setAutoReload, - }) => ( - { - if (newUrlState && newUrlState.time) { - setTimeRange(newUrlState.time); - } - if (newUrlState && newUrlState.autoReload) { - setAutoReload(true); - } else if ( - newUrlState && - typeof newUrlState.autoReload !== 'undefined' && - !newUrlState.autoReload - ) { - setAutoReload(false); - } - if (newUrlState && newUrlState.refreshInterval) { - setRefreshInterval(newUrlState.refreshInterval); - } - }} - onInitialize={initialUrlState => { - if (initialUrlState && initialUrlState.time) { - if ( - timeRange.from !== initialUrlState.time.from || - timeRange.to !== initialUrlState.time.to || - timeRange.interval !== initialUrlState.time.interval - ) { - setTimeRange(initialUrlState.time); - } - } - if (initialUrlState && initialUrlState.autoReload) { - setAutoReload(true); - } - if (initialUrlState && initialUrlState.refreshInterval) { - setRefreshInterval(initialUrlState.refreshInterval); - } - }} - /> - )} - -); - -const mapToUrlState = (value: any): MetricsTimeUrlState | undefined => - value - ? { - time: mapToTimeUrlState(value.time), - autoReload: mapToAutoReloadUrlState(value.autoReload), - refreshInterval: mapToRefreshInterval(value.refreshInterval), - } - : undefined; - -const MetricsTimeRT = rt.type({ - from: rt.union([rt.string, rt.number]), - to: rt.union([rt.string, rt.number]), - interval: rt.string, -}); - -const mapToTimeUrlState = (value: any) => { - const result = MetricsTimeRT.decode(value); - if (isRight(result)) { - const resultValue = result.right; - const to = isNumber(resultValue.to) ? moment(resultValue.to).toISOString() : resultValue.to; - const from = isNumber(resultValue.from) - ? moment(resultValue.from).toISOString() - : resultValue.from; - return { ...resultValue, from, to }; - } - return undefined; -}; - -const mapToAutoReloadUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined); - -const mapToRefreshInterval = (value: any) => (typeof value === 'number' ? value : undefined); - -export const replaceMetricTimeInQueryString = (from: number, to: number) => - Number.isNaN(from) || Number.isNaN(to) - ? (value: string) => value - : replaceStateKeyInQueryString('metricTime', { - autoReload: false, - time: { - interval: '>=1m', - from: moment(from).toISOString(), - to: moment(to).toISOString(), - }, - }); diff --git a/x-pack/plugins/infra/public/pages/metrics/containers/metrics_time.test.tsx b/x-pack/plugins/infra/public/pages/metrics/hooks/metrics_time.test.tsx similarity index 96% rename from x-pack/plugins/infra/public/pages/metrics/containers/metrics_time.test.tsx rename to x-pack/plugins/infra/public/pages/metrics/hooks/metrics_time.test.tsx index 350fa90810935..17fcc05406470 100644 --- a/x-pack/plugins/infra/public/pages/metrics/containers/metrics_time.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hooks/metrics_time.test.tsx @@ -6,7 +6,7 @@ import { mountHook } from 'test_utils/enzyme_helpers'; -import { useMetricsTime } from './with_metrics_time'; +import { useMetricsTime } from './use_metrics_time'; describe('useMetricsTime hook', () => { describe('timeRange state', () => { diff --git a/x-pack/plugins/infra/public/pages/metrics/hooks/use_metrics_time.ts b/x-pack/plugins/infra/public/pages/metrics/hooks/use_metrics_time.ts new file mode 100644 index 0000000000000..2ed86863535ff --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hooks/use_metrics_time.ts @@ -0,0 +1,121 @@ +/* + * 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 createContainer from 'constate'; +import { useState, useCallback, useEffect } from 'react'; +import moment from 'moment'; +import dateMath from '@elastic/datemath'; +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import { useUrlState } from '../../../utils/use_url_state'; +import { replaceStateKeyInQueryString } from '../../../utils/url_state'; + +const parseRange = (range: MetricsTimeInput) => { + const parsedFrom = dateMath.parse(range.from.toString()); + const parsedTo = dateMath.parse(range.to.toString(), { roundUp: true }); + return { + ...range, + from: + (parsedFrom && parsedFrom.valueOf()) || + moment() + .subtract(1, 'hour') + .valueOf(), + to: (parsedTo && parsedTo.valueOf()) || moment().valueOf(), + }; +}; + +const DEFAULT_TIMERANGE: MetricsTimeInput = { + from: 'now-1h', + to: 'now', + interval: '>=1m', +}; + +const DEFAULT_URL_STATE: MetricsTimeUrlState = { + time: DEFAULT_TIMERANGE, + autoReload: false, + refreshInterval: 5000, +}; + +export const useMetricsTime = () => { + const [urlState, setUrlState] = useUrlState({ + defaultState: DEFAULT_URL_STATE, + decodeUrlState, + encodeUrlState, + urlStateKey: 'metricTime', + }); + + const [isAutoReloading, setAutoReload] = useState(urlState.autoReload || false); + const [refreshInterval, setRefreshInterval] = useState(urlState.refreshInterval || 5000); + const [lastRefresh, setLastRefresh] = useState(moment().valueOf()); + const [timeRange, setTimeRange] = useState(urlState.time || DEFAULT_TIMERANGE); + + useEffect(() => { + const newState = { + time: timeRange, + autoReload: isAutoReloading, + refreshInterval, + }; + return setUrlState(newState); + }, [isAutoReloading, refreshInterval, setUrlState, timeRange]); + + const [parsedTimeRange, setParsedTimeRange] = useState( + parseRange(urlState.time || DEFAULT_TIMERANGE) + ); + + const updateTimeRange = useCallback((range: MetricsTimeInput) => { + setTimeRange(range); + setParsedTimeRange(parseRange(range)); + }, []); + + return { + timeRange, + setTimeRange: updateTimeRange, + parsedTimeRange, + refreshInterval, + setRefreshInterval, + isAutoReloading, + setAutoReload, + lastRefresh, + triggerRefresh: useCallback(() => { + return setLastRefresh(moment().valueOf()); + }, [setLastRefresh]), + }; +}; + +export const MetricsTimeInputRT = rt.type({ + from: rt.union([rt.string, rt.number]), + to: rt.union([rt.string, rt.number]), + interval: rt.string, +}); +export type MetricsTimeInput = rt.TypeOf; + +export const MetricsTimeUrlStateRT = rt.partial({ + time: MetricsTimeInputRT, + autoReload: rt.boolean, + refreshInterval: rt.number, +}); +export type MetricsTimeUrlState = rt.TypeOf; + +const encodeUrlState = MetricsTimeUrlStateRT.encode; +const decodeUrlState = (value: unknown) => + pipe(MetricsTimeUrlStateRT.decode(value), fold(constant(undefined), identity)); + +export const replaceMetricTimeInQueryString = (from: number, to: number) => + Number.isNaN(from) || Number.isNaN(to) + ? (value: string) => value + : replaceStateKeyInQueryString('metricTime', { + autoReload: false, + time: { + interval: '>=1m', + from: moment(from).toISOString(), + to: moment(to).toISOString(), + }, + }); + +export const MetricsTimeContainer = createContainer(useMetricsTime); +export const [MetricsTimeProvider, useMetricsTimeContext] = MetricsTimeContainer; diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 52c9825a4d614..531be40d2dc43 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -9,7 +9,6 @@ import { euiStyled, EuiTheme, withTheme } from '../../../../observability/public import { DocumentTitle } from '../../components/document_title'; import { Header } from '../../components/header'; import { ColumnarPage, PageContent } from '../../components/page'; -import { WithMetricsTime, WithMetricsTimeUrlState } from './containers/with_metrics_time'; import { withMetricPageProviders } from './page_providers'; import { useMetadata } from '../../containers/metadata/use_metadata'; import { Source } from '../../containers/source'; @@ -19,6 +18,7 @@ import { NavItem } from './lib/side_nav_context'; import { NodeDetailsPage } from './components/node_details_page'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { InventoryItemType } from '../../../common/inventory_models/types'; +import { useMetricsTimeContext } from './hooks/use_metrics_time'; import { useLinkProps } from '../../hooks/use_link_props'; const DetailPageContent = euiStyled(PageContent)` @@ -37,19 +37,29 @@ interface Props { } export const MetricDetail = withMetricPageProviders( - withTheme(({ match, theme }: Props) => { + withTheme(({ match }: Props) => { const uiCapabilities = useKibana().services.application?.capabilities; const nodeId = match.params.node; const nodeType = match.params.type as InventoryItemType; const inventoryModel = findInventoryModel(nodeType); const { sourceId } = useContext(Source.Context); + const { + timeRange, + parsedTimeRange, + setTimeRange, + refreshInterval, + setRefreshInterval, + isAutoReloading, + setAutoReload, + triggerRefresh, + } = useMetricsTimeContext(); const { name, filteredRequiredMetrics, loading: metadataLoading, cloudId, metadata, - } = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId); + } = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId, parsedTimeRange); const [sideNav, setSideNav] = useState([]); @@ -90,58 +100,41 @@ export const MetricDetail = withMetricPageProviders( } return ( - - {({ - timeRange, - parsedTimeRange, - setTimeRange, - refreshInterval, - setRefreshInterval, - isAutoReloading, - setAutoReload, - triggerRefresh, - }) => ( - -
- - +
+ + + {metadata ? ( + - - {metadata ? ( - - ) : null} - - - )} - + ) : null} + + ); }) ); diff --git a/x-pack/plugins/infra/public/pages/metrics/page_providers.tsx b/x-pack/plugins/infra/public/pages/metrics/page_providers.tsx index 0abbd597dd65c..d3f10adec06ed 100644 --- a/x-pack/plugins/infra/public/pages/metrics/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/page_providers.tsx @@ -6,15 +6,15 @@ import React from 'react'; -import { MetricsTimeContainer } from './containers/with_metrics_time'; import { Source } from '../../containers/source'; +import { MetricsTimeProvider } from './hooks/use_metrics_time'; export const withMetricPageProviders = (Component: React.ComponentType) => ( props: T ) => ( - + - + ); diff --git a/x-pack/plugins/infra/public/pages/metrics/types.ts b/x-pack/plugins/infra/public/pages/metrics/types.ts index fd6243292ec07..2cc261df28977 100644 --- a/x-pack/plugins/infra/public/pages/metrics/types.ts +++ b/x-pack/plugins/infra/public/pages/metrics/types.ts @@ -7,7 +7,7 @@ import rt from 'io-ts'; import { EuiTheme } from '../../../../observability/public'; import { InventoryFormatterTypeRT } from '../../../common/inventory_models/types'; -import { MetricsTimeInput } from './containers/with_metrics_time'; +import { MetricsTimeInput } from './hooks/use_metrics_time'; import { NodeDetailsMetricData } from '../../../common/http_api/node_details_api'; export interface LayoutProps { diff --git a/x-pack/plugins/infra/public/store/actions.ts b/x-pack/plugins/infra/public/store/actions.ts deleted file mode 100644 index 8a5d1f6c668d0..0000000000000 --- a/x-pack/plugins/infra/public/store/actions.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { waffleFilterActions, waffleTimeActions, waffleOptionsActions } from './local'; diff --git a/x-pack/plugins/infra/public/store/local/actions.ts b/x-pack/plugins/infra/public/store/local/actions.ts deleted file mode 100644 index 1c79d5a515cd4..0000000000000 --- a/x-pack/plugins/infra/public/store/local/actions.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { waffleFilterActions } from './waffle_filter'; -export { waffleTimeActions } from './waffle_time'; -export { waffleOptionsActions } from './waffle_options'; diff --git a/x-pack/plugins/infra/public/store/local/reducer.ts b/x-pack/plugins/infra/public/store/local/reducer.ts deleted file mode 100644 index 9e194a5d37f49..0000000000000 --- a/x-pack/plugins/infra/public/store/local/reducer.ts +++ /dev/null @@ -1,33 +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 { combineReducers } from 'redux'; - -import { initialWaffleFilterState, waffleFilterReducer, WaffleFilterState } from './waffle_filter'; -import { - initialWaffleOptionsState, - waffleOptionsReducer, - WaffleOptionsState, -} from './waffle_options'; -import { initialWaffleTimeState, waffleTimeReducer, WaffleTimeState } from './waffle_time'; - -export interface LocalState { - waffleFilter: WaffleFilterState; - waffleTime: WaffleTimeState; - waffleMetrics: WaffleOptionsState; -} - -export const initialLocalState: LocalState = { - waffleFilter: initialWaffleFilterState, - waffleTime: initialWaffleTimeState, - waffleMetrics: initialWaffleOptionsState, -}; - -export const localReducer = combineReducers({ - waffleFilter: waffleFilterReducer, - waffleTime: waffleTimeReducer, - waffleMetrics: waffleOptionsReducer, -}); diff --git a/x-pack/plugins/infra/public/store/local/selectors.ts b/x-pack/plugins/infra/public/store/local/selectors.ts deleted file mode 100644 index 56ffc53c2bc72..0000000000000 --- a/x-pack/plugins/infra/public/store/local/selectors.ts +++ /dev/null @@ -1,26 +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 { globalizeSelectors } from '../../utils/typed_redux'; -import { LocalState } from './reducer'; -import { waffleFilterSelectors as innerWaffleFilterSelectors } from './waffle_filter'; -import { waffleOptionsSelectors as innerWaffleOptionsSelectors } from './waffle_options'; -import { waffleTimeSelectors as innerWaffleTimeSelectors } from './waffle_time'; - -export const waffleFilterSelectors = globalizeSelectors( - (state: LocalState) => state.waffleFilter, - innerWaffleFilterSelectors -); - -export const waffleTimeSelectors = globalizeSelectors( - (state: LocalState) => state.waffleTime, - innerWaffleTimeSelectors -); - -export const waffleOptionsSelectors = globalizeSelectors( - (state: LocalState) => state.waffleMetrics, - innerWaffleOptionsSelectors -); diff --git a/x-pack/plugins/infra/public/store/local/waffle_filter/actions.ts b/x-pack/plugins/infra/public/store/local/waffle_filter/actions.ts deleted file mode 100644 index a23f9b3108b5b..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_filter/actions.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import actionCreatorFactory from 'typescript-fsa'; - -import { FilterQuery, SerializedFilterQuery } from './reducer'; - -const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_filter'); - -export const setWaffleFilterQueryDraft = actionCreator( - 'SET_WAFFLE_FILTER_QUERY_DRAFT' -); - -export const applyWaffleFilterQuery = actionCreator( - 'APPLY_WAFFLE_FILTER_QUERY' -); diff --git a/x-pack/plugins/infra/public/store/local/waffle_filter/index.ts b/x-pack/plugins/infra/public/store/local/waffle_filter/index.ts deleted file mode 100644 index 558314f2aeda8..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_filter/index.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 * as waffleFilterActions from './actions'; -import * as waffleFilterSelectors from './selectors'; - -export { waffleFilterActions, waffleFilterSelectors }; -export * from './reducer'; diff --git a/x-pack/plugins/infra/public/store/local/waffle_filter/reducer.ts b/x-pack/plugins/infra/public/store/local/waffle_filter/reducer.ts deleted file mode 100644 index 912ad96357334..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_filter/reducer.ts +++ /dev/null @@ -1,43 +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 { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; - -import { applyWaffleFilterQuery, setWaffleFilterQueryDraft } from './actions'; - -export interface KueryFilterQuery { - kind: 'kuery'; - expression: string; -} - -export type FilterQuery = KueryFilterQuery; - -export interface SerializedFilterQuery { - query: FilterQuery | null; - serializedQuery: string | null; -} - -export interface WaffleFilterState { - filterQuery: SerializedFilterQuery | null; - filterQueryDraft: KueryFilterQuery | null; -} - -export const initialWaffleFilterState: WaffleFilterState = { - filterQuery: null, - filterQueryDraft: null, -}; - -export const waffleFilterReducer = reducerWithInitialState(initialWaffleFilterState) - .case(setWaffleFilterQueryDraft, (state, filterQueryDraft) => ({ - ...state, - filterQueryDraft, - })) - .case(applyWaffleFilterQuery, (state, filterQuery) => ({ - ...state, - filterQuery, - filterQueryDraft: filterQuery.query, - })) - .build(); diff --git a/x-pack/plugins/infra/public/store/local/waffle_filter/selectors.ts b/x-pack/plugins/infra/public/store/local/waffle_filter/selectors.ts deleted file mode 100644 index 047dabd3f0dd3..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_filter/selectors.ts +++ /dev/null @@ -1,33 +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 { createSelector } from 'reselect'; - -import { esKuery } from '../../../../../../../src/plugins/data/public'; -import { WaffleFilterState } from './reducer'; - -export const selectWaffleFilterQuery = (state: WaffleFilterState) => - state.filterQuery ? state.filterQuery.query : null; - -export const selectWaffleFilterQueryAsJson = (state: WaffleFilterState) => - state.filterQuery ? state.filterQuery.serializedQuery : null; - -export const selectWaffleFilterQueryDraft = (state: WaffleFilterState) => state.filterQueryDraft; - -export const selectIsWaffleFilterQueryDraftValid = createSelector( - selectWaffleFilterQueryDraft, - filterQueryDraft => { - if (filterQueryDraft && filterQueryDraft.kind === 'kuery') { - try { - esKuery.fromKueryExpression(filterQueryDraft.expression); - } catch (err) { - return false; - } - } - - return true; - } -); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts b/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts deleted file mode 100644 index 88229c31b2056..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_options/actions.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 actionCreatorFactory from 'typescript-fsa'; -import { - SnapshotGroupBy, - SnapshotMetricInput, - SnapshotCustomMetricInput, -} from '../../../../common/http_api/snapshot_api'; -import { InventoryItemType } from '../../../../common/inventory_models/types'; -import { InfraGroupByOptions, InfraWaffleMapBounds } from '../../../lib/lib'; - -const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_options'); - -export const changeMetric = actionCreator('CHANGE_METRIC'); -export const changeGroupBy = actionCreator('CHANGE_GROUP_BY'); -export const changeCustomOptions = actionCreator('CHANGE_CUSTOM_OPTIONS'); -export const changeNodeType = actionCreator('CHANGE_NODE_TYPE'); -export const changeView = actionCreator('CHANGE_VIEW'); -export const changeBoundsOverride = actionCreator('CHANGE_BOUNDS_OVERRIDE'); -export const changeAutoBounds = actionCreator('CHANGE_AUTO_BOUNDS'); -export const changeAccount = actionCreator('CHANGE_ACCOUNT'); -export const changeRegion = actionCreator('CHANGE_REGION'); -export const changeCustomMetrics = actionCreator( - 'CHANGE_CUSTOM_METRICS' -); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/index.ts b/x-pack/plugins/infra/public/store/local/waffle_options/index.ts deleted file mode 100644 index 3ecf108eb49d4..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_options/index.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 * as waffleOptionsActions from './actions'; -import * as waffleOptionsSelectors from './selector'; - -export { waffleOptionsActions, waffleOptionsSelectors }; -export * from './reducer'; diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts b/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts deleted file mode 100644 index 3789228a7c16b..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts +++ /dev/null @@ -1,113 +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 { combineReducers } from 'redux'; -import { reducerWithInitialState } from 'typescript-fsa-reducers'; -import { - SnapshotMetricInput, - SnapshotGroupBy, - SnapshotCustomMetricInput, -} from '../../../../common/http_api/snapshot_api'; -import { InfraGroupByOptions, InfraWaffleMapBounds } from '../../../lib/lib'; -import { - changeAutoBounds, - changeBoundsOverride, - changeCustomOptions, - changeGroupBy, - changeMetric, - changeNodeType, - changeView, - changeAccount, - changeRegion, - changeCustomMetrics, -} from './actions'; -import { InventoryItemType } from '../../../../common/inventory_models/types'; - -export interface WaffleOptionsState { - metric: SnapshotMetricInput; - groupBy: SnapshotGroupBy; - nodeType: InventoryItemType; - view: string; - customOptions: InfraGroupByOptions[]; - boundsOverride: InfraWaffleMapBounds; - autoBounds: boolean; - accountId: string; - region: string; - customMetrics: SnapshotCustomMetricInput[]; -} - -export const initialWaffleOptionsState: WaffleOptionsState = { - metric: { type: 'cpu' }, - groupBy: [], - nodeType: 'host', - view: 'map', - customOptions: [], - boundsOverride: { max: 1, min: 0 }, - autoBounds: true, - accountId: '', - region: '', - customMetrics: [], -}; - -const currentMetricReducer = reducerWithInitialState(initialWaffleOptionsState.metric).case( - changeMetric, - (current, target) => target -); - -const currentCustomOptionsReducer = reducerWithInitialState( - initialWaffleOptionsState.customOptions -).case(changeCustomOptions, (current, target) => target); - -const currentGroupByReducer = reducerWithInitialState(initialWaffleOptionsState.groupBy).case( - changeGroupBy, - (current, target) => target -); - -const currentNodeTypeReducer = reducerWithInitialState(initialWaffleOptionsState.nodeType).case( - changeNodeType, - (current, target) => target -); - -const currentViewReducer = reducerWithInitialState(initialWaffleOptionsState.view).case( - changeView, - (current, target) => target -); - -const currentBoundsOverrideReducer = reducerWithInitialState( - initialWaffleOptionsState.boundsOverride -).case(changeBoundsOverride, (current, target) => target); - -const currentAutoBoundsReducer = reducerWithInitialState(initialWaffleOptionsState.autoBounds).case( - changeAutoBounds, - (current, target) => target -); - -const currentAccountIdReducer = reducerWithInitialState(initialWaffleOptionsState.accountId).case( - changeAccount, - (current, target) => target -); - -const currentRegionReducer = reducerWithInitialState(initialWaffleOptionsState.region).case( - changeRegion, - (current, target) => target -); - -const currentCustomMetricsReducer = reducerWithInitialState( - initialWaffleOptionsState.customMetrics -).case(changeCustomMetrics, (current, target) => target); - -export const waffleOptionsReducer = combineReducers({ - metric: currentMetricReducer, - groupBy: currentGroupByReducer, - nodeType: currentNodeTypeReducer, - view: currentViewReducer, - customOptions: currentCustomOptionsReducer, - boundsOverride: currentBoundsOverrideReducer, - autoBounds: currentAutoBoundsReducer, - accountId: currentAccountIdReducer, - region: currentRegionReducer, - customMetrics: currentCustomMetricsReducer, -}); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts b/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts deleted file mode 100644 index 4487af156df97..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { WaffleOptionsState } from './reducer'; - -export const selectMetric = (state: WaffleOptionsState) => state.metric; -export const selectGroupBy = (state: WaffleOptionsState) => state.groupBy; -export const selectCustomOptions = (state: WaffleOptionsState) => state.customOptions; -export const selectNodeType = (state: WaffleOptionsState) => state.nodeType; -export const selectView = (state: WaffleOptionsState) => state.view; -export const selectBoundsOverride = (state: WaffleOptionsState) => state.boundsOverride; -export const selectAutoBounds = (state: WaffleOptionsState) => state.autoBounds; -export const selectAccountId = (state: WaffleOptionsState) => state.accountId; -export const selectRegion = (state: WaffleOptionsState) => state.region; -export const selectCustomMetrics = (state: WaffleOptionsState) => state.customMetrics; diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/actions.ts b/x-pack/plugins/infra/public/store/local/waffle_time/actions.ts deleted file mode 100644 index fe79f2f536a93..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/actions.ts +++ /dev/null @@ -1,15 +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 actionCreatorFactory from 'typescript-fsa'; - -const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_time'); - -export const jumpToTime = actionCreator('JUMP_TO_TIME'); - -export const startAutoReload = actionCreator('START_AUTO_RELOAD'); - -export const stopAutoReload = actionCreator('STOP_AUTO_RELOAD'); diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/epic.ts b/x-pack/plugins/infra/public/store/local/waffle_time/epic.ts deleted file mode 100644 index 986d6b17a2424..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/epic.ts +++ /dev/null @@ -1,38 +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 { Action } from 'redux'; -import { Epic } from 'redux-observable'; -import { timer } from 'rxjs'; -import { exhaustMap, filter, map, takeUntil, withLatestFrom } from 'rxjs/operators'; - -import { jumpToTime, startAutoReload, stopAutoReload } from './actions'; - -interface WaffleTimeEpicDependencies { - selectWaffleTimeUpdatePolicyInterval: (state: State) => number | null; -} - -export const createWaffleTimeEpic = (): Epic< - Action, - Action, - State, - WaffleTimeEpicDependencies -> => (action$, state$, { selectWaffleTimeUpdatePolicyInterval }) => { - const updateInterval$ = state$.pipe(map(selectWaffleTimeUpdatePolicyInterval), filter(isNotNull)); - - return action$.pipe( - filter(startAutoReload.match), - withLatestFrom(updateInterval$), - exhaustMap(([action, updateInterval]) => - timer(0, updateInterval).pipe( - map(() => jumpToTime(Date.now())), - takeUntil(action$.pipe(filter(stopAutoReload.match))) - ) - ) - ); -}; - -const isNotNull = (value: T | null): value is T => value !== null; diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/index.ts b/x-pack/plugins/infra/public/store/local/waffle_time/index.ts deleted file mode 100644 index 2b99a6d6d5760..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/index.ts +++ /dev/null @@ -1,12 +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 waffleTimeActions from './actions'; -import * as waffleTimeSelectors from './selectors'; - -export { waffleTimeActions, waffleTimeSelectors }; -export * from './epic'; -export * from './reducer'; diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/reducer.ts b/x-pack/plugins/infra/public/store/local/waffle_time/reducer.ts deleted file mode 100644 index 026e5decf5d37..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/reducer.ts +++ /dev/null @@ -1,52 +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 { combineReducers } from 'redux'; -import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; - -import { jumpToTime, startAutoReload, stopAutoReload } from './actions'; - -interface ManualTimeUpdatePolicy { - policy: 'manual'; -} - -interface IntervalTimeUpdatePolicy { - policy: 'interval'; - interval: number; -} - -type TimeUpdatePolicy = ManualTimeUpdatePolicy | IntervalTimeUpdatePolicy; - -export interface WaffleTimeState { - currentTime: number; - updatePolicy: TimeUpdatePolicy; -} - -export const initialWaffleTimeState: WaffleTimeState = { - currentTime: Date.now(), - updatePolicy: { - policy: 'manual', - }, -}; - -const currentTimeReducer = reducerWithInitialState(initialWaffleTimeState.currentTime).case( - jumpToTime, - (currentTime, targetTime) => targetTime -); - -const updatePolicyReducer = reducerWithInitialState(initialWaffleTimeState.updatePolicy) - .case(startAutoReload, () => ({ - policy: 'interval', - interval: 5000, - })) - .case(stopAutoReload, () => ({ - policy: 'manual', - })); - -export const waffleTimeReducer = combineReducers({ - currentTime: currentTimeReducer, - updatePolicy: updatePolicyReducer, -}); diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/selectors.ts b/x-pack/plugins/infra/public/store/local/waffle_time/selectors.ts deleted file mode 100644 index 0b6d01bdf5288..0000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/selectors.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createSelector } from 'reselect'; - -import { WaffleTimeState } from './reducer'; - -export const selectCurrentTime = (state: WaffleTimeState) => state.currentTime; - -export const selectIsAutoReloading = (state: WaffleTimeState) => - state.updatePolicy.policy === 'interval'; - -export const selectTimeUpdatePolicyInterval = (state: WaffleTimeState) => - state.updatePolicy.policy === 'interval' ? state.updatePolicy.interval : null; - -export const selectCurrentTimeRange = createSelector(selectCurrentTime, currentTime => ({ - from: currentTime - 1000 * 60 * 5, - interval: '1m', - to: currentTime, -})); diff --git a/x-pack/plugins/infra/public/store/selectors.ts b/x-pack/plugins/infra/public/store/selectors.ts deleted file mode 100644 index f4011c232cba4..0000000000000 --- a/x-pack/plugins/infra/public/store/selectors.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { globalizeSelectors } from '../utils/typed_redux'; -import { - waffleFilterSelectors as localWaffleFilterSelectors, - waffleOptionsSelectors as localWaffleOptionsSelectors, - waffleTimeSelectors as localWaffleTimeSelectors, -} from './local'; -import { State } from './reducer'; -/** - * local selectors - */ - -const selectLocal = (state: State) => state.local; - -export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors); -export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors); -export const waffleOptionsSelectors = globalizeSelectors(selectLocal, localWaffleOptionsSelectors); diff --git a/x-pack/plugins/infra/public/store/store.ts b/x-pack/plugins/infra/public/store/store.ts deleted file mode 100644 index cae0622c5e4a1..0000000000000 --- a/x-pack/plugins/infra/public/store/store.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Action, applyMiddleware, compose, createStore as createBasicStore } from 'redux'; -import { createEpicMiddleware } from 'redux-observable'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -import { createRootEpic, initialState, reducer, State, waffleTimeSelectors } from '.'; -import { InfraApolloClient, InfraObservableApi } from '../lib/lib'; - -declare global { - interface Window { - __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: typeof compose; - } -} - -export interface StoreDependencies { - apolloClient: Observable; - observableApi: Observable; -} - -export function createStore({ apolloClient, observableApi }: StoreDependencies) { - const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - - const middlewareDependencies = { - postToApi$: observableApi.pipe(map(({ post }) => post)), - apolloClient$: apolloClient, - selectWaffleTimeUpdatePolicyInterval: waffleTimeSelectors.selectTimeUpdatePolicyInterval, - }; - - const epicMiddleware = createEpicMiddleware( - { - dependencies: middlewareDependencies, - } - ); - - const store = createBasicStore( - reducer, - initialState, - composeEnhancers(applyMiddleware(epicMiddleware)) - ); - - epicMiddleware.run(createRootEpic()); - - return store; -} diff --git a/x-pack/plugins/infra/public/utils/redux_context.tsx b/x-pack/plugins/infra/public/utils/redux_context.tsx deleted file mode 100644 index f249d72a6b56f..0000000000000 --- a/x-pack/plugins/infra/public/utils/redux_context.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useSelector } from 'react-redux'; -import React, { createContext } from 'react'; -import { State, initialState } from '../store'; - -export const ReduxStateContext = createContext(initialState); - -export const ReduxStateContextProvider = ({ children }: { children: JSX.Element }) => { - const state = useSelector((store: State) => store); - return {children}; -}; diff --git a/x-pack/plugins/infra/server/routes/metadata/index.ts b/x-pack/plugins/infra/server/routes/metadata/index.ts index 03d28110d612a..c45f191b1130d 100644 --- a/x-pack/plugins/infra/server/routes/metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/metadata/index.ts @@ -38,7 +38,7 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { }, async (requestContext, request, response) => { try { - const { nodeId, nodeType, sourceId } = pipe( + const { nodeId, nodeType, sourceId, timeRange } = pipe( InfraMetadataRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); @@ -52,7 +52,8 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { requestContext, configuration, nodeId, - nodeType + nodeType, + timeRange ); const metricFeatures = pickFeatureName(metricsMetadata.buckets).map( nameToFeature('metrics') @@ -62,7 +63,13 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { const cloudInstanceId = get(info, 'cloud.instance.id'); const cloudMetricsMetadata = cloudInstanceId - ? await getCloudMetricsMetadata(framework, requestContext, configuration, cloudInstanceId) + ? await getCloudMetricsMetadata( + framework, + requestContext, + configuration, + cloudInstanceId, + timeRange + ) : { buckets: [] }; const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map( nameToFeature('metrics') diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts index 75ca3ae3caee2..54a1ca0aaa7e0 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts @@ -21,7 +21,8 @@ export const getCloudMetricsMetadata = async ( framework: KibanaFramework, requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, - instanceId: string + instanceId: string, + timeRange: { from: number; to: number } ): Promise => { const metricQuery = { allowNoIndices: true, @@ -30,7 +31,18 @@ export const getCloudMetricsMetadata = async ( body: { query: { bool: { - filter: [{ match: { 'cloud.instance.id': instanceId } }], + filter: [ + { match: { 'cloud.instance.id': instanceId } }, + { + range: { + [sourceConfiguration.fields.timestamp]: { + gte: timeRange.from, + lte: timeRange.to, + format: 'epoch_millis', + }, + }, + }, + ], should: CLOUD_METRICS_MODULES.map(module => ({ match: { 'event.module': module } })), }, }, diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts index 191339565b813..7753d3161039b 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts @@ -26,7 +26,8 @@ export const getMetricMetadata = async ( requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeId: string, - nodeType: InventoryItemType + nodeType: InventoryItemType, + timeRange: { from: number; to: number } ): Promise => { const fields = findInventoryFields(nodeType, sourceConfiguration.fields); const metricQuery = { @@ -41,6 +42,15 @@ export const getMetricMetadata = async ( { match: { [fields.id]: nodeId }, }, + { + range: { + [sourceConfiguration.fields.timestamp]: { + gte: timeRange.from, + lte: timeRange.to, + format: 'epoch_millis', + }, + }, + }, ], }, }, diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts index b897c03e89f82..3496ea782ee99 100644 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts +++ b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts @@ -38,6 +38,10 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { fooVar: { value: 'foo-value' }, fooVar2: { value: [1, 2] }, }, + pkg_stream: { + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, }, { id: 'test-logs-bar', @@ -95,7 +99,7 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { }); }); - it('returns agent datasource config with flattened input and stream configs', () => { + it('returns agent datasource config with flattened input and package stream', () => { expect(storedDatasourceToAgentDatasource({ ...mockDatasource, inputs: [mockInput] })).toEqual({ id: 'mock-datasource', namespace: 'default', @@ -105,34 +109,18 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { { type: 'test-logs', enabled: true, - inputVar: 'input-value', - inputVar3: { - testField: 'test', - }, streams: [ { id: 'test-logs-foo', enabled: true, dataset: 'foo', - fooVar: 'foo-value', - fooVar2: [1, 2], + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], }, { id: 'test-logs-bar', enabled: true, dataset: 'bar', - barVar: 'bar-value', - barVar2: [1, 2], - barVar3: [ - { - namespace: 'mockNamespace', - anotherProp: 'test', - }, - { - namespace: 'mockNamespace2', - anotherProp: 'test2', - }, - ], }, ], }, @@ -160,17 +148,13 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { { type: 'test-logs', enabled: true, - inputVar: 'input-value', - inputVar3: { - testField: 'test', - }, streams: [ { id: 'test-logs-foo', enabled: true, dataset: 'foo', - fooVar: 'foo-value', - fooVar2: [1, 2], + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], }, ], }, diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts index 20bbbec8919d6..b509878b7f945 100644 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts +++ b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts @@ -3,38 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { safeLoad } from 'js-yaml'; -import { - Datasource, - NewDatasource, - DatasourceConfigRecord, - DatasourceConfigRecordEntry, - FullAgentConfigDatasource, -} from '../types'; +import { Datasource, NewDatasource, FullAgentConfigDatasource } from '../types'; import { DEFAULT_OUTPUT } from '../constants'; -const configReducer = ( - configResult: DatasourceConfigRecord, - configEntry: [string, DatasourceConfigRecordEntry] -): DatasourceConfigRecord => { - const [configName, { type: configType, value: configValue }] = configEntry; - if (configValue !== undefined && configValue !== '') { - if (configType === 'yaml') { - try { - const yamlValue = safeLoad(configValue); - if (yamlValue) { - configResult[configName] = yamlValue; - } - } catch (e) { - // Silently swallow parsing error - } - } else { - configResult[configName] = configValue; - } - } - return configResult; -}; - export const storedDatasourceToAgentDatasource = ( datasource: Datasource | NewDatasource ): FullAgentConfigDatasource => { @@ -50,14 +21,14 @@ export const storedDatasourceToAgentDatasource = ( .map(input => { const fullInput = { ...input, - ...Object.entries(input.config || {}).reduce(configReducer, {}), streams: input.streams .filter(stream => stream.enabled) .map(stream => { const fullStream = { ...stream, - ...Object.entries(stream.config || {}).reduce(configReducer, {}), + ...stream.pkg_stream, }; + delete fullStream.pkg_stream; delete fullStream.config; return fullStream; }), diff --git a/x-pack/plugins/ingest_manager/common/types/models/datasource.ts b/x-pack/plugins/ingest_manager/common/types/models/datasource.ts index ee4d24ab11777..48243a12120f9 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/datasource.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/datasource.ts @@ -23,6 +23,7 @@ export interface DatasourceInputStream { dataset: string; processors?: string[]; config?: DatasourceConfigRecord; + pkg_stream?: any; } export interface DatasourceInput { diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/ingest_manager/common/types/models/epm.ts index 5524e7505d74b..064341c68a97a 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts @@ -19,7 +19,7 @@ export enum InstallStatus { uninstalling = 'uninstalling', } -export type DetailViewPanelName = 'overview' | 'data-sources'; +export type DetailViewPanelName = 'overview' | 'data-sources' | 'settings'; export type ServiceName = 'kibana' | 'elasticsearch'; export type AssetType = KibanaAssetType | ElasticsearchAssetType | AgentAssetType; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/datasource.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/datasource.ts index f630602503f0a..61f1f15d49259 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/datasource.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/datasource.ts @@ -4,10 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ import { Datasource, NewDatasource } from '../models'; -import { ListWithKuery } from './common'; export interface GetDatasourcesRequest { - query: ListWithKuery; + query: { + page: number; + perPage: number; + kuery?: string; + }; +} + +export interface GetDatasourcesResponse { + items: Datasource[]; + total: number; + page: number; + perPage: number; + success: boolean; } export interface GetOneDatasourceRequest { @@ -16,6 +27,11 @@ export interface GetOneDatasourceRequest { }; } +export interface GetOneDatasourceResponse { + item: Datasource; + success: boolean; +} + export interface CreateDatasourceRequest { body: NewDatasource; } @@ -29,6 +45,8 @@ export type UpdateDatasourceRequest = GetOneDatasourceRequest & { body: NewDatasource; }; +export type UpdateDatasourceResponse = CreateDatasourceResponse; + export interface DeleteDatasourcesRequest { body: { datasourceIds: string[]; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/datasource.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/datasource.ts index d0072f0355993..0d19ecd0cb735 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/datasource.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/datasource.ts @@ -3,12 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { sendRequest } from './use_request'; +import { sendRequest, useRequest } from './use_request'; import { datasourceRouteService } from '../../services'; import { CreateDatasourceRequest, CreateDatasourceResponse } from '../../types'; import { DeleteDatasourcesRequest, DeleteDatasourcesResponse, + GetDatasourcesRequest, + GetDatasourcesResponse, } from '../../../../../common/types/rest_spec'; export const sendCreateDatasource = (body: CreateDatasourceRequest['body']) => { @@ -26,3 +28,11 @@ export const sendDeleteDatasource = (body: DeleteDatasourcesRequest['body']) => body: JSON.stringify(body), }); }; + +export function useGetDatasources(query: GetDatasourcesRequest['query']) { + return useRequest({ + method: 'get', + path: datasourceRouteService.getListPath(), + query, + }); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx index 48986481b6061..fbc00fbadcfaa 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx @@ -7,7 +7,7 @@ export { useLinks } from './use_links'; export { useLocalSearch, searchIdField } from './use_local_search'; export { PackageInstallProvider, - useDeletePackage, + useUninstallPackage, useGetPackageInstallStatus, useInstallPackage, useSetPackageInstallStatus, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx index 537a2616f1786..0c5f45cdc47a7 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx @@ -6,8 +6,8 @@ import createContainer from 'constate'; import React, { useCallback, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; import { NotificationsStart } from 'src/core/public'; -import { useLinks } from '.'; import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public'; import { PackageInfo } from '../../../types'; import { sendInstallPackage, sendRemovePackage } from '../../../hooks'; @@ -25,7 +25,6 @@ type InstallPackageProps = Pick; function usePackageInstall({ notifications }: { notifications: NotificationsStart }) { const [packages, setPackage] = useState({}); - const { toDetailView } = useLinks(); const setPackageInstallStatus = useCallback( ({ name, status }: { name: PackageInfo['name']; status: InstallStatus }) => { @@ -46,34 +45,43 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar if (res.error) { setPackageInstallStatus({ name, status: InstallStatus.notInstalled }); notifications.toasts.addWarning({ - title: `Failed to install ${title} package`, - text: - 'Something went wrong while trying to install this package. Please try again later.', + title: toMountPoint( + + ), + text: toMountPoint( + + ), iconType: 'alert', }); } else { setPackageInstallStatus({ name, status: InstallStatus.installed }); - const SuccessMsg =

Successfully installed {name}

; notifications.toasts.addSuccess({ - title: `Installed ${title} package`, - text: toMountPoint(SuccessMsg), + title: toMountPoint( + + ), + text: toMountPoint( + + ), }); - - // TODO: this should probably live somewhere else and use , - // this hook could return the request state and a component could - // use that state. the component should be able to unsubscribe to prevent memory leaks - const packageUrl = toDetailView({ name, version }); - const dataSourcesUrl = toDetailView({ - name, - version, - panel: 'data-sources', - withAppRoot: false, - }); - if (window.location.href.includes(packageUrl)) window.location.hash = dataSourcesUrl; } }, - [notifications.toasts, setPackageInstallStatus, toDetailView] + [notifications.toasts, setPackageInstallStatus] ); const getPackageInstallStatus = useCallback( @@ -83,7 +91,7 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar [packages] ); - const deletePackage = useCallback( + const uninstallPackage = useCallback( async ({ name, version, title }: Pick) => { setPackageInstallStatus({ name, status: InstallStatus.uninstalling }); const pkgkey = `${name}-${version}`; @@ -92,30 +100,43 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar if (res.error) { setPackageInstallStatus({ name, status: InstallStatus.installed }); notifications.toasts.addWarning({ - title: `Failed to delete ${title} package`, - text: 'Something went wrong while trying to delete this package. Please try again later.', + title: toMountPoint( + + ), + text: toMountPoint( + + ), iconType: 'alert', }); } else { setPackageInstallStatus({ name, status: InstallStatus.notInstalled }); - const SuccessMsg =

Successfully deleted {title}

; - notifications.toasts.addSuccess({ - title: `Deleted ${title} package`, - text: toMountPoint(SuccessMsg), - }); - - const packageUrl = toDetailView({ name, version }); - const dataSourcesUrl = toDetailView({ - name, - version, - panel: 'data-sources', + title: toMountPoint( + + ), + text: toMountPoint( + + ), }); - if (window.location.href.includes(packageUrl)) window.location.href = dataSourcesUrl; } }, - [notifications.toasts, setPackageInstallStatus, toDetailView] + [notifications.toasts, setPackageInstallStatus] ); return { @@ -123,7 +144,7 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar installPackage, setPackageInstallStatus, getPackageInstallStatus, - deletePackage, + uninstallPackage, }; } @@ -132,11 +153,11 @@ export const [ useInstallPackage, useSetPackageInstallStatus, useGetPackageInstallStatus, - useDeletePackage, + useUninstallPackage, ] = createContainer( usePackageInstall, value => value.installPackage, value => value.setPackageInstallStatus, value => value.getPackageInstallStatus, - value => value.deletePackage + value => value.uninstallPackage ); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_delete.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_delete.tsx deleted file mode 100644 index 2b3be04ac476b..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_delete.tsx +++ /dev/null @@ -1,32 +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 { EuiCallOut, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import React from 'react'; - -interface ConfirmPackageDeleteProps { - onCancel: () => void; - onConfirm: () => void; - packageName: string; - numOfAssets: number; -} -export const ConfirmPackageDelete = (props: ConfirmPackageDeleteProps) => { - const { onCancel, onConfirm, packageName, numOfAssets } = props; - return ( - - - - - - ); -}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx index 137d9cf226b4d..ac30815a941ee 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx @@ -5,6 +5,7 @@ */ import { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic/eui'; import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; interface ConfirmPackageInstallProps { onCancel: () => void; @@ -17,18 +18,46 @@ export const ConfirmPackageInstall = (props: ConfirmPackageInstallProps) => { return ( + } onCancel={onCancel} onConfirm={onConfirm} - cancelButtonText="Cancel" - confirmButtonText="Install package" + cancelButtonText={ + + } + confirmButtonText={ + + } defaultFocusedButton="confirm" > - + + } + />

- and will only be accessible to users who have permission to view this Space. Elasticsearch - assets are installed globally and will be accessible to all Kibana users. +

diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_uninstall.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_uninstall.tsx new file mode 100644 index 0000000000000..14b9bf77c3a00 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_uninstall.tsx @@ -0,0 +1,73 @@ +/* + * 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 { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +interface ConfirmPackageUninstallProps { + onCancel: () => void; + onConfirm: () => void; + packageName: string; + numOfAssets: number; +} +export const ConfirmPackageUninstall = (props: ConfirmPackageUninstallProps) => { + const { onCancel, onConfirm, packageName, numOfAssets } = props; + return ( + + + } + onCancel={onCancel} + onConfirm={onConfirm} + cancelButtonText={ + + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + buttonColor="danger" + > + + } + > +

+ +

+
+ +

+ +

+
+
+ ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx index 384cbbeed378e..0d4b395895322 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx @@ -15,6 +15,7 @@ import { CenterColumn, LeftColumn, RightColumn } from './layout'; import { OverviewPanel } from './overview_panel'; import { SideNavLinks } from './side_nav_links'; import { DataSourcesPanel } from './data_sources_panel'; +import { SettingsPanel } from './settings_panel'; type ContentProps = PackageInfo & Pick & { hasIconPanel: boolean }; export function Content(props: ContentProps) { @@ -49,8 +50,10 @@ export function Content(props: ContentProps) { type ContentPanelProps = PackageInfo & Pick; export function ContentPanel(props: ContentPanelProps) { - const { panel, name, version } = props; + const { panel, name, version, assets, title } = props; switch (panel) { + case 'settings': + return ; case 'data-sources': return ; case 'overview': diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx index 8a8afed5570ed..cbbf1ce53c4ea 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx @@ -5,21 +5,21 @@ */ import { EuiButton } from '@elastic/eui'; import React, { Fragment, useCallback, useMemo, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; import { PackageInfo, InstallStatus } from '../../../../types'; import { useCapabilities } from '../../../../hooks'; -import { useDeletePackage, useGetPackageInstallStatus, useInstallPackage } from '../../hooks'; -import { ConfirmPackageDelete } from './confirm_package_delete'; +import { useUninstallPackage, useGetPackageInstallStatus, useInstallPackage } from '../../hooks'; +import { ConfirmPackageUninstall } from './confirm_package_uninstall'; import { ConfirmPackageInstall } from './confirm_package_install'; -interface InstallationButtonProps { - package: PackageInfo; -} - +type InstallationButtonProps = Pick & { + disabled: boolean; +}; export function InstallationButton(props: InstallationButtonProps) { - const { assets, name, title, version } = props.package; + const { assets, name, title, version, disabled = true } = props; const hasWriteCapabilites = useCapabilities().write; const installPackage = useInstallPackage(); - const deletePackage = useDeletePackage(); + const uninstallPackage = useUninstallPackage(); const getPackageInstallStatus = useGetPackageInstallStatus(); const installationStatus = getPackageInstallStatus(name); @@ -36,11 +36,12 @@ export function InstallationButton(props: InstallationButtonProps) { toggleModal(); }, [installPackage, name, title, toggleModal, version]); - const handleClickDelete = useCallback(() => { - deletePackage({ name, version, title }); + const handleClickUninstall = useCallback(() => { + uninstallPackage({ name, version, title }); toggleModal(); - }, [deletePackage, name, title, toggleModal, version]); + }, [uninstallPackage, name, title, toggleModal, version]); + // counts the number of assets in the package const numOfAssets = useMemo( () => Object.entries(assets).reduce( @@ -56,30 +57,68 @@ export function InstallationButton(props: InstallationButtonProps) { ); const installButton = ( - - {isInstalling ? 'Installing' : 'Install package'} + + {isInstalling ? ( + + ) : ( + + )} ); - const installedButton = ( - - {isInstalling ? 'Deleting' : 'Delete package'} + const uninstallButton = ( + + {isRemoving ? ( + + ) : ( + + )} ); - const deletionModal = ( - ); - const installationModal = ( + const installModal = ( - {isInstalled ? installedButton : installButton} - {isModalVisible && (isInstalled ? deletionModal : installationModal)} + {isInstalled || isRemoving ? uninstallButton : installButton} + {isModalVisible && (isInstalled ? uninstallModal : installModal)} ) : null; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx new file mode 100644 index 0000000000000..ff7ecf97714b6 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx @@ -0,0 +1,118 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; +import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; +import { useGetPackageInstallStatus } from '../../hooks'; +import { InstallStatus, PackageInfo } from '../../../../types'; +import { InstallationButton } from './installation_button'; +import { useGetDatasources } from '../../../../hooks'; + +export const SettingsPanel = ( + props: Pick +) => { + const getPackageInstallStatus = useGetPackageInstallStatus(); + const { data: datasourcesData } = useGetDatasources({ + perPage: 0, + page: 1, + kuery: `datasources.package.name:${props.name}`, + }); + const { name, title } = props; + const packageInstallStatus = getPackageInstallStatus(name); + const packageHasDatasources = !!datasourcesData?.total; + + return ( + + +

+ +

+
+ + {packageInstallStatus === InstallStatus.notInstalled || + packageInstallStatus === InstallStatus.installing ? ( +
+ +

+ +

+
+ +

+ +

+
+ ) : ( +
+ +

+ +

+
+ +

+ +

+
+ )} + + +

+ +

+
+
+ {packageHasDatasources && ( +

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

+ )} +
+ ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx index 39a6fca2e4318..05729ccfc1af4 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx @@ -17,6 +17,7 @@ export type NavLinkProps = Pick & { const PanelDisplayNames: Record = { overview: 'Overview', 'data-sources': 'Data Sources', + settings: 'Settings', }; export function SideNavLinks({ name, version, active }: NavLinkProps) { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx index e4f7202aeee10..7520f88215efe 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx @@ -72,7 +72,7 @@ const ApiKeyField: React.FunctionComponent<{ apiKeyId: string }> = ({ apiKeyId } {key} ) : ( - •••••••••••••••••••••••••• + ••••••••••••••••••••• )} @@ -151,11 +151,10 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { defaultMessage: 'Name', }), truncateText: true, - width: '300px', textOnly: true, render: (name: string) => { return ( - + {name} ); @@ -166,7 +165,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { name: i18n.translate('xpack.ingestManager.enrollmentTokensList.secretTitle', { defaultMessage: 'Secret', }), - width: '245px', + width: '215px', render: (apiKeyId: string) => { return ; }, @@ -186,7 +185,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { name: i18n.translate('xpack.ingestManager.enrollmentTokensList.createdAtTitle', { defaultMessage: 'Created on', }), - width: '200px', + width: '150px', render: (createdAt: string) => { return createdAt ? ( @@ -198,7 +197,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { name: i18n.translate('xpack.ingestManager.enrollmentTokensList.activeTitle', { defaultMessage: 'Active', }), - width: '80px', + width: '70px', render: (active: boolean) => { return ( @@ -212,7 +211,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { name: i18n.translate('xpack.ingestManager.enrollmentTokensList.actionsTitle', { defaultMessage: 'Actions', }), - width: '100px', + width: '70px', render: (_: any, apiKey: EnrollmentAPIKey) => { return ( apiKey.active && ( diff --git a/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts index 7ae562cf130ab..56d6053a1451b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { TypeOf } from '@kbn/config-schema'; +import Boom from 'boom'; import { RequestHandler } from 'src/core/server'; import { appContextService, datasourceService } from '../../services'; import { ensureInstalledPackage } from '../../services/epm/packages'; @@ -75,6 +76,7 @@ export const createDatasourceHandler: RequestHandler< const soClient = context.core.savedObjects.client; const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; + const newData = { ...request.body }; try { // Make sure the datasource package is installed if (request.body.package?.name) { @@ -83,10 +85,18 @@ export const createDatasourceHandler: RequestHandler< pkgName: request.body.package.name, callCluster, }); + + newData.inputs = (await datasourceService.assignPackageStream( + { + pkgName: request.body.package.name, + pkgVersion: request.body.package.version, + }, + request.body.inputs + )) as TypeOf['inputs']; } // Create datasource - const datasource = await datasourceService.create(soClient, request.body, { user }); + const datasource = await datasourceService.create(soClient, newData, { user }); const body: CreateDatasourceResponse = { item: datasource, success: true }; return response.ok({ body, @@ -107,14 +117,33 @@ export const updateDatasourceHandler: RequestHandler< const soClient = context.core.savedObjects.client; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; try { - const datasource = await datasourceService.update( + const datasource = await datasourceService.get(soClient, request.params.datasourceId); + + if (!datasource) { + throw Boom.notFound('Datasource not found'); + } + + const newData = { ...request.body }; + const pkg = newData.package || datasource.package; + const inputs = newData.inputs || datasource.inputs; + if (pkg && (newData.inputs || newData.package)) { + newData.inputs = (await datasourceService.assignPackageStream( + { + pkgName: pkg.name, + pkgVersion: pkg.version, + }, + inputs + )) as TypeOf['inputs']; + } + + const updatedDatasource = await datasourceService.update( soClient, request.params.datasourceId, - request.body, + newData, { user } ); return response.ok({ - body: { item: datasource, success: true }, + body: { item: updatedDatasource, success: true }, }); } catch (e) { return response.customError({ diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index 6800cb4056700..1c36fda36847d 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -137,6 +137,7 @@ export const savedObjectMappings = { dataset: { type: 'keyword' }, processors: { type: 'keyword' }, config: { type: 'flattened' }, + pkg_stream: { type: 'flattened' }, }, }, }, diff --git a/x-pack/plugins/ingest_manager/server/services/datasource.test.ts b/x-pack/plugins/ingest_manager/server/services/datasource.test.ts new file mode 100644 index 0000000000000..09c59998388d1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/datasource.test.ts @@ -0,0 +1,137 @@ +/* + * 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 { datasourceService } from './datasource'; + +async function mockedGetAssetsData(_a: any, _b: any, dataset: string) { + if (dataset === 'dataset1') { + return [ + { + buffer: Buffer.from(` +type: log +metricset: ["dataset1"] +paths: +{{#each paths}} +- {{this}} +{{/each}} +`), + }, + ]; + } + return []; +} + +jest.mock('./epm/packages/assets', () => { + return { + getAssetsDataForPackageKey: mockedGetAssetsData, + }; +}); + +describe('Datasource service', () => { + describe('assignPackageStream', () => { + it('should work with cofig variables from the stream', async () => { + const inputs = await datasourceService.assignPackageStream( + { + pkgName: 'package', + pkgVersion: '1.0.0', + }, + [ + { + type: 'log', + enabled: true, + streams: [ + { + id: 'dataset01', + dataset: 'package.dataset1', + enabled: true, + config: { + paths: { + value: ['/var/log/set.log'], + }, + }, + }, + ], + }, + ] + ); + + expect(inputs).toEqual([ + { + type: 'log', + enabled: true, + streams: [ + { + id: 'dataset01', + dataset: 'package.dataset1', + enabled: true, + config: { + paths: { + value: ['/var/log/set.log'], + }, + }, + pkg_stream: { + metricset: ['dataset1'], + paths: ['/var/log/set.log'], + type: 'log', + }, + }, + ], + }, + ]); + }); + + it('should work with config variables at the input level', async () => { + const inputs = await datasourceService.assignPackageStream( + { + pkgName: 'package', + pkgVersion: '1.0.0', + }, + [ + { + type: 'log', + enabled: true, + config: { + paths: { + value: ['/var/log/set.log'], + }, + }, + streams: [ + { + id: 'dataset01', + dataset: 'package.dataset1', + enabled: true, + }, + ], + }, + ] + ); + + expect(inputs).toEqual([ + { + type: 'log', + enabled: true, + config: { + paths: { + value: ['/var/log/set.log'], + }, + }, + streams: [ + { + id: 'dataset01', + dataset: 'package.dataset1', + enabled: true, + pkg_stream: { + metricset: ['dataset1'], + paths: ['/var/log/set.log'], + type: 'log', + }, + }, + ], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/datasource.ts b/x-pack/plugins/ingest_manager/server/services/datasource.ts index 1b8f2a690b94d..f27252aaa9a84 100644 --- a/x-pack/plugins/ingest_manager/server/services/datasource.ts +++ b/x-pack/plugins/ingest_manager/server/services/datasource.ts @@ -4,16 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ import { SavedObjectsClientContract } from 'src/core/server'; +import { safeLoad } from 'js-yaml'; import { AuthenticatedUser } from '../../../security/server'; -import { DeleteDatasourcesResponse, packageToConfigDatasource } from '../../common'; +import { + DeleteDatasourcesResponse, + packageToConfigDatasource, + DatasourceInput, + DatasourceInputStream, +} from '../../common'; import { DATASOURCE_SAVED_OBJECT_TYPE } from '../constants'; import { NewDatasource, Datasource, ListWithKuery } from '../types'; import { agentConfigService } from './agent_config'; import { getPackageInfo, getInstallation } from './epm/packages'; import { outputService } from './output'; +import { getAssetsDataForPackageKey } from './epm/packages/assets'; +import { createStream } from './epm/agent/agent'; const SAVED_OBJECT_TYPE = DATASOURCE_SAVED_OBJECT_TYPE; +function getDataset(st: string) { + return st.split('.')[1]; +} + class DatasourceService { public async create( soClient: SavedObjectsClientContract, @@ -187,6 +199,61 @@ class DatasourceService { } } } + + public async assignPackageStream( + pkgInfo: { pkgName: string; pkgVersion: string }, + inputs: DatasourceInput[] + ): Promise { + const inputsPromises = inputs.map(input => _assignPackageStreamToInput(pkgInfo, input)); + return Promise.all(inputsPromises); + } +} + +const _isAgentStream = (p: string) => !!p.match(/agent\/stream\/stream\.yml/); + +async function _assignPackageStreamToInput( + pkgInfo: { pkgName: string; pkgVersion: string }, + input: DatasourceInput +) { + const streamsPromises = input.streams.map(stream => + _assignPackageStreamToStream(pkgInfo, input, stream) + ); + + const streams = await Promise.all(streamsPromises); + return { ...input, streams }; +} + +async function _assignPackageStreamToStream( + pkgInfo: { pkgName: string; pkgVersion: string }, + input: DatasourceInput, + stream: DatasourceInputStream +) { + if (!stream.enabled) { + return { ...stream, pkg_stream: undefined }; + } + const dataset = getDataset(stream.dataset); + const assetsData = await getAssetsDataForPackageKey(pkgInfo, _isAgentStream, dataset); + + const [pkgStream] = assetsData; + if (!pkgStream || !pkgStream.buffer) { + throw new Error(`Stream template not found for dataset ${dataset}`); + } + + // Populate template variables from input config and stream config + const data: { [k: string]: string | string[] } = {}; + if (input.config) { + for (const key of Object.keys(input.config)) { + data[key] = input.config[key].value; + } + } + if (stream.config) { + for (const key of Object.keys(stream.config)) { + data[key] = stream.config[key].value; + } + } + const yaml = safeLoad(createStream(data, pkgStream.buffer.toString())); + stream.pkg_stream = yaml; + return { ...stream }; } export const datasourceService = new DatasourceService(); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts index 4f75ba0332418..21de625532f03 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts @@ -4,29 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import fs from 'fs'; -import * as yaml from 'js-yaml'; -import path from 'path'; -import { createInput } from './agent'; +import { createStream } from './agent'; -test('test converting input and manifest into template', () => { - const manifest = yaml.safeLoad( - fs.readFileSync(path.join(__dirname, 'tests/manifest.yml'), 'utf8') - ); +test('Test creating a stream from template', () => { + const streamTemplate = ` +input: log +paths: +{{#each paths}} + - {{this}} +{{/each}} +exclude_files: [".gz$"] +processors: + - add_locale: ~ + `; + const vars = { + paths: ['/usr/local/var/log/nginx/access.log'], + }; - const inputTemplate = fs.readFileSync(path.join(__dirname, 'tests/input.yml'), 'utf8'); - const output = createInput(manifest.vars, inputTemplate); + const output = createStream(vars, streamTemplate); - // Golden file path - const generatedFile = path.join(__dirname, './tests/input.generated.yaml'); - - // Regenerate the file if `-generate` flag is used - if (process.argv.includes('-generate')) { - fs.writeFileSync(generatedFile, output); - } - - const outputData = fs.readFileSync(generatedFile, 'utf-8'); - - // Check that content file and generated file are equal - expect(outputData).toBe(output); + expect(output).toBe(` +input: log +paths: + - /usr/local/var/log/nginx/access.log +exclude_files: [".gz$"] +processors: + - add_locale: ~ + `); }); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts index c7dd3dab38bc1..5d9a6d409aa1a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts @@ -5,19 +5,12 @@ */ import Handlebars from 'handlebars'; -import { RegistryVarsEntry } from '../../../types'; -/** - * This takes a dataset object as input and merges it with the input template. - * It returns the resolved template as a string. - */ -export function createInput(vars: RegistryVarsEntry[], inputTemplate: string): string { - const view: Record = {}; - - for (const v of vars) { - view[v.name] = v.default; - } +interface StreamVars { + [k: string]: string | string[]; +} - const template = Handlebars.compile(inputTemplate); - return template(view); +export function createStream(vars: StreamVars, streamTemplate: string) { + const template = Handlebars.compile(streamTemplate); + return template(vars); } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.generated.yaml b/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.generated.yaml deleted file mode 100644 index 451ed554ce259..0000000000000 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.generated.yaml +++ /dev/null @@ -1,5 +0,0 @@ -type: log -paths: - - "/var/log/nginx/access.log*" - -tags: nginx diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.yml b/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.yml deleted file mode 100644 index 65a23fc2fa9ad..0000000000000 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/input.yml +++ /dev/null @@ -1,7 +0,0 @@ -type: log -paths: -{{#each paths}} - - "{{this}}" -{{/each}} - -tags: {{tags}} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/manifest.yml b/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/manifest.yml deleted file mode 100644 index 46a38179fe132..0000000000000 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/tests/manifest.yml +++ /dev/null @@ -1,20 +0,0 @@ -title: Nginx Acess Logs -release: beta -type: logs -ingest_pipeline: default - -vars: - - name: paths - # Should we define this as array? How will the UI best make sense of it? - type: textarea - default: - - /var/log/nginx/access.log* - # I suggest to use ECS fields for this config options here: https://github.com/elastic/ecs/blob/master/schemas/os.yml - # This would need to be based on a predefined definition on what can be filtered on - os.darwin: - - /usr/local/var/log/nginx/access.log* - os.windows: - - c:/programdata/nginx/logs/*access.log* - - name: tags - default: [nginx] - type: text diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts index 7026d9eae24c3..50d347c69cc24 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts @@ -71,3 +71,13 @@ export async function getAssetsData( return entries; } + +export async function getAssetsDataForPackageKey( + { pkgName, pkgVersion }: { pkgName: string; pkgVersion: string }, + filter = (path: string): boolean => true, + datasetName?: string +): Promise { + const registryPkgInfo = await Registry.fetchInfo(pkgName, pkgVersion); + + return getAssetsData(registryPkgInfo, filter, datasetName); +} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts index 0e2c2a3d26073..d76584225877c 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts @@ -67,7 +67,7 @@ export async function getPackageInfo(options: { pkgVersion: string; }): Promise { const { savedObjectsClient, pkgName, pkgVersion } = options; - const [item, savedObject] = await Promise.all([ + const [item, savedObject, assets] = await Promise.all([ Registry.fetchInfo(pkgName, pkgVersion), getInstallationObject({ savedObjectsClient, pkgName }), Registry.getArchiveInfo(pkgName, pkgVersion), @@ -80,7 +80,7 @@ export async function getPackageInfo(options: { const updated = { ...item, title: item.title || nameAsTitle(item.name), - assets: Registry.groupPathsByService(item?.assets || []), + assets: Registry.groupPathsByService(assets || []), }; return createInstallableFrom(updated, savedObject); } diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/ingest_manager/server/services/setup.ts index bbaf083fb8396..167a24481aba5 100644 --- a/x-pack/plugins/ingest_manager/server/services/setup.ts +++ b/x-pack/plugins/ingest_manager/server/services/setup.ts @@ -123,8 +123,18 @@ async function addPackageToConfig( pkgName: packageToInstall.name, pkgVersion: packageToInstall.version, }); - await datasourceService.create( - soClient, - packageToConfigDatasource(packageInfo, config.id, defaultOutput.id, undefined, config.namespace) + + const newDatasource = packageToConfigDatasource( + packageInfo, + config.id, + defaultOutput.id, + undefined, + config.namespace + ); + newDatasource.inputs = await datasourceService.assignPackageStream( + { pkgName: packageToInstall.name, pkgVersion: packageToInstall.version }, + newDatasource.inputs ); + + await datasourceService.create(soClient, newDatasource); } diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_upgrade_routes.js b/x-pack/plugins/lens/config.ts old mode 100755 new mode 100644 similarity index 54% rename from x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_upgrade_routes.js rename to x-pack/plugins/lens/config.ts index a198f82613e37..84cf02a7ea541 --- a/x-pack/legacy/plugins/logstash/server/routes/api/upgrade/register_upgrade_routes.js +++ b/x-pack/plugins/lens/config.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerExecuteRoute } from './register_execute_route'; +import { schema, TypeOf } from '@kbn/config-schema'; -export function registerLogstashUpgradeRoutes(server) { - registerExecuteRoute(server); -} +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type ConfigSchema = TypeOf; diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 6abdaad7903be..ce544b31b88ef 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -3,7 +3,15 @@ "version": "8.0.0", "kibanaVersion": "kibana", "server": true, - "ui": false, - "optionalPlugins": ["usageCollection", "taskManager"], + "ui": true, + "requiredPlugins": [ + "data", + "expressions", + "navigation", + "kibanaLegacy", + "uiActions", + "visualizations" + ], + "optionalPlugins": ["embeddable", "usageCollection", "taskManager"], "configPath": ["xpack", "lens"] } diff --git a/x-pack/legacy/plugins/lens/public/_mixins.scss b/x-pack/plugins/lens/public/_mixins.scss similarity index 100% rename from x-pack/legacy/plugins/lens/public/_mixins.scss rename to x-pack/plugins/lens/public/_mixins.scss diff --git a/x-pack/legacy/plugins/lens/public/_variables.scss b/x-pack/plugins/lens/public/_variables.scss similarity index 100% rename from x-pack/legacy/plugins/lens/public/_variables.scss rename to x-pack/plugins/lens/public/_variables.scss diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/_app.scss b/x-pack/plugins/lens/public/app_plugin/_app.scss similarity index 100% rename from x-pack/legacy/plugins/lens/public/app_plugin/_app.scss rename to x-pack/plugins/lens/public/app_plugin/_app.scss diff --git a/x-pack/plugins/lens/public/app_plugin/_index.scss b/x-pack/plugins/lens/public/app_plugin/_index.scss new file mode 100644 index 0000000000000..e72e824224956 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/_index.scss @@ -0,0 +1 @@ +@import 'app'; diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx similarity index 96% rename from x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx rename to x-pack/plugins/lens/public/app_plugin/app.test.tsx index be72dd4b4edef..d49c128dff604 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -9,7 +9,7 @@ import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { App } from './app'; import { EditorFrameInstance } from '../types'; -import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { Document, SavedObjectStore } from '../persistence'; import { mount } from 'enzyme'; import { @@ -17,25 +17,24 @@ import { FilterManager, IFieldType, IIndexPattern, -} from '../../../../../../src/plugins/data/public'; -import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +} from '../../../../../src/plugins/data/public'; +import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; const dataStartMock = dataPluginMock.createStartContract(); -import { TopNavMenuData } from '../../../../../../src/plugins/navigation/public'; +import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks'; +import { TopNavMenuData } from '../../../../../src/plugins/navigation/public'; import { coreMock } from 'src/core/public/mocks'; -jest.mock('ui/new_platform'); jest.mock('../persistence'); jest.mock('src/core/public'); -import { npStart } from 'ui/new_platform'; -jest - .spyOn(npStart.plugins.navigation.ui.TopNavMenu.prototype, 'constructor') - .mockImplementation(() => { - return
; - }); +const navigationStartMock = navigationPluginMock.createStartContract(); + +jest.spyOn(navigationStartMock.ui.TopNavMenu.prototype, 'constructor').mockImplementation(() => { + return
; +}); -const { TopNavMenu } = npStart.plugins.navigation.ui; +const { TopNavMenu } = navigationStartMock.ui; function createMockFrame(): jest.Mocked { return { @@ -99,6 +98,7 @@ describe('Lens App', () => { function makeDefaultArgs(): jest.Mocked<{ editorFrame: EditorFrameInstance; data: typeof dataStartMock; + navigation: typeof navigationStartMock; core: typeof core; storage: Storage; docId?: string; @@ -107,6 +107,7 @@ describe('Lens App', () => { addToDashboardMode?: boolean; }> { return ({ + navigation: navigationStartMock, editorFrame: createMockFrame(), core: { ...core, @@ -140,6 +141,7 @@ describe('Lens App', () => { }, redirectTo: jest.fn(id => {}), } as unknown) as jest.Mocked<{ + navigation: typeof navigationStartMock; editorFrame: EditorFrameInstance; data: typeof dataStartMock; core: typeof core; @@ -338,6 +340,7 @@ describe('Lens App', () => { let defaultArgs: jest.Mocked<{ editorFrame: EditorFrameInstance; + navigation: typeof navigationStartMock; data: typeof dataStartMock; core: typeof core; storage: Storage; @@ -654,6 +657,7 @@ describe('Lens App', () => { let defaultArgs: jest.Mocked<{ editorFrame: EditorFrameInstance; data: typeof dataStartMock; + navigation: typeof navigationStartMock; core: typeof core; storage: Storage; docId?: string; diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx similarity index 96% rename from x-pack/legacy/plugins/lens/public/app_plugin/app.tsx rename to x-pack/plugins/lens/public/app_plugin/app.tsx index dfea2e39fcbc5..2d8f1650e4008 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -9,12 +9,12 @@ import React, { useState, useEffect, useCallback } from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { Query, DataPublicPluginStart } from 'src/plugins/data/public'; -import { AppMountContext, NotificationsStart } from 'src/core/public'; +import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; +import { AppMountContext, NotificationsStart } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; -import { npStart } from 'ui/new_platform'; import { FormattedMessage } from '@kbn/i18n/react'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { SavedObjectSaveModal } from '../../../../../../src/plugins/saved_objects/public'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { SavedObjectSaveModal } from '../../../../../src/plugins/saved_objects/public'; import { Document, SavedObjectStore } from '../persistence'; import { EditorFrameInstance } from '../types'; import { NativeRenderer } from '../native_renderer'; @@ -25,7 +25,7 @@ import { IndexPattern as IndexPatternInstance, IndexPatternsContract, SavedQuery, -} from '../../../../../../src/plugins/data/public'; +} from '../../../../../src/plugins/data/public'; interface State { isLoading: boolean; @@ -53,9 +53,11 @@ export function App({ docStorage, redirectTo, addToDashboardMode, + navigation, }: { editorFrame: EditorFrameInstance; data: DataPublicPluginStart; + navigation: NavigationPublicPluginStart; core: AppMountContext['core']; storage: IStorageWrapper; docId?: string; @@ -188,7 +190,7 @@ export function App({ [] ); - const { TopNavMenu } = npStart.plugins.navigation.ui; + const { TopNavMenu } = navigation.ui; const confirmButton = addToDashboardMode ? ( { const mockVisualization = createMockVisualization(); diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/save.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/save.ts rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts similarity index 99% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts index 4aaf2a3ee9e81..1f62929783b63 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts @@ -5,7 +5,7 @@ */ import { getInitialState, reducer } from './state_management'; -import { EditorFrameProps } from '.'; +import { EditorFrameProps } from './index'; import { Datasource, Visualization } from '../../types'; import { createExpressionRendererMock } from '../mocks'; import { coreMock } from 'src/core/public/mocks'; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts similarity index 99% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts index 7d763bcac2cc9..bb6daf5641a64 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EditorFrameProps } from '../editor_frame'; +import { EditorFrameProps } from './index'; import { Document } from '../../persistence/saved_object_store'; export interface PreviewState { diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx similarity index 92% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx index b146f2467c46c..240bdff40b51c 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx @@ -15,7 +15,8 @@ import { createMockFramePublicAPI, } from '../mocks'; import { act } from 'react-dom/test-utils'; -import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; +import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; +import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plugins/data/public'; import { SuggestionPanel, SuggestionPanelProps } from './suggestion_panel'; import { getSuggestions, Suggestion } from './suggestion_helpers'; import { EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; @@ -243,14 +244,25 @@ describe('suggestion_panel', () => { (mockVisualization.toPreviewExpression as jest.Mock).mockReturnValueOnce('test | expression'); mockDatasource.toExpression.mockReturnValue('datasource_expression'); - mount(); + const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; + const field = ({ name: 'myfield' } as unknown) as IFieldType; + + mount( + + ); expect(expressionRendererMock).toHaveBeenCalledTimes(1); const passedExpression = (expressionRendererMock as jest.Mock).mock.calls[0][0].expression; expect(passedExpression).toMatchInlineSnapshot(` "kibana - | kibana_context timeRange=\\"{\\\\\\"from\\\\\\":\\\\\\"now-7d\\\\\\",\\\\\\"to\\\\\\":\\\\\\"now\\\\\\"}\\" query=\\"{\\\\\\"query\\\\\\":\\\\\\"\\\\\\",\\\\\\"language\\\\\\":\\\\\\"lucene\\\\\\"}\\" filters=\\"[]\\" + | kibana_context timeRange=\\"{\\\\\\"from\\\\\\":\\\\\\"now-7d\\\\\\",\\\\\\"to\\\\\\":\\\\\\"now\\\\\\"}\\" query=\\"{\\\\\\"query\\\\\\":\\\\\\"\\\\\\",\\\\\\"language\\\\\\":\\\\\\"lucene\\\\\\"}\\" filters=\\"[{\\\\\\"meta\\\\\\":{\\\\\\"index\\\\\\":\\\\\\"index1\\\\\\"},\\\\\\"exists\\\\\\":{\\\\\\"field\\\\\\":\\\\\\"myfield\\\\\\"}}]\\" | lens_merge_tables layerIds=\\"first\\" tables={datasource_expression} | test | expression" diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx similarity index 99% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 93f6ea6ea67ac..867214d15578a 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -24,7 +24,7 @@ import classNames from 'classnames'; import { Action, PreviewState } from './state_management'; import { Datasource, Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../types'; import { getSuggestions, switchToSuggestion } from './suggestion_helpers'; -import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; +import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; import { prependDatasourceExpression, prependKibanaContext } from './expression_helpers'; import { debouncedComponent } from '../../debounced_component'; import { trackUiEvent, trackSuggestionEvent } from '../../lens_ui_telemetry'; @@ -235,6 +235,7 @@ export function SuggestionPanel({ const expressionContext = { query: frame.query, + filters: frame.filters, timeRange: { from: frame.dateRange.fromDate, to: frame.dateRange.toDate, diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx similarity index 99% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx index 748e5b876da95..33ecee53fa3bc 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { ReactExpressionRendererProps } from '../../../../../../../src/plugins/expressions/public'; +import { ReactExpressionRendererProps } from '../../../../../../src/plugins/expressions/public'; import { FramePublicAPI, TableSuggestion, Visualization } from '../../types'; import { createMockVisualization, @@ -21,7 +21,7 @@ import { ReactWrapper } from 'enzyme'; import { DragDrop, ChildDragDropProvider } from '../../drag_drop'; import { Ast } from '@kbn/interpreter/common'; import { coreMock } from 'src/core/public/mocks'; -import { esFilters, IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/public'; +import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plugins/data/public'; describe('workspace_panel', () => { let mockVisualization: jest.Mocked; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx similarity index 98% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx index c2a5c16e405a2..1f741ca37934f 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx @@ -16,8 +16,8 @@ import { EuiBetaBadge, EuiButtonEmpty, } from '@elastic/eui'; -import { CoreStart, CoreSetup } from 'src/core/public'; -import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; +import { CoreStart, CoreSetup } from 'kibana/public'; +import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; import { Action } from './state_management'; import { Datasource, Visualization, FramePublicAPI } from '../../types'; import { DragDrop, DragContext } from '../../drag_drop'; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel_wrapper.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel_wrapper.tsx rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel_wrapper.tsx diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx similarity index 96% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx rename to x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index 55363ebe4d8f3..aeae64514b0fd 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -9,9 +9,9 @@ import { Embeddable } from './embeddable'; import { ReactExpressionRendererProps } from 'src/plugins/expressions/public'; import { Query, TimeRange, Filter, TimefilterContract } from 'src/plugins/data/public'; import { Document } from '../../persistence'; -import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; -jest.mock('../../../../../../../src/plugins/inspector/public/', () => ({ +jest.mock('../../../../../../src/plugins/inspector/public/', () => ({ isAvailable: false, open: false, })); diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx similarity index 94% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx rename to x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index c2ab1c72af545..0ef5f6d1a5470 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -16,15 +16,15 @@ import { } from 'src/plugins/data/public'; import { Subscription } from 'rxjs'; -import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/plugins/visualizations/public'; +import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public'; import { Embeddable as AbstractEmbeddable, EmbeddableOutput, IContainer, EmbeddableInput, -} from '../../../../../../../src/plugins/embeddable/public'; +} from '../../../../../../src/plugins/embeddable/public'; import { Document, DOC_TYPE } from '../../persistence'; import { ExpressionWrapper } from './expression_wrapper'; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts similarity index 91% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts rename to x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts index 99a59c756e228..68dbff263f60d 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts @@ -15,17 +15,17 @@ import { IndexPatternsContract, IndexPattern, TimefilterContract, -} from '../../../../../../../src/plugins/data/public'; -import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; +} from '../../../../../../src/plugins/data/public'; +import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; import { EmbeddableFactoryDefinition, ErrorEmbeddable, EmbeddableInput, IContainer, -} from '../../../../../../../src/plugins/embeddable/public'; +} from '../../../../../../src/plugins/embeddable/public'; import { Embeddable } from './embeddable'; import { SavedObjectIndexStore, DOC_TYPE } from '../../persistence'; -import { getEditPath } from '../../../../../../plugins/lens/common'; +import { getEditPath } from '../../../common'; interface StartServices { timefilter: TimefilterContract; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx rename to x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/format_column.ts b/x-pack/plugins/lens/public/editor_frame_service/format_column.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/format_column.ts rename to x-pack/plugins/lens/public/editor_frame_service/format_column.ts diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/index.ts b/x-pack/plugins/lens/public/editor_frame_service/index.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/index.ts rename to x-pack/plugins/lens/public/editor_frame_service/index.ts diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/merge_tables.test.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts similarity index 98% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/merge_tables.test.ts rename to x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts index 9368674de31c5..243441f2c8ab3 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/merge_tables.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts @@ -8,8 +8,6 @@ import moment from 'moment'; import { mergeTables } from './merge_tables'; import { KibanaDatatable } from 'src/plugins/expressions'; -jest.mock('ui/new_platform'); - describe('lens_merge_tables', () => { it('should produce a row with the nested table as defined', () => { const sampleTable1: KibanaDatatable = { diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/merge_tables.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts similarity index 96% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/merge_tables.ts rename to x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts index c06640fb25de6..7c10ee4a57fad 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/merge_tables.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts @@ -10,7 +10,7 @@ import { ExpressionValueSearchContext, KibanaDatatable, } from 'src/plugins/expressions/public'; -import { search } from '../../../../../../src/plugins/data/public'; +import { search } from '../../../../../src/plugins/data/public'; const { toAbsoluteDates } = search.aggs; import { LensMultiTable } from '../types'; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx similarity index 92% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/mocks.tsx rename to x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index 5d2f68a5567eb..50cd1ad8bd53a 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -9,12 +9,12 @@ import { ReactExpressionRendererProps, ExpressionsSetup, ExpressionsStart, -} from '../../../../../../src/plugins/expressions/public'; -import { embeddablePluginMock } from '../../../../../../src/plugins/embeddable/public/mocks'; -import { expressionsPluginMock } from '../../../../../../src/plugins/expressions/public/mocks'; +} from '../../../../../src/plugins/expressions/public'; +import { embeddablePluginMock } from '../../../../../src/plugins/embeddable/public/mocks'; +import { expressionsPluginMock } from '../../../../../src/plugins/expressions/public/mocks'; import { DatasourcePublicAPI, FramePublicAPI, Datasource, Visualization } from '../types'; import { EditorFrameSetupPlugins, EditorFrameStartPlugins } from './service'; -import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; export function createMockVisualization(): jest.Mocked { return { diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx similarity index 98% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx rename to x-pack/plugins/lens/public/editor_frame_service/service.test.tsx index 42a1fcc055a1e..fbd65c5044d51 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx @@ -14,8 +14,6 @@ import { } from './mocks'; import { CoreSetup } from 'kibana/public'; -jest.mock('ui/new_platform'); - // mock away actual dependencies to prevent all of it being loaded jest.mock('./embeddable/embeddable_factory', () => ({ EmbeddableFactory: class Mock {}, diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx similarity index 91% rename from x-pack/legacy/plugins/lens/public/editor_frame_service/service.tsx rename to x-pack/plugins/lens/public/editor_frame_service/service.tsx index 1375c60060ca8..15fe449d6563b 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -7,16 +7,13 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; -import { CoreSetup, CoreStart } from 'src/core/public'; -import { - ExpressionsSetup, - ExpressionsStart, -} from '../../../../../../src/plugins/expressions/public'; -import { EmbeddableSetup, EmbeddableStart } from '../../../../../../src/plugins/embeddable/public'; +import { CoreSetup, CoreStart } from 'kibana/public'; +import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public'; +import { EmbeddableSetup, EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; import { DataPublicPluginSetup, DataPublicPluginStart, -} from '../../../../../../src/plugins/data/public'; +} from '../../../../../src/plugins/data/public'; import { Datasource, Visualization, @@ -32,13 +29,13 @@ import { getActiveDatasourceIdFromDoc } from './editor_frame/state_management'; export interface EditorFrameSetupPlugins { data: DataPublicPluginSetup; - embeddable: EmbeddableSetup; + embeddable?: EmbeddableSetup; expressions: ExpressionsSetup; } export interface EditorFrameStartPlugins { data: DataPublicPluginStart; - embeddable: EmbeddableStart; + embeddable?: EmbeddableStart; expressions: ExpressionsStart; } @@ -79,7 +76,9 @@ export class EditorFrameService { }; }; - plugins.embeddable.registerEmbeddableFactory('lens', new EmbeddableFactory(getStartServices)); + if (plugins.embeddable) { + plugins.embeddable.registerEmbeddableFactory('lens', new EmbeddableFactory(getStartServices)); + } return { registerDatasource: datasource => { diff --git a/x-pack/legacy/plugins/lens/public/help_menu_util.tsx b/x-pack/plugins/lens/public/help_menu_util.tsx similarity index 63% rename from x-pack/legacy/plugins/lens/public/help_menu_util.tsx rename to x-pack/plugins/lens/public/help_menu_util.tsx index 9ead31690e854..333a90df4731b 100644 --- a/x-pack/legacy/plugins/lens/public/help_menu_util.tsx +++ b/x-pack/plugins/lens/public/help_menu_util.tsx @@ -4,16 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; -import { ChromeStart } from 'kibana/public'; +import { ChromeStart, DocLinksStart } from 'kibana/public'; -export function addHelpMenuToAppChrome(chrome: ChromeStart) { +export function addHelpMenuToAppChrome(chrome: ChromeStart, docLinks: DocLinksStart) { chrome.setHelpExtension({ appName: 'Lens', links: [ { linkType: 'documentation', - href: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/lens.html`, + href: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/lens.html`, }, { linkType: 'github', diff --git a/x-pack/legacy/plugins/lens/public/helpers/index.ts b/x-pack/plugins/lens/public/helpers/index.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/helpers/index.ts rename to x-pack/plugins/lens/public/helpers/index.ts diff --git a/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts b/x-pack/plugins/lens/public/helpers/url_helper.test.ts similarity index 96% rename from x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts rename to x-pack/plugins/lens/public/helpers/url_helper.test.ts index ef960fb52952b..37e35ca17e0b3 100644 --- a/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts +++ b/x-pack/plugins/lens/public/helpers/url_helper.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../../../../src/plugins/dashboard/public', () => ({ +jest.mock('../../../../../src/plugins/dashboard/public', () => ({ DashboardConstants: { ADD_EMBEDDABLE_ID: 'addEmbeddableId', ADD_EMBEDDABLE_TYPE: 'addEmbeddableType', diff --git a/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts b/x-pack/plugins/lens/public/helpers/url_helper.ts similarity index 95% rename from x-pack/legacy/plugins/lens/public/helpers/url_helper.ts rename to x-pack/plugins/lens/public/helpers/url_helper.ts index 3495c15118ce7..0a97ba4b2edf7 100644 --- a/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts +++ b/x-pack/plugins/lens/public/helpers/url_helper.ts @@ -5,7 +5,7 @@ */ import { parseUrl, stringify } from 'query-string'; -import { DashboardConstants } from '../../../../../../src/plugins/dashboard/public'; +import { DashboardConstants } from '../../../../../src/plugins/dashboard/public'; type UrlVars = Record; diff --git a/x-pack/legacy/plugins/lens/public/id_generator/id_generator.test.ts b/x-pack/plugins/lens/public/id_generator/id_generator.test.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/id_generator/id_generator.test.ts rename to x-pack/plugins/lens/public/id_generator/id_generator.test.ts diff --git a/x-pack/legacy/plugins/lens/public/id_generator/id_generator.ts b/x-pack/plugins/lens/public/id_generator/id_generator.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/id_generator/id_generator.ts rename to x-pack/plugins/lens/public/id_generator/id_generator.ts diff --git a/x-pack/legacy/plugins/lens/public/id_generator/index.ts b/x-pack/plugins/lens/public/id_generator/index.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/id_generator/index.ts rename to x-pack/plugins/lens/public/id_generator/index.ts diff --git a/x-pack/legacy/plugins/lens/public/index.scss b/x-pack/plugins/lens/public/index.scss similarity index 54% rename from x-pack/legacy/plugins/lens/public/index.scss rename to x-pack/plugins/lens/public/index.scss index 2f91d14c397c7..67bbac12be8c3 100644 --- a/x-pack/legacy/plugins/lens/public/index.scss +++ b/x-pack/plugins/lens/public/index.scss @@ -1,12 +1,13 @@ // Import the EUI global scope so we can use EUI constants -@import 'src/legacy/ui/public/styles/_styling_constants'; +@import '@elastic/eui/src/global_styling/variables/index'; +@import '@elastic/eui/src/global_styling/mixins/index'; -@import './variables'; -@import './mixins'; +@import 'variables'; +@import 'mixins'; -@import './app_plugin/index'; +@import 'app_plugin/index'; @import 'datatable_visualization/index'; -@import './drag_drop/index'; +@import 'drag_drop/index'; @import 'editor_frame_service/index'; @import 'indexpattern_datasource/index'; @import 'xy_visualization/index'; diff --git a/x-pack/legacy/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/index.ts rename to x-pack/plugins/lens/public/index.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/loader.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/__mocks__/state_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/state_helpers.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/__mocks__/state_helpers.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/state_helpers.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap b/x-pack/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap rename to x-pack/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_datapanel.scss b/x-pack/plugins/lens/public/indexpattern_datasource/_datapanel.scss similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/_datapanel.scss rename to x-pack/plugins/lens/public/indexpattern_datasource/_datapanel.scss diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_field_item.scss b/x-pack/plugins/lens/public/indexpattern_datasource/_field_item.scss similarity index 98% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/_field_item.scss rename to x-pack/plugins/lens/public/indexpattern_datasource/_field_item.scss index 89f6bbf908419..41919b900c71f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_field_item.scss +++ b/x-pack/plugins/lens/public/indexpattern_datasource/_field_item.scss @@ -14,7 +14,7 @@ } .lnsFieldItem--missing { - background: lightOrDarkTheme(transparentize($euiColorMediumShade, 0.9), $euiColorEmptyShade); + background: lightOrDarkTheme(transparentize($euiColorMediumShade, .9), $euiColorEmptyShade); color: $euiColorDarkShade; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/_index.scss b/x-pack/plugins/lens/public/indexpattern_datasource/_index.scss new file mode 100644 index 0000000000000..e5d8b408e33e5 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/_index.scss @@ -0,0 +1,4 @@ +@import 'datapanel'; +@import 'field_item'; + +@import 'dimension_panel/index'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/auto_date.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/auto_date.test.ts similarity index 96% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/auto_date.test.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/auto_date.test.ts index cc1a74a1854ce..5f35ef650a08c 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/auto_date.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/auto_date.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { getAutoDate } from './auto_date'; describe('auto_date', () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/auto_date.ts b/x-pack/plugins/lens/public/indexpattern_datasource/auto_date.ts similarity index 92% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/auto_date.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/auto_date.ts index 063cbb4d217a7..97a46f4a3e176 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/auto_date.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/auto_date.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DataPublicPluginSetup } from '../../../../../../src/plugins/data/public'; +import { DataPublicPluginSetup } from '../../../../../src/plugins/data/public'; import { ExpressionFunctionDefinition, KibanaContext, -} from '../../../../../../src/plugins/expressions/public'; +} from '../../../../../src/plugins/expressions/public'; interface LensAutoDateProps { aggConfigs: string; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index 3066ac0e11325..c396f0efee42e 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -6,6 +6,7 @@ import React, { ChangeEvent } from 'react'; import { createMockedDragDropContext } from './mocks'; +import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { InnerIndexPatternDataPanel, IndexPatternDataPanel, MemoizedDataPanel } from './datapanel'; import { FieldItem } from './field_item'; import { act } from 'react-dom/test-utils'; @@ -16,8 +17,6 @@ import { ChangeIndexPattern } from './change_indexpattern'; import { EuiProgress } from '@elastic/eui'; import { documentField } from './document_field'; -jest.mock('ui/new_platform'); - const initialState: IndexPatternPrivateState = { indexPatternRefs: [], existingFields: {}, @@ -218,6 +217,7 @@ describe('IndexPattern Data Panel', () => { defaultProps = { indexPatternRefs: [], existingFields: {}, + data: dataPluginMock.createStartContract(), dragDropContext: createMockedDragDropContext(), currentIndexPatternId: '1', indexPatterns: initialState.indexPatterns, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx similarity index 98% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 7a3c04b67fbc4..79dcdafd916b4 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -27,6 +27,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; import { DatasourceDataPanelProps, DataType, StateSetter } from '../types'; import { ChildDragDropProvider, DragContextState } from '../drag_drop'; import { FieldItem } from './field_item'; @@ -40,9 +41,10 @@ import { trackUiEvent } from '../lens_ui_telemetry'; import { syncExistingFields } from './loader'; import { fieldExists } from './pure_helpers'; import { Loader } from '../loader'; -import { esQuery, IIndexPattern } from '../../../../../../src/plugins/data/public'; +import { esQuery, IIndexPattern } from '../../../../../src/plugins/data/public'; export type Props = DatasourceDataPanelProps & { + data: DataPublicPluginStart; changeIndexPattern: ( id: string, state: IndexPatternPrivateState, @@ -78,6 +80,7 @@ export function IndexPatternDataPanel({ state, dragDropContext, core, + data, query, filters, dateRange, @@ -152,6 +155,7 @@ export function IndexPatternDataPanel({ showEmptyFields={state.showEmptyFields} onToggleEmptyFields={onToggleEmptyFields} core={core} + data={data} onChangeIndexPattern={onChangeIndexPattern} existingFields={state.existingFields} /> @@ -177,8 +181,10 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ showEmptyFields, onToggleEmptyFields, core, + data, existingFields, }: Pick> & { + data: DataPublicPluginStart; currentIndexPatternId: string; indexPatternRefs: IndexPatternRef[]; indexPatterns: Record; @@ -441,6 +447,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ {specialFields.map(field => ( { let defaultProps: FieldItemProps; let indexPattern: IndexPattern; let core: ReturnType; + let data: DataPublicPluginStart; beforeEach(() => { indexPattern = { @@ -61,9 +60,11 @@ describe('IndexPattern Field Item', () => { } as IndexPattern; core = coreMock.createSetup(); + data = dataPluginMock.createStartContract(); core.http.post.mockClear(); defaultProps = { indexPattern, + data, core, highlight: '', dateRange: { @@ -81,7 +82,7 @@ describe('IndexPattern Field Item', () => { exists: true, }; - npStart.plugins.data.fieldFormats = ({ + data.fieldFormats = ({ getDefaultInstance: jest.fn(() => ({ convert: jest.fn((s: unknown) => JSON.stringify(s)), })), diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx similarity index 97% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_item.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index b98f589bc5b98..c4d2a6f8780c6 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -20,7 +20,6 @@ import { EuiText, EuiToolTip, } from '@elastic/eui'; -import { npStart } from 'ui/new_platform'; import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; import { Axis, @@ -33,6 +32,7 @@ import { TooltipType, } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; import { Query, KBN_FIELD_TYPES, @@ -40,17 +40,18 @@ import { Filter, esQuery, IIndexPattern, -} from '../../../../../../src/plugins/data/public'; +} from '../../../../../src/plugins/data/public'; import { DraggedField } from './indexpattern'; import { DragDrop } from '../drag_drop'; import { DatasourceDataPanelProps, DataType } from '../types'; -import { BucketedAggregation, FieldStatsResponse } from '../../../../../plugins/lens/common'; +import { BucketedAggregation, FieldStatsResponse } from '../../common'; import { IndexPattern, IndexPatternField } from './types'; import { LensFieldIcon } from './lens_field_icon'; import { trackUiEvent } from '../lens_ui_telemetry'; export interface FieldItemProps { core: DatasourceDataPanelProps['core']; + data: DataPublicPluginStart; field: IndexPatternField; indexPattern: IndexPattern; highlight?: string; @@ -237,8 +238,16 @@ export function FieldItem(props: FieldItemProps) { } function FieldItemPopoverContents(props: State & FieldItemProps) { - const fieldFormats = npStart.plugins.data.fieldFormats; - const { histogram, topValues, indexPattern, field, dateRange, core, sampledValues } = props; + const { + histogram, + topValues, + indexPattern, + field, + dateRange, + core, + sampledValues, + data: { fieldFormats }, + } = props; const IS_DARK_THEME = core.uiSettings.get('theme:darkMode'); const chartTheme = IS_DARK_THEME ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts similarity index 84% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/index.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/index.ts index 8a5c562ebd455..fe14f472341af 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup } from 'src/core/public'; -import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; +import { CoreSetup } from 'kibana/public'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { getIndexPatternDatasource } from './indexpattern'; import { renameColumns } from './rename_columns'; import { getAutoDate } from './auto_date'; -import { ExpressionsSetup } from '../../../../../../src/plugins/expressions/public'; +import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; import { DataPublicPluginSetup, DataPublicPluginStart, -} from '../../../../../../src/plugins/data/public'; +} from '../../../../../src/plugins/data/public'; import { Datasource, EditorFrameSetup } from '../types'; export interface IndexPatternDatasourceSetupPlugins { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts similarity index 98% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 76e59a170a9e9..dbdbe4e3f9442 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -8,13 +8,11 @@ import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { getIndexPatternDatasource, IndexPatternColumn, uniqueLabels } from './indexpattern'; import { DatasourcePublicAPI, Operation, Datasource } from '../types'; import { coreMock } from 'src/core/public/mocks'; -import { pluginsMock } from 'ui/new_platform/__mocks__/helpers'; import { IndexPatternPersistedState, IndexPatternPrivateState } from './types'; +import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; jest.mock('./loader'); jest.mock('../id_generator'); -// Contains old and new platform data plugins, used for interpreter and filter ratio -jest.mock('ui/new_platform'); const expectedIndexPatterns = { 1: { @@ -140,7 +138,7 @@ describe('IndexPattern Data Source', () => { indexPatternDatasource = getIndexPatternDatasource({ storage: {} as IStorageWrapper, core: coreMock.createStart(), - data: pluginsMock.createStart().data, + data: dataPluginMock.createStartContract(), }); persistedState = { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx similarity index 96% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 9c2a9c9bf4a09..b8f0460f2a9ab 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -8,7 +8,7 @@ import _ from 'lodash'; import React from 'react'; import { render } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; -import { CoreStart } from 'src/core/public'; +import { CoreStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { @@ -42,10 +42,10 @@ import { IndexPatternPrivateState, IndexPatternPersistedState, } from './types'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { Plugin as DataPlugin } from '../../../../../../src/plugins/data/public'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { deleteColumn } from './state_helpers'; -import { Datasource, StateSetter } from '..'; +import { Datasource, StateSetter } from '../index'; export { OperationType, IndexPatternColumn } from './operations'; @@ -105,7 +105,7 @@ export function getIndexPatternDatasource({ }: { core: CoreStart; storage: IStorageWrapper; - data: ReturnType; + data: DataPublicPluginStart; }) { const savedObjectsClient = core.savedObjects.client; const uiSettings = core.uiSettings; @@ -209,6 +209,7 @@ export function getIndexPatternDatasource({ onError: onIndexPatternLoadError, }); }} + data={data} {...props} /> , diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index fe14e5de5c1e3..2008b326a539c 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -12,7 +12,6 @@ import { getDatasourceSuggestionsFromCurrentState, } from './indexpattern_suggestions'; -jest.mock('ui/new_platform'); jest.mock('./loader'); jest.mock('../id_generator'); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx index 219a6d935e436..4dd29d7925916 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx @@ -12,7 +12,6 @@ import { ShallowWrapper } from 'enzyme'; import { EuiSelectable, EuiSelectableList } from '@elastic/eui'; import { ChangeIndexPattern } from './change_indexpattern'; -jest.mock('ui/new_platform'); jest.mock('./state_helpers'); const initialState: IndexPatternPrivateState = { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/layerpanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/layerpanel.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx similarity index 86% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx index 06eda73748cef..bcc83e799d889 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { FieldIcon, FieldIconProps } from '../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon, FieldIconProps } from '../../../../../src/plugins/kibana_react/public'; import { DataType } from '../types'; import { normalizeOperationDataType } from './utils'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/loader.test.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts index ea9c8213ba909..cacf729ba0caf 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -16,8 +16,6 @@ import { import { IndexPatternPersistedState, IndexPatternPrivateState } from './types'; import { documentField } from './document_field'; -// TODO: This should not be necessary -jest.mock('ui/new_platform'); jest.mock('./operations'); const sampleIndexPatterns = { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts similarity index 96% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/loader.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index f4d5857f4826d..23faab768eba6 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -5,8 +5,8 @@ */ import _ from 'lodash'; -import { SavedObjectsClientContract, SavedObjectAttributes, HttpSetup } from 'src/core/public'; -import { SimpleSavedObject } from 'src/core/public'; +import { SavedObjectsClientContract, SavedObjectAttributes, HttpSetup } from 'kibana/public'; +import { SimpleSavedObject } from 'kibana/public'; import { StateSetter } from '../types'; import { IndexPattern, @@ -16,14 +16,14 @@ import { IndexPatternField, } from './types'; import { updateLayerIndexPattern } from './state_helpers'; -import { DateRange, ExistingFields } from '../../../../../plugins/lens/common/types'; -import { BASE_API_URL } from '../../../../../plugins/lens/common'; +import { DateRange, ExistingFields } from '../../common/types'; +import { BASE_API_URL } from '../../common'; import { documentField } from './document_field'; import { indexPatterns as indexPatternsUtils, IFieldType, IndexPatternTypeMeta, -} from '../../../../../../src/plugins/data/public'; +} from '../../../../../src/plugins/data/public'; interface SavedIndexPatternAttributes extends SavedObjectAttributes { title: string; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/mocks.ts b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/mocks.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx similarity index 98% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index 33325016deaeb..9491ca9ea3787 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { OperationDefinition } from '.'; +import { OperationDefinition } from './index'; import { FormattedIndexPatternColumn } from './column_types'; const supportedTypes = new Set(['string', 'boolean', 'number', 'ip', 'date']); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx similarity index 97% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx index 1592b1049f666..1dcaf78b58a6c 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { OperationDefinition } from '.'; +import { OperationDefinition } from './index'; import { FormattedIndexPatternColumn } from './column_types'; import { IndexPatternField } from '../../types'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index dc279fca82d4b..e3b6061248f3b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -6,21 +6,19 @@ import React from 'react'; import { DateHistogramIndexPatternColumn } from './date_histogram'; -import { dateHistogramOperation } from '.'; +import { dateHistogramOperation } from './index'; import { shallow } from 'enzyme'; import { EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; -import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'src/core/public'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; import { coreMock } from 'src/core/public/mocks'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { dataPluginMock, getCalculateAutoTimeExpression, -} from '../../../../../../../../src/plugins/data/public/mocks'; +} from '../../../../../../../src/plugins/data/public/mocks'; import { createMockedIndexPattern } from '../../mocks'; import { IndexPatternPrivateState } from '../../types'; -jest.mock('ui/new_platform'); - const dataStart = dataPluginMock.createStartContract(); dataStart.search.aggs.calculateAutoTimeExpression = getCalculateAutoTimeExpression({ ...coreMock.createStart().uiSettings, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx similarity index 98% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 452d5c9140868..7a36d52ad897b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -21,12 +21,9 @@ import { EuiSpacer, } from '@elastic/eui'; import { updateColumnParam } from '../../state_helpers'; -import { OperationDefinition } from '.'; +import { OperationDefinition } from './index'; import { FieldBasedIndexPatternColumn } from './column_types'; -import { - IndexPatternAggRestrictions, - search, -} from '../../../../../../../../src/plugins/data/public'; +import { IndexPatternAggRestrictions, search } from '../../../../../../../src/plugins/data/public'; const { isValidInterval } = search.aggs; const autoInterval = 'auto'; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts similarity index 97% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 2db5296905000..ef12fca690f0c 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'src/core/public'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { termsOperation } from './terms'; import { cardinalityOperation } from './cardinality'; @@ -14,8 +14,8 @@ import { countOperation } from './count'; import { DimensionPriority, StateSetter, OperationMetadata } from '../../../types'; import { BaseIndexPatternColumn } from './column_types'; import { IndexPatternPrivateState, IndexPattern, IndexPatternField } from '../../types'; -import { DateRange } from '../../../../../../../plugins/lens/common'; -import { DataPublicPluginStart } from '../../../../../../../../src/plugins/data/public'; +import { DateRange } from '../../../../common'; +import { DataPublicPluginStart } from '../../../../../../../src/plugins/data/public'; // List of all operation definitions registered to this data source. // If you want to implement a new operation, add it to this array and diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx similarity index 98% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index c2d9478c6ea15..3da635dc13d10 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { OperationDefinition } from '.'; +import { OperationDefinition } from './index'; import { FormattedIndexPatternColumn } from './column_types'; type MetricColumn = FormattedIndexPatternColumn & { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx similarity index 98% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx index fc0c9746b2f98..8f6130e74b5b8 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx @@ -7,16 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; import { EuiRange, EuiSelect } from '@elastic/eui'; -import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'src/core/public'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; -import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { createMockedIndexPattern } from '../../mocks'; import { TermsIndexPatternColumn } from './terms'; -import { termsOperation } from '.'; +import { termsOperation } from './index'; import { IndexPatternPrivateState } from '../../types'; -jest.mock('ui/new_platform'); - const defaultProps = { storage: {} as IStorageWrapper, uiSettings: {} as IUiSettingsClient, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx index 387b197c9235c..29e5787fa4f54 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx @@ -10,7 +10,7 @@ import { EuiForm, EuiFormRow, EuiRange, EuiSelect } from '@elastic/eui'; import { IndexPatternColumn } from '../../indexpattern'; import { updateColumnParam } from '../../state_helpers'; import { DataType } from '../../../types'; -import { OperationDefinition } from '.'; +import { OperationDefinition } from './index'; import { FieldBasedIndexPatternColumn } from './column_types'; type PropType = C extends React.ComponentType ? P : unknown; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/index.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts index 3602491c6eb2c..111b1040de989 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts @@ -4,13 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOperationTypesForField, getAvailableOperationsByMetadata, buildColumn } from '.'; +import { getOperationTypesForField, getAvailableOperationsByMetadata, buildColumn } from './index'; import { AvgIndexPatternColumn, MinIndexPatternColumn } from './definitions/metrics'; import { CountIndexPatternColumn } from './definitions/count'; import { IndexPatternPrivateState } from '../types'; import { documentField } from '../document_field'; -jest.mock('ui/new_platform'); jest.mock('../loader'); const expectedIndexPatterns = { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/operations.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/operations.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/pure_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.test.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/pure_helpers.test.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.test.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/pure_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/pure_helpers.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts similarity index 96% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts index 9da7591305a6c..4bfd6a4f93c75 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts @@ -5,8 +5,8 @@ */ import { renameColumns } from './rename_columns'; -import { KibanaDatatable } from '../../../../../../src/plugins/expressions/public'; -import { createMockExecutionContext } from '../../../../../../src/plugins/expressions/common/mocks'; +import { KibanaDatatable } from '../../../../../src/plugins/expressions/public'; +import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; describe('rename_columns', () => { it('should rename columns of a given datatable', () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/rename_columns.ts b/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/rename_columns.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts similarity index 99% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts index 0a58853f1ef4f..1e3251a8dedd8 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts @@ -17,7 +17,6 @@ import { DateHistogramIndexPatternColumn } from './operations/definitions/date_h import { AvgIndexPatternColumn } from './operations/definitions/metrics'; import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from './types'; -jest.mock('ui/new_platform'); jest.mock('./operations'); describe('state_helpers', () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/state_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/state_helpers.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/to_expression.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts similarity index 94% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/types.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/types.ts index 3820ff3b387bb..563af40ed2720 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -5,7 +5,7 @@ */ import { IndexPatternColumn } from './operations'; -import { IndexPatternAggRestrictions } from '../../../../../../src/plugins/data/public'; +import { IndexPatternAggRestrictions } from '../../../../../src/plugins/data/public'; export interface IndexPattern { id: string; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/indexpattern_datasource/utils.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/utils.ts diff --git a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts rename to x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts diff --git a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts similarity index 96% rename from x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts rename to x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts index 73750a65c50b8..10b052c66efed 100644 --- a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts +++ b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts @@ -5,10 +5,10 @@ */ import moment from 'moment'; -import { HttpSetup } from 'src/core/public'; +import { HttpSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; -import { BASE_API_URL } from '../../../../../plugins/lens/common'; +import { BASE_API_URL } from '../../common'; const STORAGE_KEY = 'lens-ui-telemetry'; diff --git a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/index.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/index.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/lens_ui_telemetry/index.ts rename to x-pack/plugins/lens/public/lens_ui_telemetry/index.ts diff --git a/x-pack/legacy/plugins/lens/public/loader.test.tsx b/x-pack/plugins/lens/public/loader.test.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/loader.test.tsx rename to x-pack/plugins/lens/public/loader.test.tsx diff --git a/x-pack/legacy/plugins/lens/public/loader.tsx b/x-pack/plugins/lens/public/loader.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/loader.tsx rename to x-pack/plugins/lens/public/loader.tsx diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/auto_scale.test.tsx b/x-pack/plugins/lens/public/metric_visualization/auto_scale.test.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/metric_visualization/auto_scale.test.tsx rename to x-pack/plugins/lens/public/metric_visualization/auto_scale.test.tsx diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/auto_scale.tsx b/x-pack/plugins/lens/public/metric_visualization/auto_scale.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/metric_visualization/auto_scale.tsx rename to x-pack/plugins/lens/public/metric_visualization/auto_scale.tsx diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/index.scss b/x-pack/plugins/lens/public/metric_visualization/index.scss similarity index 100% rename from x-pack/legacy/plugins/lens/public/metric_visualization/index.scss rename to x-pack/plugins/lens/public/metric_visualization/index.scss diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/index.ts b/x-pack/plugins/lens/public/metric_visualization/index.ts similarity index 88% rename from x-pack/legacy/plugins/lens/public/metric_visualization/index.ts rename to x-pack/plugins/lens/public/metric_visualization/index.ts index 65f064258a5e2..2960da52191e4 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization/index.ts +++ b/x-pack/plugins/lens/public/metric_visualization/index.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup } from 'src/core/public'; +import { CoreSetup } from 'kibana/public'; import { metricVisualization } from './metric_visualization'; -import { ExpressionsSetup } from '../../../../../../src/plugins/expressions/public'; +import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; import { metricChart, getMetricChartRenderer } from './metric_expression'; import { EditorFrameSetup, FormatFactory } from '../types'; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/metric_expression.test.tsx b/x-pack/plugins/lens/public/metric_visualization/metric_expression.test.tsx similarity index 94% rename from x-pack/legacy/plugins/lens/public/metric_visualization/metric_expression.test.tsx rename to x-pack/plugins/lens/public/metric_visualization/metric_expression.test.tsx index 3da38d486aecd..2406e7cd42ebc 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization/metric_expression.test.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/metric_expression.test.tsx @@ -9,8 +9,8 @@ import { LensMultiTable } from '../types'; import React from 'react'; import { shallow } from 'enzyme'; import { MetricConfig } from './types'; -import { createMockExecutionContext } from '../../../../../../src/plugins/expressions/common/mocks'; -import { IFieldFormat } from '../../../../../../src/plugins/data/public'; +import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; +import { IFieldFormat } from '../../../../../src/plugins/data/public'; function sampleArgs() { const data: LensMultiTable = { diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/metric_expression.tsx b/x-pack/plugins/lens/public/metric_visualization/metric_expression.tsx similarity index 98% rename from x-pack/legacy/plugins/lens/public/metric_visualization/metric_expression.tsx rename to x-pack/plugins/lens/public/metric_visualization/metric_expression.tsx index a80552e57a9e0..3484837f65b43 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization/metric_expression.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/metric_expression.tsx @@ -10,7 +10,7 @@ import { ExpressionFunctionDefinition, ExpressionRenderDefinition, IInterpreterRenderHandlers, -} from '../../../../../../src/plugins/expressions/public'; +} from '../../../../../src/plugins/expressions/public'; import { MetricConfig } from './types'; import { FormatFactory, LensMultiTable } from '../types'; import { AutoScale } from './auto_scale'; diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/metric_suggestions.test.ts b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.test.ts similarity index 98% rename from x-pack/legacy/plugins/lens/public/metric_visualization/metric_suggestions.test.ts rename to x-pack/plugins/lens/public/metric_visualization/metric_suggestions.test.ts index c9bfadbefaf5f..ef93f0b5bf064 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization/metric_suggestions.test.ts +++ b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.test.ts @@ -5,7 +5,7 @@ */ import { getSuggestions } from './metric_suggestions'; -import { TableSuggestionColumn, TableSuggestion } from '..'; +import { TableSuggestionColumn, TableSuggestion } from '../index'; describe('metric_suggestions', () => { function numCol(columnId: string): TableSuggestionColumn { diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/metric_suggestions.ts b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/metric_visualization/metric_suggestions.ts rename to x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/metric_visualization.test.ts b/x-pack/plugins/lens/public/metric_visualization/metric_visualization.test.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/metric_visualization/metric_visualization.test.ts rename to x-pack/plugins/lens/public/metric_visualization/metric_visualization.test.ts diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/metric_visualization.tsx b/x-pack/plugins/lens/public/metric_visualization/metric_visualization.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/metric_visualization/metric_visualization.tsx rename to x-pack/plugins/lens/public/metric_visualization/metric_visualization.tsx diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization/types.ts b/x-pack/plugins/lens/public/metric_visualization/types.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/metric_visualization/types.ts rename to x-pack/plugins/lens/public/metric_visualization/types.ts diff --git a/x-pack/legacy/plugins/lens/public/native_renderer/index.ts b/x-pack/plugins/lens/public/native_renderer/index.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/native_renderer/index.ts rename to x-pack/plugins/lens/public/native_renderer/index.ts diff --git a/x-pack/legacy/plugins/lens/public/native_renderer/native_renderer.test.tsx b/x-pack/plugins/lens/public/native_renderer/native_renderer.test.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/native_renderer/native_renderer.test.tsx rename to x-pack/plugins/lens/public/native_renderer/native_renderer.test.tsx diff --git a/x-pack/legacy/plugins/lens/public/native_renderer/native_renderer.tsx b/x-pack/plugins/lens/public/native_renderer/native_renderer.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/native_renderer/native_renderer.tsx rename to x-pack/plugins/lens/public/native_renderer/native_renderer.tsx diff --git a/x-pack/legacy/plugins/lens/public/persistence/index.ts b/x-pack/plugins/lens/public/persistence/index.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/persistence/index.ts rename to x-pack/plugins/lens/public/persistence/index.ts diff --git a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.test.ts b/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/persistence/saved_object_store.test.ts rename to x-pack/plugins/lens/public/persistence/saved_object_store.test.ts diff --git a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts b/x-pack/plugins/lens/public/persistence/saved_object_store.ts similarity index 94% rename from x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts rename to x-pack/plugins/lens/public/persistence/saved_object_store.ts index ac0b3322b400e..015f4b9b825f4 100644 --- a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts +++ b/x-pack/plugins/lens/public/persistence/saved_object_store.ts @@ -5,8 +5,8 @@ */ // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectAttributes } from 'src/core/server'; -import { Query, Filter } from '../../../../../../src/plugins/data/public'; +import { SavedObjectAttributes } from 'kibana/server'; +import { Query, Filter } from '../../../../../src/plugins/data/public'; export interface Document { id?: string; diff --git a/x-pack/legacy/plugins/lens/public/plugin.tsx b/x-pack/plugins/lens/public/plugin.tsx similarity index 90% rename from x-pack/legacy/plugins/lens/public/plugin.tsx rename to x-pack/plugins/lens/public/plugin.tsx index b426a12d07f9b..8d760eb0df501 100644 --- a/x-pack/legacy/plugins/lens/public/plugin.tsx +++ b/x-pack/plugins/lens/public/plugin.tsx @@ -11,14 +11,15 @@ import { render, unmountComponentAtNode } from 'react-dom'; import rison, { RisonObject, RisonValue } from 'rison-node'; import { isObject } from 'lodash'; -import { AppMountParameters, CoreSetup, CoreStart } from 'src/core/public'; +import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public'; import { VisualizationsSetup } from 'src/plugins/visualizations/public'; +import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public'; -import { DashboardConstants } from '../../../../../src/plugins/dashboard/public'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { DashboardConstants } from '../../../../src/plugins/dashboard/public'; +import { Storage } from '../../../../src/plugins/kibana_utils/public'; import { EditorFrameService } from './editor_frame_service'; import { IndexPatternDatasource } from './indexpattern_datasource'; import { addHelpMenuToAppChrome } from './help_menu_util'; @@ -34,17 +35,19 @@ import { trackUiEvent, } from './lens_ui_telemetry'; -import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; -import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../../../plugins/lens/common'; +import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; import { addEmbeddableToDashboardUrl, getUrlVars } from './helpers'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; +import './index.scss'; + export interface LensPluginSetupDependencies { kibanaLegacy: KibanaLegacySetup; expressions: ExpressionsSetup; data: DataPublicPluginSetup; - embeddable: EmbeddableSetup; + embeddable?: EmbeddableSetup; visualizations: VisualizationsSetup; } @@ -52,6 +55,7 @@ export interface LensPluginStartDependencies { data: DataPublicPluginStart; embeddable: EmbeddableStart; expressions: ExpressionsStart; + navigation: NavigationPublicPluginStart; uiActions: UiActionsStart; } @@ -75,7 +79,7 @@ export class LensPlugin { } setup( - core: CoreSetup, + core: CoreSetup, { kibanaLegacy, expressions, data, embeddable, visualizations }: LensPluginSetupDependencies ) { const editorFrameSetupInterface = this.editorFrameService.setup(core, { @@ -103,9 +107,9 @@ export class LensPlugin { title: NOT_INTERNATIONALIZED_PRODUCT_NAME, mount: async (params: AppMountParameters) => { const [coreStart, startDependencies] = await core.getStartServices(); - const dataStart = startDependencies.data; + const { data: dataStart, navigation } = startDependencies; const savedObjectsClient = coreStart.savedObjects.client; - addHelpMenuToAppChrome(coreStart.chrome); + addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks); const instance = await this.createEditorFrame!(); @@ -157,6 +161,7 @@ export class LensPlugin { void; diff --git a/x-pack/legacy/plugins/lens/public/vis_type_alias.ts b/x-pack/plugins/lens/public/vis_type_alias.ts similarity index 95% rename from x-pack/legacy/plugins/lens/public/vis_type_alias.ts rename to x-pack/plugins/lens/public/vis_type_alias.ts index 123b994e6ccce..807504ee2b9c2 100644 --- a/x-pack/legacy/plugins/lens/public/vis_type_alias.ts +++ b/x-pack/plugins/lens/public/vis_type_alias.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { VisTypeAlias } from 'src/plugins/visualizations/public'; -import { getBasePath, getEditPath } from '../../../../plugins/lens/common'; +import { getBasePath, getEditPath } from '../common'; export const getLensAliasConfig = (): VisTypeAlias => ({ aliasUrl: getBasePath(), diff --git a/x-pack/legacy/plugins/lens/public/visualization_container.test.tsx b/x-pack/plugins/lens/public/visualization_container.test.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/visualization_container.test.tsx rename to x-pack/plugins/lens/public/visualization_container.test.tsx diff --git a/x-pack/legacy/plugins/lens/public/visualization_container.tsx b/x-pack/plugins/lens/public/visualization_container.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/visualization_container.tsx rename to x-pack/plugins/lens/public/visualization_container.tsx diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap rename to x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap rename to x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap diff --git a/x-pack/plugins/lens/public/xy_visualization/_index.scss b/x-pack/plugins/lens/public/xy_visualization/_index.scss new file mode 100644 index 0000000000000..110a9589a6fb4 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/_index.scss @@ -0,0 +1 @@ +@import 'xy_expression'; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/_xy_expression.scss b/x-pack/plugins/lens/public/xy_visualization/_xy_expression.scss similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/_xy_expression.scss rename to x-pack/plugins/lens/public/xy_visualization/_xy_expression.scss diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/index.ts b/x-pack/plugins/lens/public/xy_visualization/index.ts similarity index 89% rename from x-pack/legacy/plugins/lens/public/xy_visualization/index.ts rename to x-pack/plugins/lens/public/xy_visualization/index.ts index 8cc5abb44d6e1..5dfae097be834 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/index.ts +++ b/x-pack/plugins/lens/public/xy_visualization/index.ts @@ -5,14 +5,14 @@ */ import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; -import { CoreSetup, IUiSettingsClient, CoreStart } from 'src/core/public'; +import { CoreSetup, IUiSettingsClient, CoreStart } from 'kibana/public'; import moment from 'moment-timezone'; -import { ExpressionsSetup } from '../../../../../../src/plugins/expressions/public'; +import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; import { xyVisualization } from './xy_visualization'; import { xyChart, getXyChartRenderer } from './xy_expression'; import { legendConfig, xConfig, layerConfig } from './types'; import { EditorFrameSetup, FormatFactory } from '../types'; -import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { setExecuteTriggerActions } from './services'; export interface XyVisualizationPluginSetupPlugins { diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/services.ts b/x-pack/plugins/lens/public/xy_visualization/services.ts similarity index 70% rename from x-pack/legacy/plugins/lens/public/xy_visualization/services.ts rename to x-pack/plugins/lens/public/xy_visualization/services.ts index af683efb86534..51289fe0c63e7 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/services.ts +++ b/x-pack/plugins/lens/public/xy_visualization/services.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createGetterSetter } from '../../../../../../src/plugins/kibana_utils/public'; -import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; +import { createGetterSetter } from '../../../../../src/plugins/kibana_utils/public'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; export const [getExecuteTriggerActions, setExecuteTriggerActions] = createGetterSetter< UiActionsStart['executeTriggerActions'] diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/state_helpers.ts b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/state_helpers.ts rename to x-pack/plugins/lens/public/xy_visualization/state_helpers.ts diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/to_expression.test.ts rename to x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/to_expression.ts rename to x-pack/plugins/lens/public/xy_visualization/to_expression.ts diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts similarity index 99% rename from x-pack/legacy/plugins/lens/public/xy_visualization/types.ts rename to x-pack/plugins/lens/public/xy_visualization/types.ts index f7b4afc76ec4b..7a5837d382c7b 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -15,7 +15,7 @@ import chartBarHorizontalSVG from '../assets/chart_bar_horizontal.svg'; import chartBarHorizontalStackedSVG from '../assets/chart_bar_horizontal_stacked.svg'; import chartLineSVG from '../assets/chart_line.svg'; -import { VisualizationType } from '..'; +import { VisualizationType } from '../index'; export interface LegendConfig { isVisible: boolean; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/xy_config_panel.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx similarity index 73% rename from x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx index 54abc2c2bb667..80d33d1b95b61 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx @@ -18,14 +18,11 @@ import { } from '@elastic/charts'; import { xyChart, XYChart } from './xy_expression'; import { LensMultiTable } from '../types'; -import { - KibanaDatatable, - KibanaDatatableRow, -} from '../../../../../../src/plugins/expressions/public'; +import { KibanaDatatable, KibanaDatatableRow } from '../../../../../src/plugins/expressions/public'; import React from 'react'; import { shallow } from 'enzyme'; import { XYArgs, LegendConfig, legendConfig, layerConfig, LayerArgs } from './types'; -import { createMockExecutionContext } from '../../../../../../src/plugins/expressions/common/mocks'; +import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; const executeTriggerActions = jest.fn(); @@ -633,70 +630,242 @@ describe('xy_expression', () => { expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(false); }); - test('it names the series for multiple accessors', () => { - const { data, args } = sampleArgs(); - - const component = shallow( - - ); - const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; - - expect( - nameFn( - { - seriesKeys: ['a', 'b', 'c', 'd'], - key: '', - specId: 'a', - yAccessor: '', - splitAccessors: new Map(), + describe('provides correct series naming', () => { + const dataWithoutFormats: LensMultiTable = { + type: 'lens_multitable', + tables: { + first: { + type: 'kibana_datatable', + columns: [ + { id: 'a', name: 'a' }, + { id: 'b', name: 'b' }, + { id: 'c', name: 'c' }, + { id: 'd', name: 'd' }, + ], + rows: [ + { a: 1, b: 2, c: 'I', d: 'Row 1' }, + { a: 1, b: 5, c: 'J', d: 'Row 2' }, + ], }, - false - ) - ).toEqual('Label A - Label B - c - Label D'); - }); - - test('it names the series for a single accessor', () => { - const { data, args } = sampleArgs(); - - const component = shallow( - - ); - const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; - - expect( - nameFn( - { - seriesKeys: ['a', 'b', 'c', 'd'], - key: '', - specId: 'a', - yAccessor: '', - splitAccessors: new Map(), }, - false - ) - ).toEqual('Label A'); + }, + }; + + const nameFnArgs = { + seriesKeys: [], + key: '', + specId: 'a', + yAccessor: '', + splitAccessors: new Map(), + }; + + const getRenderedComponent = (data: LensMultiTable, args: XYArgs) => { + return shallow( + + ); + }; + + test('simplest xy chart without human-readable name', () => { + const args = createArgsWithLayers(); + const newArgs = { + ...args, + layers: [ + { + ...args.layers[0], + accessors: ['a'], + splitAccessor: undefined, + columnToLabel: '', + }, + ], + }; + + const component = getRenderedComponent(dataWithoutFormats, newArgs); + const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; + + // In this case, the ID is used as the name. This shouldn't happen in practice + expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual(''); + expect(nameFn({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual(''); + }); + + test('simplest xy chart with empty name', () => { + const args = createArgsWithLayers(); + const newArgs = { + ...args, + layers: [ + { + ...args.layers[0], + accessors: ['a'], + splitAccessor: undefined, + columnToLabel: '{"a":""}', + }, + ], + }; + + const component = getRenderedComponent(dataWithoutFormats, newArgs); + const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; + + // In this case, the ID is used as the name. This shouldn't happen in practice + expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual(''); + expect(nameFn({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual(''); + }); + + test('simplest xy chart with human-readable name', () => { + const args = createArgsWithLayers(); + const newArgs = { + ...args, + layers: [ + { + ...args.layers[0], + accessors: ['a'], + splitAccessor: undefined, + columnToLabel: '{"a":"Column A"}', + }, + ], + }; + + const component = getRenderedComponent(dataWithoutFormats, newArgs); + const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; + + expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('Column A'); + }); + + test('multiple y accessors', () => { + const args = createArgsWithLayers(); + const newArgs = { + ...args, + layers: [ + { + ...args.layers[0], + accessors: ['a', 'b'], + splitAccessor: undefined, + columnToLabel: '{"a": "Label A"}', + }, + ], + }; + + const component = getRenderedComponent(dataWithoutFormats, newArgs); + const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; + + // This accessor has a human-readable name + expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('Label A'); + // This accessor does not + expect(nameFn({ ...nameFnArgs, seriesKeys: ['b'] }, false)).toEqual(''); + expect(nameFn({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual(''); + }); + + test('split series without formatting and single y accessor', () => { + const args = createArgsWithLayers(); + const newArgs = { + ...args, + layers: [ + { + ...args.layers[0], + accessors: ['a'], + splitAccessor: 'd', + columnToLabel: '{"a": "Label A"}', + }, + ], + }; + + const component = getRenderedComponent(dataWithoutFormats, newArgs); + const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; + + expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual('split1'); + }); + + test('split series with formatting and single y accessor', () => { + const args = createArgsWithLayers(); + const newArgs = { + ...args, + layers: [ + { + ...args.layers[0], + accessors: ['a'], + splitAccessor: 'd', + columnToLabel: '{"a": "Label A"}', + }, + ], + }; + + const component = getRenderedComponent(dataWithFormats, newArgs); + const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; + + convertSpy.mockReturnValueOnce('formatted'); + expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual('formatted'); + expect(getFormatSpy).toHaveBeenCalledWith({ id: 'custom' }); + }); + + test('split series without formatting with multiple y accessors', () => { + const args = createArgsWithLayers(); + const newArgs = { + ...args, + layers: [ + { + ...args.layers[0], + accessors: ['a', 'b'], + splitAccessor: 'd', + columnToLabel: '{"a": "Label A","b": "Label B"}', + }, + ], + }; + + const component = getRenderedComponent(dataWithoutFormats, newArgs); + const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; + + expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual( + 'split1 - Label B' + ); + }); + + test('split series with formatting with multiple y accessors', () => { + const args = createArgsWithLayers(); + const newArgs = { + ...args, + layers: [ + { + ...args.layers[0], + accessors: ['a', 'b'], + splitAccessor: 'd', + columnToLabel: '{"a": "Label A","b": "Label B"}', + }, + ], + }; + + const component = getRenderedComponent(dataWithFormats, newArgs); + const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn; + + convertSpy.mockReturnValueOnce('formatted1').mockReturnValueOnce('formatted2'); + expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual( + 'formatted1 - Label A' + ); + expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual( + 'formatted2 - Label B' + ); + }); }); test('it set the scale of the x axis according to the args prop', () => { diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx similarity index 88% rename from x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx index f5798688badc5..f12a0e5b907c7 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx @@ -28,14 +28,14 @@ import { import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EmbeddableVisTriggerContext } from '../../../../../../src/plugins/embeddable/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public'; +import { EmbeddableVisTriggerContext } from '../../../../../src/plugins/embeddable/public'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; import { LensMultiTable, FormatFactory } from '../types'; import { XYArgs, SeriesType, visualizationTypes } from './types'; import { VisualizationContainer } from '../visualization_container'; import { isHorizontalChart } from './state_helpers'; -import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; -import { parseInterval } from '../../../../../../src/plugins/data/common'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; +import { parseInterval } from '../../../../../src/plugins/data/common'; import { getExecuteTriggerActions } from './services'; type InferPropType = T extends React.FunctionComponent ? P : T; @@ -361,12 +361,31 @@ export function XYChart({ enableHistogramMode: isHistogram && (seriesType.includes('stacked') || !splitAccessor), timeZone, name(d) { + const splitHint = table.columns.find(col => col.id === splitAccessor)?.formatHint; + + // For multiple y series, the name of the operation is used on each, either: + // * Key - Y name + // * Formatted value - Y name if (accessors.length > 1) { return d.seriesKeys - .map((key: string | number) => columnToLabelMap[key] || key) + .map((key: string | number, i) => { + if (i === 0 && splitHint) { + return formatFactory(splitHint).convert(key); + } + return splitAccessor && i === 0 ? key : columnToLabelMap[key] ?? ''; + }) .join(' - '); } - return columnToLabelMap[d.seriesKeys[0]] ?? d.seriesKeys[0]; + + // For formatted split series, format the key + // This handles splitting by dates, for example + if (splitHint) { + return formatFactory(splitHint).convert(d.seriesKeys[0]); + } + // This handles both split and single-y cases: + // * If split series without formatting, show the value literally + // * If single Y, the seriesKey will be the acccessor, so we show the human-readable name + return splitAccessor ? d.seriesKeys[0] : columnToLabelMap[d.seriesKeys[0]] ?? ''; }, }; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_suggestions.test.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/xy_suggestions.test.ts rename to x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/xy_suggestions.ts rename to x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/xy_visualization.test.ts similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/xy_visualization.test.ts rename to x-pack/plugins/lens/public/xy_visualization/xy_visualization.test.ts diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_visualization.tsx similarity index 100% rename from x-pack/legacy/plugins/lens/public/xy_visualization/xy_visualization.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_visualization.tsx diff --git a/x-pack/legacy/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md similarity index 100% rename from x-pack/legacy/plugins/lens/readme.md rename to x-pack/plugins/lens/readme.md diff --git a/x-pack/plugins/lens/server/index.ts b/x-pack/plugins/lens/server/index.ts index 3b9e94986d247..8aeeeab4539b6 100644 --- a/x-pack/plugins/lens/server/index.ts +++ b/x-pack/plugins/lens/server/index.ts @@ -4,10 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'kibana/server'; +import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server'; import { LensServerPlugin } from './plugin'; export * from './plugin'; +import { configSchema, ConfigSchema } from '../config'; + +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; + export const plugin = (initializerContext: PluginInitializerContext) => new LensServerPlugin(initializerContext); diff --git a/x-pack/plugins/lens/server/migrations.test.ts b/x-pack/plugins/lens/server/migrations.test.ts new file mode 100644 index 0000000000000..e80308cc9acdb --- /dev/null +++ b/x-pack/plugins/lens/server/migrations.test.ts @@ -0,0 +1,161 @@ +/* + * 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 { migrations } from './migrations'; +import { SavedObjectMigrationContext } from 'src/core/server'; + +describe('Lens migrations', () => { + describe('7.7.0 missing dimensions in XY', () => { + const context = {} as SavedObjectMigrationContext; + + const example = { + type: 'lens', + attributes: { + expression: + 'kibana\n| kibana_context query="{\\"language\\":\\"kuery\\",\\"query\\":\\"\\"}" \n| lens_merge_tables layerIds="c61a8afb-a185-4fae-a064-fb3846f6c451" \n tables={esaggs index="logstash-*" metricsAtAllLevels=false partialRows=false includeFormatHints=true aggConfigs="[{\\"id\\":\\"2cd09808-3915-49f4-b3b0-82767eba23f7\\",\\"enabled\\":true,\\"type\\":\\"max\\",\\"schema\\":\\"metric\\",\\"params\\":{\\"field\\":\\"bytes\\"}}]" | lens_rename_columns idMap="{\\"col-0-2cd09808-3915-49f4-b3b0-82767eba23f7\\":\\"2cd09808-3915-49f4-b3b0-82767eba23f7\\"}"}\n| lens_metric_chart title="Maximum of bytes" accessor="2cd09808-3915-49f4-b3b0-82767eba23f7"', + state: { + datasourceMetaData: { + filterableIndexPatterns: [ + { + id: 'logstash-*', + title: 'logstash-*', + }, + ], + }, + datasourceStates: { + indexpattern: { + currentIndexPatternId: 'logstash-*', + layers: { + 'c61a8afb-a185-4fae-a064-fb3846f6c451': { + columnOrder: ['2cd09808-3915-49f4-b3b0-82767eba23f7'], + columns: { + '2cd09808-3915-49f4-b3b0-82767eba23f7': { + dataType: 'number', + isBucketed: false, + label: 'Maximum of bytes', + operationType: 'max', + scale: 'ratio', + sourceField: 'bytes', + }, + 'd3e62a7a-c259-4fff-a2fc-eebf20b7008a': { + dataType: 'number', + isBucketed: false, + label: 'Minimum of bytes', + operationType: 'min', + scale: 'ratio', + sourceField: 'bytes', + }, + 'd6e40cea-6299-43b4-9c9d-b4ee305a2ce8': { + dataType: 'date', + isBucketed: true, + label: 'Date Histogram of @timestamp', + operationType: 'date_histogram', + params: { + interval: 'auto', + }, + scale: 'interval', + sourceField: '@timestamp', + }, + }, + indexPatternId: 'logstash-*', + }, + }, + }, + }, + filters: [], + query: { + language: 'kuery', + query: '', + }, + visualization: { + accessor: '2cd09808-3915-49f4-b3b0-82767eba23f7', + isHorizontal: false, + layerId: 'c61a8afb-a185-4fae-a064-fb3846f6c451', + layers: [ + { + accessors: [ + 'd3e62a7a-c259-4fff-a2fc-eebf20b7008a', + '26ef70a9-c837-444c-886e-6bd905ee7335', + ], + layerId: 'c61a8afb-a185-4fae-a064-fb3846f6c451', + seriesType: 'area', + splitAccessor: '54cd64ed-2a44-4591-af84-b2624504569a', + xAccessor: 'd6e40cea-6299-43b4-9c9d-b4ee305a2ce8', + }, + ], + legend: { + isVisible: true, + position: 'right', + }, + preferredSeriesType: 'area', + }, + }, + title: 'Artistpreviouslyknownaslens', + visualizationType: 'lnsXY', + }, + }; + + it('should not change anything by XY visualizations', () => { + const target = { + ...example, + attributes: { + ...example.attributes, + visualizationType: 'lnsMetric', + }, + }; + const result = migrations['7.7.0'](target, context); + expect(result).toEqual(target); + }); + + it('should handle missing layers', () => { + const result = migrations['7.7.0']( + { + ...example, + attributes: { + ...example.attributes, + state: { + ...example.attributes.state, + datasourceStates: { + indexpattern: { + layers: [], + }, + }, + }, + }, + }, + context + ); + + expect(result.attributes.state.visualization.layers).toEqual([ + { + layerId: 'c61a8afb-a185-4fae-a064-fb3846f6c451', + seriesType: 'area', + // Removed split accessor + splitAccessor: undefined, + xAccessor: undefined, + // Removed a yAcccessor + accessors: [], + }, + ]); + }); + + it('should remove only missing accessors', () => { + const result = migrations['7.7.0'](example, context); + + expect(result.attributes.state.visualization.layers).toEqual([ + { + layerId: 'c61a8afb-a185-4fae-a064-fb3846f6c451', + seriesType: 'area', + xAccessor: 'd6e40cea-6299-43b4-9c9d-b4ee305a2ce8', + // Removed split accessor + splitAccessor: undefined, + // Removed a yAcccessor + accessors: ['d3e62a7a-c259-4fff-a2fc-eebf20b7008a'], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/lens/server/migrations.ts b/x-pack/plugins/lens/server/migrations.ts new file mode 100644 index 0000000000000..3d238723b7438 --- /dev/null +++ b/x-pack/plugins/lens/server/migrations.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { cloneDeep } from 'lodash'; +import { SavedObjectMigrationFn } from 'src/core/server'; + +interface XYLayerPre77 { + layerId: string; + xAccessor: string; + splitAccessor: string; + accessors: string[]; +} + +export const migrations: Record = { + '7.7.0': doc => { + const newDoc = cloneDeep(doc); + if (newDoc.attributes?.visualizationType === 'lnsXY') { + const datasourceState = newDoc.attributes.state?.datasourceStates?.indexpattern; + const datasourceLayers = datasourceState?.layers ?? {}; + const xyState = newDoc.attributes.state?.visualization; + newDoc.attributes.state.visualization.layers = xyState.layers.map((layer: XYLayerPre77) => { + const layerId = layer.layerId; + const datasource = datasourceLayers[layerId]; + return { + ...layer, + xAccessor: datasource?.columns[layer.xAccessor] ? layer.xAccessor : undefined, + splitAccessor: datasource?.columns[layer.splitAccessor] ? layer.splitAccessor : undefined, + accessors: layer.accessors.filter(accessor => !!datasource?.columns[accessor]), + }; + }) as typeof xyState.layers; + } + return newDoc; + }, +}; diff --git a/x-pack/plugins/lens/server/saved_objects.ts b/x-pack/plugins/lens/server/saved_objects.ts index 42dc750878f45..b2eb325c09266 100644 --- a/x-pack/plugins/lens/server/saved_objects.ts +++ b/x-pack/plugins/lens/server/saved_objects.ts @@ -6,6 +6,7 @@ import { CoreSetup } from 'kibana/server'; import { getEditPath } from '../common'; +import { migrations } from './migrations'; export function setupSavedObjects(core: CoreSetup) { core.savedObjects.registerType({ @@ -22,6 +23,7 @@ export function setupSavedObjects(core: CoreSetup) { uiCapabilitiesPath: 'visualize.show', }), }, + migrations, mappings: { properties: { title: { diff --git a/x-pack/plugins/lens/server/usage/task.ts b/x-pack/plugins/lens/server/usage/task.ts index e2462be149745..469457f973b62 100644 --- a/x-pack/plugins/lens/server/usage/task.ts +++ b/x-pack/plugins/lens/server/usage/task.ts @@ -184,7 +184,10 @@ export function telemetryTaskRunner( ) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; - const callCluster = core.elasticsearch.adminClient.callAsInternalUser; + const callCluster = async (...args: Parameters) => { + const [coreStart] = await core.getStartServices(); + return coreStart.elasticsearch.legacy.client.callAsInternalUser(...args); + }; return { async run() { diff --git a/x-pack/plugins/licensing/common/licensing.mock.ts b/x-pack/plugins/licensing/common/licensing.mock.ts index bf8b85e3e981b..4a6b27255587a 100644 --- a/x-pack/plugins/licensing/common/licensing.mock.ts +++ b/x-pack/plugins/licensing/common/licensing.mock.ts @@ -53,6 +53,7 @@ const createLicenseMock = () => { }; mock.check.mockReturnValue({ state: 'valid' }); mock.hasAtLeast.mockReturnValue(true); + mock.getFeature.mockReturnValue({ isAvailable: true, isEnabled: true }); return mock; }; export const licenseMock = { diff --git a/x-pack/plugins/licensing/server/index.ts b/x-pack/plugins/licensing/server/index.ts index 0e14ead7c6c57..76e65afc595c4 100644 --- a/x-pack/plugins/licensing/server/index.ts +++ b/x-pack/plugins/licensing/server/index.ts @@ -12,3 +12,4 @@ export const plugin = (context: PluginInitializerContext) => new LicensingPlugin export * from '../common/types'; export * from './types'; export { config } from './licensing_config'; +export { CheckLicense, wrapRouteWithLicenseCheck } from './wrap_route_with_license_check'; diff --git a/x-pack/plugins/licensing/server/wrap_route_with_license_check.test.ts b/x-pack/plugins/licensing/server/wrap_route_with_license_check.test.ts new file mode 100644 index 0000000000000..7abdd3f6190ce --- /dev/null +++ b/x-pack/plugins/licensing/server/wrap_route_with_license_check.test.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 { httpServerMock } from 'src/core/server/mocks'; + +import { wrapRouteWithLicenseCheck, CheckLicense } from './wrap_route_with_license_check'; + +const context = { + licensing: { + license: {}, + }, +} as any; +const request = httpServerMock.createKibanaRequest(); + +describe('wrapRouteWithLicenseCheck', () => { + it('calls route handler if checkLicense returns "valid": true', async () => { + const checkLicense: CheckLicense = () => ({ valid: true, message: null }); + const routeHandler = jest.fn(); + const wrapper = wrapRouteWithLicenseCheck(checkLicense, routeHandler); + const response = httpServerMock.createResponseFactory(); + + await wrapper(context, request, response); + + expect(routeHandler).toHaveBeenCalledTimes(1); + expect(routeHandler).toHaveBeenCalledWith(context, request, response); + }); + + it('does not call route handler if checkLicense returns "valid": false', async () => { + const checkLicense: CheckLicense = () => ({ valid: false, message: 'reason' }); + const routeHandler = jest.fn(); + const wrapper = wrapRouteWithLicenseCheck(checkLicense, routeHandler); + const response = httpServerMock.createResponseFactory(); + + await wrapper(context, request, response); + + expect(routeHandler).toHaveBeenCalledTimes(0); + expect(response.forbidden).toHaveBeenCalledTimes(1); + expect(response.forbidden).toHaveBeenCalledWith({ body: 'reason' }); + }); + + it('allows an exception to bubble up if handler throws', async () => { + const checkLicense: CheckLicense = () => ({ valid: true, message: null }); + const routeHandler = () => { + throw new Error('reason'); + }; + const wrapper = wrapRouteWithLicenseCheck(checkLicense, routeHandler); + const response = httpServerMock.createResponseFactory(); + + await expect(wrapper(context, request, response)).rejects.toThrowErrorMatchingInlineSnapshot( + `"reason"` + ); + }); + + it('allows an exception to bubble up if "checkLicense" throws', async () => { + const checkLicense: CheckLicense = () => { + throw new Error('reason'); + }; + const routeHandler = jest.fn(); + const wrapper = wrapRouteWithLicenseCheck(checkLicense, routeHandler); + const response = httpServerMock.createResponseFactory(); + + await expect(wrapper(context, request, response)).rejects.toThrowErrorMatchingInlineSnapshot( + `"reason"` + ); + + expect(routeHandler).toHaveBeenCalledTimes(0); + }); +}); diff --git a/x-pack/plugins/licensing/server/wrap_route_with_license_check.ts b/x-pack/plugins/licensing/server/wrap_route_with_license_check.ts new file mode 100644 index 0000000000000..e0cac8d9db208 --- /dev/null +++ b/x-pack/plugins/licensing/server/wrap_route_with_license_check.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + RequestHandler, + RequestHandlerContext, + KibanaRequest, + RouteMethod, + KibanaResponseFactory, +} from 'src/core/server'; + +import { ILicense } from '../common/types'; + +export type CheckLicense = ( + license: ILicense +) => { valid: false; message: string } | { valid: true; message: null }; + +export function wrapRouteWithLicenseCheck( + checkLicense: CheckLicense, + handler: RequestHandler +): RequestHandler { + return async ( + context: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) => { + const licenseCheckResult = checkLicense(context.licensing.license); + + if (licenseCheckResult.valid) { + return handler(context, request, response); + } else { + return response.forbidden({ + body: licenseCheckResult.message, + }); + } + }; +} diff --git a/x-pack/legacy/plugins/logstash/common/constants/es_scroll_settings.js b/x-pack/plugins/logstash/common/constants/es_scroll_settings.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/common/constants/es_scroll_settings.js rename to x-pack/plugins/logstash/common/constants/es_scroll_settings.ts diff --git a/x-pack/legacy/plugins/logstash/common/constants/index.js b/x-pack/plugins/logstash/common/constants/index.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/common/constants/index.js rename to x-pack/plugins/logstash/common/constants/index.ts diff --git a/x-pack/legacy/plugins/logstash/common/constants/index_names.js b/x-pack/plugins/logstash/common/constants/index_names.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/common/constants/index_names.js rename to x-pack/plugins/logstash/common/constants/index_names.ts diff --git a/x-pack/legacy/plugins/logstash/common/constants/monitoring.js b/x-pack/plugins/logstash/common/constants/monitoring.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/common/constants/monitoring.js rename to x-pack/plugins/logstash/common/constants/monitoring.ts diff --git a/x-pack/legacy/plugins/logstash/common/constants/pagination.js b/x-pack/plugins/logstash/common/constants/pagination.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/common/constants/pagination.js rename to x-pack/plugins/logstash/common/constants/pagination.ts diff --git a/x-pack/legacy/plugins/logstash/common/constants/pipeline.js b/x-pack/plugins/logstash/common/constants/pipeline.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/common/constants/pipeline.js rename to x-pack/plugins/logstash/common/constants/pipeline.ts diff --git a/x-pack/legacy/plugins/logstash/common/constants/plugin.js b/x-pack/plugins/logstash/common/constants/plugin.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/common/constants/plugin.js rename to x-pack/plugins/logstash/common/constants/plugin.ts diff --git a/x-pack/legacy/plugins/logstash/common/constants/routes.js b/x-pack/plugins/logstash/common/constants/routes.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/common/constants/routes.js rename to x-pack/plugins/logstash/common/constants/routes.ts diff --git a/x-pack/legacy/plugins/logstash/common/constants/tooltips.js b/x-pack/plugins/logstash/common/constants/tooltips.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/common/constants/tooltips.js rename to x-pack/plugins/logstash/common/constants/tooltips.ts diff --git a/x-pack/plugins/logstash/kibana.json b/x-pack/plugins/logstash/kibana.json new file mode 100644 index 0000000000000..bcc926535d3c2 --- /dev/null +++ b/x-pack/plugins/logstash/kibana.json @@ -0,0 +1,12 @@ +{ + "id": "logstash", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["xpack", "logstash"], + "requiredPlugins": [ + "licensing" + ], + "optionalPlugins": ["security"], + "server": true, + "ui": false +} diff --git a/x-pack/plugins/logstash/server/index.ts b/x-pack/plugins/logstash/server/index.ts new file mode 100644 index 0000000000000..cc65184a1f3a0 --- /dev/null +++ b/x-pack/plugins/logstash/server/index.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 { schema } from '@kbn/config-schema'; +import { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/server'; +import { LogstashPlugin } from './plugin'; + +export const plugin = (context: PluginInitializerContext) => new LogstashPlugin(context); + +export const config: PluginConfigDescriptor = { + schema: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), +}; diff --git a/x-pack/plugins/logstash/server/lib/check_license/check_license.test.ts b/x-pack/plugins/logstash/server/lib/check_license/check_license.test.ts new file mode 100755 index 0000000000000..324e0a22ff378 --- /dev/null +++ b/x-pack/plugins/logstash/server/lib/check_license/check_license.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { licensingMock } from '../../../../licensing/server/mocks'; +import { checkLicense } from './check_license'; + +describe('check_license', function() { + describe('returns "valid": false & message when', () => { + it('license information is not available', () => { + const license = licensingMock.createLicenseMock(); + license.isAvailable = false; + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(false); + expect(message).toStrictEqual(expect.any(String)); + }); + + it('license level is not enough', () => { + const license = licensingMock.createLicenseMock(); + license.hasAtLeast.mockReturnValue(false); + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(false); + expect(message).toStrictEqual(expect.any(String)); + }); + + it('license is expired', () => { + const license = licensingMock.createLicenseMock(); + license.isActive = false; + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(false); + expect(message).toStrictEqual(expect.any(String)); + }); + + it('elasticsearch security is disabled', () => { + const license = licensingMock.createLicenseMock(); + license.getFeature.mockReturnValue({ isEnabled: false, isAvailable: false }); + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(false); + expect(message).toStrictEqual(expect.any(String)); + }); + }); + + it('returns "valid": true without message otherwise', () => { + const license = licensingMock.createLicenseMock(); + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(true); + expect(message).toBe(null); + }); +}); diff --git a/x-pack/plugins/logstash/server/lib/check_license/check_license.ts b/x-pack/plugins/logstash/server/lib/check_license/check_license.ts new file mode 100644 index 0000000000000..4eef2eb9b0681 --- /dev/null +++ b/x-pack/plugins/logstash/server/lib/check_license/check_license.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 { i18n } from '@kbn/i18n'; + +import { CheckLicense } from '../../../../licensing/server'; + +export const checkLicense: CheckLicense = license => { + if (!license.isAvailable) { + return { + valid: false, + message: i18n.translate( + 'xpack.logstash.managementSection.notPossibleToManagePipelinesMessage', + { + defaultMessage: + 'You cannot manage Logstash pipelines because license information is not available at this time.', + } + ), + }; + } + + if (!license.hasAtLeast('standard')) { + return { + valid: false, + message: i18n.translate('xpack.logstash.managementSection.licenseDoesNotSupportDescription', { + defaultMessage: + 'Your {licenseType} license does not support Logstash pipeline management features. Please upgrade your license.', + values: { licenseType: license.type }, + }), + }; + } + + if (!license.isActive) { + return { + valid: false, + message: i18n.translate( + 'xpack.logstash.managementSection.pipelineCrudOperationsNotAllowedDescription', + { + defaultMessage: + 'You cannot edit, create, or delete your Logstash pipelines because your {licenseType} license has expired.', + values: { licenseType: license.type }, + } + ), + }; + } + + if (!license.getFeature('security').isEnabled) { + return { + valid: false, + message: i18n.translate('xpack.logstash.managementSection.enableSecurityDescription', { + defaultMessage: + 'Security must be enabled in order to use Logstash pipeline management features.' + + ' Please set xpack.security.enabled: true in your elasticsearch.yml.', + }), + }; + } + + return { + valid: true, + message: null, + }; +}; diff --git a/x-pack/plugins/infra/public/containers/waffle/index.ts b/x-pack/plugins/logstash/server/lib/check_license/index.ts similarity index 83% rename from x-pack/plugins/infra/public/containers/waffle/index.ts rename to x-pack/plugins/logstash/server/lib/check_license/index.ts index 40c4bfc8cf678..f2c070fd44b6e 100644 --- a/x-pack/plugins/infra/public/containers/waffle/index.ts +++ b/x-pack/plugins/logstash/server/lib/check_license/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './with_waffle_filters'; +export { checkLicense } from './check_license'; diff --git a/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.test.ts b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.test.ts new file mode 100755 index 0000000000000..8cd6b70d47570 --- /dev/null +++ b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.test.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 { fetchAllFromScroll } from './fetch_all_from_scroll'; + +describe('fetch_all_from_scroll', () => { + let stubCallWithRequest: jest.Mock; + + beforeEach(() => { + stubCallWithRequest = jest.fn(); + stubCallWithRequest + .mockResolvedValueOnce({ + hits: { + hits: ['newhit'], + }, + _scroll_id: 'newScrollId', + }) + .mockResolvedValueOnce({ + hits: { + hits: [], + }, + }); + }); + + describe('#fetchAllFromScroll', () => { + describe('when the passed-in response has no hits', () => { + const mockResponse = { + hits: { + hits: [], + }, + }; + + it('should return an empty array of hits', async () => { + const hits = await fetchAllFromScroll(mockResponse as any, stubCallWithRequest); + expect(hits).toEqual([]); + }); + + it('should not call callWithRequest', async () => { + await fetchAllFromScroll(mockResponse as any, stubCallWithRequest); + expect(stubCallWithRequest).toHaveBeenCalledTimes(0); + }); + }); + + describe('when the passed-in response has some hits', () => { + const mockResponse = { + hits: { + hits: ['foo', 'bar'], + }, + _scroll_id: 'originalScrollId', + }; + + it('should return the hits from the response', async () => { + const hits = await fetchAllFromScroll(mockResponse as any, stubCallWithRequest); + expect(hits).toEqual(['foo', 'bar', 'newhit']); + }); + + it('should call callWithRequest', async () => { + await fetchAllFromScroll(mockResponse as any, stubCallWithRequest); + expect(stubCallWithRequest).toHaveBeenCalledTimes(2); + + const firstCallWithRequestCallArgs = stubCallWithRequest.mock.calls[0]; + expect(firstCallWithRequestCallArgs[1].body.scroll_id).toBe('originalScrollId'); + + const secondCallWithRequestCallArgs = stubCallWithRequest.mock.calls[1]; + expect(secondCallWithRequestCallArgs[1].body.scroll_id).toBe('newScrollId'); + }); + }); + }); +}); diff --git a/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts new file mode 100755 index 0000000000000..060cf188a4c60 --- /dev/null +++ b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { APICaller } from 'src/core/server'; +import { SearchResponse } from 'elasticsearch'; + +import { ES_SCROLL_SETTINGS } from '../../../common/constants'; +import { Hits } from '../../types'; + +export async function fetchAllFromScroll( + response: SearchResponse, + callWithRequest: APICaller, + hits: Hits = [] +): Promise { + const newHits = response.hits?.hits || []; + const scrollId = response._scroll_id; + + if (newHits.length > 0) { + hits.push(...newHits); + + const innerResponse = await callWithRequest('scroll', { + body: { + scroll: ES_SCROLL_SETTINGS.KEEPALIVE, + scroll_id: scrollId, + }, + }); + + return await fetchAllFromScroll(innerResponse, callWithRequest, hits); + } + + return hits; +} diff --git a/x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/index.js b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/index.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/server/lib/fetch_all_from_scroll/index.js rename to x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/index.ts diff --git a/x-pack/legacy/plugins/logstash/server/models/cluster/__tests__/cluster.js b/x-pack/plugins/logstash/server/models/cluster/cluster.test.ts similarity index 81% rename from x-pack/legacy/plugins/logstash/server/models/cluster/__tests__/cluster.js rename to x-pack/plugins/logstash/server/models/cluster/cluster.test.ts index 08a447a160a1a..63f9f1e58f6ec 100755 --- a/x-pack/legacy/plugins/logstash/server/models/cluster/__tests__/cluster.js +++ b/x-pack/plugins/logstash/server/models/cluster/cluster.test.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { Cluster } from '../cluster'; +import { Cluster } from './cluster'; describe('cluster', () => { describe('Cluster', () => { @@ -16,7 +15,7 @@ describe('cluster', () => { it('returns correct Cluster instance', () => { const cluster = Cluster.fromUpstreamJSON(upstreamJSON); - expect(cluster.uuid).to.be(upstreamJSON.cluster_uuid); + expect(cluster.uuid).toEqual(upstreamJSON.cluster_uuid); }); }); }); diff --git a/x-pack/legacy/plugins/logstash/server/models/cluster/cluster.js b/x-pack/plugins/logstash/server/models/cluster/cluster.ts similarity index 68% rename from x-pack/legacy/plugins/logstash/server/models/cluster/cluster.js rename to x-pack/plugins/logstash/server/models/cluster/cluster.ts index b114162fb0986..54f03605e14d6 100755 --- a/x-pack/legacy/plugins/logstash/server/models/cluster/cluster.js +++ b/x-pack/plugins/logstash/server/models/cluster/cluster.ts @@ -10,11 +10,12 @@ import { get } from 'lodash'; * This model deals with a cluster object from ES and converts it to Kibana downstream */ export class Cluster { - constructor(props) { - this.uuid = props.uuid; + public readonly uuid: string; + constructor({ uuid }: { uuid: string }) { + this.uuid = uuid; } - get downstreamJSON() { + public get downstreamJSON() { const json = { uuid: this.uuid, }; @@ -23,8 +24,8 @@ export class Cluster { } // generate Pipeline object from elasticsearch response - static fromUpstreamJSON(upstreamCluster) { - const uuid = get(upstreamCluster, 'cluster_uuid'); + static fromUpstreamJSON(upstreamCluster: Record) { + const uuid = get(upstreamCluster, 'cluster_uuid'); return new Cluster({ uuid }); } } diff --git a/x-pack/legacy/plugins/logstash/server/models/cluster/index.js b/x-pack/plugins/logstash/server/models/cluster/index.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/server/models/cluster/index.js rename to x-pack/plugins/logstash/server/models/cluster/index.ts diff --git a/x-pack/legacy/plugins/logstash/server/models/pipeline/index.js b/x-pack/plugins/logstash/server/models/pipeline/index.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/server/models/pipeline/index.js rename to x-pack/plugins/logstash/server/models/pipeline/index.ts diff --git a/x-pack/legacy/plugins/logstash/server/models/pipeline/__tests__/pipeline.js b/x-pack/plugins/logstash/server/models/pipeline/pipeline.test.ts similarity index 74% rename from x-pack/legacy/plugins/logstash/server/models/pipeline/__tests__/pipeline.js rename to x-pack/plugins/logstash/server/models/pipeline/pipeline.test.ts index 41869c22271f0..82ce0d72e2052 100755 --- a/x-pack/legacy/plugins/logstash/server/models/pipeline/__tests__/pipeline.js +++ b/x-pack/plugins/logstash/server/models/pipeline/pipeline.test.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { Pipeline } from '../pipeline'; +import { Pipeline } from './pipeline'; describe('pipeline', () => { describe('Pipeline', () => { @@ -25,10 +24,10 @@ describe('pipeline', () => { it('returns correct Pipeline instance', () => { const pipeline = Pipeline.fromUpstreamJSON(upstreamJSON); - expect(pipeline.id).to.be(upstreamJSON._id); - expect(pipeline.description).to.be(upstreamJSON._source.description); - expect(pipeline.username).to.be(upstreamJSON._source.username); - expect(pipeline.pipeline).to.be(upstreamJSON._source.pipeline); + expect(pipeline.id).toBe(upstreamJSON._id); + expect(pipeline.description).toBe(upstreamJSON._source.description); + expect(pipeline.username).toBe(upstreamJSON._source.username); + expect(pipeline.pipeline).toBe(upstreamJSON._source.pipeline); }); it('throws if pipeline argument does not contain an id property', () => { @@ -39,7 +38,7 @@ describe('pipeline', () => { const testFromUpstreamJsonError = () => { return Pipeline.fromUpstreamJSON(badJSON); }; - expect(testFromUpstreamJsonError).to.throwError( + expect(testFromUpstreamJsonError).toThrowError( /upstreamPipeline argument must contain an id property/i ); }); @@ -64,12 +63,12 @@ describe('pipeline', () => { pipeline: 'input {} filter { grok {} }\n output {}', }; // can't do an object level comparison because modified field is always `now` - expect(pipeline.upstreamJSON.last_modified).to.be.a('string'); - expect(pipeline.upstreamJSON.description).to.be(expectedUpstreamJSON.description); - expect(pipeline.upstreamJSON.pipeline_metadata).to.eql( + expect(pipeline.upstreamJSON.last_modified).toStrictEqual(expect.any(String)); + expect(pipeline.upstreamJSON.description).toBe(expectedUpstreamJSON.description); + expect(pipeline.upstreamJSON.pipeline_metadata).toEqual( expectedUpstreamJSON.pipeline_metadata ); - expect(pipeline.upstreamJSON.pipeline).to.be(expectedUpstreamJSON.pipeline); + expect(pipeline.upstreamJSON.pipeline).toBe(expectedUpstreamJSON.pipeline); }); }); }); diff --git a/x-pack/legacy/plugins/logstash/server/models/pipeline/pipeline.js b/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts similarity index 59% rename from x-pack/legacy/plugins/logstash/server/models/pipeline/pipeline.js rename to x-pack/plugins/logstash/server/models/pipeline/pipeline.ts index f02d303cb0380..3f2debeebeb46 100755 --- a/x-pack/legacy/plugins/logstash/server/models/pipeline/pipeline.js +++ b/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts @@ -9,19 +9,38 @@ import { badRequest } from 'boom'; import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; +interface PipelineOptions { + id: string; + description: string; + pipeline: string; + username?: string; + settings?: Record; +} + +interface DownstreamPipeline { + description: string; + pipeline: string; + settings?: Record; +} /** * This model deals with a pipeline object from ES and converts it to Kibana downstream */ export class Pipeline { - constructor(props) { - this.id = props.id; - this.description = props.description; - this.username = props.username; - this.pipeline = props.pipeline; - this.settings = props.settings || {}; + public readonly id: string; + public readonly description: string; + public readonly username?: string; + public readonly pipeline: string; + private readonly settings: Record; + + constructor(options: PipelineOptions) { + this.id = options.id; + this.description = options.description; + this.username = options.username; + this.pipeline = options.pipeline; + this.settings = options.settings || {}; } - get downstreamJSON() { + public get downstreamJSON() { const json = { id: this.id, description: this.description, @@ -41,7 +60,7 @@ export class Pipeline { * pipeline_metadata.type is the Logstash config type (future: LIR, json, etc) * @return {[JSON]} [Elasticsearch JSON] */ - get upstreamJSON() { + public get upstreamJSON() { return { description: this.description, last_modified: moment().toISOString(), @@ -56,7 +75,11 @@ export class Pipeline { } // generate Pipeline object from kibana response - static fromDownstreamJSON(downstreamPipeline, pipelineId, username) { + static fromDownstreamJSON( + downstreamPipeline: DownstreamPipeline, + pipelineId: string, + username?: string + ) { const opts = { id: pipelineId, description: downstreamPipeline.description, @@ -69,7 +92,7 @@ export class Pipeline { } // generate Pipeline object from elasticsearch response - static fromUpstreamJSON(upstreamPipeline) { + static fromUpstreamJSON(upstreamPipeline: Record) { if (!upstreamPipeline._id) { throw badRequest( i18n.translate( @@ -80,13 +103,13 @@ export class Pipeline { ) ); } - const id = get(upstreamPipeline, '_id'); - const description = get(upstreamPipeline, '_source.description'); - const username = get(upstreamPipeline, '_source.username'); - const pipeline = get(upstreamPipeline, '_source.pipeline'); - const settings = get(upstreamPipeline, '_source.pipeline_settings'); + const id = get(upstreamPipeline, '_id'); + const description = get(upstreamPipeline, '_source.description'); + const username = get(upstreamPipeline, '_source.username'); + const pipeline = get(upstreamPipeline, '_source.pipeline'); + const settings = get>(upstreamPipeline, '_source.pipeline_settings'); - const opts = { id, description, username, pipeline, settings }; + const opts: PipelineOptions = { id, description, username, pipeline, settings }; return new Pipeline(opts); } diff --git a/x-pack/legacy/plugins/logstash/server/models/pipeline_list_item/index.js b/x-pack/plugins/logstash/server/models/pipeline_list_item/index.ts similarity index 100% rename from x-pack/legacy/plugins/logstash/server/models/pipeline_list_item/index.js rename to x-pack/plugins/logstash/server/models/pipeline_list_item/index.ts diff --git a/x-pack/legacy/plugins/logstash/server/models/pipeline_list_item/__tests__/pipeline_list_item.js b/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.test.ts similarity index 72% rename from x-pack/legacy/plugins/logstash/server/models/pipeline_list_item/__tests__/pipeline_list_item.js rename to x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.test.ts index 4f3a447f030c7..c557e84443b02 100755 --- a/x-pack/legacy/plugins/logstash/server/models/pipeline_list_item/__tests__/pipeline_list_item.js +++ b/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.test.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { PipelineListItem } from '../pipeline_list_item'; +import { PipelineListItem } from './pipeline_list_item'; describe('pipeline_list_item', () => { describe('PipelineListItem', () => { @@ -21,15 +20,18 @@ describe('pipeline_list_item', () => { username: 'elastic', pipeline: 'input {} filter { grok {} }\n output {}', }, + _index: 'index', + _type: 'type', + _score: 100, }; describe('fromUpstreamJSON factory method', () => { it('returns correct PipelineListItem instance', () => { const pipelineListItem = PipelineListItem.fromUpstreamJSON(upstreamJSON); - expect(pipelineListItem.id).to.be(upstreamJSON._id); - expect(pipelineListItem.description).to.be(upstreamJSON._source.description); - expect(pipelineListItem.username).to.be(upstreamJSON._source.username); - expect(pipelineListItem.last_modified).to.be(upstreamJSON._source.last_modified); + expect(pipelineListItem.id).toBe(upstreamJSON._id); + expect(pipelineListItem.description).toBe(upstreamJSON._source.description); + expect(pipelineListItem.username).toBe(upstreamJSON._source.username); + expect(pipelineListItem.last_modified).toBe(upstreamJSON._source.last_modified); }); }); @@ -42,7 +44,7 @@ describe('pipeline_list_item', () => { username: 'elastic', last_modified: '2017-05-14T02:50:51.250Z', }; - expect(pipelineListItem.downstreamJSON).to.eql(expectedDownstreamJSON); + expect(pipelineListItem.downstreamJSON).toEqual(expectedDownstreamJSON); }); }); }); diff --git a/x-pack/legacy/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.js b/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.ts similarity index 50% rename from x-pack/legacy/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.js rename to x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.ts index bbb506766897e..98c91fca1fcca 100755 --- a/x-pack/legacy/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.js +++ b/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.ts @@ -5,16 +5,21 @@ */ import { get } from 'lodash'; +import { Hit, PipelineListItemOptions } from '../../types'; export class PipelineListItem { - constructor(props) { - this.id = props.id; - this.description = props.description; - this.last_modified = props.last_modified; - this.username = props.username; + public readonly id: string; + public readonly description: string; + public readonly last_modified: string; + public readonly username: string; + constructor(options: PipelineListItemOptions) { + this.id = options.id; + this.description = options.description; + this.last_modified = options.last_modified; + this.username = options.username; } - get downstreamJSON() { + public get downstreamJSON() { const json = { id: this.id, description: this.description, @@ -29,12 +34,12 @@ export class PipelineListItem { * Takes the json GET response from ES and constructs a pipeline model to be used * in Kibana downstream */ - static fromUpstreamJSON(pipeline) { + static fromUpstreamJSON(pipeline: Hit) { const opts = { id: pipeline._id, - description: get(pipeline, '_source.description'), - last_modified: get(pipeline, '_source.last_modified'), - username: get(pipeline, '_source.username'), + description: get(pipeline, '_source.description'), + last_modified: get(pipeline, '_source.last_modified'), + username: get(pipeline, '_source.username'), }; return new PipelineListItem(opts); diff --git a/x-pack/plugins/logstash/server/plugin.ts b/x-pack/plugins/logstash/server/plugin.ts new file mode 100644 index 0000000000000..c048dd13bfb0c --- /dev/null +++ b/x-pack/plugins/logstash/server/plugin.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + CoreSetup, + CoreStart, + ICustomClusterClient, + Logger, + Plugin, + PluginInitializerContext, +} from 'src/core/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { SecurityPluginSetup } from '../../security/server'; + +import { registerRoutes } from './routes'; + +interface SetupDeps { + licensing: LicensingPluginSetup; + security?: SecurityPluginSetup; +} + +export class LogstashPlugin implements Plugin { + private readonly logger: Logger; + private esClient?: ICustomClusterClient; + private coreSetup?: CoreSetup; + constructor(context: PluginInitializerContext) { + this.logger = context.logger.get(); + } + + setup(core: CoreSetup, deps: SetupDeps) { + this.logger.debug('Setting up Logstash plugin'); + + this.coreSetup = core; + registerRoutes(core.http.createRouter(), deps.security); + } + + start(core: CoreStart) { + const esClient = core.elasticsearch.legacy.createClient('logstash'); + + this.coreSetup!.http.registerRouteHandlerContext('logstash', async (context, request) => { + return { esClient: esClient.asScoped(request) }; + }); + } + stop() { + if (this.esClient) { + this.esClient.close(); + } + } +} diff --git a/x-pack/plugins/logstash/server/routes/cluster/index.ts b/x-pack/plugins/logstash/server/routes/cluster/index.ts new file mode 100644 index 0000000000000..c43c409cf5a17 --- /dev/null +++ b/x-pack/plugins/logstash/server/routes/cluster/index.ts @@ -0,0 +1,6 @@ +/* + * 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 { registerClusterLoadRoute } from './load'; diff --git a/x-pack/plugins/logstash/server/routes/cluster/load.ts b/x-pack/plugins/logstash/server/routes/cluster/load.ts new file mode 100644 index 0000000000000..18fe21f3da675 --- /dev/null +++ b/x-pack/plugins/logstash/server/routes/cluster/load.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IRouter } from 'src/core/server'; +import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; +import { Cluster } from '../../models/cluster'; +import { checkLicense } from '../../lib/check_license'; + +export function registerClusterLoadRoute(router: IRouter) { + router.get( + { + path: '/api/logstash/cluster', + validate: false, + }, + wrapRouteWithLicenseCheck(checkLicense, async (context, request, response) => { + try { + const client = context.logstash!.esClient; + const info = await client.callAsCurrentUser('info'); + return response.ok({ + body: { + cluster: Cluster.fromUpstreamJSON(info).downstreamJSON, + }, + }); + } catch (err) { + if (err.status === 403) { + return response.ok(); + } + return response.internalError(); + } + }) + ); +} diff --git a/x-pack/plugins/logstash/server/routes/index.ts b/x-pack/plugins/logstash/server/routes/index.ts new file mode 100644 index 0000000000000..0c7183b409055 --- /dev/null +++ b/x-pack/plugins/logstash/server/routes/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IRouter } from 'src/core/server'; +import { SecurityPluginSetup } from '../../../security/server'; +import { registerClusterLoadRoute } from './cluster'; +import { + registerPipelineDeleteRoute, + registerPipelineLoadRoute, + registerPipelineSaveRoute, +} from './pipeline'; +import { registerPipelinesListRoute, registerPipelinesDeleteRoute } from './pipelines'; +import { registerUpgradeRoute } from './upgrade'; + +export function registerRoutes(router: IRouter, security?: SecurityPluginSetup) { + registerClusterLoadRoute(router); + + registerPipelineDeleteRoute(router); + registerPipelineLoadRoute(router); + registerPipelineSaveRoute(router, security); + + registerPipelinesListRoute(router); + registerPipelinesDeleteRoute(router); + + registerUpgradeRoute(router); +} diff --git a/x-pack/plugins/logstash/server/routes/pipeline/delete.ts b/x-pack/plugins/logstash/server/routes/pipeline/delete.ts new file mode 100644 index 0000000000000..4aeae3e0ae2d5 --- /dev/null +++ b/x-pack/plugins/logstash/server/routes/pipeline/delete.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'src/core/server'; +import { INDEX_NAMES } from '../../../common/constants'; +import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; + +import { checkLicense } from '../../lib/check_license'; + +export function registerPipelineDeleteRoute(router: IRouter) { + router.delete( + { + path: '/api/logstash/pipeline/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + wrapRouteWithLicenseCheck( + checkLicense, + router.handleLegacyErrors(async (context, request, response) => { + const client = context.logstash!.esClient; + + await client.callAsCurrentUser('delete', { + index: INDEX_NAMES.PIPELINES, + id: request.params.id, + refresh: 'wait_for', + }); + + return response.noContent(); + }) + ) + ); +} diff --git a/x-pack/plugins/infra/public/store/index.ts b/x-pack/plugins/logstash/server/routes/pipeline/index.ts similarity index 60% rename from x-pack/plugins/infra/public/store/index.ts rename to x-pack/plugins/logstash/server/routes/pipeline/index.ts index 025da41ec40d5..e7db6e18ddaf3 100644 --- a/x-pack/plugins/infra/public/store/index.ts +++ b/x-pack/plugins/logstash/server/routes/pipeline/index.ts @@ -3,9 +3,6 @@ * 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 './actions'; -export * from './epics'; -export * from './reducer'; -export * from './selectors'; -export { createStore } from './store'; +export { registerPipelineDeleteRoute } from './delete'; +export { registerPipelineLoadRoute } from './load'; +export { registerPipelineSaveRoute } from './save'; diff --git a/x-pack/plugins/logstash/server/routes/pipeline/load.ts b/x-pack/plugins/logstash/server/routes/pipeline/load.ts new file mode 100644 index 0000000000000..fec9097114d26 --- /dev/null +++ b/x-pack/plugins/logstash/server/routes/pipeline/load.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'src/core/server'; + +import { INDEX_NAMES } from '../../../common/constants'; +import { Pipeline } from '../../models/pipeline'; +import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; +import { checkLicense } from '../../lib/check_license'; + +export function registerPipelineLoadRoute(router: IRouter) { + router.get( + { + path: '/api/logstash/pipeline/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + wrapRouteWithLicenseCheck( + checkLicense, + router.handleLegacyErrors(async (context, request, response) => { + const client = context.logstash!.esClient; + + const result = await client.callAsCurrentUser('get', { + index: INDEX_NAMES.PIPELINES, + id: request.params.id, + _source: ['description', 'username', 'pipeline', 'pipeline_settings'], + ignore: [404], + }); + + if (!result.found) { + return response.notFound(); + } + + return response.ok({ + body: Pipeline.fromUpstreamJSON(result).downstreamJSON, + }); + }) + ) + ); +} diff --git a/x-pack/plugins/logstash/server/routes/pipeline/save.ts b/x-pack/plugins/logstash/server/routes/pipeline/save.ts new file mode 100644 index 0000000000000..556c281944a85 --- /dev/null +++ b/x-pack/plugins/logstash/server/routes/pipeline/save.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { IRouter } from 'src/core/server'; + +import { INDEX_NAMES } from '../../../common/constants'; +import { Pipeline } from '../../models/pipeline'; +import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; +import { SecurityPluginSetup } from '../../../../security/server'; +import { checkLicense } from '../../lib/check_license'; + +export function registerPipelineSaveRoute(router: IRouter, security?: SecurityPluginSetup) { + router.put( + { + path: '/api/logstash/pipeline/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({ + id: schema.string(), + description: schema.string(), + pipeline: schema.string(), + username: schema.string(), + settings: schema.maybe(schema.object({}, { unknowns: 'allow' })), + }), + }, + }, + wrapRouteWithLicenseCheck( + checkLicense, + router.handleLegacyErrors(async (context, request, response) => { + try { + let username: string | undefined; + if (security) { + const user = await security.authc.getCurrentUser(request); + username = user?.username; + } + + const client = context.logstash!.esClient; + const pipeline = Pipeline.fromDownstreamJSON(request.body, request.params.id, username); + + await client.callAsCurrentUser('index', { + index: INDEX_NAMES.PIPELINES, + id: pipeline.id, + body: pipeline.upstreamJSON, + refresh: 'wait_for', + }); + + return response.noContent(); + } catch (err) { + const statusCode = err.statusCode; + // handles the permissions issue of Elasticsearch + if (statusCode === 403) { + return response.forbidden({ + body: i18n.translate('xpack.logstash.insufficientUserPermissionsDescription', { + defaultMessage: 'Insufficient user permissions for managing Logstash pipelines', + }), + }); + } + throw err; + } + }) + ) + ); +} diff --git a/x-pack/plugins/logstash/server/routes/pipelines/delete.ts b/x-pack/plugins/logstash/server/routes/pipelines/delete.ts new file mode 100644 index 0000000000000..ac3097ac0424b --- /dev/null +++ b/x-pack/plugins/logstash/server/routes/pipelines/delete.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 { schema } from '@kbn/config-schema'; +import { APICaller, IRouter } from 'src/core/server'; +import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; + +import { INDEX_NAMES } from '../../../common/constants'; +import { checkLicense } from '../../lib/check_license'; + +async function deletePipelines(callWithRequest: APICaller, pipelineIds: string[]) { + const deletePromises = pipelineIds.map(pipelineId => { + return callWithRequest('delete', { + index: INDEX_NAMES.PIPELINES, + id: pipelineId, + refresh: 'wait_for', + }) + .then(success => ({ success })) + .catch(error => ({ error })); + }); + + const results = await Promise.all(deletePromises); + const successes = results.filter(result => Reflect.has(result, 'success')); + const errors = results.filter(result => Reflect.has(result, 'error')); + + return { + numSuccesses: successes.length, + numErrors: errors.length, + }; +} + +export function registerPipelinesDeleteRoute(router: IRouter) { + router.post( + { + path: '/api/logstash/pipelines/delete', + validate: { + body: schema.object({ + pipelineIds: schema.arrayOf(schema.string()), + }), + }, + }, + wrapRouteWithLicenseCheck( + checkLicense, + router.handleLegacyErrors(async (context, request, response) => { + const client = context.logstash!.esClient; + const results = await deletePipelines(client.callAsCurrentUser, request.body.pipelineIds); + + return response.ok({ body: { results } }); + }) + ) + ); +} diff --git a/x-pack/plugins/infra/public/store/local/index.ts b/x-pack/plugins/logstash/server/routes/pipelines/index.ts similarity index 68% rename from x-pack/plugins/infra/public/store/local/index.ts rename to x-pack/plugins/logstash/server/routes/pipelines/index.ts index c2843320bfd0c..36681502cfc70 100644 --- a/x-pack/plugins/infra/public/store/local/index.ts +++ b/x-pack/plugins/logstash/server/routes/pipelines/index.ts @@ -3,8 +3,5 @@ * 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 './actions'; -export * from './epic'; -export * from './reducer'; -export * from './selectors'; +export { registerPipelinesListRoute } from './list'; +export { registerPipelinesDeleteRoute } from './delete'; diff --git a/x-pack/plugins/logstash/server/routes/pipelines/list.ts b/x-pack/plugins/logstash/server/routes/pipelines/list.ts new file mode 100644 index 0000000000000..bc477a25a7988 --- /dev/null +++ b/x-pack/plugins/logstash/server/routes/pipelines/list.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 { i18n } from '@kbn/i18n'; +import { SearchResponse } from 'elasticsearch'; +import { APICaller, IRouter } from 'src/core/server'; +import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; + +import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../common/constants'; +import { PipelineListItem } from '../../models/pipeline_list_item'; +import { fetchAllFromScroll } from '../../lib/fetch_all_from_scroll'; +import { checkLicense } from '../../lib/check_license'; + +async function fetchPipelines(callWithRequest: APICaller) { + const params = { + index: INDEX_NAMES.PIPELINES, + scroll: ES_SCROLL_SETTINGS.KEEPALIVE, + body: { + size: ES_SCROLL_SETTINGS.PAGE_SIZE, + }, + ignore: [404], + }; + + const response = await callWithRequest>('search', params); + return fetchAllFromScroll(response, callWithRequest); +} + +export function registerPipelinesListRoute(router: IRouter) { + router.get( + { + path: '/api/logstash/pipelines', + validate: false, + }, + wrapRouteWithLicenseCheck( + checkLicense, + router.handleLegacyErrors(async (context, request, response) => { + try { + const client = context.logstash!.esClient; + const pipelinesHits = await fetchPipelines(client.callAsCurrentUser); + + const pipelines = pipelinesHits.map(pipeline => { + return PipelineListItem.fromUpstreamJSON(pipeline).downstreamJSON; + }); + + return response.ok({ body: { pipelines } }); + } catch (err) { + const statusCode = err.statusCode; + // handles the permissions issue of Elasticsearch + if (statusCode === 403) { + return response.forbidden({ + body: i18n.translate('xpack.logstash.insufficientUserPermissionsDescription', { + defaultMessage: 'Insufficient user permissions for managing Logstash pipelines', + }), + }); + } + throw err; + } + }) + ) + ); +} diff --git a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/index.js b/x-pack/plugins/logstash/server/routes/upgrade/index.ts old mode 100755 new mode 100644 similarity index 76% rename from x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/index.js rename to x-pack/plugins/logstash/server/routes/upgrade/index.ts index 0743e443955f4..3a5b0868b446b --- a/x-pack/legacy/plugins/logstash/server/lib/license_pre_routing_factory/index.js +++ b/x-pack/plugins/logstash/server/routes/upgrade/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { licensePreRoutingFactory } from './license_pre_routing_factory'; +export { registerUpgradeRoute } from './upgrade'; diff --git a/x-pack/plugins/logstash/server/routes/upgrade/upgrade.ts b/x-pack/plugins/logstash/server/routes/upgrade/upgrade.ts new file mode 100644 index 0000000000000..2bd2c0f89e190 --- /dev/null +++ b/x-pack/plugins/logstash/server/routes/upgrade/upgrade.ts @@ -0,0 +1,46 @@ +/* + * 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 { IRouter } from 'src/core/server'; +import { wrapRouteWithLicenseCheck } from '../../../../licensing/server'; + +import { INDEX_NAMES } from '../../../common/constants'; +import { checkLicense } from '../../lib/check_license'; + +export function registerUpgradeRoute(router: IRouter) { + router.post( + { + path: '/api/logstash/upgrade', + validate: false, + }, + wrapRouteWithLicenseCheck( + checkLicense, + router.handleLegacyErrors(async (context, request, response) => { + const client = context.logstash!.esClient; + + const doesIndexExist = await client.callAsCurrentUser('indices.exists', { + index: INDEX_NAMES.PIPELINES, + }); + + // If index doesn't exist yet, there is no mapping to upgrade + if (doesIndexExist) { + await client.callAsCurrentUser('indices.putMapping', { + index: INDEX_NAMES.PIPELINES, + body: { + properties: { + pipeline_settings: { + dynamic: false, + type: 'object', + }, + }, + }, + }); + } + + return response.ok({ body: { is_upgraded: true } }); + }) + ) + ); +} diff --git a/x-pack/plugins/logstash/server/types.ts b/x-pack/plugins/logstash/server/types.ts new file mode 100644 index 0000000000000..2b266b2f27708 --- /dev/null +++ b/x-pack/plugins/logstash/server/types.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { SearchResponse } from 'elasticsearch'; +import { IScopedClusterClient } from 'src/core/server'; + +type UnwrapArray = T extends Array ? U : never; + +export type Hits = SearchResponse['hits']['hits']; +export type Hit = UnwrapArray; + +export interface PipelineListItemOptions { + id: string; + description: string; + last_modified: string; + username: string; +} + +declare module 'src/core/server' { + interface RequestHandlerContext { + logstash?: { + esClient: IScopedClusterClient; + }; + } +} diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts index ceba2fe56db12..26044d28d53a3 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts @@ -65,7 +65,7 @@ export type DataMeta = Partial & export type DataRequestDescriptor = { dataId: string; - dataMetaAtStart?: DataMeta; + dataMetaAtStart?: DataMeta | null; dataRequestToken?: symbol; data?: object; dataMeta?: DataMeta; diff --git a/x-pack/plugins/maps/public/layers/util/data_request.ts b/x-pack/plugins/maps/public/layers/util/data_request.ts index eeef5c49c6ef8..44b7b2ffb6ae7 100644 --- a/x-pack/plugins/maps/public/layers/util/data_request.ts +++ b/x-pack/plugins/maps/public/layers/util/data_request.ts @@ -26,9 +26,13 @@ export class DataRequest { } getMeta(): DataMeta { - return this.hasData() - ? _.get(this._descriptor, 'dataMeta', {}) - : _.get(this._descriptor, 'dataMetaAtStart', {}); + if (this._descriptor.dataMetaAtStart) { + return this._descriptor.dataMetaAtStart; + } else if (this._descriptor.dataMeta) { + return this._descriptor.dataMeta; + } else { + return {}; + } } hasData(): boolean { diff --git a/x-pack/plugins/maps/public/reducers/map.js b/x-pack/plugins/maps/public/reducers/map.js index 7e07569b44b83..251a2304538ed 100644 --- a/x-pack/plugins/maps/public/reducers/map.js +++ b/x-pack/plugins/maps/public/reducers/map.js @@ -57,8 +57,13 @@ const updateLayerInList = (state, layerId, attribute, newValue) => { if (!layerId) { return state; } + const { layerList } = state; const layerIdx = getLayerIndex(layerList, layerId); + if (layerIdx === -1) { + return state; + } + const updatedLayer = { ...layerList[layerIdx], // Update layer w/ new value. If no value provided, toggle boolean value diff --git a/x-pack/plugins/ml/common/constants/file_datavisualizer.ts b/x-pack/plugins/ml/common/constants/file_datavisualizer.ts index 81d51bfa25816..7e18b36fd7bab 100644 --- a/x-pack/plugins/ml/common/constants/file_datavisualizer.ts +++ b/x-pack/plugins/ml/common/constants/file_datavisualizer.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export const MAX_BYTES = 104857600; -export const ABSOLUTE_MAX_BYTES = MAX_BYTES * 5; +export const MAX_FILE_SIZE = '100MB'; +export const MAX_FILE_SIZE_BYTES = 104857600; // 100MB + +export const ABSOLUTE_MAX_FILE_SIZE_BYTES = 1073741274; // 1GB export const FILE_SIZE_DISPLAY_FORMAT = '0,0.[0] b'; // Value to use in the Elasticsearch index mapping meta data to identify the diff --git a/x-pack/plugins/ml/common/types/file_datavisualizer.ts b/x-pack/plugins/ml/common/types/file_datavisualizer.ts index f771547b97811..c997a4e24f868 100644 --- a/x-pack/plugins/ml/common/types/file_datavisualizer.ts +++ b/x-pack/plugins/ml/common/types/file_datavisualizer.ts @@ -67,13 +67,15 @@ export interface ImportResponse { export interface ImportFailure { item: number; reason: string; - doc: Doc; + doc: ImportDoc; } export interface Doc { message: string; } +export type ImportDoc = Doc | string; + export interface Settings { pipeline?: string; index: string; diff --git a/x-pack/plugins/ml/common/types/ml_config.ts b/x-pack/plugins/ml/common/types/ml_config.ts index 8fd9fd22bad8a..f2ddadccb2170 100644 --- a/x-pack/plugins/ml/common/types/ml_config.ts +++ b/x-pack/plugins/ml/common/types/ml_config.ts @@ -5,11 +5,11 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { MAX_BYTES } from '../constants/file_datavisualizer'; +import { MAX_FILE_SIZE } from '../constants/file_datavisualizer'; export const configSchema = schema.object({ file_data_visualizer: schema.object({ - max_file_size_bytes: schema.number({ defaultValue: MAX_BYTES }), + max_file_size: schema.string({ defaultValue: MAX_FILE_SIZE }), }), }); diff --git a/x-pack/plugins/ml/common/util/__tests__/anomaly_utils.js b/x-pack/plugins/ml/common/util/__tests__/anomaly_utils.js deleted file mode 100644 index 515304d222c8c..0000000000000 --- a/x-pack/plugins/ml/common/util/__tests__/anomaly_utils.js +++ /dev/null @@ -1,443 +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 expect from '@kbn/expect'; -import { - getSeverity, - getSeverityWithLow, - getSeverityColor, - getMultiBucketImpactLabel, - getEntityFieldName, - getEntityFieldValue, - getEntityFieldList, - showActualForFunction, - showTypicalForFunction, - isRuleSupported, - aggregationTypeTransform, -} from '../anomaly_utils'; - -describe('ML - anomaly utils', () => { - const partitionEntityRecord = { - job_id: 'farequote', - result_type: 'record', - probability: 0.012818, - record_score: 0.0162059, - bucket_span: 300, - detector_index: 0, - timestamp: 1455047400000, - partition_field_name: 'airline', - partition_field_value: 'AAL', - function: 'mean', - function_description: 'mean', - field_name: 'responsetime', - }; - - const byEntityRecord = { - job_id: 'farequote', - result_type: 'record', - probability: 0.012818, - record_score: 0.0162059, - bucket_span: 300, - detector_index: 0, - timestamp: 1455047400000, - by_field_name: 'airline', - by_field_value: 'JZA', - function: 'mean', - function_description: 'mean', - field_name: 'responsetime', - }; - - const overEntityRecord = { - job_id: 'gallery', - result_type: 'record', - probability: 2.81806e-9, - record_score: 59.055, - bucket_span: 3600, - detector_index: 4, - timestamp: 1420552800000, - function: 'sum', - function_description: 'sum', - field_name: 'bytes', - by_field_name: 'method', - over_field_name: 'clientip', - over_field_value: '37.157.32.164', - }; - - const noEntityRecord = { - job_id: 'farequote_no_by', - result_type: 'record', - probability: 0.0191711, - record_score: 4.38431, - initial_record_score: 19.654, - bucket_span: 300, - detector_index: 0, - timestamp: 1454890500000, - function: 'mean', - function_description: 'mean', - field_name: 'responsetime', - }; - - const metricNoEntityRecord = { - job_id: 'farequote_metric', - result_type: 'record', - probability: 0.030133495093182184, - record_score: 0.024881740359975164, - initial_record_score: 0.024881740359975164, - bucket_span: 900, - detector_index: 0, - is_interim: false, - timestamp: 1486845000000, - function: 'metric', - function_description: 'mean', - typical: [545.7764658569108], - actual: [758.8220213274412], - field_name: 'responsetime', - influencers: [ - { - influencer_field_name: 'airline', - influencer_field_values: ['NKS'], - }, - ], - airline: ['NKS'], - }; - - const rareEntityRecord = { - job_id: 'gallery', - result_type: 'record', - probability: 0.02277014211908481, - record_score: 4.545378107075983, - initial_record_score: 4.545378107075983, - bucket_span: 3600, - detector_index: 0, - is_interim: false, - timestamp: 1495879200000, - by_field_name: 'status', - function: 'rare', - function_description: 'rare', - over_field_name: 'clientip', - over_field_value: '173.252.74.112', - causes: [ - { - probability: 0.02277014211908481, - by_field_name: 'status', - by_field_value: '206', - function: 'rare', - function_description: 'rare', - typical: [0.00014832458182211878], - actual: [1], - over_field_name: 'clientip', - over_field_value: '173.252.74.112', - }, - ], - influencers: [ - { - influencer_field_name: 'uri', - influencer_field_values: [ - '/wp-content/uploads/2013/06/dune_house_oil_on_canvas_24x20-298x298.jpg', - '/wp-content/uploads/2013/10/Case-dAste-1-11-298x298.png', - ], - }, - { - influencer_field_name: 'status', - influencer_field_values: ['206'], - }, - { - influencer_field_name: 'clientip', - influencer_field_values: ['173.252.74.112'], - }, - ], - clientip: ['173.252.74.112'], - uri: [ - '/wp-content/uploads/2013/06/dune_house_oil_on_canvas_24x20-298x298.jpg', - '/wp-content/uploads/2013/10/Case-dAste-1-11-298x298.png', - ], - status: ['206'], - }; - - describe('getSeverity', () => { - it('returns warning for 0 <= score < 25', () => { - expect(getSeverity(0).id).to.be('warning'); - expect(getSeverity(0.001).id).to.be('warning'); - expect(getSeverity(24.99).id).to.be('warning'); - }); - - it('returns minor for 25 <= score < 50', () => { - expect(getSeverity(25).id).to.be('minor'); - expect(getSeverity(49.99).id).to.be('minor'); - }); - - it('returns minor for 50 <= score < 75', () => { - expect(getSeverity(50).id).to.be('major'); - expect(getSeverity(74.99).id).to.be('major'); - }); - - it('returns critical for score >= 75', () => { - expect(getSeverity(75).id).to.be('critical'); - expect(getSeverity(100).id).to.be('critical'); - expect(getSeverity(1000).id).to.be('critical'); - }); - - it('returns unknown for scores less than 0 or string input', () => { - expect(getSeverity(-10).id).to.be('unknown'); - expect(getSeverity('value').id).to.be('unknown'); - }); - }); - - describe('getSeverityWithLow', () => { - it('returns low for 0 <= score < 3', () => { - expect(getSeverityWithLow(0).id).to.be('low'); - expect(getSeverityWithLow(0.001).id).to.be('low'); - expect(getSeverityWithLow(2.99).id).to.be('low'); - }); - - it('returns warning for 3 <= score < 25', () => { - expect(getSeverityWithLow(3).id).to.be('warning'); - expect(getSeverityWithLow(24.99).id).to.be('warning'); - }); - - it('returns minor for 25 <= score < 50', () => { - expect(getSeverityWithLow(25).id).to.be('minor'); - expect(getSeverityWithLow(49.99).id).to.be('minor'); - }); - - it('returns minor for 50 <= score < 75', () => { - expect(getSeverityWithLow(50).id).to.be('major'); - expect(getSeverityWithLow(74.99).id).to.be('major'); - }); - - it('returns critical for score >= 75', () => { - expect(getSeverityWithLow(75).id).to.be('critical'); - expect(getSeverityWithLow(100).id).to.be('critical'); - expect(getSeverityWithLow(1000).id).to.be('critical'); - }); - - it('returns unknown for scores less than 0 or string input', () => { - expect(getSeverityWithLow(-10).id).to.be('unknown'); - expect(getSeverityWithLow('value').id).to.be('unknown'); - }); - }); - - describe('getSeverityColor', () => { - it('returns correct hex code for low for 0 <= score < 3', () => { - expect(getSeverityColor(0)).to.be('#d2e9f7'); - expect(getSeverityColor(0.001)).to.be('#d2e9f7'); - expect(getSeverityColor(2.99)).to.be('#d2e9f7'); - }); - - it('returns correct hex code for warning for 3 <= score < 25', () => { - expect(getSeverityColor(3)).to.be('#8bc8fb'); - expect(getSeverityColor(24.99)).to.be('#8bc8fb'); - }); - - it('returns correct hex code for minor for 25 <= score < 50', () => { - expect(getSeverityColor(25)).to.be('#fdec25'); - expect(getSeverityColor(49.99)).to.be('#fdec25'); - }); - - it('returns correct hex code for major for 50 <= score < 75', () => { - expect(getSeverityColor(50)).to.be('#fba740'); - expect(getSeverityColor(74.99)).to.be('#fba740'); - }); - - it('returns correct hex code for critical for score >= 75', () => { - expect(getSeverityColor(75)).to.be('#fe5050'); - expect(getSeverityColor(100)).to.be('#fe5050'); - expect(getSeverityColor(1000)).to.be('#fe5050'); - }); - - it('returns correct hex code for unknown for scores less than 0 or string input', () => { - expect(getSeverityColor(-10)).to.be('#ffffff'); - expect(getSeverityColor('value')).to.be('#ffffff'); - }); - }); - - describe('getMultiBucketImpactLabel', () => { - it('returns high for 3 <= score <= 5', () => { - expect(getMultiBucketImpactLabel(3)).to.be('high'); - expect(getMultiBucketImpactLabel(5)).to.be('high'); - }); - - it('returns medium for 2 <= score < 3', () => { - expect(getMultiBucketImpactLabel(2)).to.be('medium'); - expect(getMultiBucketImpactLabel(2.99)).to.be('medium'); - }); - - it('returns low for 1 <= score < 2', () => { - expect(getMultiBucketImpactLabel(1)).to.be('low'); - expect(getMultiBucketImpactLabel(1.99)).to.be('low'); - }); - - it('returns none for -5 <= score < 1', () => { - expect(getMultiBucketImpactLabel(-5)).to.be('none'); - expect(getMultiBucketImpactLabel(0.99)).to.be('none'); - }); - - it('returns expected label when impact outside normal bounds', () => { - expect(getMultiBucketImpactLabel(10)).to.be('high'); - expect(getMultiBucketImpactLabel(-10)).to.be('none'); - }); - }); - - describe('getEntityFieldName', () => { - it('returns the by field name', () => { - expect(getEntityFieldName(byEntityRecord)).to.be('airline'); - }); - - it('returns the partition field name', () => { - expect(getEntityFieldName(partitionEntityRecord)).to.be('airline'); - }); - - it('returns the over field name', () => { - expect(getEntityFieldName(overEntityRecord)).to.be('clientip'); - }); - - it('returns undefined if no by, over or partition fields', () => { - expect(getEntityFieldName(noEntityRecord)).to.be(undefined); - }); - }); - - describe('getEntityFieldValue', () => { - it('returns the by field value', () => { - expect(getEntityFieldValue(byEntityRecord)).to.be('JZA'); - }); - - it('returns the partition field value', () => { - expect(getEntityFieldValue(partitionEntityRecord)).to.be('AAL'); - }); - - it('returns the over field value', () => { - expect(getEntityFieldValue(overEntityRecord)).to.be('37.157.32.164'); - }); - - it('returns undefined if no by, over or partition fields', () => { - expect(getEntityFieldValue(noEntityRecord)).to.be(undefined); - }); - }); - - describe('getEntityFieldList', () => { - it('returns an empty list for a record with no by, over or partition fields', () => { - expect(getEntityFieldList(noEntityRecord)).to.be.empty(); - }); - - it('returns correct list for a record with a by field', () => { - expect(getEntityFieldList(byEntityRecord)).to.eql([ - { - fieldName: 'airline', - fieldValue: 'JZA', - fieldType: 'by', - }, - ]); - }); - - it('returns correct list for a record with a partition field', () => { - expect(getEntityFieldList(partitionEntityRecord)).to.eql([ - { - fieldName: 'airline', - fieldValue: 'AAL', - fieldType: 'partition', - }, - ]); - }); - - it('returns correct list for a record with an over field', () => { - expect(getEntityFieldList(overEntityRecord)).to.eql([ - { - fieldName: 'clientip', - fieldValue: '37.157.32.164', - fieldType: 'over', - }, - ]); - }); - - it('returns correct list for a record with a by and over field', () => { - expect(getEntityFieldList(rareEntityRecord)).to.eql([ - { - fieldName: 'clientip', - fieldValue: '173.252.74.112', - fieldType: 'over', - }, - ]); - }); - }); - - describe('showActualForFunction', () => { - it('returns true for expected function descriptions', () => { - expect(showActualForFunction('count')).to.be(true); - expect(showActualForFunction('distinct_count')).to.be(true); - expect(showActualForFunction('lat_long')).to.be(true); - expect(showActualForFunction('mean')).to.be(true); - expect(showActualForFunction('max')).to.be(true); - expect(showActualForFunction('min')).to.be(true); - expect(showActualForFunction('sum')).to.be(true); - expect(showActualForFunction('median')).to.be(true); - expect(showActualForFunction('varp')).to.be(true); - expect(showActualForFunction('info_content')).to.be(true); - expect(showActualForFunction('time')).to.be(true); - }); - - it('returns false for expected function descriptions', () => { - expect(showActualForFunction('rare')).to.be(false); - }); - }); - - describe('showTypicalForFunction', () => { - it('returns true for expected function descriptions', () => { - expect(showTypicalForFunction('count')).to.be(true); - expect(showTypicalForFunction('distinct_count')).to.be(true); - expect(showTypicalForFunction('lat_long')).to.be(true); - expect(showTypicalForFunction('mean')).to.be(true); - expect(showTypicalForFunction('max')).to.be(true); - expect(showTypicalForFunction('min')).to.be(true); - expect(showTypicalForFunction('sum')).to.be(true); - expect(showTypicalForFunction('median')).to.be(true); - expect(showTypicalForFunction('varp')).to.be(true); - expect(showTypicalForFunction('info_content')).to.be(true); - expect(showTypicalForFunction('time')).to.be(true); - }); - - it('returns false for expected function descriptions', () => { - expect(showTypicalForFunction('rare')).to.be(false); - }); - }); - - describe('isRuleSupported', () => { - it('returns true for anomalies supporting rules', () => { - expect(isRuleSupported(partitionEntityRecord)).to.be(true); - expect(isRuleSupported(byEntityRecord)).to.be(true); - expect(isRuleSupported(overEntityRecord)).to.be(true); - expect(isRuleSupported(rareEntityRecord)).to.be(true); - expect(isRuleSupported(noEntityRecord)).to.be(true); - }); - - it('returns false for anomaly not supporting rules', () => { - expect(isRuleSupported(metricNoEntityRecord)).to.be(false); - }); - }); - - describe('aggregationTypeTransform', () => { - it('returns correct ES aggregation type for ML function description', () => { - expect(aggregationTypeTransform.toES('count')).to.be('count'); - expect(aggregationTypeTransform.toES('distinct_count')).to.be('cardinality'); - expect(aggregationTypeTransform.toES('distinct_count')).to.not.be('distinct_count'); - expect(aggregationTypeTransform.toES('mean')).to.be('avg'); - expect(aggregationTypeTransform.toES('mean')).to.not.be('mean'); - expect(aggregationTypeTransform.toES('max')).to.be('max'); - expect(aggregationTypeTransform.toES('min')).to.be('min'); - expect(aggregationTypeTransform.toES('sum')).to.be('sum'); - }); - - it('returns correct ML function description for ES aggregation type', () => { - expect(aggregationTypeTransform.toML('count')).to.be('count'); - expect(aggregationTypeTransform.toML('cardinality')).to.be('distinct_count'); - expect(aggregationTypeTransform.toML('cardinality')).to.not.be('cardinality'); - expect(aggregationTypeTransform.toML('avg')).to.be('mean'); - expect(aggregationTypeTransform.toML('avg')).to.not.be('avg'); - expect(aggregationTypeTransform.toML('max')).to.be('max'); - expect(aggregationTypeTransform.toML('min')).to.be('min'); - expect(aggregationTypeTransform.toML('sum')).to.be('sum'); - }); - }); -}); diff --git a/x-pack/plugins/ml/common/util/anomaly_utils.d.ts b/x-pack/plugins/ml/common/util/anomaly_utils.d.ts deleted file mode 100644 index adeb6dc7dd5b9..0000000000000 --- a/x-pack/plugins/ml/common/util/anomaly_utils.d.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 { ANOMALY_SEVERITY } from '../constants/anomalies'; - -export function getSeverity(normalizedScore: number): string; -export function getSeverityType(normalizedScore: number): ANOMALY_SEVERITY; -export function getSeverityColor(normalizedScore: number): string; diff --git a/x-pack/plugins/ml/common/util/anomaly_utils.test.ts b/x-pack/plugins/ml/common/util/anomaly_utils.test.ts new file mode 100644 index 0000000000000..1343e4611c215 --- /dev/null +++ b/x-pack/plugins/ml/common/util/anomaly_utils.test.ts @@ -0,0 +1,444 @@ +/* + * 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 { AnomalyRecordDoc } from '../types/anomalies'; + +import { + aggregationTypeTransform, + getEntityFieldList, + getEntityFieldName, + getEntityFieldValue, + getMultiBucketImpactLabel, + getSeverity, + getSeverityWithLow, + getSeverityColor, + isRuleSupported, + showActualForFunction, + showTypicalForFunction, +} from './anomaly_utils'; + +describe('ML - anomaly utils', () => { + const partitionEntityRecord: AnomalyRecordDoc = { + job_id: 'farequote', + result_type: 'record', + probability: 0.012818, + record_score: 0.0162059, + initial_record_score: 0.0162059, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1455047400000, + partition_field_name: 'airline', + partition_field_value: 'AAL', + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + }; + + const byEntityRecord: AnomalyRecordDoc = { + job_id: 'farequote', + result_type: 'record', + probability: 0.012818, + record_score: 0.0162059, + initial_record_score: 0.0162059, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1455047400000, + by_field_name: 'airline', + by_field_value: 'JZA', + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + }; + + const overEntityRecord: AnomalyRecordDoc = { + job_id: 'gallery', + result_type: 'record', + probability: 2.81806e-9, + record_score: 59.055, + initial_record_score: 59.055, + bucket_span: 3600, + detector_index: 4, + is_interim: false, + timestamp: 1420552800000, + function: 'sum', + function_description: 'sum', + field_name: 'bytes', + by_field_name: 'method', + over_field_name: 'clientip', + over_field_value: '37.157.32.164', + }; + + const noEntityRecord: AnomalyRecordDoc = { + job_id: 'farequote_no_by', + result_type: 'record', + probability: 0.0191711, + record_score: 4.38431, + initial_record_score: 19.654, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1454890500000, + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + }; + + const metricNoEntityRecord: AnomalyRecordDoc = { + job_id: 'farequote_metric', + result_type: 'record', + probability: 0.030133495093182184, + record_score: 0.024881740359975164, + initial_record_score: 0.024881740359975164, + bucket_span: 900, + detector_index: 0, + is_interim: false, + timestamp: 1486845000000, + function: 'metric', + function_description: 'mean', + typical: [545.7764658569108], + actual: [758.8220213274412], + field_name: 'responsetime', + influencers: [ + { + influencer_field_name: 'airline', + influencer_field_values: ['NKS'], + }, + ], + airline: ['NKS'], + }; + + const rareEntityRecord: AnomalyRecordDoc = { + job_id: 'gallery', + result_type: 'record', + probability: 0.02277014211908481, + record_score: 4.545378107075983, + initial_record_score: 4.545378107075983, + bucket_span: 3600, + detector_index: 0, + is_interim: false, + timestamp: 1495879200000, + by_field_name: 'status', + function: 'rare', + function_description: 'rare', + over_field_name: 'clientip', + over_field_value: '173.252.74.112', + causes: [ + { + probability: 0.02277014211908481, + by_field_name: 'status', + by_field_value: '206', + function: 'rare', + function_description: 'rare', + typical: [0.00014832458182211878], + actual: [1], + over_field_name: 'clientip', + over_field_value: '173.252.74.112', + }, + ], + influencers: [ + { + influencer_field_name: 'uri', + influencer_field_values: [ + '/wp-content/uploads/2013/06/dune_house_oil_on_canvas_24x20-298x298.jpg', + '/wp-content/uploads/2013/10/Case-dAste-1-11-298x298.png', + ], + }, + { + influencer_field_name: 'status', + influencer_field_values: ['206'], + }, + { + influencer_field_name: 'clientip', + influencer_field_values: ['173.252.74.112'], + }, + ], + clientip: ['173.252.74.112'], + uri: [ + '/wp-content/uploads/2013/06/dune_house_oil_on_canvas_24x20-298x298.jpg', + '/wp-content/uploads/2013/10/Case-dAste-1-11-298x298.png', + ], + status: ['206'], + }; + + describe('getSeverity', () => { + test('returns warning for 0 <= score < 25', () => { + expect(getSeverity(0).id).toBe('warning'); + expect(getSeverity(0.001).id).toBe('warning'); + expect(getSeverity(24.99).id).toBe('warning'); + }); + + test('returns minor for 25 <= score < 50', () => { + expect(getSeverity(25).id).toBe('minor'); + expect(getSeverity(49.99).id).toBe('minor'); + }); + + test('returns minor for 50 <= score < 75', () => { + expect(getSeverity(50).id).toBe('major'); + expect(getSeverity(74.99).id).toBe('major'); + }); + + test('returns critical for score >= 75', () => { + expect(getSeverity(75).id).toBe('critical'); + expect(getSeverity(100).id).toBe('critical'); + expect(getSeverity(1000).id).toBe('critical'); + }); + + test('returns unknown for scores less than 0', () => { + expect(getSeverity(-10).id).toBe('unknown'); + }); + }); + + describe('getSeverityWithLow', () => { + test('returns low for 0 <= score < 3', () => { + expect(getSeverityWithLow(0).id).toBe('low'); + expect(getSeverityWithLow(0.001).id).toBe('low'); + expect(getSeverityWithLow(2.99).id).toBe('low'); + }); + + test('returns warning for 3 <= score < 25', () => { + expect(getSeverityWithLow(3).id).toBe('warning'); + expect(getSeverityWithLow(24.99).id).toBe('warning'); + }); + + test('returns minor for 25 <= score < 50', () => { + expect(getSeverityWithLow(25).id).toBe('minor'); + expect(getSeverityWithLow(49.99).id).toBe('minor'); + }); + + test('returns minor for 50 <= score < 75', () => { + expect(getSeverityWithLow(50).id).toBe('major'); + expect(getSeverityWithLow(74.99).id).toBe('major'); + }); + + test('returns critical for score >= 75', () => { + expect(getSeverityWithLow(75).id).toBe('critical'); + expect(getSeverityWithLow(100).id).toBe('critical'); + expect(getSeverityWithLow(1000).id).toBe('critical'); + }); + + test('returns unknown for scores less than 0 ', () => { + expect(getSeverityWithLow(-10).id).toBe('unknown'); + }); + }); + + describe('getSeverityColor', () => { + test('returns correct hex code for low for 0 <= score < 3', () => { + expect(getSeverityColor(0)).toBe('#d2e9f7'); + expect(getSeverityColor(0.001)).toBe('#d2e9f7'); + expect(getSeverityColor(2.99)).toBe('#d2e9f7'); + }); + + test('returns correct hex code for warning for 3 <= score < 25', () => { + expect(getSeverityColor(3)).toBe('#8bc8fb'); + expect(getSeverityColor(24.99)).toBe('#8bc8fb'); + }); + + test('returns correct hex code for minor for 25 <= score < 50', () => { + expect(getSeverityColor(25)).toBe('#fdec25'); + expect(getSeverityColor(49.99)).toBe('#fdec25'); + }); + + test('returns correct hex code for major for 50 <= score < 75', () => { + expect(getSeverityColor(50)).toBe('#fba740'); + expect(getSeverityColor(74.99)).toBe('#fba740'); + }); + + test('returns correct hex code for critical for score >= 75', () => { + expect(getSeverityColor(75)).toBe('#fe5050'); + expect(getSeverityColor(100)).toBe('#fe5050'); + expect(getSeverityColor(1000)).toBe('#fe5050'); + }); + + test('returns correct hex code for unknown for scores less than 0', () => { + expect(getSeverityColor(-10)).toBe('#ffffff'); + }); + }); + + describe('getMultiBucketImpactLabel', () => { + test('returns high for 3 <= score <= 5', () => { + expect(getMultiBucketImpactLabel(3)).toBe('high'); + expect(getMultiBucketImpactLabel(5)).toBe('high'); + }); + + test('returns medium for 2 <= score < 3', () => { + expect(getMultiBucketImpactLabel(2)).toBe('medium'); + expect(getMultiBucketImpactLabel(2.99)).toBe('medium'); + }); + + test('returns low for 1 <= score < 2', () => { + expect(getMultiBucketImpactLabel(1)).toBe('low'); + expect(getMultiBucketImpactLabel(1.99)).toBe('low'); + }); + + test('returns none for -5 <= score < 1', () => { + expect(getMultiBucketImpactLabel(-5)).toBe('none'); + expect(getMultiBucketImpactLabel(0.99)).toBe('none'); + }); + + test('returns expected label when impact outside normal bounds', () => { + expect(getMultiBucketImpactLabel(10)).toBe('high'); + expect(getMultiBucketImpactLabel(-10)).toBe('none'); + }); + }); + + describe('getEntityFieldName', () => { + it('returns the by field name', () => { + expect(getEntityFieldName(byEntityRecord)).toBe('airline'); + }); + + it('returns the partition field name', () => { + expect(getEntityFieldName(partitionEntityRecord)).toBe('airline'); + }); + + it('returns the over field name', () => { + expect(getEntityFieldName(overEntityRecord)).toBe('clientip'); + }); + + it('returns undefined if no by, over or partition fields', () => { + expect(getEntityFieldName(noEntityRecord)).toBe(undefined); + }); + }); + + describe('getEntityFieldValue', () => { + test('returns the by field value', () => { + expect(getEntityFieldValue(byEntityRecord)).toBe('JZA'); + }); + + test('returns the partition field value', () => { + expect(getEntityFieldValue(partitionEntityRecord)).toBe('AAL'); + }); + + test('returns the over field value', () => { + expect(getEntityFieldValue(overEntityRecord)).toBe('37.157.32.164'); + }); + + test('returns undefined if no by, over or partition fields', () => { + expect(getEntityFieldValue(noEntityRecord)).toBe(undefined); + }); + }); + + describe('getEntityFieldList', () => { + test('returns an empty list for a record with no by, over or partition fields', () => { + expect(getEntityFieldList(noEntityRecord)).toHaveLength(0); + }); + + test('returns correct list for a record with a by field', () => { + expect(getEntityFieldList(byEntityRecord)).toEqual([ + { + fieldName: 'airline', + fieldValue: 'JZA', + fieldType: 'by', + }, + ]); + }); + + test('returns correct list for a record with a partition field', () => { + expect(getEntityFieldList(partitionEntityRecord)).toEqual([ + { + fieldName: 'airline', + fieldValue: 'AAL', + fieldType: 'partition', + }, + ]); + }); + + test('returns correct list for a record with an over field', () => { + expect(getEntityFieldList(overEntityRecord)).toEqual([ + { + fieldName: 'clientip', + fieldValue: '37.157.32.164', + fieldType: 'over', + }, + ]); + }); + + test('returns correct list for a record with a by and over field', () => { + expect(getEntityFieldList(rareEntityRecord)).toEqual([ + { + fieldName: 'clientip', + fieldValue: '173.252.74.112', + fieldType: 'over', + }, + ]); + }); + }); + + describe('showActualForFunction', () => { + test('returns true for expected function descriptions', () => { + expect(showActualForFunction('count')).toBe(true); + expect(showActualForFunction('distinct_count')).toBe(true); + expect(showActualForFunction('lat_long')).toBe(true); + expect(showActualForFunction('mean')).toBe(true); + expect(showActualForFunction('max')).toBe(true); + expect(showActualForFunction('min')).toBe(true); + expect(showActualForFunction('sum')).toBe(true); + expect(showActualForFunction('median')).toBe(true); + expect(showActualForFunction('varp')).toBe(true); + expect(showActualForFunction('info_content')).toBe(true); + expect(showActualForFunction('time')).toBe(true); + }); + + test('returns false for expected function descriptions', () => { + expect(showActualForFunction('rare')).toBe(false); + }); + }); + + describe('showTypicalForFunction', () => { + test('returns true for expected function descriptions', () => { + expect(showTypicalForFunction('count')).toBe(true); + expect(showTypicalForFunction('distinct_count')).toBe(true); + expect(showTypicalForFunction('lat_long')).toBe(true); + expect(showTypicalForFunction('mean')).toBe(true); + expect(showTypicalForFunction('max')).toBe(true); + expect(showTypicalForFunction('min')).toBe(true); + expect(showTypicalForFunction('sum')).toBe(true); + expect(showTypicalForFunction('median')).toBe(true); + expect(showTypicalForFunction('varp')).toBe(true); + expect(showTypicalForFunction('info_content')).toBe(true); + expect(showTypicalForFunction('time')).toBe(true); + }); + + test('returns false for expected function descriptions', () => { + expect(showTypicalForFunction('rare')).toBe(false); + }); + }); + + describe('isRuleSupported', () => { + test('returns true for anomalies supporting rules', () => { + expect(isRuleSupported(partitionEntityRecord)).toBe(true); + expect(isRuleSupported(byEntityRecord)).toBe(true); + expect(isRuleSupported(overEntityRecord)).toBe(true); + expect(isRuleSupported(rareEntityRecord)).toBe(true); + expect(isRuleSupported(noEntityRecord)).toBe(true); + }); + + it('returns false for anomaly not supporting rules', () => { + expect(isRuleSupported(metricNoEntityRecord)).toBe(false); + }); + }); + + describe('aggregationTypeTransform', () => { + test('returns correct ES aggregation type for ML function description', () => { + expect(aggregationTypeTransform.toES('count')).toBe('count'); + expect(aggregationTypeTransform.toES('distinct_count')).toBe('cardinality'); + expect(aggregationTypeTransform.toES('mean')).toBe('avg'); + expect(aggregationTypeTransform.toES('max')).toBe('max'); + expect(aggregationTypeTransform.toES('min')).toBe('min'); + expect(aggregationTypeTransform.toES('sum')).toBe('sum'); + }); + + test('returns correct ML function description for ES aggregation type', () => { + expect(aggregationTypeTransform.toML('count')).toBe('count'); + expect(aggregationTypeTransform.toML('cardinality')).toBe('distinct_count'); + expect(aggregationTypeTransform.toML('avg')).toBe('mean'); + expect(aggregationTypeTransform.toML('max')).toBe('max'); + expect(aggregationTypeTransform.toML('min')).toBe('min'); + expect(aggregationTypeTransform.toML('sum')).toBe('sum'); + }); + }); +}); diff --git a/x-pack/plugins/ml/common/util/anomaly_utils.js b/x-pack/plugins/ml/common/util/anomaly_utils.ts similarity index 87% rename from x-pack/plugins/ml/common/util/anomaly_utils.js rename to x-pack/plugins/ml/common/util/anomaly_utils.ts index 16c27b6af869d..36b91f5580b39 100644 --- a/x-pack/plugins/ml/common/util/anomaly_utils.js +++ b/x-pack/plugins/ml/common/util/anomaly_utils.ts @@ -13,6 +13,24 @@ import { i18n } from '@kbn/i18n'; import { CONDITIONS_NOT_SUPPORTED_FUNCTIONS } from '../constants/detector_rule'; import { MULTI_BUCKET_IMPACT } from '../constants/multi_bucket_impact'; import { ANOMALY_SEVERITY, ANOMALY_THRESHOLD } from '../constants/anomalies'; +import { AnomalyRecordDoc } from '../types/anomalies'; + +export interface SeverityType { + id: ANOMALY_SEVERITY; + label: string; +} + +export enum ENTITY_FIELD_TYPE { + BY = 'by', + OVER = 'over', + PARTITON = 'partition', +} + +export interface EntityField { + fieldName: string; + fieldValue: string | number | undefined; + fieldType: ENTITY_FIELD_TYPE; +} // List of function descriptions for which actual values from record level results should be displayed. const DISPLAY_ACTUAL_FUNCTIONS = [ @@ -44,7 +62,7 @@ const DISPLAY_TYPICAL_FUNCTIONS = [ 'time', ]; -let severityTypes; +let severityTypes: Record; function getSeverityTypes() { if (severityTypes) { @@ -93,7 +111,7 @@ function getSeverityTypes() { // Returns a severity label (one of critical, major, minor, warning or unknown) // for the supplied normalized anomaly score (a value between 0 and 100). -export function getSeverity(normalizedScore) { +export function getSeverity(normalizedScore: number): SeverityType { const severityTypesList = getSeverityTypes(); if (normalizedScore >= ANOMALY_THRESHOLD.CRITICAL) { @@ -109,7 +127,7 @@ export function getSeverity(normalizedScore) { } } -export function getSeverityType(normalizedScore) { +export function getSeverityType(normalizedScore: number): ANOMALY_SEVERITY { if (normalizedScore >= 75) { return ANOMALY_SEVERITY.CRITICAL; } else if (normalizedScore >= 50) { @@ -128,7 +146,7 @@ export function getSeverityType(normalizedScore) { // Returns a severity label (one of critical, major, minor, warning, low or unknown) // for the supplied normalized anomaly score (a value between 0 and 100), where scores // less than 3 are assigned a severity of 'low'. -export function getSeverityWithLow(normalizedScore) { +export function getSeverityWithLow(normalizedScore: number): SeverityType { const severityTypesList = getSeverityTypes(); if (normalizedScore >= ANOMALY_THRESHOLD.CRITICAL) { @@ -148,7 +166,7 @@ export function getSeverityWithLow(normalizedScore) { // Returns a severity RGB color (one of critical, major, minor, warning, low_warning or unknown) // for the supplied normalized anomaly score (a value between 0 and 100). -export function getSeverityColor(normalizedScore) { +export function getSeverityColor(normalizedScore: number): string { if (normalizedScore >= ANOMALY_THRESHOLD.CRITICAL) { return '#fe5050'; } else if (normalizedScore >= ANOMALY_THRESHOLD.MAJOR) { @@ -167,7 +185,7 @@ export function getSeverityColor(normalizedScore) { // Returns a label to use for the multi-bucket impact of an anomaly // according to the value of the multi_bucket_impact field of a record, // which ranges from -5 to +5. -export function getMultiBucketImpactLabel(multiBucketImpact) { +export function getMultiBucketImpactLabel(multiBucketImpact: number): string { if (multiBucketImpact >= MULTI_BUCKET_IMPACT.HIGH) { return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.highLabel', { defaultMessage: 'high', @@ -190,7 +208,7 @@ export function getMultiBucketImpactLabel(multiBucketImpact) { // Returns the name of the field to use as the entity name from the source record // obtained from Elasticsearch. The function looks first for a by_field, then over_field, // then partition_field, returning undefined if none of these fields are present. -export function getEntityFieldName(record) { +export function getEntityFieldName(record: AnomalyRecordDoc): string | undefined { // Analyses with by and over fields, will have a top-level by_field_name, but // the by_field_value(s) will be in the nested causes array. if (record.by_field_name !== undefined && record.by_field_value !== undefined) { @@ -211,7 +229,7 @@ export function getEntityFieldName(record) { // Returns the value of the field to use as the entity value from the source record // obtained from Elasticsearch. The function looks first for a by_field, then over_field, // then partition_field, returning undefined if none of these fields are present. -export function getEntityFieldValue(record) { +export function getEntityFieldValue(record: AnomalyRecordDoc): string | number | undefined { if (record.by_field_value !== undefined) { return record.by_field_value; } @@ -229,13 +247,13 @@ export function getEntityFieldValue(record) { // Returns the list of partitioning entity fields for the source record as a list // of objects in the form { fieldName: airline, fieldValue: AAL, fieldType: partition } -export function getEntityFieldList(record) { - const entityFields = []; +export function getEntityFieldList(record: AnomalyRecordDoc): EntityField[] { + const entityFields: EntityField[] = []; if (record.partition_field_name !== undefined) { entityFields.push({ fieldName: record.partition_field_name, fieldValue: record.partition_field_value, - fieldType: 'partition', + fieldType: ENTITY_FIELD_TYPE.PARTITON, }); } @@ -243,7 +261,7 @@ export function getEntityFieldList(record) { entityFields.push({ fieldName: record.over_field_name, fieldValue: record.over_field_value, - fieldType: 'over', + fieldType: ENTITY_FIELD_TYPE.OVER, }); } @@ -254,7 +272,7 @@ export function getEntityFieldList(record) { entityFields.push({ fieldName: record.by_field_name, fieldValue: record.by_field_value, - fieldType: 'by', + fieldType: ENTITY_FIELD_TYPE.BY, }); } @@ -264,19 +282,19 @@ export function getEntityFieldList(record) { // Returns whether actual values should be displayed for a record with the specified function description. // Note that the 'function' field in a record contains what the user entered e.g. 'high_count', // whereas the 'function_description' field holds a ML-built display hint for function e.g. 'count'. -export function showActualForFunction(functionDescription) { +export function showActualForFunction(functionDescription: string): boolean { return DISPLAY_ACTUAL_FUNCTIONS.indexOf(functionDescription) > -1; } // Returns whether typical values should be displayed for a record with the specified function description. // Note that the 'function' field in a record contains what the user entered e.g. 'high_count', // whereas the 'function_description' field holds a ML-built display hint for function e.g. 'count'. -export function showTypicalForFunction(functionDescription) { +export function showTypicalForFunction(functionDescription: string): boolean { return DISPLAY_TYPICAL_FUNCTIONS.indexOf(functionDescription) > -1; } // Returns whether a rule can be configured against the specified anomaly. -export function isRuleSupported(record) { +export function isRuleSupported(record: AnomalyRecordDoc): boolean { // A rule can be configured with a numeric condition if the function supports it, // and/or with scope if there is a partitioning fields. return ( @@ -303,7 +321,7 @@ export function isRuleSupported(record) { // The input to toES and the output from toML correspond to the value of the // function_description field of anomaly records. export const aggregationTypeTransform = { - toES: function(oldAggType) { + toES(oldAggType: string): string { let newAggType = oldAggType; if (newAggType === 'mean') { @@ -316,7 +334,7 @@ export const aggregationTypeTransform = { return newAggType; }, - toML: function(oldAggType) { + toML(oldAggType: string): string { let newAggType = oldAggType; if (newAggType === 'avg') { diff --git a/x-pack/plugins/ml/package.json b/x-pack/plugins/ml/package.json new file mode 100644 index 0000000000000..739dd806fcbb9 --- /dev/null +++ b/x-pack/plugins/ml/package.json @@ -0,0 +1,15 @@ +{ + "author": "Elastic", + "name": "ml", + "version": "0.0.0", + "private": true, + "license": "Elastic-License", + "scripts": { + "build:apiDocScripts": "cd server/routes/apidoc_scripts && tsc", + "apiDocs": "yarn build:apiDocScripts && cd ./server/routes/ && apidoc --parse-workers apischema=./apidoc_scripts/target/schema_worker.js --parse-parsers apischema=./apidoc_scripts/target/schema_parser.js --parse-filters apiversion=./apidoc_scripts/target/version_filter.js -i . -o ../routes_doc && apidoc-markdown -p ../routes_doc -o ../routes_doc/ML_API.md" + }, + "devDependencies": { + "apidoc": "^0.20.1", + "apidoc-markdown": "^5.0.0" + } +} diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/__tests__/utils.js b/x-pack/plugins/ml/public/application/components/rule_editor/utils.test.js similarity index 66% rename from x-pack/plugins/ml/public/application/components/rule_editor/__tests__/utils.js rename to x-pack/plugins/ml/public/application/components/rule_editor/utils.test.js index b5f9bdeaa12aa..18e382f8fe5e8 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/__tests__/utils.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/utils.test.js @@ -4,14 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { isValidRule, buildRuleDescription, getAppliesToValueFromAnomaly } from '../utils'; +import { isValidRule, buildRuleDescription, getAppliesToValueFromAnomaly } from './utils'; import { ACTION, APPLIES_TO, OPERATOR, FILTER_TYPE, -} from '../../../../../common/constants/detector_rule'; +} from '../../../../common/constants/detector_rule'; describe('ML - rule editor utils', () => { const ruleWithCondition = { @@ -55,19 +54,19 @@ describe('ML - rule editor utils', () => { }; describe('isValidRule', () => { - it('returns true for a rule with an action and a condition', () => { - expect(isValidRule(ruleWithCondition)).to.be(true); + test('returns true for a rule with an action and a condition', () => { + expect(isValidRule(ruleWithCondition)).toBe(true); }); - it('returns true for a rule with an action and scope', () => { - expect(isValidRule(ruleWithScope)).to.be(true); + test('returns true for a rule with an action and scope', () => { + expect(isValidRule(ruleWithScope)).toBe(true); }); - it('returns true for a rule with an action, scope and condition', () => { - expect(isValidRule(ruleWithConditionAndScope)).to.be(true); + test('returns true for a rule with an action, scope and condition', () => { + expect(isValidRule(ruleWithConditionAndScope)).toBe(true); }); - it('returns false for a rule with no action', () => { + test('returns false for a rule with no action', () => { const ruleWithNoAction = { actions: [], conditions: [ @@ -79,27 +78,27 @@ describe('ML - rule editor utils', () => { ], }; - expect(isValidRule(ruleWithNoAction)).to.be(false); + expect(isValidRule(ruleWithNoAction)).toBe(false); }); - it('returns false for a rule with no scope or conditions', () => { + test('returns false for a rule with no scope or conditions', () => { const ruleWithNoScopeOrCondition = { actions: [ACTION.SKIP_RESULT], }; - expect(isValidRule(ruleWithNoScopeOrCondition)).to.be(false); + expect(isValidRule(ruleWithNoScopeOrCondition)).toBe(false); }); }); describe('buildRuleDescription', () => { - it('returns expected rule descriptions', () => { - expect(buildRuleDescription(ruleWithCondition)).to.be( + test('returns expected rule descriptions', () => { + expect(buildRuleDescription(ruleWithCondition)).toBe( 'skip result when actual is greater than 10' ); - expect(buildRuleDescription(ruleWithScope)).to.be( + expect(buildRuleDescription(ruleWithScope)).toBe( 'skip result when instance is in test_aws_instances' ); - expect(buildRuleDescription(ruleWithConditionAndScope)).to.be( + expect(buildRuleDescription(ruleWithConditionAndScope)).toBe( 'skip result when typical is less than 100 AND instance is not in test_aws_instances' ); }); @@ -111,16 +110,16 @@ describe('ML - rule editor utils', () => { typical: [1.23], }; - it('returns expected actual value from an anomaly', () => { - expect(getAppliesToValueFromAnomaly(anomaly, APPLIES_TO.ACTUAL)).to.be(210); + test('returns expected actual value from an anomaly', () => { + expect(getAppliesToValueFromAnomaly(anomaly, APPLIES_TO.ACTUAL)).toBe(210); }); - it('returns expected typical value from an anomaly', () => { - expect(getAppliesToValueFromAnomaly(anomaly, APPLIES_TO.TYPICAL)).to.be(1.23); + test('returns expected typical value from an anomaly', () => { + expect(getAppliesToValueFromAnomaly(anomaly, APPLIES_TO.TYPICAL)).toBe(1.23); }); - it('returns expected diff from typical value from an anomaly', () => { - expect(getAppliesToValueFromAnomaly(anomaly, APPLIES_TO.DIFF_FROM_TYPICAL)).to.be(208.77); + test('returns expected diff from typical value from an anomaly', () => { + expect(getAppliesToValueFromAnomaly(anomaly, APPLIES_TO.DIFF_FROM_TYPICAL)).toBe(208.77); }); }); }); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js index d1b615a878b2b..c73ab4b9e11c7 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js @@ -24,14 +24,11 @@ import { readFile, createUrlOverrides, processResults, - reduceData, hasImportPermission, } from '../utils'; import { MODE } from './constants'; -const UPLOAD_SIZE_MB = 5; - export class FileDataVisualizerView extends Component { constructor(props) { super(props); @@ -40,6 +37,7 @@ export class FileDataVisualizerView extends Component { files: {}, fileName: '', fileContents: '', + data: [], fileSize: 0, fileTooLarge: false, fileCouldNotBeRead: false, @@ -79,6 +77,7 @@ export class FileDataVisualizerView extends Component { loaded: false, fileName: '', fileContents: '', + data: [], fileSize: 0, fileTooLarge: false, fileCouldNotBeRead: false, @@ -97,15 +96,15 @@ export class FileDataVisualizerView extends Component { async loadFile(file) { if (file.size <= this.maxFileUploadBytes) { try { - const fileContents = await readFile(file); - const data = fileContents.data; + const { data, fileContents } = await readFile(file); this.setState({ - fileContents: data, + data, + fileContents, fileName: file.name, fileSize: file.size, }); - await this.loadSettings(data); + await this.analyzeFile(fileContents); } catch (error) { this.setState({ loaded: false, @@ -124,14 +123,9 @@ export class FileDataVisualizerView extends Component { } } - async loadSettings(data, overrides, isRetry = false) { + async analyzeFile(fileContents, overrides, isRetry = false) { try { - // reduce the amount of data being sent to the endpoint - // 5MB should be enough to contain 1000 lines - const lessData = reduceData(data, UPLOAD_SIZE_MB); - console.log('overrides', overrides); - const { analyzeFile } = ml.fileDatavisualizer; - const resp = await analyzeFile(lessData, overrides); + const resp = await ml.fileDatavisualizer.analyzeFile(fileContents, overrides); const serverSettings = processResults(resp); const serverOverrides = resp.overrides; @@ -198,7 +192,7 @@ export class FileDataVisualizerView extends Component { loading: true, loaded: false, }); - this.loadSettings(data, this.previousOverrides, true); + this.analyzeFile(fileContents, this.previousOverrides, true); } } } @@ -240,7 +234,7 @@ export class FileDataVisualizerView extends Component { }, () => { const formattedOverrides = createUrlOverrides(overrides, this.originalSettings); - this.loadSettings(this.state.fileContents, formattedOverrides); + this.analyzeFile(this.state.fileContents, formattedOverrides); } ); }; @@ -261,6 +255,7 @@ export class FileDataVisualizerView extends Component { results, explanation, fileContents, + data, fileName, fileSize, fileTooLarge, @@ -339,6 +334,7 @@ export class FileDataVisualizerView extends Component { results={results} fileName={fileName} fileContents={fileContents} + data={data} indexPatterns={this.props.indexPatterns} kibanaConfig={this.props.kibanaConfig} showBottomBar={this.showBottomBar} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js index 4c9579bfd4b46..2bf7bbeb641d0 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js @@ -94,7 +94,7 @@ export class ImportView extends Component { // TODO - sort this function out. it's a mess async import() { - const { fileContents, results, indexPatterns, kibanaConfig, showBottomBar } = this.props; + const { data, results, indexPatterns, kibanaConfig, showBottomBar } = this.props; const { format } = results; let { timeFieldName } = this.state; @@ -217,7 +217,7 @@ export class ImportView extends Component { if (success) { const importer = importerFactory(format, results, indexCreationSettings); if (importer !== undefined) { - const readResp = importer.read(fileContents, this.setReadProgress); + const readResp = importer.read(data, this.setReadProgress); success = readResp.success; this.setState({ readStatus: success ? IMPORT_STATUS.COMPLETE : IMPORT_STATUS.FAILED, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts index c97f1c147c454..718587ad15ad5 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts @@ -9,7 +9,7 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { ml } from '../../../../../services/ml_api_service'; import { - Doc, + ImportDoc, ImportFailure, ImportResponse, Mappings, @@ -20,6 +20,7 @@ import { const CHUNK_SIZE = 5000; const MAX_CHUNK_CHAR_COUNT = 1000000; const IMPORT_RETRIES = 5; +const STRING_CHUNKS_MB = 100; export interface ImportConfig { settings: Settings; @@ -34,12 +35,19 @@ export interface ImportResults { error?: any; } -export class Importer { +export interface CreateDocsResponse { + success: boolean; + remainder: number; + docs: ImportDoc[]; + error?: any; +} + +export abstract class Importer { private _settings: Settings; private _mappings: Mappings; private _pipeline: IngestPipeline; - protected _docArray: Doc[] = []; + protected _docArray: ImportDoc[] = []; constructor({ settings, mappings, pipeline }: ImportConfig) { this._settings = settings; @@ -47,7 +55,33 @@ export class Importer { this._pipeline = pipeline; } - async initializeImport(index: string) { + public read(data: ArrayBuffer) { + const decoder = new TextDecoder(); + const size = STRING_CHUNKS_MB * Math.pow(2, 20); + + // chop the data up into 100MB chunks for processing. + // if the chop produces a partial line at the end, a character "remainder" count + // is returned which is used to roll the next chunk back that many chars so + // it is included in the next chunk. + const parts = Math.ceil(data.byteLength / size); + let remainder = 0; + for (let i = 0; i < parts; i++) { + const byteArray = decoder.decode(data.slice(i * size - remainder, (i + 1) * size)); + const { success, docs, remainder: tempRemainder } = this._createDocs(byteArray); + if (success) { + this._docArray = this._docArray.concat(docs); + remainder = tempRemainder; + } else { + return { success: false }; + } + } + + return { success: true }; + } + + protected abstract _createDocs(t: string): CreateDocsResponse; + + public async initializeImport(index: string) { const settings = this._settings; const mappings = this._mappings; const pipeline = this._pipeline; @@ -75,7 +109,7 @@ export class Importer { return createIndexResp; } - async import( + public async import( id: string, index: string, pipelineId: string, @@ -201,8 +235,8 @@ function updatePipelineTimezone(ingestPipeline: IngestPipeline) { } } -function createDocumentChunks(docArray: Doc[]) { - const chunks: Doc[][] = []; +function createDocumentChunks(docArray: ImportDoc[]) { + const chunks: ImportDoc[][] = []; // chop docArray into 5000 doc chunks const tempChunks = chunk(docArray, CHUNK_SIZE); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts index 7ccc5a8d673f4..65be24d9e7be4 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Importer, ImportConfig } from './importer'; +import { Importer, ImportConfig, CreateDocsResponse } from './importer'; import { Doc, FindFileStructureResponse, @@ -33,54 +33,54 @@ export class MessageImporter extends Importer { // multiline_start_pattern regex // if it does, it is a legitimate end of line and can be pushed into the list, // if not, it must be a newline char inside a field value, so keep looking. - read(text: string) { + protected _createDocs(text: string): CreateDocsResponse { + let remainder = 0; try { - const data: Doc[] = []; + const docs: Doc[] = []; let message = ''; let line = ''; for (let i = 0; i < text.length; i++) { const char = text[i]; if (char === '\n') { - message = this.processLine(data, message, line); + message = this._processLine(docs, message, line); line = ''; } else { line += char; } } - // the last line may have been missing a newline ending - if (line !== '') { - message = this.processLine(data, message, line); - } + remainder = line.length; - // add the last message to the list if not already done + // // add the last message to the list if not already done if (message !== '') { - this.addMessage(data, message); + this._addMessage(docs, message); } // remove first line if it is blank - if (data[0] && data[0].message === '') { - data.shift(); + if (docs[0] && docs[0].message === '') { + docs.shift(); } - this._docArray = data; - return { success: true, + docs, + remainder, }; } catch (error) { return { success: false, + docs: [], + remainder, error, }; } } - processLine(data: Doc[], message: string, line: string) { + private _processLine(data: Doc[], message: string, line: string) { if (this._excludeLinesRegex === null || line.match(this._excludeLinesRegex) === null) { if (this._multilineStartRegex === null || line.match(this._multilineStartRegex) !== null) { - this.addMessage(data, message); + this._addMessage(data, message); message = ''; } else if (data.length === 0) { // discard everything before the first line that is considered the first line of a message @@ -95,7 +95,7 @@ export class MessageImporter extends Importer { return message; } - addMessage(data: Doc[], message: string) { + private _addMessage(data: Doc[], message: string) { // if the message ended \r\n (Windows line endings) // then omit the \r as well as the \n for consistency message = message.replace(/\r$/, ''); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts index 7f5f37abc5246..17c9de8ef4558 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Importer, ImportConfig } from './importer'; +import { Importer, ImportConfig, CreateDocsResponse } from './importer'; import { FindFileStructureResponse } from '../../../../../../../common/types/file_datavisualizer'; export class NdjsonImporter extends Importer { @@ -12,27 +12,42 @@ export class NdjsonImporter extends Importer { super(settings); } - read(json: string) { + protected _createDocs(json: string): CreateDocsResponse { + let remainder = 0; try { const splitJson = json.split(/}\s*\n/); + const incompleteLastLine = json.match(/}\s*\n?$/) === null; - const ndjson: any[] = []; - for (let i = 0; i < splitJson.length; i++) { - if (splitJson[i] !== '') { - // note the extra } at the end of the line, adding back - // the one that was eaten in the split - ndjson.push(`${splitJson[i]}}`); + const docs: string[] = []; + if (splitJson.length) { + for (let i = 0; i < splitJson.length - 1; i++) { + if (splitJson[i] !== '') { + // note the extra } at the end of the line, adding back + // the one that was eaten in the split + docs.push(`${splitJson[i]}}`); + } } - } - this._docArray = ndjson; + const lastDoc = splitJson[splitJson.length - 1]; + if (lastDoc) { + if (incompleteLastLine === true) { + remainder = lastDoc.length; + } else { + docs.push(`${lastDoc}}`); + } + } + } return { success: true, + docs, + remainder, }; } catch (error) { return { success: false, + docs: [], + remainder, error, }; } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts index 0f0036a7c4616..492a797f7a2f2 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts @@ -9,7 +9,6 @@ export { hasImportPermission, processResults, readFile, - reduceData, getMaxBytes, getMaxBytesFormatted, } from './utils'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts index 0d2016b71ed83..7b6464570e55c 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts @@ -9,12 +9,14 @@ import numeral from '@elastic/numeral'; import { ml } from '../../../../services/ml_api_service'; import { AnalysisResult, InputOverrides } from '../../../../../../common/types/file_datavisualizer'; import { - ABSOLUTE_MAX_BYTES, + MAX_FILE_SIZE_BYTES, + ABSOLUTE_MAX_FILE_SIZE_BYTES, FILE_SIZE_DISPLAY_FORMAT, } from '../../../../../../common/constants/file_datavisualizer'; import { getMlConfig } from '../../../../util/dependency_cache'; const DEFAULT_LINES_TO_SAMPLE = 1000; +const UPLOAD_SIZE_MB = 5; const overrideDefaults = { timestampFormat: undefined, @@ -34,15 +36,22 @@ export function readFile(file: File) { return new Promise((resolve, reject) => { if (file && file.size) { const reader = new FileReader(); - reader.readAsText(file); + reader.readAsArrayBuffer(file); reader.onload = (() => { return () => { + const decoder = new TextDecoder(); const data = reader.result; - if (data === '') { + if (data === null || typeof data === 'string') { + return reject(); + } + const size = UPLOAD_SIZE_MB * Math.pow(2, 20); + const fileContents = decoder.decode(data.slice(0, size)); + + if (fileContents === '') { reject(); } else { - resolve({ data }); + resolve({ fileContents, data }); } }; })(); @@ -52,17 +61,14 @@ export function readFile(file: File) { }); } -export function reduceData(data: string, mb: number) { - // assuming ascii characters in the file where 1 char is 1 byte - // TODO - change this when other non UTF-8 formats are - // supported for the read data - const size = mb * Math.pow(2, 20); - return data.length >= size ? data.slice(0, size) : data; -} - export function getMaxBytes() { - const maxBytes = getMlConfig().file_data_visualizer.max_file_size_bytes; - return maxBytes < ABSOLUTE_MAX_BYTES ? maxBytes : ABSOLUTE_MAX_BYTES; + const maxFileSize = getMlConfig().file_data_visualizer.max_file_size; + // @ts-ignore + const maxBytes = numeral(maxFileSize.toUpperCase()).value(); + if (maxBytes < MAX_FILE_SIZE_BYTES) { + return MAX_FILE_SIZE_BYTES; + } + return maxBytes < ABSOLUTE_MAX_FILE_SIZE_BYTES ? maxBytes : ABSOLUTE_MAX_FILE_SIZE_BYTES; } export function getMaxBytesFormatted() { diff --git a/x-pack/plugins/ml/public/application/util/__tests__/calc_auto_interval.js b/x-pack/plugins/ml/public/application/util/__tests__/calc_auto_interval.js deleted file mode 100644 index 0553cec5cd7d4..0000000000000 --- a/x-pack/plugins/ml/public/application/util/__tests__/calc_auto_interval.js +++ /dev/null @@ -1,140 +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 expect from '@kbn/expect'; -import moment from 'moment'; - -import { timeBucketsCalcAutoIntervalProvider } from '../calc_auto_interval'; - -describe('ML - calc auto intervals', () => { - const calcAuto = timeBucketsCalcAutoIntervalProvider(); - - describe('near interval', () => { - it('returns 0ms buckets for undefined / 0 bars', () => { - const interval = calcAuto.near(0, undefined); - expect(interval.asMilliseconds()).to.be(0); - }); - - it('returns 1000ms buckets for 60s / 100 bars', () => { - const interval = calcAuto.near(100, moment.duration(60, 's')); - expect(interval.asMilliseconds()).to.be(1000); - }); - - it('returns 5m buckets for 8h / 100 bars', () => { - const interval = calcAuto.near(100, moment.duration(8, 'h')); - expect(interval.asMinutes()).to.be(5); - }); - - it('returns 15m buckets for 1d / 100 bars', () => { - const interval = calcAuto.near(100, moment.duration(1, 'd')); - expect(interval.asMinutes()).to.be(15); - }); - - it('returns 1h buckets for 20d / 500 bars', () => { - const interval = calcAuto.near(500, moment.duration(20, 'd')); - expect(interval.asHours()).to.be(1); - }); - - it('returns 6h buckets for 100d / 500 bars', () => { - const interval = calcAuto.near(500, moment.duration(100, 'd')); - expect(interval.asHours()).to.be(6); - }); - - it('returns 24h buckets for 1y / 500 bars', () => { - const interval = calcAuto.near(500, moment.duration(1, 'y')); - expect(interval.asHours()).to.be(24); - }); - - it('returns 12h buckets for 1y / 1000 bars', () => { - const interval = calcAuto.near(1000, moment.duration(1, 'y')); - expect(interval.asHours()).to.be(12); - }); - }); - - describe('lessThan interval', () => { - it('returns 0ms buckets for undefined / 0 bars', () => { - const interval = calcAuto.lessThan(0, undefined); - expect(interval.asMilliseconds()).to.be(0); - }); - - it('returns 500ms buckets for 60s / 100 bars', () => { - const interval = calcAuto.lessThan(100, moment.duration(60, 's')); - expect(interval.asMilliseconds()).to.be(500); - }); - - it('returns 5m buckets for 8h / 100 bars', () => { - const interval = calcAuto.lessThan(100, moment.duration(8, 'h')); - expect(interval.asMinutes()).to.be(5); - }); - - it('returns 30m buckets for 1d / 100 bars', () => { - const interval = calcAuto.lessThan(100, moment.duration(1, 'd')); - expect(interval.asMinutes()).to.be(30); - }); - - it('returns 1h buckets for 20d / 500 bars', () => { - const interval = calcAuto.lessThan(500, moment.duration(20, 'd')); - expect(interval.asHours()).to.be(1); - }); - - it('returns 6h buckets for 100d / 500 bars', () => { - const interval = calcAuto.lessThan(500, moment.duration(100, 'd')); - expect(interval.asHours()).to.be(6); - }); - - it('returns 24h buckets for 1y / 500 bars', () => { - const interval = calcAuto.lessThan(500, moment.duration(1, 'y')); - expect(interval.asHours()).to.be(24); - }); - - it('returns 12h buckets for 1y / 1000 bars', () => { - const interval = calcAuto.lessThan(1000, moment.duration(1, 'y')); - expect(interval.asHours()).to.be(12); - }); - }); - - describe('atLeast interval', () => { - it('returns 0ms buckets for undefined / 0 bars', () => { - const interval = calcAuto.atLeast(0, undefined); - expect(interval.asMilliseconds()).to.be(0); - }); - - it('returns 100ms buckets for 60s / 100 bars', () => { - const interval = calcAuto.atLeast(100, moment.duration(60, 's')); - expect(interval.asMilliseconds()).to.be(100); - }); - - it('returns 1m buckets for 8h / 100 bars', () => { - const interval = calcAuto.atLeast(100, moment.duration(8, 'h')); - expect(interval.asMinutes()).to.be(1); - }); - - it('returns 10m buckets for 1d / 100 bars', () => { - const interval = calcAuto.atLeast(100, moment.duration(1, 'd')); - expect(interval.asMinutes()).to.be(10); - }); - - it('returns 30m buckets for 20d / 500 bars', () => { - const interval = calcAuto.atLeast(500, moment.duration(20, 'd')); - expect(interval.asMinutes()).to.be(30); - }); - - it('returns 4h buckets for 100d / 500 bars', () => { - const interval = calcAuto.atLeast(500, moment.duration(100, 'd')); - expect(interval.asHours()).to.be(4); - }); - - it('returns 12h buckets for 1y / 500 bars', () => { - const interval = calcAuto.atLeast(500, moment.duration(1, 'y')); - expect(interval.asHours()).to.be(12); - }); - - it('returns 8h buckets for 1y / 1000 bars', () => { - const interval = calcAuto.atLeast(1000, moment.duration(1, 'y')); - expect(interval.asHours()).to.be(8); - }); - }); -}); diff --git a/x-pack/plugins/ml/public/application/util/__tests__/chart_utils.js b/x-pack/plugins/ml/public/application/util/__tests__/chart_utils.js deleted file mode 100644 index 89df5946abe76..0000000000000 --- a/x-pack/plugins/ml/public/application/util/__tests__/chart_utils.js +++ /dev/null @@ -1,297 +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 'jquery'; -import d3 from 'd3'; -import expect from '@kbn/expect'; -import { - chartLimits, - filterAxisLabels, - getChartType, - numTicks, - showMultiBucketAnomalyMarker, - showMultiBucketAnomalyTooltip, -} from '../chart_utils'; -import { MULTI_BUCKET_IMPACT } from '../../../../common/constants/multi_bucket_impact'; -import { CHART_TYPE } from '../../explorer/explorer_constants'; - -describe('ML - chart utils', () => { - describe('chartLimits', () => { - it('returns NaN when called without data', () => { - const limits = chartLimits(); - expect(limits.min).to.be.NaN; - expect(limits.max).to.be.NaN; - }); - - it('returns {max: 625736376, min: 201039318} for some test data', () => { - const data = [ - { - date: new Date('2017-02-23T08:00:00.000Z'), - value: 228243469, - anomalyScore: 63.32916, - numberOfCauses: 1, - actual: [228243469], - typical: [133107.7703441773], - }, - { date: new Date('2017-02-23T09:00:00.000Z'), value: null }, - { date: new Date('2017-02-23T10:00:00.000Z'), value: null }, - { date: new Date('2017-02-23T11:00:00.000Z'), value: null }, - { - date: new Date('2017-02-23T12:00:00.000Z'), - value: 625736376, - anomalyScore: 97.32085, - numberOfCauses: 1, - actual: [625736376], - typical: [132830.424736973], - }, - { - date: new Date('2017-02-23T13:00:00.000Z'), - value: 201039318, - anomalyScore: 59.83488, - numberOfCauses: 1, - actual: [201039318], - typical: [132739.5267403542], - }, - ]; - - const limits = chartLimits(data); - - // {max: 625736376, min: 201039318} - expect(limits.min).to.be(201039318); - expect(limits.max).to.be(625736376); - }); - - it("adds 5% padding when min/max are the same, e.g. when there's only one data point", () => { - const data = [ - { - date: new Date('2017-02-23T08:00:00.000Z'), - value: 100, - anomalyScore: 50, - numberOfCauses: 1, - actual: [100], - typical: [100], - }, - ]; - - const limits = chartLimits(data); - expect(limits.min).to.be(95); - expect(limits.max).to.be(105); - }); - - it('returns minimum of 0 when data includes an anomaly for missing data', () => { - const data = [ - { date: new Date('2017-02-23T09:00:00.000Z'), value: 22.2 }, - { date: new Date('2017-02-23T10:00:00.000Z'), value: 23.3 }, - { date: new Date('2017-02-23T11:00:00.000Z'), value: 24.4 }, - { - date: new Date('2017-02-23T12:00:00.000Z'), - value: null, - anomalyScore: 97.32085, - actual: [0], - typical: [22.2], - }, - { date: new Date('2017-02-23T13:00:00.000Z'), value: 21.3 }, - { date: new Date('2017-02-23T14:00:00.000Z'), value: 21.2 }, - { date: new Date('2017-02-23T15:00:00.000Z'), value: 21.1 }, - ]; - - const limits = chartLimits(data); - expect(limits.min).to.be(0); - expect(limits.max).to.be(24.4); - }); - }); - - describe('filterAxisLabels', () => { - it('throws an error when called without arguments', () => { - expect(() => filterAxisLabels()).to.throwError(); - }); - - it('filters axis labels', () => { - // this provides a dummy structure of axis labels. - // the first one should always be filtered because it overflows on the - // left side of the axis. the last one should be filtered based on the - // given width parameter when doing the test calls. - $('body').append(` - - - - 06:00 - - - 12:00 - - - 18:00 - - - 00:00 - - - - `); - - const selector = '#filterAxisLabels .x.axis'; - - // given this width, the last tick should not be removed - filterAxisLabels(d3.selectAll(selector), 1000); - expect(d3.selectAll(selector + ' .tick text').size()).to.be(3); - - // given this width, the last tick should be removed - filterAxisLabels(d3.selectAll(selector), 790); - expect(d3.selectAll(selector + ' .tick text').size()).to.be(2); - - // clean up - $('#filterAxisLabels').remove(); - }); - }); - - describe('getChartType', () => { - const singleMetricConfig = { - metricFunction: 'avg', - functionDescription: 'mean', - fieldName: 'responsetime', - entityFields: [], - }; - - const multiMetricConfig = { - metricFunction: 'avg', - functionDescription: 'mean', - fieldName: 'responsetime', - entityFields: [ - { - fieldName: 'airline', - fieldValue: 'AAL', - fieldType: 'partition', - }, - ], - }; - - const populationConfig = { - metricFunction: 'avg', - functionDescription: 'mean', - fieldName: 'http.response.body.bytes', - entityFields: [ - { - fieldName: 'source.ip', - fieldValue: '10.11.12.13', - fieldType: 'over', - }, - ], - }; - - const rareConfig = { - metricFunction: 'count', - functionDescription: 'rare', - entityFields: [ - { - fieldName: 'http.response.status_code', - fieldValue: '404', - fieldType: 'by', - }, - ], - }; - - const varpModelPlotConfig = { - metricFunction: null, - functionDescription: 'varp', - fieldName: 'NetworkOut', - entityFields: [ - { - fieldName: 'instance', - fieldValue: 'i-ef74d410', - fieldType: 'over', - }, - ], - }; - - const overScriptFieldModelPlotConfig = { - metricFunction: 'count', - functionDescription: 'count', - fieldName: 'highest_registered_domain', - entityFields: [ - { - fieldName: 'highest_registered_domain', - fieldValue: 'elastic.co', - fieldType: 'over', - }, - ], - datafeedConfig: { - script_fields: { - highest_registered_domain: { - script: { - source: "return domainSplit(doc['query'].value, params).get(1);", - lang: 'painless', - }, - ignore_failure: false, - }, - }, - }, - }; - - it('returns single metric chart type as expected for configs', () => { - expect(getChartType(singleMetricConfig)).to.be(CHART_TYPE.SINGLE_METRIC); - expect(getChartType(multiMetricConfig)).to.be(CHART_TYPE.SINGLE_METRIC); - expect(getChartType(varpModelPlotConfig)).to.be(CHART_TYPE.SINGLE_METRIC); - expect(getChartType(overScriptFieldModelPlotConfig)).to.be(CHART_TYPE.SINGLE_METRIC); - }); - - it('returns event distribution chart type as expected for configs', () => { - expect(getChartType(rareConfig)).to.be(CHART_TYPE.EVENT_DISTRIBUTION); - }); - - it('returns population distribution chart type as expected for configs', () => { - expect(getChartType(populationConfig)).to.be(CHART_TYPE.POPULATION_DISTRIBUTION); - }); - }); - - describe('numTicks', () => { - it('returns 10 for 1000', () => { - expect(numTicks(1000)).to.be(10); - }); - }); - - describe('showMultiBucketAnomalyMarker', () => { - it('returns true for points with multiBucketImpact at or above medium impact', () => { - expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.HIGH })).to.be( - true - ); - expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.MEDIUM })).to.be( - true - ); - }); - - it('returns false for points with multiBucketImpact missing or below medium impact', () => { - expect(showMultiBucketAnomalyMarker({})).to.be(false); - expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.LOW })).to.be( - false - ); - expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.NONE })).to.be( - false - ); - }); - }); - - describe('showMultiBucketAnomalyTooltip', () => { - it('returns true for points with multiBucketImpact at or above low impact', () => { - expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.HIGH })).to.be( - true - ); - expect( - showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.MEDIUM }) - ).to.be(true); - expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.LOW })).to.be( - true - ); - }); - - it('returns false for points with multiBucketImpact missing or below medium impact', () => { - expect(showMultiBucketAnomalyTooltip({})).to.be(false); - expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.NONE })).to.be( - false - ); - }); - }); -}); diff --git a/x-pack/plugins/ml/public/application/util/__tests__/string_utils.js b/x-pack/plugins/ml/public/application/util/__tests__/string_utils.js deleted file mode 100644 index 702e9dfd96205..0000000000000 --- a/x-pack/plugins/ml/public/application/util/__tests__/string_utils.js +++ /dev/null @@ -1,229 +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 expect from '@kbn/expect'; -import { - replaceStringTokens, - detectorToString, - sortByKey, - guessTimeFormat, - toLocaleString, - mlEscape, - escapeForElasticsearchQuery, -} from '../string_utils'; - -describe('ML - string utils', () => { - describe('replaceStringTokens', () => { - const testRecord = { - job_id: 'test_job', - result_type: 'record', - probability: 0.0191711, - record_score: 4.3, - bucket_span: 300, - detector_index: 0, - timestamp: 1454890500000, - function: 'mean', - function_description: 'mean', - field_name: 'responsetime', - user: "Des O'Connor", - testfield1: 'test$tring=[+-?]', - testfield2: '{<()>}', - testfield3: 'host=\\\\test@uk.dev', - }; - - it('returns correct values without URI encoding', () => { - const result = replaceStringTokens('user=$user$,time=$timestamp$', testRecord, false); - expect(result).to.be("user=Des O'Connor,time=1454890500000"); - }); - - it('returns correct values for missing token without URI encoding', () => { - const result = replaceStringTokens('user=$username$,time=$timestamp$', testRecord, false); - expect(result).to.be('user=$username$,time=1454890500000'); - }); - - it('returns correct values with URI encoding', () => { - const testString1 = 'https://www.google.co.uk/webhp#q=$testfield1$'; - const testString2 = 'https://www.google.co.uk/webhp#q=$testfield2$'; - const testString3 = 'https://www.google.co.uk/webhp#q=$testfield3$'; - const testString4 = 'https://www.google.co.uk/webhp#q=$user$'; - - const result1 = replaceStringTokens(testString1, testRecord, true); - const result2 = replaceStringTokens(testString2, testRecord, true); - const result3 = replaceStringTokens(testString3, testRecord, true); - const result4 = replaceStringTokens(testString4, testRecord, true); - - expect(result1).to.be('https://www.google.co.uk/webhp#q=test%24tring%3D%5B%2B-%3F%5D'); - expect(result2).to.be('https://www.google.co.uk/webhp#q=%7B%3C()%3E%7D'); - expect(result3).to.be('https://www.google.co.uk/webhp#q=host%3D%5C%5Ctest%40uk.dev'); - expect(result4).to.be("https://www.google.co.uk/webhp#q=Des%20O'Connor"); - }); - - it('returns correct values for missing token with URI encoding', () => { - const testString = 'https://www.google.co.uk/webhp#q=$username$&time=$timestamp$'; - const result = replaceStringTokens(testString, testRecord, true); - expect(result).to.be('https://www.google.co.uk/webhp#q=$username$&time=1454890500000'); - }); - }); - - describe('detectorToString', () => { - it('returns the correct descriptions for detectors', () => { - const detector1 = { - function: 'count', - }; - - const detector2 = { - function: 'count', - by_field_name: 'airline', - use_null: false, - }; - - const detector3 = { - function: 'mean', - field_name: 'CPUUtilization', - partition_field_name: 'region', - by_field_name: 'host', - over_field_name: 'user', - exclude_frequent: 'all', - }; - - expect(detectorToString(detector1)).to.be('count'); - expect(detectorToString(detector2)).to.be('count by airline use_null=false'); - expect(detectorToString(detector3)).to.be( - 'mean(CPUUtilization) by host over user partition_field_name=region exclude_frequent=all' - ); - }); - }); - - describe('sortByKey', () => { - const obj = { - zebra: 'stripes', - giraffe: 'neck', - elephant: 'trunk', - }; - - const valueComparator = function(value) { - return value; - }; - - it('returns correct ordering with default comparator', () => { - const result = sortByKey(obj, false); - const keys = Object.keys(result); - expect(keys[0]).to.be('elephant'); - expect(keys[1]).to.be('giraffe'); - expect(keys[2]).to.be('zebra'); - }); - - it('returns correct ordering with default comparator and order reversed', () => { - const result = sortByKey(obj, true); - const keys = Object.keys(result); - expect(keys[0]).to.be('zebra'); - expect(keys[1]).to.be('giraffe'); - expect(keys[2]).to.be('elephant'); - }); - - it('returns correct ordering with comparator', () => { - const result = sortByKey(obj, false, valueComparator); - const keys = Object.keys(result); - expect(keys[0]).to.be('giraffe'); - expect(keys[1]).to.be('zebra'); - expect(keys[2]).to.be('elephant'); - }); - - it('returns correct ordering with comparator and order reversed', () => { - const result = sortByKey(obj, true, valueComparator); - const keys = Object.keys(result); - expect(keys[0]).to.be('elephant'); - expect(keys[1]).to.be('zebra'); - expect(keys[2]).to.be('giraffe'); - }); - }); - - describe('guessTimeFormat', () => { - it('returns correct format for various dates', () => { - expect(guessTimeFormat('2017-03-24T00:00')).to.be("yyyy-MM-dd'T'HH:mm"); - expect(guessTimeFormat('2017-03-24 00:00')).to.be('yyyy-MM-dd HH:mm'); - expect(guessTimeFormat('2017-03-24 00:00:00')).to.be('yyyy-MM-dd HH:mm:ss'); - expect(guessTimeFormat('2017-03-24 00:00:00Z')).to.be('yyyy-MM-dd HH:mm:ssX'); - expect(guessTimeFormat('2017-03-24 00:00:00.000')).to.be('yyyy-MM-dd HH:mm:ss.SSS'); - expect(guessTimeFormat('2017-03-24 00:00:00:000')).to.be('yyyy-MM-dd HH:mm:ss:SSS'); - expect(guessTimeFormat('2017-03-24 00:00:00.000+00:00:00')).to.be( - 'yyyy-MM-dd HH:mm:ss.SSSXXXXX' - ); - expect(guessTimeFormat('2017-03-24 00:00:00.000+00:00')).to.be('yyyy-MM-dd HH:mm:ss.SSSXXX'); - expect(guessTimeFormat('2017-03-24 00:00:00.000+000000')).to.be( - 'yyyy-MM-dd HH:mm:ss.SSSXXXX' - ); - expect(guessTimeFormat('2017-03-24 00:00:00.000+0000')).to.be('yyyy-MM-dd HH:mm:ss.SSSZ'); - expect(guessTimeFormat('2017-03-24 00:00:00.000+00')).to.be('yyyy-MM-dd HH:mm:ss.SSSX'); - expect(guessTimeFormat('2017-03-24 00:00:00.000Z')).to.be('yyyy-MM-dd HH:mm:ss.SSSX'); - expect(guessTimeFormat('2017-03-24 00:00:00.000 GMT')).to.be('yyyy-MM-dd HH:mm:ss.SSS zzz'); - expect(guessTimeFormat('2017-03-24 00:00:00 GMT')).to.be('yyyy-MM-dd HH:mm:ss zzz'); - expect(guessTimeFormat('2017 03 24 00:00:00.000')).to.be('yyyy MM dd HH:mm:ss.SSS'); - expect(guessTimeFormat('2017.03.24 00:00:00.000')).to.be('yyyy.MM.dd HH:mm:ss.SSS'); - expect(guessTimeFormat('2017/03/24 00:00:00.000')).to.be('yyyy/MM/dd HH:mm:ss.SSS'); - expect(guessTimeFormat('24/03/2017 00:00:00.000')).to.be('dd/MM/yyyy HH:mm:ss.SSS'); - expect(guessTimeFormat('03 24 2017 00:00:00.000')).to.be('MM dd yyyy HH:mm:ss.SSS'); - expect(guessTimeFormat('03/24/2017 00:00:00.000')).to.be('MM/dd/yyyy HH:mm:ss.SSS'); - expect(guessTimeFormat('2017 Mar 24 00:00:00.000')).to.be('yyyy MMM dd HH:mm:ss.SSS'); - expect(guessTimeFormat('Mar 24 2017 00:00:00.000')).to.be('MMM dd yyyy HH:mm:ss.SSS'); - expect(guessTimeFormat('24 Mar 2017 00:00:00.000')).to.be('dd MMM yyyy HH:mm:ss.SSS'); - expect(guessTimeFormat('1490313600')).to.be('epoch'); - expect(guessTimeFormat('1490313600000')).to.be('epoch_ms'); - }); - }); - - describe('toLocaleString', () => { - it('returns correct comma placement for large numbers', () => { - expect(toLocaleString(1)).to.be('1'); - expect(toLocaleString(10)).to.be('10'); - expect(toLocaleString(100)).to.be('100'); - expect(toLocaleString(1000)).to.be('1,000'); - expect(toLocaleString(10000)).to.be('10,000'); - expect(toLocaleString(100000)).to.be('100,000'); - expect(toLocaleString(1000000)).to.be('1,000,000'); - expect(toLocaleString(10000000)).to.be('10,000,000'); - expect(toLocaleString(100000000)).to.be('100,000,000'); - expect(toLocaleString(1000000000)).to.be('1,000,000,000'); - }); - }); - - describe('mlEscape', () => { - it('returns correct escaping of characters', () => { - expect(mlEscape('foo&bar')).to.be('foo&bar'); - expect(mlEscape('foobar')).to.be('foo>bar'); - expect(mlEscape('foo"bar')).to.be('foo"bar'); - expect(mlEscape("foo'bar")).to.be('foo'bar'); - expect(mlEscape('foo/bar')).to.be('foo/bar'); - }); - }); - - describe('escapeForElasticsearchQuery', () => { - it('returns correct escaping of reserved elasticsearch characters', () => { - expect(escapeForElasticsearchQuery('foo+bar')).to.be('foo\\+bar'); - expect(escapeForElasticsearchQuery('foo-bar')).to.be('foo\\-bar'); - expect(escapeForElasticsearchQuery('foo=bar')).to.be('foo\\=bar'); - expect(escapeForElasticsearchQuery('foo&&bar')).to.be('foo\\&\\&bar'); - expect(escapeForElasticsearchQuery('foo||bar')).to.be('foo\\|\\|bar'); - expect(escapeForElasticsearchQuery('foo>bar')).to.be('foo\\>bar'); - expect(escapeForElasticsearchQuery('foo { + const calcAuto = timeBucketsCalcAutoIntervalProvider(); + + describe('near interval', () => { + test('returns 0ms buckets for undefined / 0 bars', () => { + const interval = calcAuto.near(0, undefined); + expect(interval.asMilliseconds()).toBe(0); + }); + + test('returns 1000ms buckets for 60s / 100 bars', () => { + const interval = calcAuto.near(100, moment.duration(60, 's')); + expect(interval.asMilliseconds()).toBe(1000); + }); + + test('returns 5m buckets for 8h / 100 bars', () => { + const interval = calcAuto.near(100, moment.duration(8, 'h')); + expect(interval.asMinutes()).toBe(5); + }); + + test('returns 15m buckets for 1d / 100 bars', () => { + const interval = calcAuto.near(100, moment.duration(1, 'd')); + expect(interval.asMinutes()).toBe(15); + }); + + test('returns 1h buckets for 20d / 500 bars', () => { + const interval = calcAuto.near(500, moment.duration(20, 'd')); + expect(interval.asHours()).toBe(1); + }); + + test('returns 6h buckets for 100d / 500 bars', () => { + const interval = calcAuto.near(500, moment.duration(100, 'd')); + expect(interval.asHours()).toBe(6); + }); + + test('returns 24h buckets for 1y / 500 bars', () => { + const interval = calcAuto.near(500, moment.duration(1, 'y')); + expect(interval.asHours()).toBe(24); + }); + + test('returns 12h buckets for 1y / 1000 bars', () => { + const interval = calcAuto.near(1000, moment.duration(1, 'y')); + expect(interval.asHours()).toBe(12); + }); + }); + + describe('lessThan interval', () => { + test('returns 0ms buckets for undefined / 0 bars', () => { + const interval = calcAuto.lessThan(0, undefined); + expect(interval.asMilliseconds()).toBe(0); + }); + + test('returns 500ms buckets for 60s / 100 bars', () => { + const interval = calcAuto.lessThan(100, moment.duration(60, 's')); + expect(interval.asMilliseconds()).toBe(500); + }); + + test('returns 5m buckets for 8h / 100 bars', () => { + const interval = calcAuto.lessThan(100, moment.duration(8, 'h')); + expect(interval.asMinutes()).toBe(5); + }); + + test('returns 30m buckets for 1d / 100 bars', () => { + const interval = calcAuto.lessThan(100, moment.duration(1, 'd')); + expect(interval.asMinutes()).toBe(30); + }); + + test('returns 1h buckets for 20d / 500 bars', () => { + const interval = calcAuto.lessThan(500, moment.duration(20, 'd')); + expect(interval.asHours()).toBe(1); + }); + + test('returns 6h buckets for 100d / 500 bars', () => { + const interval = calcAuto.lessThan(500, moment.duration(100, 'd')); + expect(interval.asHours()).toBe(6); + }); + + test('returns 24h buckets for 1y / 500 bars', () => { + const interval = calcAuto.lessThan(500, moment.duration(1, 'y')); + expect(interval.asHours()).toBe(24); + }); + + test('returns 12h buckets for 1y / 1000 bars', () => { + const interval = calcAuto.lessThan(1000, moment.duration(1, 'y')); + expect(interval.asHours()).toBe(12); + }); + }); + + describe('atLeast interval', () => { + test('returns 0ms buckets for undefined / 0 bars', () => { + const interval = calcAuto.atLeast(0, undefined); + expect(interval.asMilliseconds()).toBe(0); + }); + + test('returns 100ms buckets for 60s / 100 bars', () => { + const interval = calcAuto.atLeast(100, moment.duration(60, 's')); + expect(interval.asMilliseconds()).toBe(100); + }); + + test('returns 1m buckets for 8h / 100 bars', () => { + const interval = calcAuto.atLeast(100, moment.duration(8, 'h')); + expect(interval.asMinutes()).toBe(1); + }); + + test('returns 10m buckets for 1d / 100 bars', () => { + const interval = calcAuto.atLeast(100, moment.duration(1, 'd')); + expect(interval.asMinutes()).toBe(10); + }); + + test('returns 30m buckets for 20d / 500 bars', () => { + const interval = calcAuto.atLeast(500, moment.duration(20, 'd')); + expect(interval.asMinutes()).toBe(30); + }); + + test('returns 4h buckets for 100d / 500 bars', () => { + const interval = calcAuto.atLeast(500, moment.duration(100, 'd')); + expect(interval.asHours()).toBe(4); + }); + + test('returns 12h buckets for 1y / 500 bars', () => { + const interval = calcAuto.atLeast(500, moment.duration(1, 'y')); + expect(interval.asHours()).toBe(12); + }); + + test('returns 8h buckets for 1y / 1000 bars', () => { + const interval = calcAuto.atLeast(1000, moment.duration(1, 'y')); + expect(interval.asHours()).toBe(8); + }); + }); +}); diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.test.js b/x-pack/plugins/ml/public/application/util/chart_utils.test.js index 4b33cb131be7f..57aea3c0ab5aa 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.test.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.test.js @@ -29,246 +29,488 @@ const timefilter = getTimefilter(); import d3 from 'd3'; import moment from 'moment'; -import { mount } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; import { + chartLimits, + getChartType, getExploreSeriesLink, getTickValues, - isLabelLengthAboveThreshold, getXTransform, + isLabelLengthAboveThreshold, + numTicks, removeLabelOverlap, + showMultiBucketAnomalyMarker, + showMultiBucketAnomalyTooltip, } from './chart_utils'; +import { MULTI_BUCKET_IMPACT } from '../../../common/constants/multi_bucket_impact'; +import { CHART_TYPE } from '../explorer/explorer_constants'; + timefilter.setTime({ from: moment(seriesConfig.selectedEarliest).toISOString(), to: moment(seriesConfig.selectedLatest).toISOString(), }); -describe('getExploreSeriesLink', () => { - test('get timeseriesexplorer link', () => { - const link = getExploreSeriesLink(seriesConfig); - const expectedLink = - `#/timeseriesexplorer?_g=(ml:(jobIds:!(population-03)),` + - `refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2017-02-23T00:00:00.000Z',mode:absolute,` + - `to:'2017-02-23T23:59:59.999Z'))&_a=(mlTimeSeriesExplorer%3A(detectorIndex%3A0%2Centities%3A` + - `(nginx.access.remote_ip%3A'72.57.0.53')%2Czoom%3A(from%3A'2017-02-19T20%3A00%3A00.000Z'%2Cto%3A'2017-02-27T04%3A00%3A00.000Z'))` + - `%2Cquery%3A(query_string%3A(analyze_wildcard%3A!t%2Cquery%3A'*')))`; - - expect(link).toBe(expectedLink); - }); -}); +describe('ML - chart utils', () => { + describe('chartLimits', () => { + test('returns NaN when called without data', () => { + const limits = chartLimits(); + expect(limits.min).toBeNaN(); + expect(limits.max).toBeNaN(); + }); -describe('getTickValues', () => { - test('farequote sample data', () => { - const tickValues = getTickValues(1486656000000, 14400000, 1486606500000, 1486719900000); - - expect(tickValues).toEqual([ - 1486612800000, - 1486627200000, - 1486641600000, - 1486656000000, - 1486670400000, - 1486684800000, - 1486699200000, - 1486713600000, - ]); - }); + test('returns {max: 625736376, min: 201039318} for some test data', () => { + const data = [ + { + date: new Date('2017-02-23T08:00:00.000Z'), + value: 228243469, + anomalyScore: 63.32916, + numberOfCauses: 1, + actual: [228243469], + typical: [133107.7703441773], + }, + { date: new Date('2017-02-23T09:00:00.000Z'), value: null }, + { date: new Date('2017-02-23T10:00:00.000Z'), value: null }, + { date: new Date('2017-02-23T11:00:00.000Z'), value: null }, + { + date: new Date('2017-02-23T12:00:00.000Z'), + value: 625736376, + anomalyScore: 97.32085, + numberOfCauses: 1, + actual: [625736376], + typical: [132830.424736973], + }, + { + date: new Date('2017-02-23T13:00:00.000Z'), + value: 201039318, + anomalyScore: 59.83488, + numberOfCauses: 1, + actual: [201039318], + typical: [132739.5267403542], + }, + ]; + + const limits = chartLimits(data); + + // {max: 625736376, min: 201039318} + expect(limits.min).toBe(201039318); + expect(limits.max).toBe(625736376); + }); - test('filebeat sample data', () => { - const tickValues = getTickValues(1486080000000, 14400000, 1485860400000, 1486314000000); - expect(tickValues).toEqual([ - 1485864000000, - 1485878400000, - 1485892800000, - 1485907200000, - 1485921600000, - 1485936000000, - 1485950400000, - 1485964800000, - 1485979200000, - 1485993600000, - 1486008000000, - 1486022400000, - 1486036800000, - 1486051200000, - 1486065600000, - 1486080000000, - 1486094400000, - 1486108800000, - 1486123200000, - 1486137600000, - 1486152000000, - 1486166400000, - 1486180800000, - 1486195200000, - 1486209600000, - 1486224000000, - 1486238400000, - 1486252800000, - 1486267200000, - 1486281600000, - 1486296000000, - 1486310400000, - ]); + test("adds 5% padding when min/max are the same, e.g. when there's only one data point", () => { + const data = [ + { + date: new Date('2017-02-23T08:00:00.000Z'), + value: 100, + anomalyScore: 50, + numberOfCauses: 1, + actual: [100], + typical: [100], + }, + ]; + + const limits = chartLimits(data); + expect(limits.min).toBe(95); + expect(limits.max).toBe(105); + }); + + test('returns minimum of 0 when data includes an anomaly for missing data', () => { + const data = [ + { date: new Date('2017-02-23T09:00:00.000Z'), value: 22.2 }, + { date: new Date('2017-02-23T10:00:00.000Z'), value: 23.3 }, + { date: new Date('2017-02-23T11:00:00.000Z'), value: 24.4 }, + { + date: new Date('2017-02-23T12:00:00.000Z'), + value: null, + anomalyScore: 97.32085, + actual: [0], + typical: [22.2], + }, + { date: new Date('2017-02-23T13:00:00.000Z'), value: 21.3 }, + { date: new Date('2017-02-23T14:00:00.000Z'), value: 21.2 }, + { date: new Date('2017-02-23T15:00:00.000Z'), value: 21.1 }, + ]; + + const limits = chartLimits(data); + expect(limits.min).toBe(0); + expect(limits.max).toBe(24.4); + }); }); - test('gallery sample data', () => { - const tickValues = getTickValues(1518652800000, 604800000, 1518274800000, 1519635600000); - expect(tickValues).toEqual([1518652800000, 1519257600000]); + describe('getChartType', () => { + const singleMetricConfig = { + metricFunction: 'avg', + functionDescription: 'mean', + fieldName: 'responsetime', + entityFields: [], + }; + + const multiMetricConfig = { + metricFunction: 'avg', + functionDescription: 'mean', + fieldName: 'responsetime', + entityFields: [ + { + fieldName: 'airline', + fieldValue: 'AAL', + fieldType: 'partition', + }, + ], + }; + + const populationConfig = { + metricFunction: 'avg', + functionDescription: 'mean', + fieldName: 'http.response.body.bytes', + entityFields: [ + { + fieldName: 'source.ip', + fieldValue: '10.11.12.13', + fieldType: 'over', + }, + ], + }; + + const rareConfig = { + metricFunction: 'count', + functionDescription: 'rare', + entityFields: [ + { + fieldName: 'http.response.status_code', + fieldValue: '404', + fieldType: 'by', + }, + ], + }; + + const varpModelPlotConfig = { + metricFunction: null, + functionDescription: 'varp', + fieldName: 'NetworkOut', + entityFields: [ + { + fieldName: 'instance', + fieldValue: 'i-ef74d410', + fieldType: 'over', + }, + ], + }; + + const overScriptFieldModelPlotConfig = { + metricFunction: 'count', + functionDescription: 'count', + fieldName: 'highest_registered_domain', + entityFields: [ + { + fieldName: 'highest_registered_domain', + fieldValue: 'elastic.co', + fieldType: 'over', + }, + ], + datafeedConfig: { + script_fields: { + highest_registered_domain: { + script: { + source: "return domainSplit(doc['query'].value, params).get(1);", + lang: 'painless', + }, + ignore_failure: false, + }, + }, + }, + }; + + test('returns single metric chart type as expected for configs', () => { + expect(getChartType(singleMetricConfig)).toBe(CHART_TYPE.SINGLE_METRIC); + expect(getChartType(multiMetricConfig)).toBe(CHART_TYPE.SINGLE_METRIC); + expect(getChartType(varpModelPlotConfig)).toBe(CHART_TYPE.SINGLE_METRIC); + expect(getChartType(overScriptFieldModelPlotConfig)).toBe(CHART_TYPE.SINGLE_METRIC); + }); + + test('returns event distribution chart type as expected for configs', () => { + expect(getChartType(rareConfig)).toBe(CHART_TYPE.EVENT_DISTRIBUTION); + }); + + test('returns population distribution chart type as expected for configs', () => { + expect(getChartType(populationConfig)).toBe(CHART_TYPE.POPULATION_DISTRIBUTION); + }); }); - test('invalid tickIntervals trigger an error', () => { - expect(() => { - getTickValues(1518652800000, 0, 1518274800000, 1519635600000); - }).toThrow(); - expect(() => { - getTickValues(1518652800000, -604800000, 1518274800000, 1519635600000); - }).toThrow(); + describe('getExploreSeriesLink', () => { + test('get timeseriesexplorer link', () => { + const link = getExploreSeriesLink(seriesConfig); + const expectedLink = + `#/timeseriesexplorer?_g=(ml:(jobIds:!(population-03)),` + + `refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2017-02-23T00:00:00.000Z',mode:absolute,` + + `to:'2017-02-23T23:59:59.999Z'))&_a=(mlTimeSeriesExplorer%3A(detectorIndex%3A0%2Centities%3A` + + `(nginx.access.remote_ip%3A'72.57.0.53')%2Czoom%3A(from%3A'2017-02-19T20%3A00%3A00.000Z'%2Cto%3A'2017-02-27T04%3A00%3A00.000Z'))` + + `%2Cquery%3A(query_string%3A(analyze_wildcard%3A!t%2Cquery%3A'*')))`; + + expect(link).toBe(expectedLink); + }); }); -}); -describe('isLabelLengthAboveThreshold', () => { - test('short label', () => { - const isLongLabel = isLabelLengthAboveThreshold({ - detectorLabel: 'count', - entityFields: seriesConfig.entityFields, + describe('numTicks', () => { + test('returns 10 for 1000', () => { + expect(numTicks(1000)).toBe(10); }); - expect(isLongLabel).toBeFalsy(); }); - test('long label', () => { - const isLongLabel = isLabelLengthAboveThreshold(seriesConfig); - expect(isLongLabel).toBeTruthy(); + describe('showMultiBucketAnomalyMarker', () => { + test('returns true for points with multiBucketImpact at or above medium impact', () => { + expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.HIGH })).toBe( + true + ); + expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.MEDIUM })).toBe( + true + ); + }); + + test('returns false for points with multiBucketImpact missing or below medium impact', () => { + expect(showMultiBucketAnomalyMarker({})).toBe(false); + expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.LOW })).toBe( + false + ); + expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.NONE })).toBe( + false + ); + }); }); -}); -describe('getXTransform', () => { - const expectedXTransform = 0.007167499999999999; + describe('showMultiBucketAnomalyTooltip', () => { + test('returns true for points with multiBucketImpact at or above low impact', () => { + expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.HIGH })).toBe( + true + ); + expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.MEDIUM })).toBe( + true + ); + expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.LOW })).toBe( + true + ); + }); - test('Chrome/Safari/Firefox String variant.', () => { - const transformStr = 'translate(0.007167499999999999,0)'; - const xTransform = getXTransform(transformStr); - expect(xTransform).toEqual(expectedXTransform); + test('returns false for points with multiBucketImpact missing or below medium impact', () => { + expect(showMultiBucketAnomalyTooltip({})).toBe(false); + expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.NONE })).toBe( + false + ); + }); }); - test('IE11 String variant.', () => { - const transformStr = 'translate(0.007167499999999999)'; - const xTransform = getXTransform(transformStr); - expect(xTransform).toEqual(expectedXTransform); + describe('getTickValues', () => { + test('farequote sample data', () => { + const tickValues = getTickValues(1486656000000, 14400000, 1486606500000, 1486719900000); + + expect(tickValues).toEqual([ + 1486612800000, + 1486627200000, + 1486641600000, + 1486656000000, + 1486670400000, + 1486684800000, + 1486699200000, + 1486713600000, + ]); + }); + + test('filebeat sample data', () => { + const tickValues = getTickValues(1486080000000, 14400000, 1485860400000, 1486314000000); + expect(tickValues).toEqual([ + 1485864000000, + 1485878400000, + 1485892800000, + 1485907200000, + 1485921600000, + 1485936000000, + 1485950400000, + 1485964800000, + 1485979200000, + 1485993600000, + 1486008000000, + 1486022400000, + 1486036800000, + 1486051200000, + 1486065600000, + 1486080000000, + 1486094400000, + 1486108800000, + 1486123200000, + 1486137600000, + 1486152000000, + 1486166400000, + 1486180800000, + 1486195200000, + 1486209600000, + 1486224000000, + 1486238400000, + 1486252800000, + 1486267200000, + 1486281600000, + 1486296000000, + 1486310400000, + ]); + }); + + test('gallery sample data', () => { + const tickValues = getTickValues(1518652800000, 604800000, 1518274800000, 1519635600000); + expect(tickValues).toEqual([1518652800000, 1519257600000]); + }); + + test('invalid tickIntervals trigger an error', () => { + expect(() => { + getTickValues(1518652800000, 0, 1518274800000, 1519635600000); + }).toThrow(); + expect(() => { + getTickValues(1518652800000, -604800000, 1518274800000, 1519635600000); + }).toThrow(); + }); }); - test('Invalid String.', () => { - const transformStr = 'translate()'; - const xTransform = getXTransform(transformStr); - expect(xTransform).toEqual(NaN); + describe('isLabelLengthAboveThreshold', () => { + test('short label', () => { + const isLongLabel = isLabelLengthAboveThreshold({ + detectorLabel: 'count', + entityFields: seriesConfig.entityFields, + }); + expect(isLongLabel).toBeFalsy(); + }); + + test('long label', () => { + const isLongLabel = isLabelLengthAboveThreshold(seriesConfig); + expect(isLongLabel).toBeTruthy(); + }); }); -}); -describe('removeLabelOverlap', () => { - const originalGetBBox = SVGElement.prototype.getBBox; - - // This resembles how ExplorerChart renders its x axis. - // We set up this boilerplate so we can then run removeLabelOverlap() - // on some "real" structure. - function axisSetup({ interval, plotEarliest, plotLatest, startTimeMs, xAxisTickFormat }) { - const wrapper = mount(
); - const node = wrapper.getDOMNode(); - - const chartHeight = 170; - const margin = { top: 10, right: 0, bottom: 30, left: 60 }; - const svgWidth = 500; - const svgHeight = chartHeight + margin.top + margin.bottom; - const vizWidth = 500; - - const chartElement = d3.select(node); - - const lineChartXScale = d3.time - .scale() - .range([0, vizWidth]) - .domain([plotEarliest, plotLatest]); - - const xAxis = d3.svg - .axis() - .scale(lineChartXScale) - .orient('bottom') - .innerTickSize(-chartHeight) - .outerTickSize(0) - .tickPadding(10) - .tickFormat(d => moment(d).format(xAxisTickFormat)); - - const tickValues = getTickValues(startTimeMs, interval, plotEarliest, plotLatest); - xAxis.tickValues(tickValues); - - const svg = chartElement - .append('svg') - .attr('width', svgWidth) - .attr('height', svgHeight); - - const axes = svg.append('g'); - - const gAxis = axes - .append('g') - .attr('class', 'x axis') - .attr('transform', 'translate(0,' + chartHeight + ')') - .call(xAxis); - - return { - gAxis, - node, - vizWidth, - }; - } + describe('getXTransform', () => { + const expectedXTransform = 0.007167499999999999; - test('farequote sample data', () => { - const mockedGetBBox = { width: 27.21875 }; - SVGElement.prototype.getBBox = () => mockedGetBBox; + test('Chrome/Safari/Firefox String variant.', () => { + const transformStr = 'translate(0.007167499999999999,0)'; + const xTransform = getXTransform(transformStr); + expect(xTransform).toEqual(expectedXTransform); + }); - const startTimeMs = 1486656000000; - const interval = 14400000; + test('IE11 String variant.', () => { + const transformStr = 'translate(0.007167499999999999)'; + const xTransform = getXTransform(transformStr); + expect(xTransform).toEqual(expectedXTransform); + }); - const { gAxis, node, vizWidth } = axisSetup({ - interval, - plotEarliest: 1486606500000, - plotLatest: 1486719900000, - startTimeMs, - xAxisTickFormat: 'HH:mm', + test('Invalid String.', () => { + const transformStr = 'translate()'; + const xTransform = getXTransform(transformStr); + expect(xTransform).toEqual(NaN); }); + }); - expect(node.getElementsByTagName('text')).toHaveLength(8); + describe('removeLabelOverlap', () => { + const originalGetBBox = SVGElement.prototype.getBBox; + + // This resembles how ExplorerChart renders its x axis. + // We set up this boilerplate so we can then run removeLabelOverlap() + // on some "real" structure. + function axisSetup({ interval, plotEarliest, plotLatest, startTimeMs, xAxisTickFormat }) { + const { container } = render(
); + const node = container.querySelector('.content-wrapper'); + + const chartHeight = 170; + const margin = { top: 10, right: 0, bottom: 30, left: 60 }; + const svgWidth = 500; + const svgHeight = chartHeight + margin.top + margin.bottom; + const vizWidth = 500; + + const chartElement = d3.select(node); + + const lineChartXScale = d3.time + .scale() + .range([0, vizWidth]) + .domain([plotEarliest, plotLatest]); + + const xAxis = d3.svg + .axis() + .scale(lineChartXScale) + .orient('bottom') + .innerTickSize(-chartHeight) + .outerTickSize(0) + .tickPadding(10) + .tickFormat(d => moment(d).format(xAxisTickFormat)); + + const tickValues = getTickValues(startTimeMs, interval, plotEarliest, plotLatest); + xAxis.tickValues(tickValues); + + const svg = chartElement + .append('svg') + .attr('width', svgWidth) + .attr('height', svgHeight); + + const axes = svg.append('g'); + + const gAxis = axes + .append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + chartHeight + ')') + .call(xAxis); - removeLabelOverlap(gAxis, startTimeMs, interval, vizWidth); + return { + gAxis, + node, + vizWidth, + }; + } - // at the vizWidth of 500, the most left and right tick label - // will get removed because it overflows the chart area - expect(node.getElementsByTagName('text')).toHaveLength(6); + test('farequote sample data', () => { + const mockedGetBBox = { width: 27.21875 }; + SVGElement.prototype.getBBox = () => mockedGetBBox; - SVGElement.prototype.getBBox = originalGetBBox; - }); + const startTimeMs = 1486656000000; + const interval = 14400000; + + const { gAxis, node, vizWidth } = axisSetup({ + interval, + plotEarliest: 1486606500000, + plotLatest: 1486719900000, + startTimeMs, + xAxisTickFormat: 'HH:mm', + }); - test('filebeat sample data', () => { - const mockedGetBBox = { width: 85.640625 }; - SVGElement.prototype.getBBox = () => mockedGetBBox; + expect(node.getElementsByTagName('text')).toHaveLength(8); - const startTimeMs = 1486080000000; - const interval = 14400000; + removeLabelOverlap(gAxis, startTimeMs, interval, vizWidth); - const { gAxis, node, vizWidth } = axisSetup({ - interval, - plotEarliest: 1485860400000, - plotLatest: 1486314000000, - startTimeMs, - xAxisTickFormat: 'YYYY-MM-DD HH:mm', + // at the vizWidth of 500, the most left and right tick label + // will get removed because it overflows the chart area + expect(node.getElementsByTagName('text')).toHaveLength(6); + + SVGElement.prototype.getBBox = originalGetBBox; }); - expect(node.getElementsByTagName('text')).toHaveLength(32); + test('filebeat sample data', () => { + const mockedGetBBox = { width: 85.640625 }; + SVGElement.prototype.getBBox = () => mockedGetBBox; + + const startTimeMs = 1486080000000; + const interval = 14400000; - removeLabelOverlap(gAxis, startTimeMs, interval, vizWidth); + const { gAxis, node, vizWidth } = axisSetup({ + interval, + plotEarliest: 1485860400000, + plotLatest: 1486314000000, + startTimeMs, + xAxisTickFormat: 'YYYY-MM-DD HH:mm', + }); - // In this case labels get reduced significantly because of the wider - // labels (full dates + time) and the narrow interval. - expect(node.getElementsByTagName('text')).toHaveLength(3); + expect(node.getElementsByTagName('text')).toHaveLength(32); - SVGElement.prototype.getBBox = originalGetBBox; + removeLabelOverlap(gAxis, startTimeMs, interval, vizWidth); + + // In this case labels get reduced significantly because of the wider + // labels (full dates + time) and the narrow interval. + expect(node.getElementsByTagName('text')).toHaveLength(3); + + SVGElement.prototype.getBBox = originalGetBBox; + }); }); }); diff --git a/x-pack/plugins/ml/public/application/util/string_utils.d.ts b/x-pack/plugins/ml/public/application/util/string_utils.d.ts index b5063907e1fdf..531e44e3e78c1 100644 --- a/x-pack/plugins/ml/public/application/util/string_utils.d.ts +++ b/x-pack/plugins/ml/public/application/util/string_utils.d.ts @@ -14,4 +14,8 @@ export function replaceStringTokens( export function detectorToString(dtr: any): string; +export function sortByKey(list: any, reverse: boolean, comparator?: any): any; + export function toLocaleString(x: number): string; + +export function mlEscape(str: string): string; diff --git a/x-pack/plugins/ml/public/application/util/string_utils.js b/x-pack/plugins/ml/public/application/util/string_utils.js index 172d334099b3d..66835984df5e5 100644 --- a/x-pack/plugins/ml/public/application/util/string_utils.js +++ b/x-pack/plugins/ml/public/application/util/string_utils.js @@ -99,211 +99,6 @@ export function sortByKey(list, reverse, comparator) { ); } -// guess the time format for a given time string -export function guessTimeFormat(time) { - let format = ''; - let matched = false; - if (isNaN(time)) { - let match; - - // match date format - if (!matched) { - let reg = ''; - - reg += '('; // 1 ( date - - reg += '('; // 2 ( yyyy-MM-dd - reg += '(\\d{4})'; // 3 yyyy - reg += '([-/.\\s])'; // 4 - or . or \s - reg += '('; // 5 ( month - reg += '([01]\\d)'; // 6 MM - reg += '|'; // or - reg += '(\\w{3})'; // 7 MMM - reg += ')'; // ) end month - reg += '([-/.\\s])'; // 8 - or . or \s - reg += '([0-3]\\d)'; // 9 dd 0-3 and 0-9 - reg += ')'; // ) end yyyy-MM-dd - - reg += '|'; // or - - reg += '('; // 10 ( d[d]-MM[M]-yyyy or MM[M]-d[d]-yyyy - - reg += '('; // 11 ( day or month - reg += '(\\d{1,2})'; // 12 d or M or dd or MM - reg += '|'; // or - reg += '(\\w{3})'; // 13 MMM - reg += ')'; // ) end day or month - - reg += '([-/.\\s])'; // 14 - or . or \s - - reg += '('; // 15 ( day or month - reg += '(\\d{1,2})'; // 12 d or M or dd or MM - reg += '|'; // or - reg += '(\\w{3})'; // 17 MMM - reg += ')'; // ) end day or month - - reg += '([-/.\\s])'; // 18 - or . or \s - reg += '(\\d{4})'; // 19 yyyy - reg += ')'; // ) end d[d]-MM[M]-yyyy or MM[M]-d[d]-yyyy - - reg += ')'; // ) end date - - reg += '([T\\s])?'; // 20 T or space - - reg += '([0-2]\\d)'; // 21 HH 0-2 and 0-9 - reg += '([:.])'; // 22 :. - reg += '([0-5]\\d)'; // 23 mm 0-5 and 0-9 - reg += '('; // 24 ( optional secs - reg += '([:.])'; // 25 :. - reg += '([0-5]\\d)'; // 26 ss 0-5 and 0-9 - reg += ')?'; // ) end optional secs - reg += '('; // 27 ( optional millisecs - reg += '([:.])'; // 28 :. - reg += '(\\d{3})'; // 29 3 * 0-9 - reg += ')?'; // ) end optional millisecs - reg += '('; // 30 ( optional timezone matches - reg += '([+-]\\d{2}[:.]\\d{2}[:.]\\d{2})'; // 31 +- 0-9 0-9 :. 0-9 0-9 :. 0-9 0-9 e.g. +00:00:00 - reg += '|'; // or - reg += '([+-]\\d{2}[:.]\\d{2})'; // 32 +- 0-9 0-9 :. 0-9 0-9 e.g. +00:00 - reg += '|'; // or - reg += '([+-]\\d{6})'; // 33 +- 6 * 0-9 e.g. +000000 - reg += '|'; // or - reg += '([+-]\\d{4})'; // 34 +- 4 * 0-9 e.g. +0000 - reg += '|'; // or - reg += '(Z)'; // 35 Z - reg += '|'; // or - reg += '([+-]\\d{2})'; // 36 +- 0-9 0-9 e.g. +00 - reg += '|'; // or - reg += '('; // 37 ( string timezone - reg += '(\\s)'; // 38 optional space - reg += '(\\w{1,4})'; // 39 1-4 letters e.g UTC - reg += ')'; // ) end string timezone - reg += ')?'; // ) end optional timezone - - console.log('guessTimeFormat: time format regex: ' + reg); - - match = time.match(new RegExp(reg)); - // console.log(match); - if (match) { - // add the standard data and time - if (match[2] !== undefined) { - // match yyyy-[MM MMM]-dd - format += 'yyyy'; - format += match[4]; - if (match[6] !== undefined) { - format += 'MM'; - } else if (match[7] !== undefined) { - format += 'MMM'; - } - format += match[8]; - format += 'dd'; - } else if (match[10] !== undefined) { - // match dd-MM[M]-yyyy or MM[M]-dd-yyyy - - if (match[13] !== undefined) { - // found a word as the first part - // e.g., Jan 01 2000 - format += 'MMM'; - format += match[14]; - format += 'dd'; - } else if (match[17] !== undefined) { - // found a word as the second part - // e.g., 01 Jan 2000 - format += 'dd'; - format += match[14]; - format += 'MMM'; - } else { - // check to see if the first number is greater than 12 - // e.g., 24/03/1981 - // this is a guess, but is only thing we can do - // with one line from the data set - if (match[12] !== undefined && +match[12] > 12) { - format += 'dd'; - format += match[14]; - format += 'MM'; - } else { - // default to US format. - format += 'MM'; - format += match[14]; - format += 'dd'; - } - } - - format += match[18]; - format += 'yyyy'; - } - - // optional T or space splitter - // wrap T in single quotes - format += match[20] === 'T' ? "'" + match[20] + "'" : match[20]; - format += 'HH'; - format += match[22]; - format += 'mm'; - - // add optional secs - if (match[24] !== undefined) { - format += match[25]; - format += 'ss'; - } - - // add optional millisecs - if (match[27] !== undefined) { - // .000 - format += match[28]; - format += 'SSS'; - } - - // add optional time zone - if (match[31] !== undefined) { - // +00:00:00 - format += 'XXXXX'; - } else if (match[32] !== undefined) { - // +00:00 - format += 'XXX'; - } else if (match[33] !== undefined) { - // +000000 - format += 'XXXX'; - } else if (match[34] !== undefined) { - // +0000 - format += 'Z'; - } else if (match[35] !== undefined || match[36] !== undefined) { - // Z or +00 - format += 'X'; - } else if (match[37] !== undefined) { - // UTC - if (match[38] !== undefined) { - // add optional space char - format += match[38]; - } - // add time zone name, up to 4 chars - for (let i = 0; i < match[39].length; i++) { - format += 'z'; - } - } - matched = true; - } - } - } else { - // time field is a number, so probably epoch or epoch_ms - if (time > 10000000000) { - // probably milliseconds - format = 'epoch_ms'; - } else { - // probably seconds - format = 'epoch'; - } - matched = true; - } - - if (matched) { - console.log('guessTimeFormat: guessed time format: ', format); - } else { - console.log('guessTimeFormat: time format could not be guessed from: ' + time); - } - - return format; -} - // add commas to large numbers // Number.toLocaleString is not supported on safari export function toLocaleString(x) { diff --git a/x-pack/plugins/ml/public/application/util/string_utils.test.ts b/x-pack/plugins/ml/public/application/util/string_utils.test.ts new file mode 100644 index 0000000000000..d940fce2ee1d5 --- /dev/null +++ b/x-pack/plugins/ml/public/application/util/string_utils.test.ts @@ -0,0 +1,193 @@ +/* + * 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 { + replaceStringTokens, + detectorToString, + sortByKey, + toLocaleString, + mlEscape, + escapeForElasticsearchQuery, +} from './string_utils'; + +describe('ML - string utils', () => { + describe('replaceStringTokens', () => { + const testRecord = { + job_id: 'test_job', + result_type: 'record', + probability: 0.0191711, + record_score: 4.3, + bucket_span: 300, + detector_index: 0, + timestamp: 1454890500000, + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + user: "Des O'Connor", + testfield1: 'test$tring=[+-?]', + testfield2: '{<()>}', + testfield3: 'host=\\\\test@uk.dev', + }; + + test('returns correct values without URI encoding', () => { + const result = replaceStringTokens('user=$user$,time=$timestamp$', testRecord, false); + expect(result).toBe("user=Des O'Connor,time=1454890500000"); + }); + + test('returns correct values for missing token without URI encoding', () => { + const result = replaceStringTokens('user=$username$,time=$timestamp$', testRecord, false); + expect(result).toBe('user=$username$,time=1454890500000'); + }); + + test('returns correct values with URI encoding', () => { + const testString1 = 'https://www.google.co.uk/webhp#q=$testfield1$'; + const testString2 = 'https://www.google.co.uk/webhp#q=$testfield2$'; + const testString3 = 'https://www.google.co.uk/webhp#q=$testfield3$'; + const testString4 = 'https://www.google.co.uk/webhp#q=$user$'; + + const result1 = replaceStringTokens(testString1, testRecord, true); + const result2 = replaceStringTokens(testString2, testRecord, true); + const result3 = replaceStringTokens(testString3, testRecord, true); + const result4 = replaceStringTokens(testString4, testRecord, true); + + expect(result1).toBe('https://www.google.co.uk/webhp#q=test%24tring%3D%5B%2B-%3F%5D'); + expect(result2).toBe('https://www.google.co.uk/webhp#q=%7B%3C()%3E%7D'); + expect(result3).toBe('https://www.google.co.uk/webhp#q=host%3D%5C%5Ctest%40uk.dev'); + expect(result4).toBe("https://www.google.co.uk/webhp#q=Des%20O'Connor"); + }); + + test('returns correct values for missing token with URI encoding', () => { + const testString = 'https://www.google.co.uk/webhp#q=$username$&time=$timestamp$'; + const result = replaceStringTokens(testString, testRecord, true); + expect(result).toBe('https://www.google.co.uk/webhp#q=$username$&time=1454890500000'); + }); + }); + + describe('detectorToString', () => { + test('returns the correct descriptions for detectors', () => { + const detector1 = { + function: 'count', + }; + + const detector2 = { + function: 'count', + by_field_name: 'airline', + use_null: false, + }; + + const detector3 = { + function: 'mean', + field_name: 'CPUUtilization', + partition_field_name: 'region', + by_field_name: 'host', + over_field_name: 'user', + exclude_frequent: 'all', + }; + + expect(detectorToString(detector1)).toBe('count'); + expect(detectorToString(detector2)).toBe('count by airline use_null=false'); + expect(detectorToString(detector3)).toBe( + 'mean(CPUUtilization) by host over user partition_field_name=region exclude_frequent=all' + ); + }); + }); + + describe('sortByKey', () => { + const obj = { + zebra: 'stripes', + giraffe: 'neck', + elephant: 'trunk', + }; + + const valueComparator = function(value: string) { + return value; + }; + + test('returns correct ordering with default comparator', () => { + const result = sortByKey(obj, false); + const keys = Object.keys(result); + expect(keys[0]).toBe('elephant'); + expect(keys[1]).toBe('giraffe'); + expect(keys[2]).toBe('zebra'); + }); + + test('returns correct ordering with default comparator and order reversed', () => { + const result = sortByKey(obj, true); + const keys = Object.keys(result); + expect(keys[0]).toBe('zebra'); + expect(keys[1]).toBe('giraffe'); + expect(keys[2]).toBe('elephant'); + }); + + test('returns correct ordering with comparator', () => { + const result = sortByKey(obj, false, valueComparator); + const keys = Object.keys(result); + expect(keys[0]).toBe('giraffe'); + expect(keys[1]).toBe('zebra'); + expect(keys[2]).toBe('elephant'); + }); + + test('returns correct ordering with comparator and order reversed', () => { + const result = sortByKey(obj, true, valueComparator); + const keys = Object.keys(result); + expect(keys[0]).toBe('elephant'); + expect(keys[1]).toBe('zebra'); + expect(keys[2]).toBe('giraffe'); + }); + }); + + describe('toLocaleString', () => { + test('returns correct comma placement for large numbers', () => { + expect(toLocaleString(1)).toBe('1'); + expect(toLocaleString(10)).toBe('10'); + expect(toLocaleString(100)).toBe('100'); + expect(toLocaleString(1000)).toBe('1,000'); + expect(toLocaleString(10000)).toBe('10,000'); + expect(toLocaleString(100000)).toBe('100,000'); + expect(toLocaleString(1000000)).toBe('1,000,000'); + expect(toLocaleString(10000000)).toBe('10,000,000'); + expect(toLocaleString(100000000)).toBe('100,000,000'); + expect(toLocaleString(1000000000)).toBe('1,000,000,000'); + }); + }); + + describe('mlEscape', () => { + test('returns correct escaping of characters', () => { + expect(mlEscape('foo&bar')).toBe('foo&bar'); + expect(mlEscape('foobar')).toBe('foo>bar'); + expect(mlEscape('foo"bar')).toBe('foo"bar'); + expect(mlEscape("foo'bar")).toBe('foo'bar'); + expect(mlEscape('foo/bar')).toBe('foo/bar'); + }); + }); + + describe('escapeForElasticsearchQuery', () => { + test('returns correct escaping of reserved elasticsearch characters', () => { + expect(escapeForElasticsearchQuery('foo+bar')).toBe('foo\\+bar'); + expect(escapeForElasticsearchQuery('foo-bar')).toBe('foo\\-bar'); + expect(escapeForElasticsearchQuery('foo=bar')).toBe('foo\\=bar'); + expect(escapeForElasticsearchQuery('foo&&bar')).toBe('foo\\&\\&bar'); + expect(escapeForElasticsearchQuery('foo||bar')).toBe('foo\\|\\|bar'); + expect(escapeForElasticsearchQuery('foo>bar')).toBe('foo\\>bar'); + expect(escapeForElasticsearchQuery('foo, Promise, @@ -298,7 +301,9 @@ export function jobsProvider(callAsCurrentUser: APICaller) { if (calendarResults) { calendarResults.forEach(cal => { cal.job_ids.forEach(id => { - if (groups[id]) { + if (id === GLOBAL_CALENDAR) { + globalCalendars.push(cal.calendar_id); + } else if (groups[id]) { groups[id].forEach(jId => { if (calendarsByJobId[jId] !== undefined) { calendarsByJobId[jId].push(cal.calendar_id); @@ -325,8 +330,12 @@ export function jobsProvider(callAsCurrentUser: APICaller) { jobResults.jobs.forEach(job => { const tempJob = job as CombinedJobWithStats; - if (calendarsByJobId[tempJob.job_id].length) { - tempJob.calendars = calendarsByJobId[tempJob.job_id]; + const calendars: string[] = [ + ...(calendarsByJobId[tempJob.job_id] || []), + ...(globalCalendars || []), + ]; + if (calendars.length) { + tempJob.calendars = calendars; } if (jobStatsResults && jobStatsResults.jobs) { diff --git a/x-pack/plugins/ml/server/routes/README.md b/x-pack/plugins/ml/server/routes/README.md index 1d08335af3d2e..70af73c37dadd 100644 --- a/x-pack/plugins/ml/server/routes/README.md +++ b/x-pack/plugins/ml/server/routes/README.md @@ -6,11 +6,14 @@ Each route handler requires [apiDoc](https://github.com/apidoc/apidoc) annotatio to generate documentation. The [apidoc-markdown](https://github.com/rigwild/apidoc-markdown) package is also required in order to generate the markdown. -For now the process is pretty manual. You need to make sure the packages mentioned above are installed globally -to execute the following command from the directory in which this README file is located. +There are custom parser and worker (`x-pack/plugins/ml/server/routes/apidoc_scripts`) to process api schemas for each documentation entry. It's written with typescript so make sure all the scripts in the folder are compiled before executing `apidoc` command. + +Make sure you have run `yarn kbn bootstrap` to get all requires dev dependencies. Then execute the following command from the ml plugin folder: ``` -apidoc -i . -o ../routes_doc && apidoc-markdown -p ../routes_doc -o ../routes_doc/ML_API.md +yarn run apiDocs ``` +It compiles all the required scripts and generates the documentation both in HTML and Markdown formats. + It will create a new directory `routes_doc` (next to the `routes` folder) which contains the documentation in HTML format -as well as `ML_API.md` file. \ No newline at end of file +as well as `ML_API.md` file. diff --git a/x-pack/plugins/ml/server/routes/annotations.ts b/x-pack/plugins/ml/server/routes/annotations.ts index 56c0b639e2c85..d5abebda00caa 100644 --- a/x-pack/plugins/ml/server/routes/annotations.ts +++ b/x-pack/plugins/ml/server/routes/annotations.ts @@ -5,10 +5,8 @@ */ import Boom from 'boom'; -import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { schema } from '@kbn/config-schema'; import { SecurityPluginSetup } from '../../../security/server'; import { isAnnotationsFeatureAvailable } from '../lib/check_annotations'; import { annotationServiceProvider } from '../models/annotation_service'; @@ -45,10 +43,7 @@ export function annotationRoutes( * @apiName GetAnnotations * @apiDescription Gets annotations. * - * @apiParam {String[]} jobIds List of job IDs - * @apiParam {String} earliestMs - * @apiParam {Number} latestMs - * @apiParam {Number} maxAnnotations Max limit of annotations returned + * @apiSchema (body) getAnnotationsSchema * * @apiSuccess {Boolean} success * @apiSuccess {Object} annotations @@ -57,7 +52,7 @@ export function annotationRoutes( { path: '/api/ml/annotations', validate: { - body: schema.object(getAnnotationsSchema), + body: getAnnotationsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -83,14 +78,13 @@ export function annotationRoutes( * @apiName IndexAnnotations * @apiDescription Index the annotation. * - * @apiParam {Object} annotation - * @apiParam {String} username + * @apiSchema (body) indexAnnotationSchema */ router.put( { path: '/api/ml/annotations/index', validate: { - body: schema.object(indexAnnotationSchema), + body: indexAnnotationSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -124,17 +118,17 @@ export function annotationRoutes( /** * @apiGroup Annotations * - * @api {delete} /api/ml/annotations/index Deletes annotation + * @api {delete} /api/ml/annotations/delete/:annotationId Deletes annotation * @apiName DeleteAnnotation * @apiDescription Deletes specified annotation * - * @apiParam {String} annotationId + * @apiSchema (params) deleteAnnotationSchema */ router.delete( { path: '/api/ml/annotations/delete/{annotationId}', validate: { - params: schema.object(deleteAnnotationSchema), + params: deleteAnnotationSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts index d03e76072c315..a675eb58dc792 100644 --- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts @@ -10,6 +10,13 @@ import { RouteInitialization } from '../types'; import { anomalyDetectionJobSchema, anomalyDetectionUpdateJobSchema, + jobIdSchema, + getRecordsSchema, + getBucketsSchema, + getOverallBucketsSchema, + getCategoriesSchema, + forecastAnomalyDetector, + getBucketParamsSchema, } from './schemas/anomaly_detectors_schema'; /** @@ -50,15 +57,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetAnomalyDetectorsById * @apiDescription Returns the anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.get( { path: '/api/ml/anomaly_detectors/{jobId}', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -108,15 +113,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetAnomalyDetectorsStatsById * @apiDescription Returns anomaly detection job statistics. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.get( { path: '/api/ml/anomaly_detectors/{jobId}/_stats', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -139,15 +142,14 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName CreateAnomalyDetectors * @apiDescription Creates an anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) anomalyDetectionJobSchema */ router.put( { path: '/api/ml/anomaly_detectors/{jobId}', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, body: schema.object(anomalyDetectionJobSchema), }, }, @@ -174,16 +176,15 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName UpdateAnomalyDetectors * @apiDescription Updates certain properties of an anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) anomalyDetectionUpdateJobSchema */ router.post( { path: '/api/ml/anomaly_detectors/{jobId}/_update', validate: { - params: schema.object({ - jobId: schema.string(), - }), - body: schema.object({ ...anomalyDetectionUpdateJobSchema }), + params: jobIdSchema, + body: anomalyDetectionUpdateJobSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -209,15 +210,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName OpenAnomalyDetectorsJob * @apiDescription Opens an anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.post( { path: '/api/ml/anomaly_detectors/{jobId}/_open', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -242,15 +241,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName CloseAnomalyDetectorsJob * @apiDescription Closes an anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.post( { path: '/api/ml/anomaly_detectors/{jobId}/_close', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -279,15 +276,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName DeleteAnomalyDetectorsJob * @apiDescription Deletes specified anomaly detection job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema */ router.delete( { path: '/api/ml/anomaly_detectors/{jobId}', validate: { - params: schema.object({ - jobId: schema.string(), - }), + params: jobIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -315,8 +310,6 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/anomaly_detectors/_validate/detector Validate detector * @apiName ValidateAnomalyDetector * @apiDescription Validates specified detector. - * - * @apiParam {String} jobId Job ID. */ router.post( { @@ -346,16 +339,15 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName ForecastAnomalyDetector * @apiDescription Creates a forecast for the specified anomaly detection job, predicting the future behavior of a time series by using its historical behavior. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) forecastAnomalyDetector */ router.post( { path: '/api/ml/anomaly_detectors/{jobId}/_forecast', validate: { - params: schema.object({ - jobId: schema.string(), - }), - body: schema.object({ duration: schema.any() }), + params: jobIdSchema, + body: forecastAnomalyDetector, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -382,7 +374,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetRecords * @apiDescription Retrieves anomaly records for a job. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) getRecordsSchema * * @apiSuccess {Number} count * @apiSuccess {Object[]} records @@ -391,23 +384,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/anomaly_detectors/{jobId}/results/records', validate: { - params: schema.object({ - jobId: schema.string(), - }), - body: schema.object({ - desc: schema.maybe(schema.boolean()), - end: schema.maybe(schema.string()), - exclude_interim: schema.maybe(schema.boolean()), - page: schema.maybe( - schema.object({ - from: schema.maybe(schema.number()), - size: schema.maybe(schema.number()), - }) - ), - record_score: schema.maybe(schema.number()), - sort: schema.maybe(schema.string()), - start: schema.maybe(schema.string()), - }), + params: jobIdSchema, + body: getRecordsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -432,8 +410,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetBuckets * @apiDescription The get buckets API presents a chronological view of the records, grouped by bucket. * - * @apiParam {String} jobId Job ID. - * @apiParam {String} timestamp. + * @apiSchema (params) getBucketParamsSchema + * @apiSchema (body) getBucketsSchema * * @apiSuccess {Number} count * @apiSuccess {Object[]} buckets @@ -442,25 +420,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/anomaly_detectors/{jobId}/results/buckets/{timestamp?}', validate: { - params: schema.object({ - jobId: schema.string(), - timestamp: schema.maybe(schema.string()), - }), - body: schema.object({ - anomaly_score: schema.maybe(schema.number()), - desc: schema.maybe(schema.boolean()), - end: schema.maybe(schema.string()), - exclude_interim: schema.maybe(schema.boolean()), - expand: schema.maybe(schema.boolean()), - page: schema.maybe( - schema.object({ - from: schema.maybe(schema.number()), - size: schema.maybe(schema.number()), - }) - ), - sort: schema.maybe(schema.string()), - start: schema.maybe(schema.string()), - }), + params: getBucketParamsSchema, + body: getBucketsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -486,7 +447,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetOverallBuckets * @apiDescription Retrieves overall bucket results that summarize the bucket results of multiple anomaly detection jobs. * - * @apiParam {String} jobId Job ID. + * @apiSchema (params) jobIdSchema + * @apiSchema (body) getOverallBucketsSchema * * @apiSuccess {Number} count * @apiSuccess {Object[]} overall_buckets @@ -495,15 +457,8 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/anomaly_detectors/{jobId}/results/overall_buckets', validate: { - params: schema.object({ - jobId: schema.string(), - }), - body: schema.object({ - topN: schema.number(), - bucketSpan: schema.string(), - start: schema.number(), - end: schema.number(), - }), + params: jobIdSchema, + body: getOverallBucketsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -531,17 +486,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetCategories * @apiDescription Returns the categories results for the specified job ID and category ID. * - * @apiParam {String} jobId Job ID. - * @apiParam {String} categoryId Category ID. + * @apiSchema (params) getCategoriesSchema */ router.get( { path: '/api/ml/anomaly_detectors/{jobId}/results/categories/{categoryId}', validate: { - params: schema.object({ - categoryId: schema.string(), - jobId: schema.string(), - }), + params: getCategoriesSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json index c5aa3e4d792fd..4848de6db7049 100644 --- a/x-pack/plugins/ml/server/routes/apidoc.json +++ b/x-pack/plugins/ml/server/routes/apidoc.json @@ -1,6 +1,6 @@ { "name": "ml_kibana_api", - "version": "0.1.0", + "version": "7.8.0", "description": "ML Kibana API", "title": "ML Kibana API", "order": [ @@ -9,61 +9,65 @@ "GetDataFrameAnalyticsById", "GetDataFrameAnalyticsStats", "GetDataFrameAnalyticsStatsById", - "UpdateDataFrameAnalytics", "EvaluateDataFrameAnalytics", "ExplainDataFrameAnalytics", - "DeleteDataFrameAnalytics", "StartDataFrameAnalyticsJob", "StopsDataFrameAnalyticsJob", "GetDataFrameAnalyticsMessages", + "UpdateDataFrameAnalytics", + "DeleteDataFrameAnalytics", + "DataVisualizer", "GetOverallStats", "GetStatsForFields", + "AnomalyDetectors", + "CreateAnomalyDetectors", + "OpenAnomalyDetectorsJob", "GetAnomalyDetectors", "GetAnomalyDetectorsById", "GetAnomalyDetectorsStats", "GetAnomalyDetectorsStatsById", - "CreateAnomalyDetectors", - "UpdateAnomalyDetectors", - "OpenAnomalyDetectorsJob", "CloseAnomalyDetectorsJob", - "DeleteAnomalyDetectorsJob", "ValidateAnomalyDetector", "ForecastAnomalyDetector", "GetRecords", "GetBuckets", "GetOverallBuckets", "GetCategories", + "UpdateAnomalyDetectors", + "DeleteAnomalyDetectorsJob", + "FileDataVisualizer", "AnalyzeFile", "ImportFile", + "ResultsService", "GetAnomaliesTableData", "GetCategoryDefinition", "GetMaxAnomalyScore", "GetCategoryExamples", "GetPartitionFieldsValues", + "DataRecognizer", "RecognizeIndex", "GetModule", "SetupModule", "CheckExistingModuleJobs", + "Annotations", "GetAnnotations", "IndexAnnotations", "DeleteAnnotation", + "JobService", "ForceStartDatafeeds", "StopDatafeeds", - "DeleteJobs", "CloseJobs", "JobsSummary", "JobsWithTimeRange", "CreateFullJobsList", "GetAllGroups", - "UpdateGroups", - "DeletingJobTasks", "JobsExist", "NewJobCaps", "NewJobLineChart", @@ -72,42 +76,60 @@ "GetLookBackProgress", "ValidateCategoryExamples", "TopCategories", + "UpdateGroups", + "DeletingJobTasks", + "DeleteJobs", + + "Calendars", + "PutCalendars", + "GetCalendars", + "GetCalendarById", + "UpdateCalendarById", + "DeleteCalendarById", + "Filters", + "CreateFilter", "GetFilters", "GetFilterById", - "CreateFilter", + "GetFiltersStats", "UpdateFilter", "DeleteFilter", - "GetFiltersStats", + "Indices", "FieldCaps", + "SystemRoutes", "HasPrivileges", "MlCapabilities", "MlNodeCount", "MlInfo", "MlEsSearch", + "JobAuditMessages", "GetJobAuditMessages", "GetAllJobAuditMessages", + "JobValidation", "EstimateBucketSpan", "CalculateModelMemoryLimit", "ValidateCardinality", "ValidateJob", + "NotificationSettings", "GetNotificationSettings", + "DatafeedService", + "CreateDatafeed", + "PreviewDatafeed", "GetDatafeeds", "GetDatafeed", "GetDatafeedsStats", "GetDatafeedStats", - "CreateDatafeed", "UpdateDatafeed", - "DeleteDatafeed", "StartDatafeed", "StopDatafeed", - "PreviewDatafeed", + "DeleteDatafeed", + "FieldsService", "GetCardinalityOfFields", "GetTimeFieldRange" diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts new file mode 100644 index 0000000000000..01adcb462689e --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.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 * as ts from 'typescript'; + +export interface DocEntry { + name: string; + documentation?: string; + type: string; + optional?: boolean; + nested?: DocEntry[]; +} + +/** Generate documentation for all schema definitions in a set of .ts files */ +export function extractDocumentation( + fileNames: string[], + options: ts.CompilerOptions = { + target: ts.ScriptTarget.ES2015, + module: ts.ModuleKind.CommonJS, + } +): Map { + // Build a program using the set of root file names in fileNames + const program = ts.createProgram(fileNames, options); + + // Get the checker, we will use it to find more about properties + const checker: ts.TypeChecker = program.getTypeChecker(); + + // Result map + const result = new Map(); + + // Visit every sourceFile in the program + for (const sourceFile of program.getSourceFiles()) { + if (!sourceFile.isDeclarationFile) { + // Walk the tree to search for schemas + ts.forEachChild(sourceFile, visit); + } + } + + return result; + + /** visit nodes finding exported schemas */ + function visit(node: ts.Node) { + if (isNodeExported(node) && ts.isVariableDeclaration(node)) { + const schemaName = node.name.getText(); + const schemaType = checker.getTypeAtLocation(node); + result.set(schemaName, extractDocEntries(schemaType!)); + } + + if (node.getChildCount() > 0) { + ts.forEachChild(node, visit); + } + } + + /** + * Extracts doc entries for the schema definition + * @param schemaType + */ + function extractDocEntries(schemaType: ts.Type): DocEntry[] { + const collection: DocEntry[] = []; + + const members = getTypeMembers(schemaType); + + if (!members) { + return collection; + } + + members.forEach(member => { + collection.push(serializeProperty(member)); + }); + + return collection; + } + + /** + * Resolves members of the type + * @param type + */ + function getTypeMembers(type: ts.Type): ts.Symbol[] | undefined { + const argsOfType = checker.getTypeArguments((type as unknown) as ts.TypeReference); + + let members = type.getProperties(); + + if (argsOfType && argsOfType.length > 0) { + members = argsOfType[0].getProperties(); + } + + return members; + } + + function resolveTypeArgument(type: ts.Type): ts.SymbolTable | string { + // required to extract members + type.getProperty('type'); + + // @ts-ignores + let members = type.members; + + const typeArguments = checker.getTypeArguments((type as unknown) as ts.TypeReference); + + if (type.aliasTypeArguments) { + // @ts-ignores + members = type.aliasTypeArguments[0].members; + } + + if (typeArguments.length > 0) { + members = resolveTypeArgument(typeArguments[0]); + } + + if (members === undefined) { + members = checker.typeToString(type); + } + + return members; + } + + function serializeProperty(symbol: ts.Symbol): DocEntry { + // @ts-ignore + const typeOfSymbol = symbol.type; + const typeArguments = checker.getTypeArguments((typeOfSymbol as unknown) as ts.TypeReference); + + let resultType: ts.Type = typeOfSymbol; + + let members; + if (typeArguments.length > 0) { + members = resolveTypeArgument(typeArguments[0]); + resultType = typeArguments[0]; + } + + let typeAsString = checker.typeToString(resultType); + + const nestedEntries: DocEntry[] = []; + if (members && typeof members !== 'string' && members.size > 0) { + // we hit an object or collection + typeAsString = + resultType.symbol.name === 'Array' || typeOfSymbol.symbol.name === 'Array' + ? `${symbol.getName()}[]` + : symbol.getName(); + + members.forEach(member => { + nestedEntries.push(serializeProperty(member)); + }); + } + + return { + name: symbol.getName(), + documentation: getCommentString(symbol), + type: typeAsString, + ...(nestedEntries.length > 0 ? { nested: nestedEntries } : {}), + }; + } + + function getCommentString(symbol: ts.Symbol): string { + return ts.displayPartsToString(symbol.getDocumentationComment(checker)).replace(/\n/g, ' '); + } + + /** + * True if this is visible outside this file, false otherwise + */ + function isNodeExported(node: ts.Node): boolean { + return ( + // eslint-disable-next-line no-bitwise + (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0 || + (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile) + ); + } +} diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_parser.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_parser.ts new file mode 100644 index 0000000000000..eabe7dcd7bd8f --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_parser.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +function parse(content?: string) { + const schema = typeof content === 'string' && content.trim(); + + if (!schema) { + return null; + } + + const result = schema.match(/\((\w+)\)\s+(\w+)/); + + if (result === null || result.length < 3) { + throw new Error( + 'Invalid schema definition. Required format is `@apiSchema () `' + ); + } + + const group = result[1]; + + return { + group, + name: result[2], + }; +} + +/** + * Exports + */ +module.exports = { + parse, + path: 'local.schemas', + method: 'push', +}; diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts new file mode 100644 index 0000000000000..7514e482783b3 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_worker.ts @@ -0,0 +1,80 @@ +/* + * 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 fs from 'fs'; +import * as path from 'path'; +import { DocEntry, extractDocumentation } from './schema_extractor'; +import { ApiParameter, Block } from './types'; + +export function postProcess(parsedFiles: any[]): void { + const schemasDirPath = `${__dirname}${path.sep}..${path.sep}..${path.sep}schemas${path.sep}`; + const schemaFiles = fs + .readdirSync(schemasDirPath) + .map(filename => path.resolve(schemasDirPath + filename)); + + const schemaDocs = extractDocumentation(schemaFiles); + + parsedFiles.forEach(parsedFile => { + parsedFile.forEach((block: Block) => { + const { + local: { schemas }, + } = block; + if (!schemas || schemas.length === 0) return; + + for (const schema of schemas) { + const { name: schemaName, group: paramsGroup } = schema; + const schemaFields = schemaDocs.get(schemaName); + + if (!schemaFields) return; + + updateBlockParameters(schemaFields, block, paramsGroup); + } + }); + }); +} + +/** + * Extracts schema's doc entries to apidoc parameters + * @param docEntries + * @param block + * @param paramsGroup + */ +function updateBlockParameters(docEntries: DocEntry[], block: Block, paramsGroup: string): void { + if (!block.local.parameter) { + block.local.parameter = { + fields: {}, + }; + } + + if (!block.local.parameter.fields![paramsGroup]) { + block.local.parameter.fields![paramsGroup] = []; + } + const collection = block.local.parameter.fields![paramsGroup] as ApiParameter[]; + + for (const field of docEntries) { + collection.push({ + group: paramsGroup, + type: escapeSpecial(field.type), + size: undefined, + allowedValues: undefined, + optional: !!field.optional, + field: field.name, + defaultValue: undefined, + description: field.documentation, + }); + + if (field.nested) { + updateBlockParameters(field.nested, block, field.name); + } + } +} + +/** + * Escape special character to make sure the markdown table isn't broken + */ +function escapeSpecial(str: string): string { + return str.replace(/\|/g, '\\|'); +} diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/tsconfig.json b/x-pack/plugins/ml/server/routes/apidoc_scripts/tsconfig.json new file mode 100644 index 0000000000000..e3108b8c759f4 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "target": "es6", + "moduleResolution": "node" + }, + "include": [ + "schema_worker.ts", + "schema_parser.ts", + "schema_extractor.ts", + "version_filter.ts" + ] +} diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/types.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/types.ts new file mode 100644 index 0000000000000..08a443905ee05 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/types.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ApiParameter { + group: string; + type: any; + size: undefined; + allowedValues: undefined; + optional: boolean; + field: string; + defaultValue: undefined; + description?: string; +} + +interface Local { + group: string; + type: string; + url: string; + title: string; + name: string; + description: string; + parameter: { + fields?: { + [key: string]: ApiParameter[] | undefined; + }; + }; + success: { fields: ObjectConstructor[] }; + version: string; + filename: string; + schemas?: Array<{ + name: string; + group: string; + }>; +} + +export interface Block { + global: any; + local: Local; +} diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts new file mode 100644 index 0000000000000..8cbe38d667b2c --- /dev/null +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/version_filter.ts @@ -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 { Block } from './types'; + +const API_VERSION = '7.8.0'; + +/** + * Post Filter parsed results. + * Updates api version of the endpoints. + */ +export function postFilter(parsedFiles: any[]) { + parsedFiles.forEach(parsedFile => { + parsedFile.forEach((block: Block) => { + block.local.version = API_VERSION; + }); + }); +} diff --git a/x-pack/plugins/ml/server/routes/calendars.ts b/x-pack/plugins/ml/server/routes/calendars.ts index 34950c6ed79f7..a17601f74ae93 100644 --- a/x-pack/plugins/ml/server/routes/calendars.ts +++ b/x-pack/plugins/ml/server/routes/calendars.ts @@ -5,10 +5,9 @@ */ import { RequestHandlerContext } from 'kibana/server'; -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; -import { calendarSchema } from './schemas/calendars_schema'; +import { calendarSchema, calendarIdSchema, calendarIdsSchema } from './schemas/calendars_schema'; import { CalendarManager, Calendar, FormCalendar } from '../models/calendar'; function getAllCalendars(context: RequestHandlerContext) { @@ -42,7 +41,13 @@ function getCalendarsByIds(context: RequestHandlerContext, calendarIds: string) } export function calendars({ router, mlLicense }: RouteInitialization) { - // Gets calendars - size limit has been explicitly set to 1000 + /** + * @apiGroup Calendars + * + * @api {get} /api/ml/calendars Gets calendars + * @apiName GetCalendars + * @apiDescription Gets calendars - size limit has been explicitly set to 1000 + */ router.get( { path: '/api/ml/calendars', @@ -61,11 +66,20 @@ export function calendars({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup Calendars + * + * @api {get} /api/ml/calendars/:calendarIds Gets a calendar + * @apiName GetCalendarById + * @apiDescription Gets calendar by id + * + * @apiSchema (params) calendarIdsSchema + */ router.get( { path: '/api/ml/calendars/{calendarIds}', validate: { - params: schema.object({ calendarIds: schema.string() }), + params: calendarIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -88,11 +102,20 @@ export function calendars({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup Calendars + * + * @api {put} /api/ml/calendars Creates a calendar + * @apiName PutCalendars + * @apiDescription Creates a calendar + * + * @apiSchema (body) calendarSchema + */ router.put( { path: '/api/ml/calendars', validate: { - body: schema.object({ ...calendarSchema }), + body: calendarSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -109,12 +132,22 @@ export function calendars({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup Calendars + * + * @api {put} /api/ml/calendars/:calendarId Updates a calendar + * @apiName UpdateCalendarById + * @apiDescription Updates a calendar + * + * @apiSchema (params) calendarIdSchema + * @apiSchema (body) calendarSchema + */ router.put( { path: '/api/ml/calendars/{calendarId}', validate: { - params: schema.object({ calendarId: schema.string() }), - body: schema.object({ ...calendarSchema }), + params: calendarIdSchema, + body: calendarSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -132,11 +165,20 @@ export function calendars({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup Calendars + * + * @api {delete} /api/ml/calendars/:calendarId Deletes a calendar + * @apiName DeleteCalendarById + * @apiDescription Deletes a calendar + * + * @apiSchema (params) calendarIdSchema + */ router.delete( { path: '/api/ml/calendars/{calendarId}', validate: { - params: schema.object({ calendarId: schema.string() }), + params: calendarIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 7ed1aa02b24ab..dd9e0ea66aa9d 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages'; import { RouteInitialization } from '../types'; @@ -12,6 +11,8 @@ import { dataAnalyticsJobConfigSchema, dataAnalyticsEvaluateSchema, dataAnalyticsExplainSchema, + analyticsIdSchema, + stopsDataFrameAnalyticsJobQuerySchema, } from './schemas/data_analytics_schema'; /** @@ -31,9 +32,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat router.get( { path: '/api/ml/data_frame/analytics', - validate: { - params: schema.object({ analyticsId: schema.maybe(schema.string()) }), - }, + validate: false, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { try { @@ -54,13 +53,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName GetDataFrameAnalyticsById * @apiDescription Returns the data frame analytics job. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.get( { path: '/api/ml/data_frame/analytics/{analyticsId}', validate: { - params: schema.object({ analyticsId: schema.string() }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -111,13 +110,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName GetDataFrameAnalyticsStatsById * @apiDescription Returns data frame analytics job statistics. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.get( { path: '/api/ml/data_frame/analytics/{analyticsId}/_stats', validate: { - params: schema.object({ analyticsId: schema.string() }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -146,16 +145,15 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiDescription This API creates a data frame analytics job that performs an analysis * on the source index and stores the outcome in a destination index. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema + * @apiSchema (body) dataAnalyticsJobConfigSchema */ router.put( { path: '/api/ml/data_frame/analytics/{analyticsId}', validate: { - params: schema.object({ - analyticsId: schema.string(), - }), - body: schema.object(dataAnalyticsJobConfigSchema), + params: analyticsIdSchema, + body: dataAnalyticsJobConfigSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -183,12 +181,14 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @api {post} /api/ml/data_frame/_evaluate Evaluate the data frame analytics for an annotated index * @apiName EvaluateDataFrameAnalytics * @apiDescription Evaluates the data frame analytics for an annotated index. + * + * @apiSchema (body) dataAnalyticsEvaluateSchema */ router.post( { path: '/api/ml/data_frame/_evaluate', validate: { - body: schema.object({ ...dataAnalyticsEvaluateSchema }), + body: dataAnalyticsEvaluateSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -216,19 +216,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiDescription This API provides explanations for a data frame analytics config * that either exists already or one that has not been created yet. * - * @apiParam {String} [description] - * @apiParam {Object} [dest] - * @apiParam {Object} source - * @apiParam {String} source.index - * @apiParam {Object} analysis - * @apiParam {Object} [analyzed_fields] - * @apiParam {String} [model_memory_limit] + * @apiSchema (body) dataAnalyticsExplainSchema */ router.post( { path: '/api/ml/data_frame/analytics/_explain', validate: { - body: schema.object({ ...dataAnalyticsExplainSchema }), + body: dataAnalyticsExplainSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -255,15 +249,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName DeleteDataFrameAnalytics * @apiDescription Deletes specified data frame analytics job. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.delete( { path: '/api/ml/data_frame/analytics/{analyticsId}', validate: { - params: schema.object({ - analyticsId: schema.string(), - }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -291,15 +283,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName StartDataFrameAnalyticsJob * @apiDescription Starts a data frame analytics job. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.post( { path: '/api/ml/data_frame/analytics/{analyticsId}/_start', validate: { - params: schema.object({ - analyticsId: schema.string(), - }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -324,16 +314,15 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName StopsDataFrameAnalyticsJob * @apiDescription Stops a data frame analytics job. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema + * @apiSchema (query) stopsDataFrameAnalyticsJobQuerySchema */ router.post( { path: '/api/ml/data_frame/analytics/{analyticsId}/_stop', validate: { - params: schema.object({ - analyticsId: schema.string(), - force: schema.maybe(schema.boolean()), - }), + params: analyticsIdSchema, + query: stopsDataFrameAnalyticsJobQuerySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -367,13 +356,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat * @apiName GetDataFrameAnalyticsMessages * @apiDescription Returns the list of audit messages for data frame analytics jobs. * - * @apiParam {String} analyticsId Analytics ID. + * @apiSchema (params) analyticsIdSchema */ router.get( { path: '/api/ml/data_frame/analytics/{analyticsId}/messages', validate: { - params: schema.object({ analyticsId: schema.string() }), + params: analyticsIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/data_visualizer.ts b/x-pack/plugins/ml/server/routes/data_visualizer.ts index b37c80b815e1a..a4c0d5553a4b2 100644 --- a/x-pack/plugins/ml/server/routes/data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/data_visualizer.ts @@ -11,6 +11,7 @@ import { Field } from '../models/data_visualizer/data_visualizer'; import { dataVisualizerFieldStatsSchema, dataVisualizerOverallStatsSchema, + indexPatternTitleSchema, } from './schemas/data_visualizer_schema'; import { RouteInitialization } from '../types'; @@ -75,12 +76,16 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) * @apiName GetStatsForFields * @apiDescription Returns fields stats of the index pattern. * - * @apiParam {String} indexPatternTitle Index pattern title. + * @apiSchema (params) indexPatternTitleSchema + * @apiSchema (body) dataVisualizerFieldStatsSchema */ router.post( { path: '/api/ml/data_visualizer/get_field_stats/{indexPatternTitle}', - validate: dataVisualizerFieldStatsSchema, + validate: { + params: indexPatternTitleSchema, + body: dataVisualizerFieldStatsSchema, + }, }, mlLicense.basicLicenseAPIGuard(async (context, request, response) => { try { @@ -127,12 +132,16 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) * @apiName GetOverallStats * @apiDescription Returns overall stats of the index pattern. * - * @apiParam {String} indexPatternTitle Index pattern title. + * @apiSchema (params) indexPatternTitleSchema + * @apiSchema (body) dataVisualizerOverallStatsSchema */ router.post( { path: '/api/ml/data_visualizer/get_overall_stats/{indexPatternTitle}', - validate: dataVisualizerOverallStatsSchema, + validate: { + params: indexPatternTitleSchema, + body: dataVisualizerOverallStatsSchema, + }, }, mlLicense.basicLicenseAPIGuard(async (context, request, response) => { try { diff --git a/x-pack/plugins/ml/server/routes/datafeeds.ts b/x-pack/plugins/ml/server/routes/datafeeds.ts index c1ee839340996..ec667e1d305f5 100644 --- a/x-pack/plugins/ml/server/routes/datafeeds.ts +++ b/x-pack/plugins/ml/server/routes/datafeeds.ts @@ -4,10 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; -import { startDatafeedSchema, datafeedConfigSchema } from './schemas/datafeeds_schema'; +import { + startDatafeedSchema, + datafeedConfigSchema, + datafeedIdSchema, + deleteDatafeedQuerySchema, +} from './schemas/datafeeds_schema'; /** * Routes for datafeed service @@ -44,12 +48,14 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {get} /api/ml/datafeeds/:datafeedId Get datafeed for given datafeed id * @apiName GetDatafeed * @apiDescription Retrieves configuration information for datafeed + * + * @apiSchema (params) datafeedIdSchema */ router.get( { path: '/api/ml/datafeeds/{datafeedId}', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -97,12 +103,14 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {get} /api/ml/datafeeds/:datafeedId/_stats Get datafeed stats for given datafeed id * @apiName GetDatafeedStats * @apiDescription Retrieves usage information for datafeed + * + * @apiSchema (params) datafeedIdSchema */ router.get( { path: '/api/ml/datafeeds/{datafeedId}/_stats', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -127,12 +135,15 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {put} /api/ml/datafeeds/:datafeedId Creates datafeed * @apiName CreateDatafeed * @apiDescription Instantiates a datafeed + * + * @apiSchema (params) datafeedIdSchema + * @apiSchema (body) datafeedConfigSchema */ router.put( { path: '/api/ml/datafeeds/{datafeedId}', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, body: datafeedConfigSchema, }, }, @@ -159,12 +170,15 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/datafeeds/:datafeedId/_update Updates datafeed for given datafeed id * @apiName UpdateDatafeed * @apiDescription Updates certain properties of a datafeed + * + * @apiSchema (params) datafeedIdSchema + * @apiSchema (body) datafeedConfigSchema */ router.post( { path: '/api/ml/datafeeds/{datafeedId}/_update', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, body: datafeedConfigSchema, }, }, @@ -191,13 +205,16 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {delete} /api/ml/datafeeds/:datafeedId Deletes datafeed * @apiName DeleteDatafeed * @apiDescription Deletes an existing datafeed + * + * @apiSchema (params) datafeedIdSchema + * @apiSchema (query) deleteDatafeedQuerySchema */ router.delete( { path: '/api/ml/datafeeds/{datafeedId}', validate: { - params: schema.object({ datafeedId: schema.string() }), - query: schema.maybe(schema.object({ force: schema.maybe(schema.any()) })), + params: datafeedIdSchema, + query: deleteDatafeedQuerySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -227,12 +244,15 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/datafeeds/:datafeedId/_start Starts datafeed for given datafeed id(s) * @apiName StartDatafeed * @apiDescription Starts one or more datafeeds + * + * @apiSchema (params) datafeedIdSchema + * @apiSchema (body) startDatafeedSchema */ router.post( { path: '/api/ml/datafeeds/{datafeedId}/_start', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, body: startDatafeedSchema, }, }, @@ -262,12 +282,14 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/datafeeds/:datafeedId/_stop Stops datafeed for given datafeed id(s) * @apiName StopDatafeed * @apiDescription Stops one or more datafeeds + * + * @apiSchema (params) datafeedIdSchema */ router.post( { path: '/api/ml/datafeeds/{datafeedId}/_stop', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -293,12 +315,14 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { * @api {get} /api/ml/datafeeds/:datafeedId/_preview Preview datafeed for given datafeed id * @apiName PreviewDatafeed * @apiDescription Previews a datafeed + * + * @apiSchema (params) datafeedIdSchema */ router.get( { path: '/api/ml/datafeeds/{datafeedId}/_preview', validate: { - params: schema.object({ datafeedId: schema.string() }), + params: datafeedIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/fields_service.ts b/x-pack/plugins/ml/server/routes/fields_service.ts index db7613b163457..9a5f47409c8a0 100644 --- a/x-pack/plugins/ml/server/routes/fields_service.ts +++ b/x-pack/plugins/ml/server/routes/fields_service.ts @@ -35,6 +35,8 @@ export function fieldsService({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/fields_service/field_cardinality Get cardinality of fields * @apiName GetCardinalityOfFields * @apiDescription Returns the cardinality of one or more fields. Returns an Object whose keys are the names of the fields, with values equal to the cardinality of the field + * + * @apiSchema (body) getCardinalityOfFieldsSchema */ router.post( { @@ -63,6 +65,8 @@ export function fieldsService({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/fields_service/time_field_range Get time field range * @apiName GetTimeFieldRange * @apiDescription Returns the timefield range for the given index + * + * @apiSchema (body) getTimeFieldRangeSchema */ router.post( { diff --git a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts index b915d13aa9720..3f3fc3f547b6a 100644 --- a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; import { RequestHandlerContext } from 'kibana/server'; -import { MAX_BYTES } from '../../common/constants/file_datavisualizer'; +import { MAX_FILE_SIZE_BYTES } from '../../common/constants/file_datavisualizer'; import { InputOverrides, Settings, @@ -22,6 +22,11 @@ import { import { RouteInitialization } from '../types'; import { updateTelemetry } from '../lib/telemetry'; +import { + analyzeFileQuerySchema, + importFileBodySchema, + importFileQuerySchema, +} from './schemas/file_data_visualizer_schema'; function analyzeFiles(context: RequestHandlerContext, data: InputData, overrides: InputOverrides) { const { analyzeFile } = fileDataVisualizerProvider(context.ml!.mlClient.callAsCurrentUser); @@ -51,35 +56,20 @@ export function fileDataVisualizerRoutes({ router, mlLicense }: RouteInitializat * @api {post} /api/ml/file_data_visualizer/analyze_file Analyze file data * @apiName AnalyzeFile * @apiDescription Performs analysis of the file data. + * + * @apiSchema (query) analyzeFileQuerySchema */ router.post( { path: '/api/ml/file_data_visualizer/analyze_file', validate: { body: schema.any(), - query: schema.maybe( - schema.object({ - charset: schema.maybe(schema.string()), - column_names: schema.maybe(schema.string()), - delimiter: schema.maybe(schema.string()), - explain: schema.maybe(schema.string()), - format: schema.maybe(schema.string()), - grok_pattern: schema.maybe(schema.string()), - has_header_row: schema.maybe(schema.string()), - line_merge_size_limit: schema.maybe(schema.string()), - lines_to_sample: schema.maybe(schema.string()), - quote: schema.maybe(schema.string()), - should_trim_fields: schema.maybe(schema.string()), - timeout: schema.maybe(schema.string()), - timestamp_field: schema.maybe(schema.string()), - timestamp_format: schema.maybe(schema.string()), - }) - ), + query: analyzeFileQuerySchema, }, options: { body: { accepts: ['text/*', 'application/json'], - maxBytes: MAX_BYTES, + maxBytes: MAX_FILE_SIZE_BYTES, }, }, }, @@ -99,29 +89,21 @@ export function fileDataVisualizerRoutes({ router, mlLicense }: RouteInitializat * @api {post} /api/ml/file_data_visualizer/import Import file data * @apiName ImportFile * @apiDescription Imports file data into elasticsearch index. + * + * @apiSchema (query) importFileQuerySchema + * @apiSchema (body) importFileBodySchema */ router.post( { path: '/api/ml/file_data_visualizer/import', validate: { - query: schema.object({ - id: schema.maybe(schema.string()), - }), - body: schema.object({ - index: schema.maybe(schema.string()), - data: schema.arrayOf(schema.any()), - settings: schema.maybe(schema.any()), - mappings: schema.any(), - ingestPipeline: schema.object({ - id: schema.maybe(schema.string()), - pipeline: schema.maybe(schema.any()), - }), - }), + query: importFileQuerySchema, + body: importFileBodySchema, }, options: { body: { accepts: ['application/json'], - maxBytes: MAX_BYTES, + maxBytes: MAX_FILE_SIZE_BYTES, }, }, }, diff --git a/x-pack/plugins/ml/server/routes/filters.ts b/x-pack/plugins/ml/server/routes/filters.ts index e827ed96b12af..738c25070358d 100644 --- a/x-pack/plugins/ml/server/routes/filters.ts +++ b/x-pack/plugins/ml/server/routes/filters.ts @@ -5,10 +5,9 @@ */ import { RequestHandlerContext } from 'kibana/server'; -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; -import { createFilterSchema, updateFilterSchema } from './schemas/filters_schema'; +import { createFilterSchema, filterIdSchema, updateFilterSchema } from './schemas/filters_schema'; import { FilterManager, FormFilter } from '../models/filter'; // TODO - add function for returning a list of just the filter IDs. @@ -79,6 +78,8 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { * @apiName GetFilterById * @apiDescription Retrieves the filter with the specified ID. * + * @apiSchema (params) filterIdSchema + * * @apiSuccess {Boolean} success * @apiSuccess {Object} filter the filter with the specified ID */ @@ -86,7 +87,7 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/filters/{filterId}', validate: { - params: schema.object({ filterId: schema.string() }), + params: filterIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -108,6 +109,8 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { * @apiName CreateFilter * @apiDescription Instantiates a filter, for use by custom rules in anomaly detection. * + * @apiSchema (body) createFilterSchema + * * @apiSuccess {Boolean} success * @apiSuccess {Object} filter created filter */ @@ -115,7 +118,7 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/filters', validate: { - body: schema.object(createFilterSchema), + body: createFilterSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -139,6 +142,9 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { * @apiName UpdateFilter * @apiDescription Updates the description of a filter, adds items or removes items. * + * @apiSchema (params) filterIdSchema + * @apiSchema (body) updateFilterSchema + * * @apiSuccess {Boolean} success * @apiSuccess {Object} filter updated filter */ @@ -146,8 +152,8 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { { path: '/api/ml/filters/{filterId}', validate: { - params: schema.object({ filterId: schema.string() }), - body: schema.object(updateFilterSchema), + params: filterIdSchema, + body: updateFilterSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -172,13 +178,13 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { * @apiName DeleteFilter * @apiDescription Deletes the filter with the specified ID. * - * @apiParam {String} filterId the ID of the filter to delete + * @apiSchema (params) filterIdSchema */ router.delete( { path: '/api/ml/filters/{filterId}', validate: { - params: schema.object({ filterId: schema.string() }), + params: filterIdSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/indices.ts b/x-pack/plugins/ml/server/routes/indices.ts index fe66cc8b01396..e434936beba63 100644 --- a/x-pack/plugins/ml/server/routes/indices.ts +++ b/x-pack/plugins/ml/server/routes/indices.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; +import { indicesSchema } from './schemas/indices_schema'; /** * Indices routes. @@ -15,18 +15,17 @@ export function indicesRoutes({ router, mlLicense }: RouteInitialization) { /** * @apiGroup Indices * - * @api {post} /api/ml/indices/field_caps + * @api {post} /api/ml/indices/field_caps Field caps * @apiName FieldCaps * @apiDescription Retrieves the capabilities of fields among multiple indices. + * + * @apiSchema (body) indicesSchema */ router.post( { path: '/api/ml/indices/field_caps', validate: { - body: schema.object({ - index: schema.maybe(schema.string()), - fields: schema.maybe(schema.arrayOf(schema.string())), - }), + body: indicesSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/job_audit_messages.ts b/x-pack/plugins/ml/server/routes/job_audit_messages.ts index 5c6d8023cc172..71499748691f6 100644 --- a/x-pack/plugins/ml/server/routes/job_audit_messages.ts +++ b/x-pack/plugins/ml/server/routes/job_audit_messages.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { jobAuditMessagesProvider } from '../models/job_audit_messages'; +import { jobAuditMessagesQuerySchema, jobIdSchema } from './schemas/job_audit_messages_schema'; /** * Routes for job audit message routes @@ -19,13 +19,16 @@ export function jobAuditMessagesRoutes({ router, mlLicense }: RouteInitializatio * @api {get} /api/ml/job_audit_messages/messages/:jobId Get audit messages * @apiName GetJobAuditMessages * @apiDescription Returns audit messages for specified job ID + * + * @apiSchema (params) jobIdSchema + * @apiSchema (query) jobAuditMessagesQuerySchema */ router.get( { path: '/api/ml/job_audit_messages/messages/{jobId}', validate: { - params: schema.object({ jobId: schema.maybe(schema.string()) }), - query: schema.maybe(schema.object({ from: schema.maybe(schema.any()) })), + params: jobIdSchema, + query: jobAuditMessagesQuerySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -52,13 +55,14 @@ export function jobAuditMessagesRoutes({ router, mlLicense }: RouteInitializatio * @api {get} /api/ml/job_audit_messages/messages Get all audit messages * @apiName GetAllJobAuditMessages * @apiDescription Returns all audit messages + * + * @apiSchema (query) jobAuditMessagesQuerySchema */ router.get( { path: '/api/ml/job_audit_messages/messages', validate: { - params: schema.object({ jobId: schema.maybe(schema.string()) }), - query: schema.maybe(schema.object({ from: schema.maybe(schema.any()) })), + query: jobAuditMessagesQuerySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index 718f9e81603b1..493974cbafe36 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -53,12 +53,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/force_start_datafeeds Start datafeeds * @apiName ForceStartDatafeeds * @apiDescription Starts one or more datafeeds + * + * @apiSchema (body) forceStartDatafeedSchema */ router.post( { path: '/api/ml/jobs/force_start_datafeeds', validate: { - body: schema.object(forceStartDatafeedSchema), + body: forceStartDatafeedSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -82,12 +84,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/stop_datafeeds Stop datafeeds * @apiName StopDatafeeds * @apiDescription Stops one or more datafeeds + * + * @apiSchema (body) datafeedIdsSchema */ router.post( { path: '/api/ml/jobs/stop_datafeeds', validate: { - body: schema.object(datafeedIdsSchema), + body: datafeedIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -111,12 +115,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/delete_jobs Delete jobs * @apiName DeleteJobs * @apiDescription Deletes an existing anomaly detection job + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/delete_jobs', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -140,12 +146,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/close_jobs Close jobs * @apiName CloseJobs * @apiDescription Closes one or more anomaly detection jobs + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/close_jobs', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -169,12 +177,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/jobs_summary Jobs summary * @apiName JobsSummary * @apiDescription Creates a summary jobs list. Jobs include job stats, datafeed stats, and calendars. + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/jobs_summary', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -198,6 +208,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/jobs_with_time_range Jobs with time range * @apiName JobsWithTimeRange * @apiDescription Creates a list of jobs with data about the job's time range + * + * @apiSchema (body) jobsWithTimerangeSchema */ router.post( { @@ -226,12 +238,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/jobs Create jobs list * @apiName CreateFullJobsList * @apiDescription Creates a list of jobs + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/jobs', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -281,6 +295,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/update_groups Update job groups * @apiName UpdateGroups * @apiDescription Updates 'groups' property of an anomaly detection job + * + * @apiSchema (body) updateGroupsSchema */ router.post( { @@ -336,12 +352,14 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/jobs_exist Check if jobs exist * @apiName JobsExist * @apiDescription Checks if each of the jobs in the specified list of IDs exist + * + * @apiSchema (body) jobIdsSchema */ router.post( { path: '/api/ml/jobs/jobs_exist', validate: { - body: schema.object(jobIdsSchema), + body: jobIdsSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -397,6 +415,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/new_job_line_chart Get job line chart data * @apiName NewJobLineChart * @apiDescription Returns line chart data for anomaly detection job + * + * @apiSchema (body) chartSchema */ router.post( { @@ -447,6 +467,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/new_job_population_chart Get population job chart data * @apiName NewJobPopulationChart * @apiDescription Returns population job chart data + * + * @apiSchema (body) chartSchema */ router.post( { @@ -523,6 +545,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/look_back_progress Get lookback progress * @apiName GetLookBackProgress * @apiDescription Returns current progress of anomaly detection job + * + * @apiSchema (body) lookBackProgressSchema */ router.post( { @@ -552,6 +576,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/categorization_field_examples Get categorization field examples * @apiName ValidateCategoryExamples * @apiDescription Validates category examples + * + * @apiSchema (body) categorizationFieldExamplesSchema */ router.post( { @@ -611,6 +637,8 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { * @api {post} /api/ml/jobs/top_categories Get top categories * @apiName TopCategories * @apiDescription Returns list of top categories + * + * @apiSchema (body) topCategoriesSchema */ router.post( { diff --git a/x-pack/plugins/ml/server/routes/job_validation.ts b/x-pack/plugins/ml/server/routes/job_validation.ts index 75d9cdf375049..dd2bd9deadf43 100644 --- a/x-pack/plugins/ml/server/routes/job_validation.ts +++ b/x-pack/plugins/ml/server/routes/job_validation.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; import { RequestHandlerContext } from 'kibana/server'; -import { schema, TypeOf } from '@kbn/config-schema'; +import { TypeOf } from '@kbn/config-schema'; import { AnalysisConfig } from '../../common/types/anomaly_detection_jobs'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; @@ -48,6 +48,8 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, * @api {post} /api/ml/validate/estimate_bucket_span Estimate bucket span * @apiName EstimateBucketSpan * @apiDescription Estimates minimum viable bucket span based on the characteristics of a pre-viewed subset of the data + * + * @apiSchema (body) estimateBucketSpanSchema */ router.post( { @@ -94,6 +96,8 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, * @apiName CalculateModelMemoryLimit * @apiDescription Calls _estimate_model_memory endpoint to retrieve model memory estimation. * + * @apiSchema (body) modelMemoryLimitSchema + * * @apiSuccess {String} modelMemoryLimit */ router.post( @@ -122,12 +126,14 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, * @api {post} /api/ml/validate/cardinality Validate cardinality * @apiName ValidateCardinality * @apiDescription Validates cardinality for the given job configuration + * + * @apiSchema (body) validateCardinalitySchema */ router.post( { path: '/api/ml/validate/cardinality', validate: { - body: schema.object(validateCardinalitySchema), + body: validateCardinalitySchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -152,6 +158,8 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, * @api {post} /api/ml/validate/job Validates job * @apiName ValidateJob * @apiDescription Validates the given job configuration + * + * @apiSchema (body) validateJobSchema */ router.post( { diff --git a/x-pack/plugins/ml/server/routes/modules.ts b/x-pack/plugins/ml/server/routes/modules.ts index 358cd0ac2871c..2d462b6dc207a 100644 --- a/x-pack/plugins/ml/server/routes/modules.ts +++ b/x-pack/plugins/ml/server/routes/modules.ts @@ -152,7 +152,7 @@ export function dataRecognizer({ router, mlLicense }: RouteInitialization) { * @apiName SetupModule * @apiDescription Created module items. * - * @apiParam {String} moduleId Module id + * @apiSchema (body) setupModuleBodySchema */ router.post( { diff --git a/x-pack/plugins/ml/server/routes/results_service.ts b/x-pack/plugins/ml/server/routes/results_service.ts index 9849410eaf0d4..89c267340fe52 100644 --- a/x-pack/plugins/ml/server/routes/results_service.ts +++ b/x-pack/plugins/ml/server/routes/results_service.ts @@ -5,7 +5,6 @@ */ import { RequestHandlerContext } from 'kibana/server'; -import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { @@ -80,12 +79,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/anomalies_table_data Prepare anomalies records for table display * @apiName GetAnomaliesTableData * @apiDescription Retrieves anomaly records for an anomaly detection job and formats them for anomalies table display + * + * @apiSchema (body) anomaliesTableDataSchema */ router.post( { path: '/api/ml/results/anomalies_table_data', validate: { - body: schema.object(anomaliesTableDataSchema), + body: anomaliesTableDataSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -107,12 +108,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/category_definition Returns category definition * @apiName GetCategoryDefinition * @apiDescription Returns the definition of the category with the specified ID and job ID + * + * @apiSchema (body) categoryDefinitionSchema */ router.post( { path: '/api/ml/results/category_definition', validate: { - body: schema.object(categoryDefinitionSchema), + body: categoryDefinitionSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -134,12 +137,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/max_anomaly_score Returns the maximum anomaly_score * @apiName GetMaxAnomalyScore * @apiDescription Returns the maximum anomaly score of the bucket results for the request job ID(s) and time range + * + * @apiSchema (body) maxAnomalyScoreSchema */ router.post( { path: '/api/ml/results/max_anomaly_score', validate: { - body: schema.object(maxAnomalyScoreSchema), + body: maxAnomalyScoreSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -161,12 +166,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/category_examples Returns category examples * @apiName GetCategoryExamples * @apiDescription Returns examples for the categories with the specified IDs from the job with the supplied ID + * + * @apiSchema (body) categoryExamplesSchema */ router.post( { path: '/api/ml/results/category_examples', validate: { - body: schema.object(categoryExamplesSchema), + body: categoryExamplesSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { @@ -188,12 +195,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) * @api {post} /api/ml/results/partition_fields_values Returns partition fields values * @apiName GetPartitionFieldsValues * @apiDescription Returns the partition fields with values that match the provided criteria for the specified job ID. + * + * @apiSchema (body) partitionFieldValuesSchema */ router.post( { path: '/api/ml/results/partition_fields_values', validate: { - body: schema.object(partitionFieldValuesSchema), + body: partitionFieldValuesSchema, }, }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { diff --git a/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts b/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts index 7d3d6aabb129c..fade2093ac842 100644 --- a/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; -export const indexAnnotationSchema = { +export const indexAnnotationSchema = schema.object({ timestamp: schema.number(), end_timestamp: schema.number(), annotation: schema.string(), @@ -16,15 +16,16 @@ export const indexAnnotationSchema = { create_username: schema.maybe(schema.string()), modified_time: schema.maybe(schema.number()), modified_username: schema.maybe(schema.string()), + /** Document id */ _id: schema.maybe(schema.string()), key: schema.maybe(schema.string()), -}; +}); -export const getAnnotationsSchema = { +export const getAnnotationsSchema = schema.object({ jobIds: schema.arrayOf(schema.string()), earliestMs: schema.oneOf([schema.nullable(schema.number()), schema.maybe(schema.number())]), latestMs: schema.oneOf([schema.nullable(schema.number()), schema.maybe(schema.number())]), maxAnnotations: schema.number(), -}; +}); -export const deleteAnnotationSchema = { annotationId: schema.string() }; +export const deleteAnnotationSchema = schema.object({ annotationId: schema.string() }); diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index 22c3d94dfb29e..ab1305d9bc354 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -26,6 +26,7 @@ const detectorSchema = schema.object({ over_field_name: schema.maybe(schema.string()), partition_field_name: schema.maybe(schema.string()), detector_description: schema.maybe(schema.string()), + /** Custom rules */ custom_rules: customRulesSchema, }); @@ -37,20 +38,24 @@ const customUrlSchema = { const customSettingsSchema = schema.object( { + /** Indicates the creator entity */ created_by: schema.maybe(schema.string()), - custom_urls: schema.maybe(schema.arrayOf(schema.maybe(schema.object({ ...customUrlSchema })))), + custom_urls: schema.maybe(schema.arrayOf(schema.maybe(schema.object(customUrlSchema)))), }, { unknowns: 'allow' } // Create / Update job API allows other fields to be added to custom_settings. ); -export const anomalyDetectionUpdateJobSchema = { +export const anomalyDetectionUpdateJobSchema = schema.object({ description: schema.maybe(schema.string()), detectors: schema.maybe( schema.arrayOf( schema.maybe( schema.object({ + /** Detector index */ detector_index: schema.number(), + /** Description */ description: schema.maybe(schema.string()), + /** Custom rules */ custom_rules: customRulesSchema, }) ) @@ -64,7 +69,7 @@ export const anomalyDetectionUpdateJobSchema = { }) ), groups: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), -}; +}); export const analysisConfigSchema = schema.object({ bucket_span: schema.maybe(schema.string()), @@ -78,6 +83,7 @@ export const anomalyDetectionJobSchema = { analysis_config: analysisConfigSchema, analysis_limits: schema.maybe( schema.object({ + /** Limit of categorization examples */ categorization_examples_limit: schema.maybe(schema.number()), model_memory_limit: schema.maybe(schema.string()), }) @@ -88,6 +94,7 @@ export const anomalyDetectionJobSchema = { allow_lazy_open: schema.maybe(schema.any()), data_counts: schema.maybe(schema.any()), data_description: schema.object({ + /** Format */ format: schema.maybe(schema.string()), time_field: schema.string(), time_format: schema.maybe(schema.string()), @@ -110,3 +117,63 @@ export const anomalyDetectionJobSchema = { results_retention_days: schema.maybe(schema.number()), state: schema.maybe(schema.string()), }; + +export const jobIdSchema = schema.object({ + /** Job id */ + jobId: schema.string(), +}); + +export const getRecordsSchema = schema.object({ + desc: schema.maybe(schema.boolean()), + end: schema.maybe(schema.string()), + exclude_interim: schema.maybe(schema.boolean()), + page: schema.maybe( + schema.object({ + from: schema.maybe(schema.number()), + size: schema.maybe(schema.number()), + }) + ), + record_score: schema.maybe(schema.number()), + sort: schema.maybe(schema.string()), + start: schema.maybe(schema.string()), +}); + +export const getBucketsSchema = schema.object({ + anomaly_score: schema.maybe(schema.number()), + desc: schema.maybe(schema.boolean()), + end: schema.maybe(schema.string()), + exclude_interim: schema.maybe(schema.boolean()), + expand: schema.maybe(schema.boolean()), + /** Page definition */ + page: schema.maybe( + schema.object({ + /** Page offset */ + from: schema.maybe(schema.number()), + /** Size of the page */ + size: schema.maybe(schema.number()), + }) + ), + sort: schema.maybe(schema.string()), + start: schema.maybe(schema.string()), +}); + +export const getBucketParamsSchema = schema.object({ + jobId: schema.string(), + timestamp: schema.maybe(schema.string()), +}); + +export const getOverallBucketsSchema = schema.object({ + topN: schema.number(), + bucketSpan: schema.string(), + start: schema.number(), + end: schema.number(), +}); + +export const getCategoriesSchema = schema.object({ + /** Category id */ + categoryId: schema.string(), + /** Job id */ + jobId: schema.string(), +}); + +export const forecastAnomalyDetector = schema.object({ duration: schema.any() }); diff --git a/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts b/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts index f5e59d983a9aa..6d8f94311816d 100644 --- a/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; -export const calendarSchema = { +export const calendarSchema = schema.object({ calendar_id: schema.maybe(schema.string()), calendarId: schema.string(), job_ids: schema.arrayOf(schema.maybe(schema.string())), @@ -22,4 +22,11 @@ export const calendarSchema = { }) ) ), -}; +}); + +export const calendarIdSchema = schema.object({ calendarId: schema.string() }); + +export const calendarIdsSchema = schema.object({ + /** Comma-separated list of calendar IDs */ + calendarIds: schema.string(), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts index 21454fa884b82..f1d4947a7abc5 100644 --- a/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; -export const dataAnalyticsJobConfigSchema = { +export const dataAnalyticsJobConfigSchema = schema.object({ description: schema.maybe(schema.string()), dest: schema.object({ index: schema.string(), @@ -17,7 +17,9 @@ export const dataAnalyticsJobConfigSchema = { query: schema.maybe(schema.any()), _source: schema.maybe( schema.object({ + /** Fields to include in results */ includes: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), + /** Fields to exclude from results */ excludes: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), }) ), @@ -26,9 +28,9 @@ export const dataAnalyticsJobConfigSchema = { analysis: schema.any(), analyzed_fields: schema.any(), model_memory_limit: schema.string(), -}; +}); -export const dataAnalyticsEvaluateSchema = { +export const dataAnalyticsEvaluateSchema = schema.object({ index: schema.string(), query: schema.maybe(schema.any()), evaluation: schema.maybe( @@ -37,15 +39,27 @@ export const dataAnalyticsEvaluateSchema = { classification: schema.maybe(schema.any()), }) ), -}; +}); -export const dataAnalyticsExplainSchema = { +export const dataAnalyticsExplainSchema = schema.object({ description: schema.maybe(schema.string()), dest: schema.maybe(schema.any()), + /** Source */ source: schema.object({ index: schema.string(), }), analysis: schema.any(), analyzed_fields: schema.maybe(schema.any()), model_memory_limit: schema.maybe(schema.string()), -}; +}); + +export const analyticsIdSchema = schema.object({ + /** + * Analytics ID + */ + analyticsId: schema.string(), +}); + +export const stopsDataFrameAnalyticsJobQuerySchema = schema.object({ + force: schema.maybe(schema.boolean()), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts index 0c10b2d5b4f16..1a1d02f991b55 100644 --- a/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts @@ -6,33 +6,27 @@ import { schema } from '@kbn/config-schema'; -export const dataVisualizerFieldStatsSchema = { - params: schema.object({ - indexPatternTitle: schema.string(), - }), - body: schema.object({ - query: schema.any(), - fields: schema.arrayOf(schema.any()), - samplerShardSize: schema.number(), - timeFieldName: schema.maybe(schema.string()), - earliest: schema.maybe(schema.number()), - latest: schema.maybe(schema.number()), - interval: schema.maybe(schema.string()), - maxExamples: schema.number(), - }), -}; +export const indexPatternTitleSchema = schema.object({ + indexPatternTitle: schema.string(), +}); -export const dataVisualizerOverallStatsSchema = { - params: schema.object({ - indexPatternTitle: schema.string(), - }), - body: schema.object({ - query: schema.any(), - aggregatableFields: schema.arrayOf(schema.string()), - nonAggregatableFields: schema.arrayOf(schema.string()), - samplerShardSize: schema.number(), - timeFieldName: schema.maybe(schema.string()), - earliest: schema.maybe(schema.number()), - latest: schema.maybe(schema.number()), - }), -}; +export const dataVisualizerFieldStatsSchema = schema.object({ + query: schema.any(), + fields: schema.arrayOf(schema.any()), + samplerShardSize: schema.number(), + timeFieldName: schema.maybe(schema.string()), + earliest: schema.maybe(schema.number()), + latest: schema.maybe(schema.number()), + interval: schema.maybe(schema.string()), + maxExamples: schema.number(), +}); + +export const dataVisualizerOverallStatsSchema = schema.object({ + query: schema.any(), + aggregatableFields: schema.arrayOf(schema.string()), + nonAggregatableFields: schema.arrayOf(schema.string()), + samplerShardSize: schema.number(), + timeFieldName: schema.maybe(schema.string()), + earliest: schema.maybe(schema.number()), + latest: schema.maybe(schema.number()), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts index 466e70197e3d1..2cfb9d7d275d5 100644 --- a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts @@ -42,3 +42,9 @@ export const datafeedConfigSchema = schema.object({ }) ), }); + +export const datafeedIdSchema = schema.object({ datafeedId: schema.string() }); + +export const deleteDatafeedQuerySchema = schema.maybe( + schema.object({ force: schema.maybe(schema.any()) }) +); diff --git a/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts b/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts new file mode 100644 index 0000000000000..9a80cf795cabf --- /dev/null +++ b/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +export const analyzeFileQuerySchema = schema.maybe( + schema.object({ + charset: schema.maybe(schema.string()), + column_names: schema.maybe(schema.string()), + delimiter: schema.maybe(schema.string()), + explain: schema.maybe(schema.string()), + format: schema.maybe(schema.string()), + grok_pattern: schema.maybe(schema.string()), + has_header_row: schema.maybe(schema.string()), + line_merge_size_limit: schema.maybe(schema.string()), + lines_to_sample: schema.maybe(schema.string()), + quote: schema.maybe(schema.string()), + should_trim_fields: schema.maybe(schema.string()), + timeout: schema.maybe(schema.string()), + timestamp_field: schema.maybe(schema.string()), + timestamp_format: schema.maybe(schema.string()), + }) +); + +export const importFileQuerySchema = schema.object({ + id: schema.maybe(schema.string()), +}); + +export const importFileBodySchema = schema.object({ + index: schema.maybe(schema.string()), + data: schema.arrayOf(schema.any()), + settings: schema.maybe(schema.any()), + /** Mappings */ + mappings: schema.any(), + /** Ingest pipeline definition */ + ingestPipeline: schema.object({ + id: schema.maybe(schema.string()), + pipeline: schema.maybe(schema.any()), + }), +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts b/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts index dffee56565c73..d33d34c7096ce 100644 --- a/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts @@ -6,14 +6,21 @@ import { schema } from '@kbn/config-schema'; -export const createFilterSchema = { +export const createFilterSchema = schema.object({ filterId: schema.string(), description: schema.maybe(schema.string()), items: schema.arrayOf(schema.string()), -}; +}); -export const updateFilterSchema = { +export const updateFilterSchema = schema.object({ description: schema.maybe(schema.string()), addItems: schema.maybe(schema.arrayOf(schema.string())), removeItems: schema.maybe(schema.arrayOf(schema.string())), -}; +}); + +export const filterIdSchema = schema.object({ + /** + * ID of the filter + */ + filterId: schema.string(), +}); diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/index.js b/x-pack/plugins/ml/server/routes/schemas/indices_schema.ts old mode 100755 new mode 100644 similarity index 55% rename from x-pack/legacy/plugins/logstash/server/routes/api/cluster/index.js rename to x-pack/plugins/ml/server/routes/schemas/indices_schema.ts index b129d8524b573..f1b06392292f0 --- a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/index.js +++ b/x-pack/plugins/ml/server/routes/schemas/indices_schema.ts @@ -3,5 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { schema } from '@kbn/config-schema'; -export { registerLogstashClusterRoutes } from './register_cluster_routes'; +export const indicesSchema = schema.object({ + index: schema.maybe(schema.string()), + fields: schema.maybe(schema.arrayOf(schema.string())), +}); diff --git a/x-pack/plugins/infra/public/store/epics.ts b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts similarity index 50% rename from x-pack/plugins/infra/public/store/epics.ts rename to x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts index b5e48a4ec6214..b94a004384eb1 100644 --- a/x-pack/plugins/infra/public/store/epics.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineEpics } from 'redux-observable'; +import { schema } from '@kbn/config-schema'; -import { createLocalEpic } from './local'; +export const jobIdSchema = schema.object({ jobId: schema.maybe(schema.string()) }); -export const createRootEpic = () => combineEpics(createLocalEpic()); +export const jobAuditMessagesQuerySchema = schema.maybe( + schema.object({ from: schema.maybe(schema.any()) }) +); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts index deb62678a777c..d2036b8a7c0fa 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts @@ -29,21 +29,25 @@ export const chartSchema = { splitFieldValue: schema.maybe(schema.nullable(schema.string())), }; -export const datafeedIdsSchema = { datafeedIds: schema.arrayOf(schema.maybe(schema.string())) }; +export const datafeedIdsSchema = schema.object({ + datafeedIds: schema.arrayOf(schema.maybe(schema.string())), +}); -export const forceStartDatafeedSchema = { +export const forceStartDatafeedSchema = schema.object({ datafeedIds: schema.arrayOf(schema.maybe(schema.string())), start: schema.maybe(schema.number()), end: schema.maybe(schema.number()), -}; +}); -export const jobIdsSchema = { +export const jobIdsSchema = schema.object({ jobIds: schema.maybe( schema.oneOf([schema.string(), schema.arrayOf(schema.maybe(schema.string()))]) ), -}; +}); -export const jobsWithTimerangeSchema = { dateFormatTz: schema.maybe(schema.string()) }; +export const jobsWithTimerangeSchema = { + dateFormatTz: schema.maybe(schema.string()), +}; export const lookBackProgressSchema = { jobId: schema.string(), diff --git a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts index 3ded6e770eed5..f12c85962a28d 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts @@ -37,7 +37,7 @@ export const validateJobSchema = schema.object({ job: schema.object(anomalyDetectionJobSchema), }); -export const validateCardinalitySchema = { +export const validateCardinalitySchema = schema.object({ ...anomalyDetectionJobSchema, datafeed_config: datafeedConfigSchema, -}; +}); diff --git a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts index 32d829db7f81b..f7317e534b33b 100644 --- a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts @@ -12,7 +12,7 @@ const criteriaFieldSchema = schema.object({ fieldValue: schema.any(), }); -export const anomaliesTableDataSchema = { +export const anomaliesTableDataSchema = schema.object({ jobIds: schema.arrayOf(schema.string()), criteriaFields: schema.arrayOf(criteriaFieldSchema), influencers: schema.arrayOf( @@ -26,29 +26,29 @@ export const anomaliesTableDataSchema = { maxRecords: schema.number(), maxExamples: schema.maybe(schema.number()), influencersFilterQuery: schema.maybe(schema.any()), -}; +}); -export const categoryDefinitionSchema = { +export const categoryDefinitionSchema = schema.object({ jobId: schema.maybe(schema.string()), categoryId: schema.string(), -}; +}); -export const maxAnomalyScoreSchema = { +export const maxAnomalyScoreSchema = schema.object({ jobIds: schema.arrayOf(schema.string()), earliestMs: schema.maybe(schema.number()), latestMs: schema.maybe(schema.number()), -}; +}); -export const categoryExamplesSchema = { +export const categoryExamplesSchema = schema.object({ jobId: schema.string(), categoryIds: schema.arrayOf(schema.string()), maxExamples: schema.number(), -}; +}); -export const partitionFieldValuesSchema = { +export const partitionFieldValuesSchema = schema.object({ jobId: schema.string(), searchTerm: schema.maybe(schema.any()), criteriaFields: schema.arrayOf(criteriaFieldSchema), earliestMs: schema.number(), latestMs: schema.number(), -}; +}); diff --git a/x-pack/plugins/oss_telemetry/server/lib/tasks/index.ts b/x-pack/plugins/oss_telemetry/server/lib/tasks/index.ts index be08338807dd0..9342c2574bedd 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/tasks/index.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/tasks/index.ts @@ -17,12 +17,12 @@ import { export function registerTasks({ taskManager, logger, - elasticsearch, + getStartServices, config, }: { taskManager?: TaskManagerSetupContract; logger: Logger; - elasticsearch: CoreSetup['elasticsearch']; + getStartServices: CoreSetup['getStartServices']; config: Observable<{ kibana: { index: string } }>; }) { if (!taskManager) { @@ -30,13 +30,17 @@ export function registerTasks({ return; } + const esClientPromise = getStartServices().then( + ([{ elasticsearch }]) => elasticsearch.legacy.client + ); + taskManager.registerTaskDefinitions({ [VIS_TELEMETRY_TASK]: { title: 'X-Pack telemetry calculator for Visualizations', type: VIS_TELEMETRY_TASK, createTaskRunner({ taskInstance }: { taskInstance: TaskInstance }) { return { - run: visualizationsTaskRunner(taskInstance, config, elasticsearch), + run: visualizationsTaskRunner(taskInstance, config, esClientPromise), }; }, }, diff --git a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts index dff5e24db3c6e..6a47983a6f4d9 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; import { getMockCallWithInternal, getMockConfig, @@ -13,6 +12,7 @@ import { } from '../../../test_utils'; import { visualizationsTaskRunner } from './task_runner'; import { TaskInstance } from '../../../../../task_manager/server'; +import { getNextMidnight } from '../../get_next_midnight'; describe('visualizationsTaskRunner', () => { let mockTaskInstance: TaskInstance; @@ -41,12 +41,6 @@ describe('visualizationsTaskRunner', () => { }); test('Summarizes visualization response data', async () => { - const getNextMidnight = () => - moment() - .add(1, 'days') - .startOf('day') - .toDate(); - const runner = visualizationsTaskRunner(mockTaskInstance, getMockConfig(), getMockEs()); const result = await runner(); diff --git a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts index 556dc465e562d..f60c44e548f3f 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts @@ -6,7 +6,7 @@ import { Observable } from 'rxjs'; import _, { countBy, groupBy, mapValues } from 'lodash'; -import { APICaller, CoreSetup } from 'kibana/server'; +import { APICaller, IClusterClient } from 'src/core/server'; import { getNextMidnight } from '../../get_next_midnight'; import { TaskInstance } from '../../../../../task_manager/server'; import { ESSearchHit } from '../../../../../apm/typings/elasticsearch'; @@ -73,17 +73,15 @@ async function getStats(callCluster: APICaller, index: string) { export function visualizationsTaskRunner( taskInstance: TaskInstance, config: Observable<{ kibana: { index: string } }>, - es: CoreSetup['elasticsearch'] + esClientPromise: Promise ) { - const { callAsInternalUser: callCluster } = es.createClient('data'); - return async () => { let stats; let error; try { const index = (await config.toPromise()).kibana.index; - stats = await getStats(callCluster, index); + stats = await getStats((await esClientPromise).callAsInternalUser, index); } catch (err) { if (err.constructor === Error) { error = err.message; diff --git a/x-pack/plugins/oss_telemetry/server/plugin.ts b/x-pack/plugins/oss_telemetry/server/plugin.ts index 430fca2d39837..6a447da66952a 100644 --- a/x-pack/plugins/oss_telemetry/server/plugin.ts +++ b/x-pack/plugins/oss_telemetry/server/plugin.ts @@ -35,7 +35,7 @@ export class OssTelemetryPlugin implements Plugin { registerTasks({ taskManager: deps.taskManager, logger: this.logger, - elasticsearch: core.elasticsearch, + getStartServices: core.getStartServices, config: this.config, }); registerCollectors( diff --git a/x-pack/plugins/oss_telemetry/server/test_utils/index.ts b/x-pack/plugins/oss_telemetry/server/test_utils/index.ts index fc8c98c164623..4d703db3dcc64 100644 --- a/x-pack/plugins/oss_telemetry/server/test_utils/index.ts +++ b/x-pack/plugins/oss_telemetry/server/test_utils/index.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { APICaller, CoreSetup } from 'kibana/server'; +import { APICaller } from 'kibana/server'; import { of } from 'rxjs'; +import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; import { ConcreteTaskInstance, TaskStatus, @@ -43,10 +44,11 @@ const defaultMockSavedObjects = [ const defaultMockTaskDocs = [getMockTaskInstance()]; -export const getMockEs = (mockCallWithInternal: APICaller = getMockCallWithInternal()) => - (({ - createClient: () => ({ callAsInternalUser: mockCallWithInternal }), - } as unknown) as CoreSetup['elasticsearch']); +export const getMockEs = async (mockCallWithInternal: APICaller = getMockCallWithInternal()) => { + const client = elasticsearchServiceMock.createClusterClient(); + (client.callAsInternalUser as any) = mockCallWithInternal; + return client; +}; export const getMockCallWithInternal = (hits: unknown[] = defaultMockSavedObjects): APICaller => { return ((() => { diff --git a/x-pack/plugins/remote_clusters/public/application/services/ui_metric.ts b/x-pack/plugins/remote_clusters/public/application/services/ui_metric.ts index 91354155cacb0..4fc3c438e76d6 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/ui_metric.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/ui_metric.ts @@ -3,17 +3,24 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { UiStatsMetricType, METRIC_TYPE } from '@kbn/analytics'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; -import { UiStatsMetricType } from '@kbn/analytics'; import { UIM_APP_NAME } from '../constants'; -export let trackUiMetric: (metricType: UiStatsMetricType, eventName: string) => void; -export let METRIC_TYPE: UsageCollectionSetup['METRIC_TYPE']; +export { METRIC_TYPE }; + +export let usageCollection: UsageCollectionSetup | undefined; + +export function init(_usageCollection: UsageCollectionSetup): void { + usageCollection = _usageCollection; +} -export function init(usageCollection: UsageCollectionSetup): void { - trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, UIM_APP_NAME); - METRIC_TYPE = usageCollection.METRIC_TYPE; +export function trackUiMetric(metricType: UiStatsMetricType, name: string) { + if (!usageCollection) { + return; + } + const { reportUiStats } = usageCollection; + reportUiStats(UIM_APP_NAME, metricType, name); } /** diff --git a/x-pack/plugins/remote_clusters/server/plugin.ts b/x-pack/plugins/remote_clusters/server/plugin.ts index b86f16228878a..fca4a5dbc5f94 100644 --- a/x-pack/plugins/remote_clusters/server/plugin.ts +++ b/x-pack/plugins/remote_clusters/server/plugin.ts @@ -29,15 +29,9 @@ export class RemoteClustersServerPlugin implements Plugin this.licenseStatus = { valid: false }; } - async setup( - { http, elasticsearch: elasticsearchService }: CoreSetup, - { licensing, cloud }: Dependencies - ) { - const elasticsearch = await elasticsearchService.adminClient; + async setup({ http }: CoreSetup, { licensing, cloud }: Dependencies) { const router = http.createRouter(); const routeDependencies: RouteDependencies = { - elasticsearch, - elasticsearchService, router, getLicenseStatus: () => this.licenseStatus, config: { diff --git a/x-pack/plugins/remote_clusters/server/types.ts b/x-pack/plugins/remote_clusters/server/types.ts index 85678cba92f19..23f4ed158c2d4 100644 --- a/x-pack/plugins/remote_clusters/server/types.ts +++ b/x-pack/plugins/remote_clusters/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter, ElasticsearchServiceSetup, IClusterClient } from 'kibana/server'; +import { IRouter } from 'kibana/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { CloudSetup } from '../../cloud/server'; @@ -16,8 +16,6 @@ export interface Dependencies { export interface RouteDependencies { router: IRouter; getLicenseStatus: () => LicenseStatus; - elasticsearchService: ElasticsearchServiceSetup; - elasticsearch: IClusterClient; config: { isCloudEnabled: boolean; }; 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 dfc94042ef930..5bdfa7d4886aa 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -78,14 +78,14 @@ describe('license features', function() { expect(subscriptionHandler.mock.calls[1]).toMatchInlineSnapshot(` Array [ Object { - "allowLogin": false, - "allowRbac": false, - "allowRoleDocumentLevelSecurity": false, - "allowRoleFieldLevelSecurity": false, - "allowSubFeaturePrivileges": false, - "showLinks": false, - "showLogin": false, - "showRoleMappingsManagement": false, + "allowLogin": true, + "allowRbac": true, + "allowRoleDocumentLevelSecurity": true, + "allowRoleFieldLevelSecurity": true, + "allowSubFeaturePrivileges": true, + "showLinks": true, + "showLogin": true, + "showRoleMappingsManagement": true, }, ] `); diff --git a/x-pack/plugins/security/server/authorization/api_authorization.ts b/x-pack/plugins/security/server/authorization/api_authorization.ts index cc672fbc69e06..88b3f2c6f7155 100644 --- a/x-pack/plugins/security/server/authorization/api_authorization.ts +++ b/x-pack/plugins/security/server/authorization/api_authorization.ts @@ -24,7 +24,6 @@ export function initAPIAuthorization( // if there are no tags starting with "access:", just continue if (actionTags.length === 0) { - logger.debug('API endpoint is not marked with "access:" tags, skipping.'); return toolkit.next(); } @@ -34,11 +33,11 @@ export function initAPIAuthorization( // we've actually authorized the request if (checkPrivilegesResponse.hasAllRequested) { - logger.debug(`authorized for "${request.url.path}"`); + logger.debug(`User authorized for "${request.url.path}"`); return toolkit.next(); } - logger.debug(`not authorized for "${request.url.path}"`); + logger.warn(`User not authorized for "${request.url.path}": responding with 404`); return response.notFound(); }); } diff --git a/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.test.ts b/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.test.ts new file mode 100644 index 0000000000000..65922ea8e6de7 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.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 { isTaskSavedObjectNotFoundError } from './is_task_not_found_error'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; +import uuid from 'uuid'; + +describe('isTaskSavedObjectNotFoundError', () => { + test('identifies SavedObjects Not Found errors', () => { + const id = uuid.v4(); + // ensure the error created by SO parses as a string with the format we expect + expect( + `${SavedObjectsErrorHelpers.createGenericNotFoundError('task', id)}`.includes(`task/${id}`) + ).toBe(true); + + const errorBySavedObjectsHelper = SavedObjectsErrorHelpers.createGenericNotFoundError( + 'task', + id + ); + + expect(isTaskSavedObjectNotFoundError(errorBySavedObjectsHelper, id)).toBe(true); + }); + + test('identifies generic errors', () => { + const id = uuid.v4(); + expect(isTaskSavedObjectNotFoundError(new Error(`not found`), id)).toBe(false); + }); +}); diff --git a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_cluster_routes.js b/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.ts old mode 100755 new mode 100644 similarity index 50% rename from x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_cluster_routes.js rename to x-pack/plugins/task_manager/server/lib/is_task_not_found_error.ts index 86e18b02ddce2..8cc1c08f2a967 --- a/x-pack/legacy/plugins/logstash/server/routes/api/cluster/register_cluster_routes.js +++ b/x-pack/plugins/task_manager/server/lib/is_task_not_found_error.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerLoadRoute } from './register_load_route'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -export function registerLogstashClusterRoutes(server) { - registerLoadRoute(server); +export function isTaskSavedObjectNotFoundError(err: Error, taskId: string) { + return SavedObjectsErrorHelpers.isNotFoundError(err) && `${err}`.includes(taskId); } diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index fdfe0c068afcf..e837fcd9c0dec 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -38,17 +38,16 @@ export class TaskManagerPlugin public setup(core: CoreSetup, plugins: any): TaskManagerSetupContract { const logger = this.initContext.logger.get('taskManager'); const config$ = this.initContext.config.create(); - const elasticsearch = core.elasticsearch.adminClient; return { registerLegacyAPI: once((__LEGACY: PluginLegacyDependencies) => { config$.subscribe(async config => { - const [{ savedObjects }] = await core.getStartServices(); + const [{ savedObjects, elasticsearch }] = await core.getStartServices(); const savedObjectsRepository = savedObjects.createInternalRepository(['task']); this.legacyTaskManager$.next( createTaskManager(core, { logger, config, - elasticsearch, + elasticsearch: elasticsearch.legacy.client, savedObjectsRepository, savedObjectsSerializer: savedObjects.createSerializer(), }) diff --git a/x-pack/plugins/task_manager/server/task_pool.test.ts b/x-pack/plugins/task_manager/server/task_pool.test.ts index a827c1436c9bd..fb87b6290a3da 100644 --- a/x-pack/plugins/task_manager/server/task_pool.test.ts +++ b/x-pack/plugins/task_manager/server/task_pool.test.ts @@ -8,6 +8,7 @@ import sinon from 'sinon'; import { TaskPool, TaskPoolRunResult } from './task_pool'; import { mockLogger, resolvable, sleep } from './test_utils'; import { asOk } from './lib/result_type'; +import { SavedObjectsErrorHelpers } from '../../../../src/core/server'; describe('TaskPool', () => { test('occupiedWorkers are a sum of running tasks', async () => { @@ -101,6 +102,30 @@ describe('TaskPool', () => { expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); }); + test('should not log when running a Task fails due to the Task SO having been deleted while in flight', async () => { + const logger = mockLogger(); + const pool = new TaskPool({ + maxWorkers: 3, + logger, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', taskFailedToRun.id); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Task TaskType \\"shooooo\\" failed in attempt to run: Saved object [task/foo] not found", + ] + `); + expect(logger.warn).not.toHaveBeenCalled(); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + test('Running a task which fails still takes up capacity', async () => { const logger = mockLogger(); const pool = new TaskPool({ diff --git a/x-pack/plugins/task_manager/server/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool.ts index 90f1880a159da..8999fb48680ce 100644 --- a/x-pack/plugins/task_manager/server/task_pool.ts +++ b/x-pack/plugins/task_manager/server/task_pool.ts @@ -11,6 +11,7 @@ import { performance } from 'perf_hooks'; import { Logger } from './types'; import { TaskRunner } from './task_runner'; +import { isTaskSavedObjectNotFoundError } from './lib/is_task_not_found_error'; interface Opts { maxWorkers: number; @@ -125,7 +126,17 @@ export class TaskPool { taskRunner .run() .catch(err => { - this.logger.warn(`Task ${taskRunner.toString()} failed in attempt to run: ${err.message}`); + // If a task Saved Object can't be found by an in flight task runner + // we asssume the underlying task has been deleted while it was running + // so we will log this as a debug, rather than a warn + const errorLogLine = `Task ${taskRunner.toString()} failed in attempt to run: ${ + err.message + }`; + if (isTaskSavedObjectNotFoundError(err, taskRunner.id)) { + this.logger.debug(errorLogLine); + } else { + this.logger.warn(errorLogLine); + } }) .then(() => this.running.delete(taskRunner)); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a8574971426f5..a5d2c20447ad5 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -798,8 +798,6 @@ "data.search.searchBar.savedQueryDescriptionLabelText": "説明", "data.search.searchBar.savedQueryDescriptionText": "再度使用するクエリテキストとフィルターを保存します。", "data.search.searchBar.savedQueryForm.titleConflictText": "タイトルが既に保存されているクエリに使用されています", - "data.search.searchBar.savedQueryForm.titleMissingText": "名前が必要です", - "data.search.searchBar.savedQueryForm.whitespaceErrorText": "タイトルの始めと終わりにはスペースを使用できません", "data.search.searchBar.savedQueryFormCancelButtonText": "キャンセル", "data.search.searchBar.savedQueryFormSaveButtonText": "保存", "data.search.searchBar.savedQueryFormTitle": "クエリを保存", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index df5cccf1a9b97..ef749e9218e11 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -799,8 +799,6 @@ "data.search.searchBar.savedQueryDescriptionLabelText": "描述", "data.search.searchBar.savedQueryDescriptionText": "保存想要再次使用的查询文本和筛选。", "data.search.searchBar.savedQueryForm.titleConflictText": "标题与现有已保存查询有冲突", - "data.search.searchBar.savedQueryForm.titleMissingText": "“名称”必填", - "data.search.searchBar.savedQueryForm.whitespaceErrorText": "标题不能包含前导或尾随空格", "data.search.searchBar.savedQueryFormCancelButtonText": "取消", "data.search.searchBar.savedQueryFormSaveButtonText": "保存", "data.search.searchBar.savedQueryFormTitle": "保存查询", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index 5bbec1221a3ac..7557b17f5b67d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -328,6 +328,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent { setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); setAddModalVisibility(true); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx index 6ad52a5416163..619d85d99719b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx @@ -146,16 +146,13 @@ export const GroupByExpression = ({ {groupByTypes[groupBy].sizeRequired ? ( - 0 && termSize !== undefined} - error={errors.termSize} - > + 0} error={errors.termSize}> 0 && termSize !== undefined} - value={termSize} + isInvalid={errors.termSize.length > 0} + value={termSize || ''} onChange={e => { const { value } = e.target; - const termSizeVal = value !== '' ? parseFloat(value) : MIN_TERM_SIZE; + const termSizeVal = value !== '' ? parseFloat(value) : undefined; onChangeSelectedTermSize(termSizeVal); }} min={MIN_TERM_SIZE} diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts index a4833d9a3d7fe..2af1a9ac38e44 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts @@ -58,7 +58,7 @@ describe('Upgrade Assistant Usage Collector', () => { }), }, elasticsearch: { - adminClient: clusterClient, + legacy: { client: clusterClient }, }, }; }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts index 79d6e53c64ec0..9c2946db7f084 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts @@ -7,7 +7,7 @@ import { set } from 'lodash'; import { APICaller, - ElasticsearchServiceSetup, + ElasticsearchServiceStart, ISavedObjectsRepository, SavedObjectsServiceStart, } from 'src/core/server'; @@ -51,7 +51,7 @@ async function getDeprecationLoggingStatusValue(callAsCurrentUser: APICaller): P } export async function fetchUpgradeAssistantMetrics( - { adminClient }: ElasticsearchServiceSetup, + { legacy: { client: esClient } }: ElasticsearchServiceStart, savedObjects: SavedObjectsServiceStart ): Promise { const savedObjectsRepository = savedObjects.createInternalRepository(); @@ -60,7 +60,7 @@ export async function fetchUpgradeAssistantMetrics( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID ); - const callAsInternalUser = adminClient.callAsInternalUser.bind(adminClient); + const callAsInternalUser = esClient.callAsInternalUser.bind(esClient); const deprecationLoggingStatusValue = await getDeprecationLoggingStatusValue(callAsInternalUser); const getTelemetrySavedObject = ( @@ -107,7 +107,7 @@ export async function fetchUpgradeAssistantMetrics( } interface Dependencies { - elasticsearch: ElasticsearchServiceSetup; + elasticsearch: ElasticsearchServiceStart; savedObjects: SavedObjectsServiceStart; usageCollection: UsageCollectionSetup; } diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts index 6ccd073a9e020..bdca506cc7338 100644 --- a/x-pack/plugins/upgrade_assistant/server/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -11,7 +11,6 @@ import { CoreStart, PluginInitializerContext, Logger, - ElasticsearchServiceSetup, SavedObjectsClient, SavedObjectsServiceStart, } from '../../../../src/core/server'; @@ -40,7 +39,6 @@ export class UpgradeAssistantServerPlugin implements Plugin { // Properties set at setup private licensing?: LicensingPluginSetup; - private elasticSearchService?: ElasticsearchServiceSetup; // Properties set at start private savedObjectsServiceStart?: SavedObjectsServiceStart; @@ -59,10 +57,9 @@ export class UpgradeAssistantServerPlugin implements Plugin { } setup( - { http, elasticsearch, getStartServices, capabilities }: CoreSetup, + { http, getStartServices, capabilities }: CoreSetup, { usageCollection, cloud, licensing }: PluginsSetup ) { - this.elasticSearchService = elasticsearch; this.licensing = licensing; const router = http.createRouter(); @@ -88,13 +85,13 @@ export class UpgradeAssistantServerPlugin implements Plugin { registerTelemetryRoutes(dependencies); if (usageCollection) { - getStartServices().then(([{ savedObjects }]) => { + getStartServices().then(([{ savedObjects, elasticsearch }]) => { registerUpgradeAssistantUsageCollector({ elasticsearch, usageCollection, savedObjects }); }); } } - start({ savedObjects }: CoreStart) { + start({ savedObjects, elasticsearch }: CoreStart) { this.savedObjectsServiceStart = savedObjects; // The ReindexWorker uses a map of request headers that contain the authentication credentials @@ -107,7 +104,7 @@ export class UpgradeAssistantServerPlugin implements Plugin { this.worker = createReindexWorker({ credentialStore: this.credentialStore, licensing: this.licensing!, - elasticsearchService: this.elasticSearchService!, + elasticsearchService: elasticsearch, logger: this.logger, savedObjects: new SavedObjectsClient( this.savedObjectsServiceStart.createInternalRepository() diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts index 686f93b771e62..3d15916b17ff9 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; import { - ElasticsearchServiceSetup, + ElasticsearchServiceStart, kibanaResponseFactory, Logger, SavedObjectsClient, @@ -38,7 +38,7 @@ import { GetBatchQueueResponse, PostBatchResponse } from './types'; interface CreateReindexWorker { logger: Logger; - elasticsearchService: ElasticsearchServiceSetup; + elasticsearchService: ElasticsearchServiceStart; credentialStore: CredentialStore; savedObjects: SavedObjectsClient; licensing: LicensingPluginSetup; @@ -51,8 +51,8 @@ export function createReindexWorker({ savedObjects, licensing, }: CreateReindexWorker) { - const { adminClient } = elasticsearchService; - return new ReindexWorker(savedObjects, credentialStore, adminClient, logger, licensing); + const esClient = elasticsearchService.legacy.client; + return new ReindexWorker(savedObjects, credentialStore, esClient, logger, licensing); } const mapAnyErrorToKibanaHttpResponse = (e: any) => { diff --git a/x-pack/plugins/uptime/server/graphql/index.ts b/x-pack/plugins/uptime/server/graphql/index.ts index 49ba5583b417b..a88afd7ac769c 100644 --- a/x-pack/plugins/uptime/server/graphql/index.ts +++ b/x-pack/plugins/uptime/server/graphql/index.ts @@ -5,14 +5,13 @@ */ import { createMonitorStatesResolvers, monitorStatesSchema } from './monitor_states'; -import { createPingsResolvers, pingsSchema } from './pings'; +import { pingsSchema } from './pings'; import { CreateUMGraphQLResolvers } from './types'; import { unsignedIntegerResolverFunctions, unsignedIntegerSchema } from './unsigned_int_scalar'; export { DEFAULT_GRAPHQL_PATH } from './constants'; export const resolvers: CreateUMGraphQLResolvers[] = [ createMonitorStatesResolvers, - createPingsResolvers, unsignedIntegerResolverFunctions, ]; export const typeDefs: any[] = [pingsSchema, unsignedIntegerSchema, monitorStatesSchema]; diff --git a/x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts b/x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts index 6ab564fdeb532..040ad99ac0c64 100644 --- a/x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts +++ b/x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts @@ -169,7 +169,7 @@ export const monitorStatesSchema = gql` DESC } - extend type Query { + type Query { "Fetches the current state of Uptime monitors for the given parameters." getMonitorStates( dateRangeStart: String! diff --git a/x-pack/plugins/uptime/server/graphql/pings/index.ts b/x-pack/plugins/uptime/server/graphql/pings/index.ts index 57ec3242a7aa9..100ce29c398ec 100644 --- a/x-pack/plugins/uptime/server/graphql/pings/index.ts +++ b/x-pack/plugins/uptime/server/graphql/pings/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createPingsResolvers } from './resolvers'; export { pingsSchema } from './schema.gql'; diff --git a/x-pack/plugins/uptime/server/graphql/pings/resolvers.ts b/x-pack/plugins/uptime/server/graphql/pings/resolvers.ts deleted file mode 100644 index 2bb1e13bc4b1f..0000000000000 --- a/x-pack/plugins/uptime/server/graphql/pings/resolvers.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 { UMResolver } from '../../../../../legacy/plugins/uptime/common/graphql/resolver_types'; -import { - AllPingsQueryArgs, - PingResults, -} from '../../../../../legacy/plugins/uptime/common/graphql/types'; -import { UMServerLibs } from '../../lib/lib'; -import { UMContext } from '../types'; -import { CreateUMGraphQLResolvers } from '../types'; -import { savedObjectsAdapter } from '../../lib/saved_objects'; - -export type UMAllPingsResolver = UMResolver< - PingResults | Promise, - any, - AllPingsQueryArgs, - UMContext ->; - -export interface UMPingResolver { - allPings: () => PingResults; -} - -export const createPingsResolvers: CreateUMGraphQLResolvers = ( - libs: UMServerLibs -): { - Query: { - allPings: UMAllPingsResolver; - }; -} => ({ - Query: { - async allPings( - _resolver, - { monitorId, sort, size, status, dateRangeStart, dateRangeEnd, location, page }, - { APICaller, savedObjectsClient } - ): Promise { - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - savedObjectsClient - ); - - return await libs.requests.getPings({ - callES: APICaller, - dynamicSettings, - dateRangeStart, - dateRangeEnd, - monitorId, - status, - sort, - size, - location, - page, - }); - }, - }, -}); diff --git a/x-pack/plugins/uptime/server/graphql/pings/schema.gql.ts b/x-pack/plugins/uptime/server/graphql/pings/schema.gql.ts index 25767fb544104..179bca53db161 100644 --- a/x-pack/plugins/uptime/server/graphql/pings/schema.gql.ts +++ b/x-pack/plugins/uptime/server/graphql/pings/schema.gql.ts @@ -7,10 +7,6 @@ import gql from 'graphql-tag'; export const pingsSchema = gql` - schema { - query: Query - } - type PingResults { "Total number of matching pings" total: UnsignedInteger! @@ -20,28 +16,6 @@ export const pingsSchema = gql` pings: [Ping!]! } - type Query { - "Get a list of all recorded pings for all monitors" - allPings( - "Optional: the direction to sort by. Accepts 'asc' and 'desc'. Defaults to 'desc'." - sort: String - "Optional: the number of results to return." - size: Int - "Optional: the monitor ID filter." - monitorId: String - "Optional: the check status to filter by." - status: String - "The lower limit of the date range." - dateRangeStart: String! - "The upper limit of the date range." - dateRangeEnd: String! - "Optional: agent location to filter by." - location: String - "Optional: current page." - page: Int - ): PingResults! - } - type ContainerImage { name: String tag: String diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts index 112c8e97d4c00..cf8414a3b0a68 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts @@ -42,10 +42,16 @@ describe('getLatestMonitor', () => { hits: { hits: [ { + _id: 'fejwio32', _source: { - timestamp: 123456, + '@timestamp': '123456', monitor: { + duration: { + us: 12345, + }, id: 'testMonitor', + status: 'down', + type: 'http', }, }, }, @@ -64,7 +70,22 @@ describe('getLatestMonitor', () => { monitorId: 'testMonitor', }); - expect(result.timestamp).toBe(123456); + expect(result).toMatchInlineSnapshot(` + Object { + "@timestamp": "123456", + "docId": "fejwio32", + "monitor": Object { + "duration": Object { + "us": 12345, + }, + "id": "testMonitor", + "status": "down", + "type": "http", + }, + "timestamp": "123456", + } + `); + expect(result.timestamp).toBe('123456'); expect(result.monitor).not.toBeFalsy(); expect(result?.monitor?.id).toBe('testMonitor'); expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetLatestSearchParams); diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts index 9145ccca1b6d1..fcf773db23de6 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts @@ -17,16 +17,34 @@ describe('getAll', () => { { _source: { '@timestamp': '2018-10-30T18:51:59.792Z', + monitor: { + duration: { us: 2134 }, + id: 'foo', + status: 'up', + type: 'http', + }, }, }, { _source: { '@timestamp': '2018-10-30T18:53:59.792Z', + monitor: { + duration: { us: 2131 }, + id: 'foo', + status: 'up', + type: 'http', + }, }, }, { _source: { '@timestamp': '2018-10-30T18:55:59.792Z', + monitor: { + duration: { us: 2132 }, + id: 'foo', + status: 'up', + type: 'http', + }, }, }, ]; @@ -48,7 +66,7 @@ describe('getAll', () => { body: { query: { bool: { - filter: [{ range: { '@timestamp': { gte: 'now-1h', lte: 'now' } } }], + filter: [{ range: { timestamp: { gte: 'now-1h', lte: 'now' } } }], }, }, aggregations: { @@ -60,8 +78,7 @@ describe('getAll', () => { }, }, }, - sort: [{ '@timestamp': { order: 'desc' } }], - size: 12, + sort: [{ timestamp: { order: 'desc' } }], }, }; }); @@ -72,8 +89,7 @@ describe('getAll', () => { const result = await getPings({ callES: mockEsClient, dynamicSettings: defaultDynamicSettings, - dateRangeStart: 'now-1h', - dateRangeEnd: 'now', + dateRange: { from: 'now-1h', to: 'now' }, sort: 'asc', size: 12, }); @@ -95,15 +111,54 @@ describe('getAll', () => { await getPings({ callES: mockEsClient, dynamicSettings: defaultDynamicSettings, - dateRangeStart: 'now-1h', - dateRangeEnd: 'now', + dateRange: { from: 'now-1h', to: 'now' }, sort: 'asc', size: 12, }); - set(expectedGetAllParams, 'body.sort[0]', { '@timestamp': { order: 'asc' } }); + set(expectedGetAllParams, 'body.sort[0]', { timestamp: { order: 'asc' } }); expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetAllParams); + expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "search", + Object { + "body": Object { + "aggregations": Object { + "locations": Object { + "terms": Object { + "field": "observer.geo.name", + "missing": "N/A", + "size": 1000, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-1h", + "lte": "now", + }, + }, + }, + ], + }, + }, + "size": 12, + "sort": Array [ + Object { + "@timestamp": Object { + "order": "asc", + }, + }, + ], + }, + "index": "heartbeat-8*", + }, + ] + `); }); it('omits the sort param when no sort passed', async () => { @@ -112,12 +167,52 @@ describe('getAll', () => { await getPings({ callES: mockEsClient, dynamicSettings: defaultDynamicSettings, - dateRangeStart: 'now-1h', - dateRangeEnd: 'now', + dateRange: { from: 'now-1h', to: 'now' }, size: 12, }); - expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetAllParams); + expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "search", + Object { + "body": Object { + "aggregations": Object { + "locations": Object { + "terms": Object { + "field": "observer.geo.name", + "missing": "N/A", + "size": 1000, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-1h", + "lte": "now", + }, + }, + }, + ], + }, + }, + "size": 12, + "sort": Array [ + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], + }, + "index": "heartbeat-8*", + }, + ] + `); }); it('omits the size param when no size passed', async () => { @@ -126,14 +221,52 @@ describe('getAll', () => { await getPings({ callES: mockEsClient, dynamicSettings: defaultDynamicSettings, - dateRangeStart: 'now-1h', - dateRangeEnd: 'now', + dateRange: { from: 'now-1h', to: 'now' }, sort: 'desc', }); - delete expectedGetAllParams.body.size; - set(expectedGetAllParams, 'body.sort[0].@timestamp.order', 'desc'); - expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetAllParams); + expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "search", + Object { + "body": Object { + "aggregations": Object { + "locations": Object { + "terms": Object { + "field": "observer.geo.name", + "missing": "N/A", + "size": 1000, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-1h", + "lte": "now", + }, + }, + }, + ], + }, + }, + "size": 25, + "sort": Array [ + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], + }, + "index": "heartbeat-8*", + }, + ] + `); }); it('adds a filter for monitor ID', async () => { @@ -142,14 +275,57 @@ describe('getAll', () => { await getPings({ callES: mockEsClient, dynamicSettings: defaultDynamicSettings, - dateRangeStart: 'now-1h', - dateRangeEnd: 'now', + dateRange: { from: 'now-1h', to: 'now' }, monitorId: 'testmonitorid', }); - delete expectedGetAllParams.body.size; - expectedGetAllParams.body.query.bool.filter.push({ term: { 'monitor.id': 'testmonitorid' } }); - expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetAllParams); + expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "search", + Object { + "body": Object { + "aggregations": Object { + "locations": Object { + "terms": Object { + "field": "observer.geo.name", + "missing": "N/A", + "size": 1000, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-1h", + "lte": "now", + }, + }, + }, + Object { + "term": Object { + "monitor.id": "testmonitorid", + }, + }, + ], + }, + }, + "size": 25, + "sort": Array [ + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], + }, + "index": "heartbeat-8*", + }, + ] + `); }); it('adds a filter for monitor status', async () => { @@ -158,13 +334,56 @@ describe('getAll', () => { await getPings({ callES: mockEsClient, dynamicSettings: defaultDynamicSettings, - dateRangeStart: 'now-1h', - dateRangeEnd: 'now', + dateRange: { from: 'now-1h', to: 'now' }, status: 'down', }); - delete expectedGetAllParams.body.size; - expectedGetAllParams.body.query.bool.filter.push({ term: { 'monitor.status': 'down' } }); - expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetAllParams); + expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "search", + Object { + "body": Object { + "aggregations": Object { + "locations": Object { + "terms": Object { + "field": "observer.geo.name", + "missing": "N/A", + "size": 1000, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-1h", + "lte": "now", + }, + }, + }, + Object { + "term": Object { + "monitor.status": "down", + }, + }, + ], + }, + }, + "size": 25, + "sort": Array [ + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], + }, + "index": "heartbeat-8*", + }, + ] + `); }); }); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts index 299e3eb6ca3cf..a8e9ccb875a08 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts @@ -5,7 +5,7 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { Ping } from '../../../../../legacy/plugins/uptime/common/graphql/types'; +import { Ping } from '../../../../../legacy/plugins/uptime/common/runtime_types'; export interface GetLatestMonitorParams { /** @member dateRangeStart timestamp bounds */ @@ -53,11 +53,9 @@ export const getLatestMonitor: UMElasticsearchQueryFn = async ({ +export const getPings: UMElasticsearchQueryFn = async ({ callES, dynamicSettings, - dateRangeStart, - dateRangeEnd, + dateRange: { from, to }, + index, monitorId, status, sort, - size, + size: sizeParam, location, - page, }) => { + const size = sizeParam ?? DEFAULT_PAGE_SIZE; const sortParam = { sort: [{ '@timestamp': { order: sort ?? 'desc' } }] }; - const sizeParam = size ? { size } : undefined; - const filter: any[] = [{ range: { '@timestamp': { gte: dateRangeStart, lte: dateRangeEnd } } }]; + const filter: any[] = [{ range: { '@timestamp': { gte: from, lte: to } } }]; if (monitorId) { filter.push({ term: { 'monitor.id': monitorId } }); } @@ -71,7 +47,7 @@ export const getPings: UMElasticsearchQueryFn = asy ...queryContext, }, ...sortParam, - ...sizeParam, + size, aggregations: { locations: { terms: { @@ -85,8 +61,8 @@ export const getPings: UMElasticsearchQueryFn = asy }, }; - if (page) { - params.body.from = page * (size ?? 25); + if (index) { + params.body.from = index * size; } const { @@ -96,25 +72,22 @@ export const getPings: UMElasticsearchQueryFn = asy const locations = aggs?.locations ?? { buckets: [{ key: 'N/A', doc_count: 0 }] }; - const pings: Ping[] = hits.map(({ _id, _source }: any) => { - const timestamp = _source['@timestamp']; - + const pings: Ping[] = hits.map((doc: any) => { + const { _id, _source } = doc; // Calculate here the length of the content string in bytes, this is easier than in client JS, where // we don't have access to Buffer.byteLength. There are some hacky ways to do this in the // client but this is cleaner. - const httpBody: HttpBody | undefined = _source?.http?.response?.body; + const httpBody: HttpResponseBody | undefined = _source?.http?.response?.body; if (httpBody && httpBody.content) { httpBody.content_bytes = Buffer.byteLength(httpBody.content); } - return { id: _id, timestamp, ..._source }; + return { ..._source, timestamp: _source['@timestamp'], docId: _id }; }); - const results: PingResults = { + return { total: total.value, locations: locations.buckets.map((bucket: { key: string }) => bucket.key), pings, }; - - return results; }; diff --git a/x-pack/plugins/uptime/server/lib/requests/index.ts b/x-pack/plugins/uptime/server/lib/requests/index.ts index 445adc3c15a93..6317f665d377f 100644 --- a/x-pack/plugins/uptime/server/lib/requests/index.ts +++ b/x-pack/plugins/uptime/server/lib/requests/index.ts @@ -13,7 +13,7 @@ export { getMonitorLocations, GetMonitorLocationsParams } from './get_monitor_lo export { getMonitorStates, GetMonitorStatesParams } from './get_monitor_states'; export { getMonitorStatus, GetMonitorStatusParams } from './get_monitor_status'; export * from './get_monitor_status'; -export { getPings, GetPingsParams } from './get_pings'; +export { getPings } from './get_pings'; export { getPingHistogram, GetPingHistogramParams } from './get_ping_histogram'; export { UptimeRequests } from './uptime_requests'; export { getSnapshotCount, GetSnapshotCountParams } from './get_snapshot_counts'; diff --git a/x-pack/plugins/uptime/server/lib/requests/types.ts b/x-pack/plugins/uptime/server/lib/requests/types.ts deleted file mode 100644 index 53a4e989e3789..0000000000000 --- a/x-pack/plugins/uptime/server/lib/requests/types.ts +++ /dev/null @@ -1,72 +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 { Ping, PingResults } from '../../../../../legacy/plugins/uptime/common/graphql/types'; -import { UMElasticsearchQueryFn } from '../adapters'; -import { - GetPingHistogramParams, - HistogramResult, -} from '../../../../../legacy/plugins/uptime/common/types'; - -export interface GetAllParams { - /** @member dateRangeStart timestamp bounds */ - dateRangeStart: string; - - /** @member dateRangeEnd timestamp bounds */ - dateRangeEnd: string; - - /** @member monitorId optional limit by monitorId */ - monitorId?: string | null; - - /** @member status optional limit by check statuses */ - status?: string | null; - - /** @member sort optional sort by timestamp */ - sort?: string | null; - - /** @member size optional limit query size */ - size?: number | null; - - /** @member location optional location value for use in filtering*/ - location?: string | null; -} - -export interface GetLatestMonitorDocsParams { - /** @member dateRangeStart timestamp bounds */ - dateStart?: string; - - /** @member dateRangeEnd timestamp bounds */ - dateEnd?: string; - - /** @member monitorId optional limit to monitorId */ - monitorId?: string | null; -} - -/** - * Count the number of documents in heartbeat indices - */ -export interface UMPingsAdapter { - getAll: UMElasticsearchQueryFn; - - // Get the monitor meta info regardless of timestamp - getMonitor: UMElasticsearchQueryFn; - - getLatestMonitorStatus: UMElasticsearchQueryFn; - - getPingHistogram: UMElasticsearchQueryFn; -} - -export interface HistogramQueryResult { - key: number; - key_as_string: string; - doc_count: number; - down: { - doc_count: number; - }; - up: { - doc_count: number; - }; -} diff --git a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts index 9d3fa5aa08aed..e9a7aa94dd3aa 100644 --- a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts +++ b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts @@ -5,7 +5,12 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { Ping, PingResults } from '../../../../../legacy/plugins/uptime/common/graphql/types'; +import { + HistogramResult, + Ping, + PingsResponse as PingResults, + GetPingsParams, +} from '../../../../../legacy/plugins/uptime/common/runtime_types'; import { GetFilterBarParams, GetLatestMonitorParams, @@ -13,7 +18,6 @@ import { GetMonitorDetailsParams, GetMonitorLocationsParams, GetMonitorStatesParams, - GetPingsParams, GetPingHistogramParams, GetMonitorStatusParams, GetMonitorStatusResult, @@ -27,10 +31,7 @@ import { } from '../../../../../legacy/plugins/uptime/common/runtime_types'; import { GetMonitorStatesResult } from './get_monitor_states'; import { GetSnapshotCountParams } from './get_snapshot_counts'; -import { - HistogramResult, - MonitorDurationResult, -} from '../../../../../legacy/plugins/uptime/common/types'; +import { MonitorDurationResult } from '../../../../../legacy/plugins/uptime/common/types'; type ESQ = UMElasticsearchQueryFn; diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index c84ea71037953..c0412e588fa93 100644 --- a/x-pack/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index.ts @@ -5,7 +5,7 @@ */ import { createGetOverviewFilters } from './overview_filters'; -import { createGetPingsRoute } from './pings'; +import { createGetPingHistogramRoute, createGetPingsRoute } from './pings'; import { createGetDynamicSettingsRoute, createPostDynamicSettingsRoute } from './dynamic_settings'; import { createLogPageViewRoute } from './telemetry'; import { createGetSnapshotCount } from './snapshot'; @@ -15,10 +15,8 @@ import { createGetMonitorLocationsRoute, createGetStatusBarRoute, } from './monitors'; -import { createGetPingHistogramRoute } from './pings/get_ping_histogram'; import { createGetMonitorDurationRoute } from './monitors/monitors_durations'; import { createGetIndexPatternRoute, createGetIndexStatusRoute } from './index_state'; - export * from './types'; export { createRouteWithAuth } from './create_route_with_auth'; export { uptimeRouteWrapper } from './uptime_route_wrapper'; diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts deleted file mode 100644 index c76892103da6b..0000000000000 --- a/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts +++ /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 { schema } from '@kbn/config-schema'; -import { UMServerLibs } from '../../lib/lib'; -import { UMRestApiRouteFactory } from '../types'; -import { API_URLS } from '../../../../../legacy/plugins/uptime/common/constants/rest_api'; - -export const createGetAllRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ - method: 'GET', - path: API_URLS.PINGS, - validate: { - query: schema.object({ - dateRangeStart: schema.string(), - dateRangeEnd: schema.string(), - location: schema.maybe(schema.string()), - monitorId: schema.maybe(schema.string()), - size: schema.maybe(schema.number()), - sort: schema.maybe(schema.string()), - status: schema.maybe(schema.string()), - }), - }, - handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { - const { dateRangeStart, dateRangeEnd, location, monitorId, size, sort, status } = request.query; - - const result = await libs.requests.getPings({ - callES, - dynamicSettings, - dateRangeStart, - dateRangeEnd, - monitorId, - status, - sort, - size, - location, - }); - - return response.ok({ - body: { - ...result, - }, - }); - }, -}); diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts index cde9a8c4e47ea..80a887a7f64a9 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts @@ -5,37 +5,41 @@ */ import { schema } from '@kbn/config-schema'; +import { isLeft } from 'fp-ts/lib/Either'; +import { PathReporter } from 'io-ts/lib/PathReporter'; import { UMServerLibs } from '../../lib/lib'; import { UMRestApiRouteFactory } from '../types'; import { API_URLS } from '../../../../../legacy/plugins/uptime/common/constants/rest_api'; +import { GetPingsParamsType } from '../../../../../legacy/plugins/uptime/common/runtime_types'; export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', path: API_URLS.PINGS, validate: { query: schema.object({ - dateRangeStart: schema.string(), - dateRangeEnd: schema.string(), + from: schema.string(), + to: schema.string(), location: schema.maybe(schema.string()), monitorId: schema.maybe(schema.string()), + index: schema.maybe(schema.number()), size: schema.maybe(schema.number()), sort: schema.maybe(schema.string()), status: schema.maybe(schema.string()), }), }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { - const { dateRangeStart, dateRangeEnd, location, monitorId, size, sort, status } = request.query; + const { from, to, ...optional } = request.query; + const params = GetPingsParamsType.decode({ dateRange: { from, to }, ...optional }); + if (isLeft(params)) { + // eslint-disable-next-line no-console + console.error(new Error(PathReporter.report(params).join(';'))); + return response.badRequest({ body: { message: 'Received invalid request parameters.' } }); + } const result = await libs.requests.getPings({ callES, dynamicSettings, - dateRangeStart, - dateRangeEnd, - monitorId, - status, - sort, - size, - location, + ...params.right, }); return response.ok({ diff --git a/x-pack/plugins/uptime/server/rest_api/pings/index.ts b/x-pack/plugins/uptime/server/rest_api/pings/index.ts index abb7da26f994f..a10ab435e4b0a 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/index.ts @@ -5,3 +5,4 @@ */ export { createGetPingsRoute } from './get_pings'; +export { createGetPingHistogramRoute } from './get_ping_histogram'; diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 4d32a5ae9f53c..457b7621e84bd 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -100,6 +100,27 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) xyzSecret2: 'credential2', }, }, + { + id: 'preconfigured-es-index-action', + actionTypeId: '.index', + name: 'preconfigured_es_index_action', + config: { + index: 'functional-test-actions-index-preconfigured', + refresh: true, + executionTimeField: 'timestamp', + }, + }, + { + id: 'preconfigured.test.index-record', + actionTypeId: 'test.index-record', + name: 'Test:_Preconfigured_Index_Record', + config: { + unencrypted: 'ignored-but-required', + }, + secrets: { + encrypted: 'this-is-also-ignored-and-also-required', + }, + }, ])}`, ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts new file mode 100644 index 0000000000000..b04bc13ffc5e4 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +// from: x-pack/test/alerting_api_integration/common/config.ts +const ACTION_ID = 'preconfigured-es-index-action'; +const ES_TEST_INDEX_NAME = 'functional-test-actions-index-preconfigured'; + +// eslint-disable-next-line import/no-default-export +export default function indexTest({ getService }: FtrProviderContext) { + const es = getService('legacyEs'); + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('preconfigured index action', () => { + after(() => esArchiver.unload('empty_kibana')); + beforeEach(() => clearTestIndex(es)); + + it('should execute successfully when expected for a single body', async () => { + const { body: result } = await supertest + .post(`/api/action/${ACTION_ID}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + documents: [{ testing: [4, 5, 6] }], + }, + }) + .expect(200); + expect(result.status).to.eql('ok'); + + const items = await getTestIndexItems(es); + expect(items.length).to.eql(1); + + // check document sans timestamp + const document = items[0]._source; + const timestamp = document.timestamp; + delete document.timestamp; + expect(document).to.eql({ testing: [4, 5, 6] }); + + // check timestamp + const timestampTime = new Date(timestamp).getTime(); + const timeNow = Date.now(); + const timeMinuteAgo = timeNow - 1000 * 60; + expect(timestampTime).to.be.within(timeMinuteAgo, timeNow); + }); + }); +} + +async function clearTestIndex(es: any) { + return await es.indices.delete({ + index: ES_TEST_INDEX_NAME, + ignoreUnavailable: true, + }); +} + +async function getTestIndexItems(es: any) { + const result = await es.search({ + index: ES_TEST_INDEX_NAME, + }); + + return result.hits.hits; +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts index 80b512f3fb5e3..0b637326d4667 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts @@ -68,6 +68,18 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, referencedByCount: 0, }, + { + id: 'preconfigured-es-index-action', + isPreconfigured: true, + actionTypeId: '.index', + name: 'preconfigured_es_index_action', + config: { + index: 'functional-test-actions-index-preconfigured', + refresh: true, + executionTimeField: 'timestamp', + }, + referencedByCount: 0, + }, { id: 'my-slack1', isPreconfigured: true, @@ -90,6 +102,16 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, referencedByCount: 0, }, + { + id: 'preconfigured.test.index-record', + isPreconfigured: true, + actionTypeId: 'test.index-record', + name: 'Test:_Preconfigured_Index_Record', + config: { + unencrypted: 'ignored-but-required', + }, + referencedByCount: 0, + }, ]); break; default: @@ -167,6 +189,18 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, referencedByCount: 1, }, + { + id: 'preconfigured-es-index-action', + isPreconfigured: true, + actionTypeId: '.index', + name: 'preconfigured_es_index_action', + config: { + index: 'functional-test-actions-index-preconfigured', + refresh: true, + executionTimeField: 'timestamp', + }, + referencedByCount: 0, + }, { id: 'my-slack1', isPreconfigured: true, @@ -189,6 +223,16 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, referencedByCount: 0, }, + { + id: 'preconfigured.test.index-record', + isPreconfigured: true, + actionTypeId: 'test.index-record', + name: 'Test:_Preconfigured_Index_Record', + config: { + unencrypted: 'ignored-but-required', + }, + referencedByCount: 0, + }, ]); break; default: @@ -232,6 +276,18 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { case 'superuser at space1': expect(response.statusCode).to.eql(200); expect(response.body).to.eql([ + { + id: 'preconfigured-es-index-action', + isPreconfigured: true, + actionTypeId: '.index', + name: 'preconfigured_es_index_action', + config: { + index: 'functional-test-actions-index-preconfigured', + refresh: true, + executionTimeField: 'timestamp', + }, + referencedByCount: 0, + }, { id: 'my-slack1', isPreconfigured: true, @@ -254,6 +310,16 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, referencedByCount: 0, }, + { + id: 'preconfigured.test.index-record', + isPreconfigured: true, + actionTypeId: 'test.index-record', + name: 'Test:_Preconfigured_Index_Record', + config: { + unencrypted: 'ignored-but-required', + }, + referencedByCount: 0, + }, ]); break; default: diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts index d7ec2e78ccb30..8e002bcc8d3da 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts @@ -11,6 +11,7 @@ export default function actionsTests({ loadTestFile }: FtrProviderContext) { describe('Actions', () => { loadTestFile(require.resolve('./builtin_action_types/email')); loadTestFile(require.resolve('./builtin_action_types/es_index')); + loadTestFile(require.resolve('./builtin_action_types/es_index_preconfigured')); loadTestFile(require.resolve('./builtin_action_types/pagerduty')); loadTestFile(require.resolve('./builtin_action_types/server_log')); loadTestFile(require.resolve('./builtin_action_types/servicenow')); 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 6eed28cc381dd..d8e4f808f5cd2 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 @@ -165,6 +165,100 @@ instanceStateValue: true } }); + it('should schedule task, run alert and schedule preconfigured actions when appropriate', async () => { + const testStart = new Date(); + const reference = alertUtils.generateReference(); + const response = await alertUtils.createAlwaysFiringAction({ + reference, + indexRecordActionId: 'preconfigured.test.index-record', + }); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'global_read at space1': + case 'space_1_all at space2': + 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); + + // Wait for the action to index a document before disabling the alert and waiting for tasks to finish + await esTestIndexTool.waitForDocs('action:test.index-record', reference); + + await taskManagerUtils.waitForAllTasksIdle(testStart); + + const alertId = response.body.id; + await alertUtils.disable(alertId); + await taskManagerUtils.waitForEmpty(testStart); + + // Ensure only 1 alert executed with proper params + const alertSearchResult = await esTestIndexTool.search( + 'alert:test.always-firing', + reference + ); + expect(alertSearchResult.hits.total.value).to.eql(1); + expect(alertSearchResult.hits.hits[0]._source).to.eql({ + source: 'alert:test.always-firing', + reference, + state: {}, + params: { + index: ES_TEST_INDEX_NAME, + reference, + }, + alertInfo: { + alertId, + spaceId: space.id, + namespace: space.id, + name: 'abc', + tags: ['tag-A', 'tag-B'], + createdBy: user.fullName, + updatedBy: user.fullName, + }, + }); + + // Ensure only 1 action executed with proper params + const actionSearchResult = await esTestIndexTool.search( + 'action:test.index-record', + reference + ); + expect(actionSearchResult.hits.total.value).to.eql(1); + expect(actionSearchResult.hits.hits[0]._source).to.eql({ + config: { + unencrypted: 'ignored-but-required', + }, + secrets: { + encrypted: 'this-is-also-ignored-and-also-required', + }, + params: { + index: ES_TEST_INDEX_NAME, + reference, + message: ` +alertId: ${alertId}, +alertName: abc, +spaceId: ${space.id}, +tags: tag-A,tag-B, +alertInstanceId: 1, +instanceContextValue: true, +instanceStateValue: true +`.trim(), + }, + reference, + source: 'action:test.index-record', + }); + + await taskManagerUtils.waitForActionTaskParamsToBeCleanedUp(testStart); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + it('should pass updated alert params to executor', async () => { const testStart = new Date(); // create an alert diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts index 517c64f178af5..ec59e56b08308 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts @@ -45,6 +45,18 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, referencedByCount: 0, }, + { + id: 'preconfigured-es-index-action', + isPreconfigured: true, + actionTypeId: '.index', + name: 'preconfigured_es_index_action', + config: { + index: 'functional-test-actions-index-preconfigured', + refresh: true, + executionTimeField: 'timestamp', + }, + referencedByCount: 0, + }, { id: 'my-slack1', isPreconfigured: true, @@ -67,6 +79,16 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, referencedByCount: 0, }, + { + id: 'preconfigured.test.index-record', + isPreconfigured: true, + actionTypeId: 'test.index-record', + name: 'Test:_Preconfigured_Index_Record', + config: { + unencrypted: 'ignored-but-required', + }, + referencedByCount: 0, + }, ]); }); @@ -88,6 +110,18 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { objectRemover.add(Spaces.space1.id, createdAction.id, 'action'); await supertest.get(`${getUrlPrefix(Spaces.other.id)}/api/action/_getAll`).expect(200, [ + { + id: 'preconfigured-es-index-action', + isPreconfigured: true, + actionTypeId: '.index', + name: 'preconfigured_es_index_action', + config: { + index: 'functional-test-actions-index-preconfigured', + refresh: true, + executionTimeField: 'timestamp', + }, + referencedByCount: 0, + }, { id: 'my-slack1', isPreconfigured: true, @@ -110,6 +144,16 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }, referencedByCount: 0, }, + { + id: 'preconfigured.test.index-record', + isPreconfigured: true, + actionTypeId: 'test.index-record', + name: 'Test:_Preconfigured_Index_Record', + config: { + unencrypted: 'ignored-but-required', + }, + referencedByCount: 0, + }, ]); }); }); diff --git a/x-pack/test/api_integration/apis/infra/metadata.ts b/x-pack/test/api_integration/apis/infra/metadata.ts index b693881abcdf7..5187cc5e3ec26 100644 --- a/x-pack/test/api_integration/apis/infra/metadata.ts +++ b/x-pack/test/api_integration/apis/infra/metadata.ts @@ -12,6 +12,28 @@ import { } from '../../../../plugins/infra/common/http_api/metadata_api'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { DATES } from './constants'; + +const timeRange700 = { + from: DATES['7.0.0'].hosts.min, + to: DATES[`7.0.0`].hosts.max, +}; + +const timeRange660 = { + from: DATES['6.6.0'].docker.min, + to: DATES[`6.6.0`].docker.max, +}; + +const timeRange800withAws = { + from: DATES['8.0.0'].logs_and_metrics_with_aws.min, + to: DATES[`8.0.0`].logs_and_metrics_with_aws.max, +}; + +const timeRange800 = { + from: DATES['8.0.0'].logs_and_metrics.min, + to: DATES[`8.0.0`].logs_and_metrics.max, +}; + export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); @@ -34,6 +56,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'demo-stack-mysql-01', nodeType: InfraNodeType.host, + timeRange: timeRange700, }); if (metadata) { expect(metadata.features.length).to.be(12); @@ -53,6 +76,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: '631f36a845514442b93c3fdd2dc91bcd8feb680b8ac5832c7fb8fdc167bb938e', nodeType: InfraNodeType.container, + timeRange: timeRange660, }); if (metadata) { expect(metadata.features.length).to.be(10); @@ -74,6 +98,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc', nodeType: InfraNodeType.host, + timeRange: timeRange800withAws, }); if (metadata) { expect(metadata.features.length).to.be(58); @@ -114,6 +139,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'ip-172-31-47-9.us-east-2.compute.internal', nodeType: InfraNodeType.host, + timeRange: timeRange800withAws, }); if (metadata) { expect(metadata.features.length).to.be(19); @@ -155,6 +181,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: '14887487-99f8-11e9-9a96-42010a84004d', nodeType: InfraNodeType.pod, + timeRange: timeRange800withAws, }); if (metadata) { expect(metadata.features.length).to.be(29); @@ -200,6 +227,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'c74b04834c6d7cc1800c3afbe31d0c8c0c267f06e9eb45c2b0c2df3e6cee40c5', nodeType: InfraNodeType.container, + timeRange: timeRange800withAws, }); if (metadata) { expect(metadata.features.length).to.be(26); @@ -251,6 +279,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc', nodeType: 'host', + timeRange: timeRange800, }); if (metadata) { expect( @@ -265,6 +294,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'c1031331-9ae0-11e9-9a96-42010a84004d', nodeType: 'pod', + timeRange: timeRange800, }); if (metadata) { expect( diff --git a/x-pack/test/api_integration/apis/logstash/cluster/index.js b/x-pack/test/api_integration/apis/logstash/cluster/index.ts similarity index 71% rename from x-pack/test/api_integration/apis/logstash/cluster/index.js rename to x-pack/test/api_integration/apis/logstash/cluster/index.ts index f016fde97ee4b..1d4fbd40b252b 100644 --- a/x-pack/test/api_integration/apis/logstash/cluster/index.js +++ b/x-pack/test/api_integration/apis/logstash/cluster/index.ts @@ -3,8 +3,9 @@ * 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 }) { +export default function({ loadTestFile }: FtrProviderContext) { describe('cluster', () => { loadTestFile(require.resolve('./load')); }); diff --git a/x-pack/test/api_integration/apis/logstash/cluster/load.js b/x-pack/test/api_integration/apis/logstash/cluster/load.ts similarity index 83% rename from x-pack/test/api_integration/apis/logstash/cluster/load.js rename to x-pack/test/api_integration/apis/logstash/cluster/load.ts index a348c6e4857b1..0678c5faf82dc 100644 --- a/x-pack/test/api_integration/apis/logstash/cluster/load.js +++ b/x-pack/test/api_integration/apis/logstash/cluster/load.ts @@ -5,8 +5,9 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; -export default function({ getService }) { +export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('legacyEs'); diff --git a/x-pack/test/api_integration/apis/logstash/index.js b/x-pack/test/api_integration/apis/logstash/index.ts similarity index 76% rename from x-pack/test/api_integration/apis/logstash/index.js rename to x-pack/test/api_integration/apis/logstash/index.ts index 53293e5ff9423..582bef5a53bf2 100644 --- a/x-pack/test/api_integration/apis/logstash/index.js +++ b/x-pack/test/api_integration/apis/logstash/index.ts @@ -3,8 +3,9 @@ * 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 }) { +export default function({ loadTestFile }: FtrProviderContext) { describe('logstash', () => { loadTestFile(require.resolve('./pipelines')); loadTestFile(require.resolve('./pipeline')); diff --git a/x-pack/test/api_integration/apis/logstash/pipeline/delete.js b/x-pack/test/api_integration/apis/logstash/pipeline/delete.ts similarity index 90% rename from x-pack/test/api_integration/apis/logstash/pipeline/delete.js rename to x-pack/test/api_integration/apis/logstash/pipeline/delete.ts index 85813f4ed04d1..cdbf5a3e6a1fe 100644 --- a/x-pack/test/api_integration/apis/logstash/pipeline/delete.js +++ b/x-pack/test/api_integration/apis/logstash/pipeline/delete.ts @@ -3,8 +3,9 @@ * 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({ getService }) { +export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); describe('delete', () => { diff --git a/x-pack/test/api_integration/apis/logstash/pipeline/index.js b/x-pack/test/api_integration/apis/logstash/pipeline/index.ts similarity index 76% rename from x-pack/test/api_integration/apis/logstash/pipeline/index.js rename to x-pack/test/api_integration/apis/logstash/pipeline/index.ts index dcc8a01378e37..2697f7f428f5f 100644 --- a/x-pack/test/api_integration/apis/logstash/pipeline/index.js +++ b/x-pack/test/api_integration/apis/logstash/pipeline/index.ts @@ -3,8 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -export default function({ loadTestFile }) { +import { FtrProviderContext } from '../../../ftr_provider_context'; +export default function({ loadTestFile }: FtrProviderContext) { describe('pipeline', () => { loadTestFile(require.resolve('./load')); loadTestFile(require.resolve('./save')); diff --git a/x-pack/test/api_integration/apis/logstash/pipeline/load.js b/x-pack/test/api_integration/apis/logstash/pipeline/load.ts similarity index 82% rename from x-pack/test/api_integration/apis/logstash/pipeline/load.js rename to x-pack/test/api_integration/apis/logstash/pipeline/load.ts index eb2ab6500a9dc..a892f527a6e61 100644 --- a/x-pack/test/api_integration/apis/logstash/pipeline/load.js +++ b/x-pack/test/api_integration/apis/logstash/pipeline/load.ts @@ -5,9 +5,11 @@ */ import expect from '@kbn/expect'; -import pipeline from './fixtures/load'; +import { FtrProviderContext } from '../../../ftr_provider_context'; -export default function({ getService }) { +import pipeline from './fixtures/load.json'; + +export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); describe('list', () => { diff --git a/x-pack/test/api_integration/apis/logstash/pipeline/save.js b/x-pack/test/api_integration/apis/logstash/pipeline/save.ts similarity index 90% rename from x-pack/test/api_integration/apis/logstash/pipeline/save.js rename to x-pack/test/api_integration/apis/logstash/pipeline/save.ts index ad35ee21f00fc..2ca9fbe7d68e0 100644 --- a/x-pack/test/api_integration/apis/logstash/pipeline/save.js +++ b/x-pack/test/api_integration/apis/logstash/pipeline/save.ts @@ -5,8 +5,9 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; -export default function({ getService }) { +export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); describe('save', () => { diff --git a/x-pack/test/api_integration/apis/logstash/pipelines/delete.js b/x-pack/test/api_integration/apis/logstash/pipelines/delete.ts similarity index 90% rename from x-pack/test/api_integration/apis/logstash/pipelines/delete.js rename to x-pack/test/api_integration/apis/logstash/pipelines/delete.ts index 98ff5b99ea744..e71dc7f08ddc9 100644 --- a/x-pack/test/api_integration/apis/logstash/pipelines/delete.js +++ b/x-pack/test/api_integration/apis/logstash/pipelines/delete.ts @@ -3,8 +3,9 @@ * 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({ getService }) { +export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); describe('delete', () => { diff --git a/x-pack/test/api_integration/apis/logstash/pipelines/index.js b/x-pack/test/api_integration/apis/logstash/pipelines/index.ts similarity index 73% rename from x-pack/test/api_integration/apis/logstash/pipelines/index.js rename to x-pack/test/api_integration/apis/logstash/pipelines/index.ts index 3abe2ee5ac43d..510bd625b54a0 100644 --- a/x-pack/test/api_integration/apis/logstash/pipelines/index.js +++ b/x-pack/test/api_integration/apis/logstash/pipelines/index.ts @@ -3,8 +3,9 @@ * 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 }) { +export default function({ loadTestFile }: FtrProviderContext) { describe('pipelines', () => { loadTestFile(require.resolve('./list')); loadTestFile(require.resolve('./delete')); diff --git a/x-pack/test/api_integration/apis/logstash/pipelines/list.js b/x-pack/test/api_integration/apis/logstash/pipelines/list.ts similarity index 82% rename from x-pack/test/api_integration/apis/logstash/pipelines/list.js rename to x-pack/test/api_integration/apis/logstash/pipelines/list.ts index fe5c3222a2ab1..a4ef52791ab70 100644 --- a/x-pack/test/api_integration/apis/logstash/pipelines/list.js +++ b/x-pack/test/api_integration/apis/logstash/pipelines/list.ts @@ -5,9 +5,10 @@ */ import expect from '@kbn/expect'; -import pipelineList from './fixtures/list'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import pipelineList from './fixtures/list.json'; -export default function({ getService }) { +export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); describe('list', () => { diff --git a/x-pack/test/api_integration/apis/uptime/feature_controls.ts b/x-pack/test/api_integration/apis/uptime/feature_controls.ts index 4c3b7f97c9544..8b82735fc38b0 100644 --- a/x-pack/test/api_integration/apis/uptime/feature_controls.ts +++ b/x-pack/test/api_integration/apis/uptime/feature_controls.ts @@ -40,7 +40,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) const executePingsRequest = async (username: string, password: string, spaceId?: string) => { const basePath = spaceId ? `/s/${spaceId}` : ''; - const url = `${basePath}${API_URLS.PINGS}?sort=desc&dateRangeStart=${PINGS_DATE_RANGE_START}&dateRangeEnd=${PINGS_DATE_RANGE_END}`; + const url = `${basePath}${API_URLS.PINGS}?sort=desc&from=${PINGS_DATE_RANGE_START}&to=${PINGS_DATE_RANGE_END}`; return await supertest .get(url) .auth(username, password) diff --git a/x-pack/test/api_integration/apis/uptime/get_all_pings.ts b/x-pack/test/api_integration/apis/uptime/get_all_pings.ts index 666986e7008b7..0b3f5faccb044 100644 --- a/x-pack/test/api_integration/apis/uptime/get_all_pings.ts +++ b/x-pack/test/api_integration/apis/uptime/get_all_pings.ts @@ -22,7 +22,7 @@ export default function({ getService }: FtrProviderContext) { it('should get all pings stored in index', async () => { const { body: apiResponse } = await supertest .get( - `/api/uptime/pings?sort=desc&dateRangeStart=${PINGS_DATE_RANGE_START}&dateRangeEnd=${PINGS_DATE_RANGE_END}` + `/api/uptime/pings?sort=desc&from=${PINGS_DATE_RANGE_START}&to=${PINGS_DATE_RANGE_END}` ) .expect(200); @@ -33,21 +33,19 @@ export default function({ getService }: FtrProviderContext) { it('should sort pings according to timestamp', async () => { const { body: apiResponse } = await supertest - .get( - `/api/uptime/pings?sort=asc&dateRangeStart=${PINGS_DATE_RANGE_START}&dateRangeEnd=${PINGS_DATE_RANGE_END}` - ) + .get(`/api/uptime/pings?sort=asc&from=${PINGS_DATE_RANGE_START}&to=${PINGS_DATE_RANGE_END}`) .expect(200); expect(apiResponse.total).to.be(2); expect(apiResponse.pings.length).to.be(2); - expect(apiResponse.pings[0].timestamp).to.be('2018-10-30T14:49:23.889Z'); - expect(apiResponse.pings[1].timestamp).to.be('2018-10-30T18:51:56.792Z'); + expect(apiResponse.pings[0]['@timestamp']).to.be('2018-10-30T14:49:23.889Z'); + expect(apiResponse.pings[1]['@timestamp']).to.be('2018-10-30T18:51:56.792Z'); }); it('should return results of n length', async () => { const { body: apiResponse } = await supertest .get( - `/api/uptime/pings?sort=desc&size=1&dateRangeStart=${PINGS_DATE_RANGE_START}&dateRangeEnd=${PINGS_DATE_RANGE_END}` + `/api/uptime/pings?sort=desc&size=1&from=${PINGS_DATE_RANGE_START}&to=${PINGS_DATE_RANGE_END}` ) .expect(200); @@ -57,10 +55,10 @@ export default function({ getService }: FtrProviderContext) { }); it('should miss pings outside of date range', async () => { - const dateRangeStart = moment('2002-01-01').valueOf(); - const dateRangeEnd = moment('2002-01-02').valueOf(); + const from = moment('2002-01-01').valueOf(); + const to = moment('2002-01-02').valueOf(); const { body: apiResponse } = await supertest - .get(`/api/uptime/pings?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}`) + .get(`/api/uptime/pings?from=${from}&to=${to}`) .expect(200); expect(apiResponse.total).to.be(0); diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list.json deleted file mode 100644 index 330ec83931a62..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list.json +++ /dev/null @@ -1,320 +0,0 @@ -{ - "allPings": { - "total": 2000, - "locations": [ - "mpls" - ], - "pings": [ - { - "timestamp": "2019-09-11T03:40:34.410Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 413 - }, - "id": "0074-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.406Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 441 - }, - "id": "0073-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.406Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 482 - }, - "id": "0099-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.406Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 558 - }, - "id": "0098-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.406Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 304 - }, - "id": "0075-intermittent", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.405Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 487 - }, - "id": "0097-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.405Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 602 - }, - "id": "0049-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.390Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 365 - }, - "id": "0047-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.389Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 870 - }, - "id": "0077-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.387Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 2808 - }, - "id": "0076-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - } - ] - } -} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list_count.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list_count.json deleted file mode 100644 index 3a619f517626a..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list_count.json +++ /dev/null @@ -1,1569 +0,0 @@ -{ - "allPings": { - "total": 2000, - "locations": [ - "mpls" - ], - "pings": [ - { - "timestamp": "2019-09-11T03:40:34.410Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 413 - }, - "id": "0074-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.406Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 441 - }, - "id": "0073-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.406Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 482 - }, - "id": "0099-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.406Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 558 - }, - "id": "0098-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.406Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 304 - }, - "id": "0075-intermittent", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.405Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 487 - }, - "id": "0097-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.405Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 602 - }, - "id": "0049-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.390Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 365 - }, - "id": "0047-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.389Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 870 - }, - "id": "0077-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.387Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 2808 - }, - "id": "0076-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.386Z", - "http": { - "response": { - "status_code": 400, - "body": { - "bytes": 3, - "hash": "26d228663f13a88592a12d16cf9587caab0388b262d6d9f126ed62f9333aca94", - "content": "400", - "content_bytes": 3 - } - } - }, - "error": { - "message": "400 Bad Request", - "type": "validate" - }, - "monitor": { - "duration": { - "us": 4258 - }, - "id": "0050-down", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.386Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 4784 - }, - "id": "0048-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.376Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 14580 - }, - "id": "0072-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.376Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 14679 - }, - "id": "0096-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.375Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 15308 - }, - "id": "0092-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.375Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 15183 - }, - "id": "0069-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.375Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 15013 - }, - "id": "0093-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.375Z", - "http": { - "response": { - "status_code": 400, - "body": { - "bytes": 3, - "hash": "26d228663f13a88592a12d16cf9587caab0388b262d6d9f126ed62f9333aca94", - "content": "400", - "content_bytes": 3 - } - } - }, - "error": { - "message": "400 Bad Request", - "type": "validate" - }, - "monitor": { - "duration": { - "us": 15117 - }, - "id": "0070-down", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.375Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 14875 - }, - "id": "0071-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.375Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 14801 - }, - "id": "0095-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.375Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 15065 - }, - "id": "0032-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.375Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 14911 - }, - "id": "0094-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.374Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 16135 - }, - "id": "0046-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.374Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 15428 - }, - "id": "0091-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.374Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 15499 - }, - "id": "0067-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.374Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 15464 - }, - "id": "0068-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.374Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 21736 - }, - "id": "0090-intermittent", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.374Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 21874 - }, - "id": "0031-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.374Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 36584 - }, - "id": "0066-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 3148 - }, - "id": "0084-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 13442 - }, - "id": "0083-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 13666 - }, - "id": "0041-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 16290 - }, - "id": "0045-intermittent", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 17255 - }, - "id": "0042-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 500, - "body": { - "bytes": 3, - "hash": "0604cd3138feed202ef293e062da2f4720f77a05d25ee036a7a01c9cfcdd1f0a", - "content": "500", - "content_bytes": 3 - } - } - }, - "error": { - "message": "500 Internal Server Error", - "type": "validate" - }, - "monitor": { - "duration": { - "us": 17146 - }, - "id": "0030-intermittent", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "down", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 17770 - }, - "id": "0063-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 18194 - }, - "id": "0061-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 17587 - }, - "id": "0065-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 22666 - }, - "id": "0062-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 33311 - }, - "id": "0026-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 33506 - }, - "id": "0085-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 33974 - }, - "id": "0025-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 33693 - }, - "id": "0088-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 33833 - }, - "id": "0089-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 34600 - }, - "id": "0087-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 35573 - }, - "id": "0028-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 35830 - }, - "id": "0086-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 35698 - }, - "id": "0064-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 35594 - }, - "id": "0029-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 35652 - }, - "id": "0044-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - } - ] - } -} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list_monitor_id.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list_monitor_id.json deleted file mode 100644 index 5826fd9f3f540..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list_monitor_id.json +++ /dev/null @@ -1,475 +0,0 @@ -{ - "allPings": { - "total": 20, - "locations": [ - "mpls" - ], - "pings": [ - { - "timestamp": "2019-09-11T03:40:34.371Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 35534 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:40:04.370Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 3080 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:39:34.370Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 7810 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:39:04.371Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 1575 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:38:34.370Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 1787 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:38:04.370Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 654 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:37:34.370Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 15915 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:37:04.370Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 2679 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:36:34.371Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 2104 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:36:04.370Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 5759 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:35:34.373Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 7166 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:35:04.371Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 26830 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:34:34.371Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 993 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:34:04.381Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 3880 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:33:34.371Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 1604 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - } - ] - } -} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list_sort.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list_sort.json deleted file mode 100644 index b9b8deae2e564..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/ping_list_sort.json +++ /dev/null @@ -1,165 +0,0 @@ -{ - "allPings": { - "total": 20, - "locations": [ - "mpls" - ], - "pings": [ - { - "timestamp": "2019-09-11T03:31:04.380Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 56940 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:31:34.366Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 9861 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:32:04.372Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 2924 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:32:34.375Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 21665 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - }, - { - "timestamp": "2019-09-11T03:33:04.370Z", - "http": { - "response": { - "status_code": 200, - "body": { - "bytes": 3, - "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf", - "content": null, - "content_bytes": null - } - } - }, - "error": null, - "monitor": { - "duration": { - "us": 2128 - }, - "id": "0001-up", - "ip": "127.0.0.1", - "name": "", - "scheme": null, - "status": "up", - "type": "http" - }, - "observer": { - "geo": { - "name": "mpls" - } - } - } - ] - } -} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/index.ts b/x-pack/test/api_integration/apis/uptime/graphql/index.ts index 2e0b5e2eea2a5..862cce47f257a 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/index.ts +++ b/x-pack/test/api_integration/apis/uptime/graphql/index.ts @@ -13,6 +13,5 @@ export default function({ loadTestFile }: FtrProviderContext) { // verifying the pre-loaded documents are returned in a way that // matches the snapshots contained in './fixtures' loadTestFile(require.resolve('./monitor_states')); - loadTestFile(require.resolve('./ping_list')); }); } diff --git a/x-pack/test/api_integration/apis/uptime/graphql/ping_list.ts b/x-pack/test/api_integration/apis/uptime/graphql/ping_list.ts deleted file mode 100644 index c84b9c382acdd..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/ping_list.ts +++ /dev/null @@ -1,116 +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 expect from '@kbn/expect'; -import { pingsQueryString } from '../../../../../legacy/plugins/uptime/public/queries'; -import { expectFixtureEql } from './helpers/expect_fixture_eql'; -import { Ping, PingResults } from '../../../../../legacy/plugins/uptime/common/graphql/types'; - -const expectPingFixtureEql = (data: { allPings: PingResults }, fixtureName: string) => { - expectFixtureEql(data, fixtureName, d => d.allPings.pings.forEach((p: Ping) => delete p.id)); -}; - -export default function({ getService }: any) { - describe('pingList query', () => { - before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat')); - after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat')); - - const supertest = getService('supertest'); - - it('returns a list of pings for the given date range and default size', async () => { - const getPingsQuery = { - operationName: 'PingList', - query: pingsQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getPingsQuery }); - const { - allPings: { pings }, - } = data; - expect(pings).length(10); - - expectPingFixtureEql(data, 'ping_list'); - }); - - it('returns a list of pings for the date range and given size', async () => { - const SIZE = 50; - const getPingsQuery = { - operationName: 'PingList', - query: pingsQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - size: SIZE, - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getPingsQuery }); - const { - allPings: { pings }, - } = data; - expect(pings).length(SIZE); - expectPingFixtureEql(data, 'ping_list_count'); - }); - - it('returns a list of pings for a monitor ID', async () => { - const SIZE = 15; - const MONITOR_ID = '0001-up'; - const getPingsQuery = { - operationName: 'PingList', - query: pingsQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - monitorId: MONITOR_ID, - size: SIZE, - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getPingsQuery }); - expectPingFixtureEql(data, 'ping_list_monitor_id'); - }); - - it('returns a list of pings sorted ascending', async () => { - const SIZE = 5; - const MONITOR_ID = '0001-up'; - const getPingsQuery = { - operationName: 'PingList', - query: pingsQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - monitorId: MONITOR_ID, - size: SIZE, - sort: 'asc', - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getPingsQuery }); - - expectPingFixtureEql(data, 'ping_list_sort'); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/uptime/index.ts b/x-pack/test/api_integration/apis/uptime/index.ts index a21db08d58c4d..8def64b71a110 100644 --- a/x-pack/test/api_integration/apis/uptime/index.ts +++ b/x-pack/test/api_integration/apis/uptime/index.ts @@ -10,11 +10,12 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { const es = getService('legacyEs'); describe('uptime', () => { - before(() => - es.indices.delete({ - index: 'heartbeat*', - ignore: [404], - }) + before( + async () => + await es.indices.delete({ + index: 'heartbeat*', + ignore: [404], + }) ); loadTestFile(require.resolve('./feature_controls')); diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json index 2e5854f4d9866..6a832ad8536f7 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json @@ -1,16 +1,11 @@ { - "timestamp": "2019-09-11T03:40:34.371Z", "observer": { - "geo": { - "name": "mpls", - "location": "37.926868, -78.024902" - }, + "geo": { "name": "mpls", "location": "37.926868, -78.024902" }, "hostname": "avc-x1x" }, + "@timestamp": "2019-09-11T03:40:34.371Z", "monitor": { - "duration": { - "us": 24627 - }, + "duration": { "us": 24627 }, "ip": "127.0.0.1", "name": "", "check_group": "d76f0762-d445-11e9-88e3-3e80641b9c71", @@ -25,5 +20,7 @@ "domain": "localhost", "query": "r=200x1", "full": "http://localhost:5678/pattern?r=200x1" - } + }, + "docId": "h5toHm0B0I9WX_CznN_V", + "timestamp": "2019-09-11T03:40:34.371Z" } diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index 9b0cd61c22462..3f8df81856f5c 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -45,10 +45,11 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./telemetry_collectors')); }); describe('with real-world data', () => { - before('load heartbeat data', async () => await esArchiver.load('uptime/full_heartbeat')); - after('unload', async () => await esArchiver.unload('uptime/full_heartbeat')); + beforeEach('load heartbeat data', async () => await esArchiver.load('uptime/full_heartbeat')); + afterEach('unload', async () => await esArchiver.unload('uptime/full_heartbeat')); loadTestFile(require.resolve('./monitor_latest_status')); loadTestFile(require.resolve('./ping_histogram')); + loadTestFile(require.resolve('./ping_list')); loadTestFile(require.resolve('./monitor_duration')); loadTestFile(require.resolve('./doc_count')); }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/ping_list.ts b/x-pack/test/api_integration/apis/uptime/rest/ping_list.ts new file mode 100644 index 0000000000000..a261763d5991f --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/ping_list.ts @@ -0,0 +1,178 @@ +/* + * 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 { isLeft } from 'fp-ts/lib/Either'; +import { PathReporter } from 'io-ts/lib/PathReporter'; +import { PingsResponseType } from '../../../../../legacy/plugins/uptime/common/runtime_types'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +function decodePingsResponseData(response: any) { + const decoded = PingsResponseType.decode(response); + if (isLeft(decoded)) { + throw Error(JSON.stringify(PathReporter.report(decoded), null, 2)); + } + return decoded.right; +} + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + describe('pingList query', () => { + before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat')); + after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat')); + + it('returns a list of pings for the given date range and default size', async () => { + const from = '2019-01-28T17:40:08.078Z'; + const to = '2025-01-28T19:00:16.078Z'; + + const apiResponse = await supertest.get(`/api/uptime/pings?from=${from}&to=${to}&size=10`); + + const { total, locations, pings } = decodePingsResponseData(apiResponse.body); + + expect(total).to.be(2000); + expect(locations).to.eql(['mpls']); + expect(pings).length(10); + expect(pings.map(({ monitor: { id } }) => id)).to.eql([ + '0074-up', + '0073-up', + '0099-up', + '0098-up', + '0075-intermittent', + '0097-up', + '0049-up', + '0047-up', + '0077-up', + '0076-up', + ]); + }); + + it('returns a list of pings for the date range and given size', async () => { + const from = '2019-01-28T17:40:08.078Z'; + const to = '2025-01-28T19:00:16.078Z'; + const size = 50; + + const apiResponse = await supertest.get( + `/api/uptime/pings?from=${from}&to=${to}&size=${size}` + ); + + const { total, locations, pings } = decodePingsResponseData(apiResponse.body); + + expect(total).to.be(2000); + expect(locations).to.eql(['mpls']); + expect(pings).length(50); + expect(pings.map(({ monitor: { id } }) => id)).to.eql([ + '0074-up', + '0073-up', + '0099-up', + '0098-up', + '0075-intermittent', + '0097-up', + '0049-up', + '0047-up', + '0077-up', + '0076-up', + '0050-down', + '0048-up', + '0072-up', + '0096-up', + '0092-up', + '0069-up', + '0093-up', + '0070-down', + '0071-up', + '0095-up', + '0032-up', + '0094-up', + '0046-up', + '0091-up', + '0067-up', + '0068-up', + '0090-intermittent', + '0031-up', + '0066-up', + '0084-up', + '0083-up', + '0041-up', + '0045-intermittent', + '0042-up', + '0030-intermittent', + '0063-up', + '0061-up', + '0065-up', + '0062-up', + '0026-up', + '0085-up', + '0025-up', + '0088-up', + '0089-up', + '0087-up', + '0028-up', + '0086-up', + '0064-up', + '0029-up', + '0044-up', + ]); + }); + + it('returns a list of pings for a monitor ID', async () => { + const from = '2019-01-28T17:40:08.078Z'; + const to = '2025-01-28T19:00:16.078Z'; + const monitorId = '0001-up'; + const size = 15; + + const apiResponse = await supertest.get( + `/api/uptime/pings?from=${from}&to=${to}&monitorId=${monitorId}&size=${size}` + ); + + const { total, locations, pings } = decodePingsResponseData(apiResponse.body); + + expect(total).to.be(20); + expect(locations).to.eql(['mpls']); + pings.forEach(({ monitor: { id } }) => expect(id).to.eql('0001-up')); + expect(pings.map(({ timestamp }) => timestamp)).to.eql([ + '2019-09-11T03:40:34.371Z', + '2019-09-11T03:40:04.370Z', + '2019-09-11T03:39:34.370Z', + '2019-09-11T03:39:04.371Z', + '2019-09-11T03:38:34.370Z', + '2019-09-11T03:38:04.370Z', + '2019-09-11T03:37:34.370Z', + '2019-09-11T03:37:04.370Z', + '2019-09-11T03:36:34.371Z', + '2019-09-11T03:36:04.370Z', + '2019-09-11T03:35:34.373Z', + '2019-09-11T03:35:04.371Z', + '2019-09-11T03:34:34.371Z', + '2019-09-11T03:34:04.381Z', + '2019-09-11T03:33:34.371Z', + ]); + }); + + it('returns a list of pings sorted ascending', async () => { + const from = '2019-01-28T17:40:08.078Z'; + const to = '2025-01-28T19:00:16.078Z'; + const monitorId = '0001-up'; + const size = 5; + const sort = 'asc'; + + const apiResponse = await supertest.get( + `/api/uptime/pings?from=${from}&to=${to}&monitorId=${monitorId}&size=${size}&sort=${sort}` + ); + + const { total, locations, pings } = decodePingsResponseData(apiResponse.body); + + expect(total).to.be(20); + expect(locations).to.eql(['mpls']); + expect(pings.map(({ timestamp }) => timestamp)).to.eql([ + '2019-09-11T03:31:04.380Z', + '2019-09-11T03:31:34.366Z', + '2019-09-11T03:32:04.372Z', + '2019-09-11T03:32:34.375Z', + '2019-09-11T03:33:04.370Z', + ]); + }); + }); +} diff --git a/x-pack/test/functional/apps/uptime/monitor.ts b/x-pack/test/functional/apps/uptime/monitor.ts index 90ad1836c69d3..388d660f21eb3 100644 --- a/x-pack/test/functional/apps/uptime/monitor.ts +++ b/x-pack/test/functional/apps/uptime/monitor.ts @@ -23,8 +23,33 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await uptimeService.navigation.goToUptime(); }); - it('loads and displays uptime data based on date range', async () => { - await uptime.loadDataAndGoToMonitorPage(dateStart, dateEnd, monitorId); + after(async () => { + await esArchiver.unload(archive); + }); + + describe('navigation to monitor page', () => { + before(async () => { + await uptime.loadDataAndGoToMonitorPage(dateStart, dateEnd, monitorId); + }); + + it('displays ping data as expected', async () => { + await uptime.checkPingListInteractions( + [ + 'XZtoHm0B0I9WX_CznN-6', + '7ZtoHm0B0I9WX_CzJ96M', + 'pptnHm0B0I9WX_Czst5X', + 'I5tnHm0B0I9WX_CzPd46', + 'y5tmHm0B0I9WX_Czx93x', + 'XZtmHm0B0I9WX_CzUt3H', + '-JtlHm0B0I9WX_Cz3dyX', + 'k5tlHm0B0I9WX_CzaNxm', + 'NZtkHm0B0I9WX_Cz89w9', + 'zJtkHm0B0I9WX_CzftsN', + ], + 'mpls', + 'up' + ); + }); }); }); }; diff --git a/x-pack/test/functional/config.edge.js b/x-pack/test/functional/config.edge.js deleted file mode 100644 index 882fb6fea3686..0000000000000 --- a/x-pack/test/functional/config.edge.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export default async function({ readConfigFile }) { - const chromeConfig = await readConfigFile(require.resolve('./config')); - - return { - ...chromeConfig.getAll(), - - browser: { - type: 'msedge', - }, - - junit: { - reportName: 'MS Chromium Edge XPack UI Functional Tests', - }, - }; -} diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/data.json.gz b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/data.json.gz index 2b204d0bde271..454d260a518cd 100644 Binary files a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/data.json.gz and b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/mappings.json b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/mappings.json index cf647f5c53212..fbcfa4cbe49b3 100644 --- a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/mappings.json +++ b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/mappings.json @@ -9,45 +9,62 @@ "mappings": { "_meta": { "migrationMappingPropertyHashes": { - "action": "c0c235fba02ebd2a2412bcda79009b58", + "action": "6e96ac5e648f57523879661ea72525b7", "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", - "alert": "d49f9b8d1277c6004506eec20dc0b108", + "agent_actions": "ed270b46812f0fa1439366c428a2cf17", + "agent_configs": "38abaf89513877745c359e7700c0c66a", + "agent_events": "3231653fafe4ef3196fe3b32ab774bf2", + "agents": "c3eeb7b9d97176f15f6d126370ab23c7", + "alert": "7b44fba6773e37c806ce290ea9b7024e", "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-services-telemetry": "07ee1939fa4302c62ddc052ec03fed90", + "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", "canvas-element": "7390014e1091044523666d97247392fc", "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "config": "87aca8fdb053154f11383fce3dbf3edf", + "cases": "08b8b110dbca273d37e8aef131ecab61", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "ae24d22d5986d04124cc6568f771066f", "dashboard": "d00f614b29a80360e1190193fd333bab", + "datasources": "d4bc0c252b2b5683ff21ea32d00acffc", + "enrollment_api_keys": "28b91e20b105b6f928e2012600085d8f", + "epm-package": "0be91c6758421dd5d0f1a58e9e5bc7c3", "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", "index-pattern": "66eccb05066c5a89924f48a9e9736499", "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", - "inventory-view": "84b320fd67209906333ffce261128462", + "inventory-view": "9ecce5b58867403613d82fe496470b34", "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", "lens": "21c3ea0763beb1ecb0162529706b88c5", "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", "map": "23d7aa4a720d4938ccde3983f87bd58d", - "maps-telemetry": "a4229f8b16a6820c6d724b7e0c1f729d", + "maps-telemetry": "268da3a48066123fc5baf35abaa55014", "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", "namespace": "2f4316de49999235636386fe51dc06c1", + "outputs": "aee9782e0d500b867859650a36280165", "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", "references": "7997cf5a56cc02bdc9c93361bde732b0", "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", "search": "181661168bbadd1eff5902361e2a0d5c", "server": "ec97f1c5da1a19609a60874e5af1100c", - "siem-ui-timeline": "6485ab095be8d15246667b98a1a34295", + "siem-detection-engine-rule-actions": "90eee2e4635260f4be0a1da8f5bc0aa0", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", - "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", - "telemetry": "358ffaa88ba34a97d55af0933a117de4", + "telemetry": "36a616f7026dfa617d6655df850fe16d", "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", "type": "2f4316de49999235636386fe51dc06c1", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "updated_at": "00da57df13e94e9d98437d13ace4bfe0", "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "b6289473c8985c79b6c47eebc19a0ca5", "url": "c7f66a0df8b1b52f17c28c4adb111105", "visualization": "52d7a13ad68a150c4525b292d23e12cc" } @@ -64,6 +81,11 @@ "type": "object" }, "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, "type": "text" }, "secrets": { @@ -85,6 +107,145 @@ } } }, + "agent_actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "flattened" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "agent_configs": { + "properties": { + "datasources": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "text" + }, + "namespace": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "updated_on": { + "type": "keyword" + } + } + }, + "agent_events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_newest_revision": { + "type": "integer" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "type": "text" + }, + "default_api_key": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "text" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "text" + }, + "version": { + "type": "keyword" + } + } + }, "alert": { "properties": { "actions": { @@ -114,15 +275,18 @@ "apiKeyOwner": { "type": "keyword" }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, "createdBy": { "type": "keyword" }, "enabled": { "type": "boolean" }, - "interval": { - "type": "keyword" - }, "muteAll": { "type": "boolean" }, @@ -130,12 +294,24 @@ "type": "keyword" }, "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, "type": "text" }, "params": { "enabled": false, "type": "object" }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, "scheduledTaskId": { "type": "keyword" }, @@ -221,52 +397,947 @@ }, "apm-telemetry": { "properties": { - "has_any_services": { - "type": "boolean" - }, - "services_per_agent": { + "agents": { "properties": { "dotnet": { - "null_value": 0, - "type": "long" + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } }, "go": { - "null_value": 0, - "type": "long" + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } }, "java": { - "null_value": 0, - "type": "long" + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } }, "js-base": { - "null_value": 0, - "type": "long" + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } }, "nodejs": { - "null_value": 0, - "type": "long" + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } }, "python": { - "null_value": 0, - "type": "long" + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } }, "ruby": { - "null_value": 0, - "type": "long" + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } }, "rum-js": { - "null_value": 0, - "type": "long" + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } } } - } - } - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" + }, + "cardinality": { + "properties": { + "transaction": { + "properties": { + "name": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + }, + "user_agent": { + "properties": { + "original": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "counts": { + "properties": { + "agent_configuration": { + "properties": { + "all": { + "type": "long" + } + } + }, + "error": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "max_error_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "max_transaction_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "services": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "sourcemap": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "span": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "traces": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + } + } + }, + "has_any_services": { + "type": "boolean" + }, + "indices": { + "properties": { + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + }, + "shards": { + "properties": { + "total": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "ml": { + "properties": { + "all_jobs_count": { + "type": "long" + } + } + } + } + }, + "retainment": { + "properties": { + "error": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "span": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + }, + "tasks": { + "properties": { + "agent_configuration": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "agents": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "cardinality": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "groupings": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "indices_stats": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "processor_events": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "versions": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "properties": { + "apm_server": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + }, + "patch": { + "type": "long" + } + } + } + } + } + } + }, + "application_usage_totals": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + } + } + }, + "application_usage_transactional": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + }, + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" }, "@timestamp": { "type": "date" @@ -285,26 +1356,257 @@ "keyword": { "type": "keyword" } - }, - "type": "text" + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } } } }, - "canvas-workpad": { - "dynamic": "false", + "cases-user-actions": { "properties": { - "@created": { - "type": "date" + "action": { + "type": "keyword" }, - "@timestamp": { + "action_at": { "type": "date" }, - "name": { - "fields": { - "keyword": { + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { "type": "keyword" } - }, + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { "type": "text" } } @@ -389,6 +1691,136 @@ } } }, + "datasources": { + "properties": { + "config_id": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "processors": { + "type": "keyword" + }, + "streams": { + "properties": { + "config": { + "type": "flattened" + }, + "dataset": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "processors": { + "type": "keyword" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + } + } + }, + "enrollment_api_keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "epm-package": { + "properties": { + "installed": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, "file-upload-telemetry": { "properties": { "filesUploadedTotalCount": { @@ -538,6 +1970,26 @@ } } }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, "customOptions": { "properties": { "field": { @@ -572,6 +2024,18 @@ }, "metric": { "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, "type": { "type": "keyword" } @@ -699,9 +2163,19 @@ } } }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, "mapsTotalCount": { "type": "long" }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, "timeCaptured": { "type": "date" } @@ -803,7 +2277,7 @@ }, "type": "text" }, - "space": { + "visualization": { "fields": { "keyword": { "ignore_above": 256, @@ -828,6 +2302,37 @@ "namespace": { "type": "keyword" }, + "outputs": { + "properties": { + "api_key": { + "type": "keyword" + }, + "ca_sha256": { + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, "query": { "properties": { "description": { @@ -917,6 +2422,73 @@ } } }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "dynamic": "true", + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, "siem-ui-timeline": { "properties": { "columns": { @@ -1051,6 +2623,9 @@ "description": { "type": "text" }, + "eventType": { + "type": "keyword" + }, "favorite": { "properties": { "favoriteDate": { @@ -1255,6 +2830,9 @@ }, "telemetry": { "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, "enabled": { "type": "boolean" }, @@ -1262,11 +2840,15 @@ "type": "date" }, "lastVersionChecked": { - "ignore_above": 256, + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { "type": "keyword" }, "sendUsageFrom": { - "ignore_above": 256, "type": "keyword" }, "userHasSeenNotice": { @@ -1315,6 +2897,13 @@ } } }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, "type": { "type": "keyword" }, @@ -1391,6 +2980,13 @@ } } }, + "uptime-dynamic-settings": { + "properties": { + "heartbeatIndices": { + "type": "keyword" + } + } + }, "url": { "properties": { "accessCount": { diff --git a/x-pack/test/functional/es_archives/reporting/historic/data.json.gz b/x-pack/test/functional/es_archives/reporting/historic/data.json.gz deleted file mode 100644 index ecb85ec6faca4..0000000000000 Binary files a/x-pack/test/functional/es_archives/reporting/historic/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/reporting/historic/mappings.json b/x-pack/test/functional/es_archives/reporting/historic/mappings.json deleted file mode 100644 index 3a5af0158ce00..0000000000000 --- a/x-pack/test/functional/es_archives/reporting/historic/mappings.json +++ /dev/null @@ -1,386 +0,0 @@ -{ - "type": "index", - "value": { - "index": ".kibana", - "mappings": { - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "defaultIndex": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "xPackMonitoring:showBanner": { - "type": "boolean" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "graph-workspace": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "spaceId": { - "type": "keyword" - }, - "timelion-sheet": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "index": ".reporting-2018.03.11", - "mappings": { - "properties": { - "attempts": { - "type": "short" - }, - "completed_at": { - "type": "date" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "jobtype": { - "type": "keyword" - }, - "max_attempts": { - "type": "short" - }, - "output": { - "properties": { - "content": { - "enabled": false, - "type": "object" - }, - "content_type": { - "type": "keyword" - }, - "max_size_reached": { - "type": "boolean" - } - } - }, - "payload": { - "enabled": false, - "type": "object" - }, - "priority": { - "type": "byte" - }, - "process_expiration": { - "type": "date" - }, - "started_at": { - "type": "date" - }, - "status": { - "type": "keyword" - }, - "timeout": { - "type": "long" - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/reporting_page.js b/x-pack/test/functional/page_objects/reporting_page.js index 7cdd1c083239b..cdfafeec1bf46 100644 --- a/x-pack/test/functional/page_objects/reporting_page.js +++ b/x-pack/test/functional/page_objects/reporting_page.js @@ -19,24 +19,10 @@ export function ReportingPageProvider({ getService, getPageObjects }) { const log = getService('log'); const config = getService('config'); const testSubjects = getService('testSubjects'); - const esArchiver = getService('esArchiver'); const browser = getService('browser'); - const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['common', 'security', 'settings', 'share', 'timePicker']); + const PageObjects = getPageObjects(['common', 'security', 'share', 'timePicker']); class ReportingPage { - async initTests() { - log.debug('ReportingPage:initTests'); - await PageObjects.settings.navigateTo(); - await esArchiver.loadIfNeeded('../../functional/es_archives/logstash_functional'); - await esArchiver.load('reporting/historic'); - await kibanaServer.uiSettings.replace({ - defaultIndex: 'logstash-*', - }); - - await browser.setWindowSize(1600, 850); - } - async forceSharedItemsContainerSize({ width }) { await browser.execute(` var el = document.querySelector('[data-shared-items-container]'); @@ -130,6 +116,20 @@ export function ReportingPageProvider({ getService, getPageObjects }) { return await retry.try(async () => await testSubjects.find('generateReportButton')); } + async isGenerateReportButtonDisabled() { + const generateReportButton = await this.getGenerateReportButton(); + return await retry.try(async () => { + const isDisabled = await generateReportButton.getAttribute('disabled'); + return isDisabled; + }); + } + + async canReportBeCreated() { + await this.clickGenerateReportButton(); + const success = await this.checkForReportingToasts(); + return success; + } + async checkUsePrintLayout() { // The print layout checkbox slides in as part of an animation, and tests can // attempt to click it too quickly, leading to flaky tests. The 500ms wait allows diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index 7157bbfb3811b..0ebcb5c87deee 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function UptimePageProvider({ getPageObjects, getService }: FtrProviderContext) { const pageObjects = getPageObjects(['common', 'timePicker']); - const { common: commonService, navigation, alerts } = getService('uptime'); + const { alerts, common: commonService, monitor, navigation } = getService('uptime'); const retry = getService('retry'); return new (class UptimePage { @@ -137,6 +137,20 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo return commonService.clickPageSizeSelectPopoverItem(size); } + public async checkPingListInteractions( + timestamps: string[], + location?: string, + status?: string + ): Promise { + if (location) { + await monitor.setPingListLocation(location); + } + if (status) { + await monitor.setPingListStatus(status); + } + return monitor.checkForPingListTimestamps(timestamps); + } + public async resetFilters() { await this.inputFilterQuery(''); await commonService.resetStatusFilter(); diff --git a/x-pack/test/functional/services/uptime/monitor.ts b/x-pack/test/functional/services/uptime/monitor.ts index 3bdec4b6749d4..a3e3d953e2eb7 100644 --- a/x-pack/test/functional/services/uptime/monitor.ts +++ b/x-pack/test/functional/services/uptime/monitor.ts @@ -27,5 +27,22 @@ export function UptimeMonitorProvider({ getService }: FtrProviderContext) { await find.descendantExistsByCssSelector('canvas.mapboxgl-canvas', mapPanel); }); }, + async setPingListLocation(location: string) { + await testSubjects.click('xpack.uptime.pingList.locationSelect', 5000); + return testSubjects.click(`xpack.uptime.pingList.locationOptions.${location}`, 5000); + }, + async setPingListStatus(status: string) { + await testSubjects.click('xpack.uptime.pingList.statusSelect', 5000); + return testSubjects.click(`xpack.uptime.pingList.statusOptions.${status}`, 5000); + }, + async checkForPingListTimestamps(timestamps: string[]): Promise { + return retry.tryForTime(10000, async () => { + await Promise.all( + timestamps.map(timestamp => + testSubjects.existOrFail(`xpack.uptime.pingList.ping-${timestamp}`) + ) + ); + }); + }, }; } diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index c94e7116c5cea..bce8e08cd16d3 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -17,6 +17,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); const supertest = getService('supertest'); const find = getService('find'); + const retry = getService('retry'); async function createAlert(overwrites: Record = {}) { const { body: createdAlert } = await supertest @@ -38,8 +39,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return createdAlert; } - // FLAKY: https://github.com/elastic/kibana/issues/62472 - describe.skip('alerts', function() { + describe('alerts', function() { before(async () => { await pageObjects.common.navigateToApp('triggersActions'); await testSubjects.click('alertsTab'); @@ -48,10 +48,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should create an alert', async () => { const alertName = generateUniqueKey(); await pageObjects.triggersActionsUI.clickCreateAlertButton(); - const nameInput = await testSubjects.find('alertNameInput'); - await nameInput.click(); - await nameInput.clearValue(); - await nameInput.type(alertName); + await testSubjects.setValue('alertNameInput', alertName); await testSubjects.click('.index-threshold-SelectOption'); await testSubjects.click('selectIndexExpression'); const comboBox = await find.byCssSelector('#indexSelectSearchBox'); @@ -60,28 +57,28 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const filterSelectItem = await find.byCssSelector(`.euiFilterSelectItem`); await filterSelectItem.click(); await testSubjects.click('thresholdAlertTimeFieldSelect'); - const fieldOptions = await find.allByCssSelector('#thresholdTimeField option'); - await fieldOptions[1].click(); + await retry.try(async () => { + const fieldOptions = await find.allByCssSelector('#thresholdTimeField option'); + expect(fieldOptions[1]).not.to.be(undefined); + await fieldOptions[1].click(); + }); + await testSubjects.click('closePopover'); // need this two out of popup clicks to close them + const nameInput = await testSubjects.find('alertNameInput'); await nameInput.click(); - // test for normal connector - await testSubjects.click('.webhook-ActionTypeSelectOption'); - const webhookBodyInput = await find.byCssSelector('.ace_text-input'); - await webhookBodyInput.focus(); - await webhookBodyInput.type('{\\"test\\":1}'); - - await testSubjects.click('addAlertActionButton'); - // pre-configured connector is loaded an displayed correctly await testSubjects.click('.slack-ActionTypeSelectOption'); - expect(await (await find.byCssSelector('#my-slack1')).isDisplayed()).to.be(true); - const loggingMessageInput = await testSubjects.find('slackMessageTextArea'); - await loggingMessageInput.click(); - await loggingMessageInput.clearValue(); - await loggingMessageInput.type('test message'); + await testSubjects.click('createActionConnectorButton'); + const slackConnectorName = generateUniqueKey(); + await testSubjects.setValue('nameInput', slackConnectorName); + await testSubjects.setValue('slackWebhookUrlInput', 'https://test'); + await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + const createdConnectorToastTitle = await pageObjects.common.closeToast(); + expect(createdConnectorToastTitle).to.eql(`Created '${slackConnectorName}'`); + await testSubjects.setValue('slackMessageTextArea', 'test message'); await testSubjects.click('messageAddVariableButton'); - const variableMenuButton = await testSubjects.find('variableMenuButton-0'); - await variableMenuButton.click(); + await testSubjects.click('variableMenuButton-0'); + await testSubjects.click('saveAlertButton'); const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql(`Saved '${alertName}'`); @@ -132,7 +129,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should edit an alert', async () => { const createdAlert = await createAlert({ alertTypeId: '.index-threshold', - name: 'new alert', + name: generateUniqueKey(), params: { aggType: 'count', termSize: 5, @@ -160,11 +157,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const editLink = await testSubjects.findAll('alertsTableCell-editLink'); await editLink[0].click(); - const updatedAlertName = 'Changed Alert Name'; - const nameInputToUpdate = await testSubjects.find('alertNameInput'); - await nameInputToUpdate.click(); - await nameInputToUpdate.clearValue(); - await nameInputToUpdate.type(updatedAlertName); + const updatedAlertName = `Changed Alert Name ${generateUniqueKey()}`; + await testSubjects.setValue('alertNameInput', updatedAlertName, { clearWithKeyboard: true }); await find.clickByCssSelector('[data-test-subj="saveEditedAlertButton"]:not(disabled)'); @@ -217,10 +211,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const editLink = await testSubjects.findAll('alertsTableCell-editLink'); await editLink[0].click(); - const throttleInputToSetInitialValue = await testSubjects.find('throttleInput'); - await throttleInputToSetInitialValue.click(); - await throttleInputToSetInitialValue.clearValue(); - await throttleInputToSetInitialValue.type('1'); + await testSubjects.setValue('throttleInput', '1', { clearWithKeyboard: true }); await find.clickByCssSelector('[data-test-subj="saveEditedAlertButton"]:not(disabled)'); @@ -308,11 +299,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const editLink = await testSubjects.findAll('alertsTableCell-editLink'); await editLink[0].click(); - const updatedAlertName = 'Changed Alert Name'; - const nameInputToUpdate = await testSubjects.find('alertNameInput'); - await nameInputToUpdate.click(); - await nameInputToUpdate.clearValue(); - await nameInputToUpdate.type(updatedAlertName); + const updatedAlertName = `Changed Alert Name ${generateUniqueKey()}`; + await testSubjects.setValue('alertNameInput', updatedAlertName); await testSubjects.click('cancelSaveEditedAlertButton'); await find.waitForDeletedByCssSelector('[data-test-subj="cancelSaveEditedAlertButton"]'); diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts index c440971225d78..d664357c3ba12 100644 --- a/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts @@ -220,9 +220,9 @@ export default function({ getService }: FtrProviderContext) { duration: 1000000, }, kibana: { - namespace: 'default', saved_objects: [ { + namespace: 'default', type: 'event_log_test', id, }, diff --git a/x-pack/test/reporting/.gitignore b/x-pack/test/reporting/.gitignore new file mode 100644 index 0000000000000..99ee4c44686a0 --- /dev/null +++ b/x-pack/test/reporting/.gitignore @@ -0,0 +1 @@ +functional/reports/session/ diff --git a/x-pack/test/reporting/configs/chromium_functional.js b/x-pack/test/reporting/configs/chromium_functional.js index 05c3b6c142946..753d2b2a20ab9 100644 --- a/x-pack/test/reporting/configs/chromium_functional.js +++ b/x-pack/test/reporting/configs/chromium_functional.js @@ -5,6 +5,7 @@ */ export default async function({ readConfigFile }) { + // TODO move reporting tests to x-pack/test/functional/apps//reporting const functionalConfig = await readConfigFile(require.resolve('../../functional/config.js')); return { diff --git a/x-pack/test/reporting/functional/reporting.js b/x-pack/test/reporting/functional/reporting.js index 012f0922c28cf..6107363986a40 100644 --- a/x-pack/test/reporting/functional/reporting.js +++ b/x-pack/test/reporting/functional/reporting.js @@ -15,8 +15,14 @@ const mkdirAsync = promisify(fs.mkdir); const REPORTS_FOLDER = path.resolve(__dirname, 'reports'); +/* + * TODO Remove this file and spread the tests to various apps + */ + export default function({ getService, getPageObjects }) { - const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + const log = getService('log'); const config = getService('config'); const PageObjects = getPageObjects([ 'reporting', @@ -27,65 +33,32 @@ export default function({ getService, getPageObjects }) { 'visualize', 'visEditor', ]); - const log = getService('log'); describe('Reporting', () => { - before('initialize tests', async () => { - await PageObjects.reporting.initTests(); - }); - - const expectDisabledGenerateReportButton = async () => { - const generateReportButton = await PageObjects.reporting.getGenerateReportButton(); - await retry.try(async () => { - const isDisabled = await generateReportButton.getAttribute('disabled'); - expect(isDisabled).to.be('true'); + describe('Dashboard', () => { + before('initialize tests', async () => { + log.debug('ReportingPage:initTests'); + await esArchiver.loadIfNeeded('reporting/ecommerce'); + await esArchiver.loadIfNeeded('reporting/ecommerce_kibana'); + await browser.setWindowSize(1600, 850); }); - }; - - const expectEnabledGenerateReportButton = async () => { - const generateReportButton = await PageObjects.reporting.getGenerateReportButton(); - await retry.try(async () => { - const isDisabled = await generateReportButton.getAttribute('disabled'); - expect(isDisabled).to.be(null); + after('clean up archives', async () => { + await esArchiver.unload('reporting/ecommerce'); + await esArchiver.unload('reporting/ecommerce_kibana'); }); - }; - - const expectReportCanBeCreated = async () => { - await PageObjects.reporting.clickGenerateReportButton(); - const success = await PageObjects.reporting.checkForReportingToasts(); - expect(success).to.be(true); - }; - - const writeSessionReport = async (name, rawPdf, reportExt = 'pdf') => { - const sessionDirectory = path.resolve(REPORTS_FOLDER, 'session'); - await mkdirAsync(sessionDirectory, { recursive: true }); - const sessionReportPath = path.resolve(sessionDirectory, `${name}.${reportExt}`); - await writeFileAsync(sessionReportPath, rawPdf); - return sessionReportPath; - }; - - const getBaselineReportPath = (fileName, reportExt = 'pdf') => { - const baselineFolder = path.resolve(REPORTS_FOLDER, 'baseline'); - const fullPath = path.resolve(baselineFolder, `${fileName}.${reportExt}`); - log.debug(`getBaselineReportPath (${fullPath})`); - return fullPath; - }; - - describe('Dashboard', () => { - beforeEach(() => PageObjects.reporting.clearToastNotifications()); describe('Print PDF button', () => { it('is not available if new', async () => { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.reporting.openPdfReportingPanel(); - await expectDisabledGenerateReportButton(); + expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true'); }); it('becomes available when saved', async () => { await PageObjects.dashboard.saveDashboard('My PDF Dashboard'); await PageObjects.reporting.openPdfReportingPanel(); - await expectEnabledGenerateReportButton(); + expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null); }); }); @@ -95,15 +68,7 @@ export default function({ getService, getPageObjects }) { // function is taking about 15 seconds per comparison in jenkins. this.timeout(300000); await PageObjects.common.navigateToApp('dashboard'); - await PageObjects.dashboard.gotoDashboardEditMode('My PDF Dashboard'); - await PageObjects.reporting.setTimepickerInDataRange(); - const visualizations = PageObjects.dashboard.getTestVisualizationNames(); - - const tileMapIndex = visualizations.indexOf('Visualization TileMap'); - visualizations.splice(tileMapIndex, 1); - - await PageObjects.dashboard.addVisualizations(visualizations); - await PageObjects.dashboard.saveDashboard('report test'); + await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.checkUsePrintLayout(); await PageObjects.reporting.clickGenerateReportButton(); @@ -121,30 +86,36 @@ export default function({ getService, getPageObjects }) { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.reporting.openPngReportingPanel(); - await expectDisabledGenerateReportButton(); + expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true'); }); it('becomes available when saved', async () => { await PageObjects.dashboard.saveDashboard('My PNG Dash'); await PageObjects.reporting.openPngReportingPanel(); - await expectEnabledGenerateReportButton(); + expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null); }); }); describe('Preserve Layout', () => { it('matches baseline report', async function() { + const writeSessionReport = async (name, rawPdf, reportExt) => { + const sessionDirectory = path.resolve(REPORTS_FOLDER, 'session'); + await mkdirAsync(sessionDirectory, { recursive: true }); + const sessionReportPath = path.resolve(sessionDirectory, `${name}.${reportExt}`); + await writeFileAsync(sessionReportPath, rawPdf); + return sessionReportPath; + }; + const getBaselineReportPath = (fileName, reportExt) => { + const baselineFolder = path.resolve(REPORTS_FOLDER, 'baseline'); + const fullPath = path.resolve(baselineFolder, `${fileName}.${reportExt}`); + log.debug(`getBaselineReportPath (${fullPath})`); + return fullPath; + }; + this.timeout(300000); await PageObjects.common.navigateToApp('dashboard'); - await PageObjects.dashboard.gotoDashboardEditMode('My PNG Dash'); - await PageObjects.reporting.setTimepickerInDataRange(); - - const visualizations = PageObjects.dashboard.getTestVisualizationNames(); - const tileMapIndex = visualizations.indexOf('Visualization TileMap'); - visualizations.splice(tileMapIndex, 1); - - await PageObjects.dashboard.addVisualizations(visualizations); - await PageObjects.dashboard.saveDashboard('PNG report test'); + await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); await PageObjects.reporting.openPngReportingPanel(); await PageObjects.reporting.forceSharedItemsContainerSize({ width: 1405 }); await PageObjects.reporting.clickGenerateReportButton(); @@ -167,44 +138,64 @@ export default function({ getService, getPageObjects }) { }); describe('Discover', () => { + before('initialize tests', async () => { + log.debug('ReportingPage:initTests'); + await esArchiver.loadIfNeeded('reporting/ecommerce'); + await browser.setWindowSize(1600, 850); + }); + after('clean up archives', async () => { + await esArchiver.unload('reporting/ecommerce'); + }); + describe('Generate CSV button', () => { beforeEach(() => PageObjects.common.navigateToApp('discover')); it('is not available if new', async () => { await PageObjects.reporting.openCsvReportingPanel(); - await expectDisabledGenerateReportButton(); + expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true'); }); it('becomes available when saved', async () => { await PageObjects.discover.saveSearch('my search - expectEnabledGenerateReportButton'); await PageObjects.reporting.openCsvReportingPanel(); - await expectEnabledGenerateReportButton(); + expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null); }); it('generates a report with data', async () => { await PageObjects.reporting.setTimepickerInDataRange(); await PageObjects.discover.saveSearch('my search - with data - expectReportCanBeCreated'); await PageObjects.reporting.openCsvReportingPanel(); - await expectReportCanBeCreated(); + expect(await PageObjects.reporting.canReportBeCreated()).to.be(true); }); it('generates a report with no data', async () => { await PageObjects.reporting.setTimepickerInNoDataRange(); await PageObjects.discover.saveSearch('my search - no data - expectReportCanBeCreated'); await PageObjects.reporting.openCsvReportingPanel(); - await expectReportCanBeCreated(); + expect(await PageObjects.reporting.canReportBeCreated()).to.be(true); }); }); }); describe('Visualize', () => { + before('initialize tests', async () => { + log.debug('ReportingPage:initTests'); + await esArchiver.loadIfNeeded('reporting/ecommerce'); + await esArchiver.loadIfNeeded('reporting/ecommerce_kibana'); + await browser.setWindowSize(1600, 850); + }); + after('clean up archives', async () => { + await esArchiver.unload('reporting/ecommerce'); + await esArchiver.unload('reporting/ecommerce_kibana'); + }); + describe('Print PDF button', () => { it('is not available if new', async () => { await PageObjects.common.navigateToUrl('visualize', 'new'); await PageObjects.visualize.clickAreaChart(); - await PageObjects.visualize.clickNewSearch(); + await PageObjects.visualize.clickNewSearch('ecommerce'); await PageObjects.reporting.openPdfReportingPanel(); - await expectDisabledGenerateReportButton(); + expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true'); }); it('becomes available when saved', async () => { @@ -214,14 +205,16 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.clickGo(); await PageObjects.visualize.saveVisualization('my viz'); await PageObjects.reporting.openPdfReportingPanel(); - await expectEnabledGenerateReportButton(); + expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null); }); - it('matches baseline report', async function() { + it('downloaded PDF has OK status', async function() { // Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs // function is taking about 15 seconds per comparison in jenkins. this.timeout(180000); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); diff --git a/x-pack/test/reporting/functional/reports/baseline/dashboard_preserve_layout.pdf b/x-pack/test/reporting/functional/reports/baseline/dashboard_preserve_layout.pdf deleted file mode 100644 index b7370109c687c..0000000000000 Binary files a/x-pack/test/reporting/functional/reports/baseline/dashboard_preserve_layout.pdf and /dev/null differ diff --git a/x-pack/test/reporting/functional/reports/baseline/dashboard_preserve_layout.png b/x-pack/test/reporting/functional/reports/baseline/dashboard_preserve_layout.png index a0dfea9ef4fa7..1eb5f29d212c2 100644 Binary files a/x-pack/test/reporting/functional/reports/baseline/dashboard_preserve_layout.png and b/x-pack/test/reporting/functional/reports/baseline/dashboard_preserve_layout.png differ diff --git a/x-pack/test/reporting/functional/reports/baseline/dashboard_print.pdf b/x-pack/test/reporting/functional/reports/baseline/dashboard_print.pdf deleted file mode 100644 index bf2bca54ca2d7..0000000000000 Binary files a/x-pack/test/reporting/functional/reports/baseline/dashboard_print.pdf and /dev/null differ diff --git a/x-pack/test/reporting/functional/reports/baseline/visualize_print.pdf b/x-pack/test/reporting/functional/reports/baseline/visualize_print.pdf deleted file mode 100644 index c11967c12ebf5..0000000000000 Binary files a/x-pack/test/reporting/functional/reports/baseline/visualize_print.pdf and /dev/null differ diff --git a/x-pack/typings/rison_node.d.ts b/x-pack/typings/rison_node.d.ts index ec8e5c1f407ad..f830adc897445 100644 --- a/x-pack/typings/rison_node.d.ts +++ b/x-pack/typings/rison_node.d.ts @@ -5,7 +5,7 @@ */ declare module 'rison-node' { - export type RisonValue = null | boolean | number | string | RisonObject | RisonArray; + export type RisonValue = undefined | null | boolean | number | string | RisonObject | RisonArray; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface RisonArray extends Array {} diff --git a/yarn.lock b/yarn.lock index 8ca25cc18a8a2..19d28aea1acfd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2557,16 +2557,16 @@ resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== -"@sindresorhus/is@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.0.tgz#6ad4ca610f696098e92954ab431ff83bea0ce13f" - integrity sha512-lXKXfypKo644k4Da4yXkPCrwcvn6SlUW2X2zFbuflKHNjf0w9htru01bo26uMhleMXsDmnZ12eJLdrAZa9MANg== - "@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.6.0.tgz#ec7670432ae9c8eb710400d112c201a362d83393" @@ -3466,12 +3466,12 @@ "@svgr/plugin-svgo" "^4.2.0" loader-utils "^1.2.3" -"@szmarczak/http-timer@^4.0.0": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" - integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== dependencies: - defer-to-connect "^2.0.0" + defer-to-connect "^1.0.1" "@testim/chrome-version@^1.0.7": version "1.0.7" @@ -3721,16 +3721,6 @@ resolved "https://registry.yarnpkg.com/@types/browserslist-useragent/-/browserslist-useragent-3.0.0.tgz#d425c9818182ce71ce53866798cee9c7d41d6e53" integrity sha512-ZBvKzg3yyWNYEkwxAzdmUzp27sFvw+1m080/+2lwrt+eltNefn1f4fnpMyrjOla31p8zLleCYqQXw+3EETfn0w== -"@types/cacheable-request@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" - integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "*" - "@types/node" "*" - "@types/responselike" "*" - "@types/caseless@*": version "0.12.2" resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" @@ -4100,11 +4090,6 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/http-cache-semantics@*": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" - integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== - "@types/indent-string@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/indent-string/-/indent-string-3.0.0.tgz#9ebb391ceda548926f5819ad16405349641b999f" @@ -4236,13 +4221,6 @@ dependencies: "@types/node" "*" -"@types/keyv@*": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" - integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== - dependencies: - "@types/node" "*" - "@types/license-checker@15.0.0": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/license-checker/-/license-checker-15.0.0.tgz#685d69e2cf61ffd862320434601f51c85e28bba1" @@ -4714,13 +4692,6 @@ "@types/tough-cookie" "*" form-data "^2.5.0" -"@types/responselike@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" - integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== - dependencies: - "@types/node" "*" - "@types/retry@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" @@ -4736,10 +4707,10 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== -"@types/selenium-webdriver@4.0.9": - version "4.0.9" - resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.0.9.tgz#12621e55b2ef8f6c98bd17fe23fa720c6cba16bd" - integrity sha512-HopIwBE7GUXsscmt/J0DhnFXLSmO04AfxT6b8HAprknwka7pqEWquWDMXxCjd+NUHK9MkCe1SDKKsMiNmCItbQ== +"@types/selenium-webdriver@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.0.5.tgz#23041a4948c82daf2df9836e4d2358fec10d3e24" + integrity sha512-ma1aL1znI3ptEbSQgbywgadrRCJouPIACSfOl/bPwu/TPNSyyE/+o9jZ6+bpDVTtIdksZuVKpq4SR1ip3DRduw== "@types/semver@^5.5.0": version "5.5.0" @@ -5967,6 +5938,40 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +apidoc-core@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/apidoc-core/-/apidoc-core-0.11.1.tgz#b04a7e0292e4ac0d714b40789f1b92f414486c81" + integrity sha512-pt/ICBdFQCZTgL38Aw1XB3G9AajDU1JA5E3yoDEgg0mqbPTCkOL8AyWdysjvNtQS/kkXgSPazCZaZzZYqrPHog== + dependencies: + fs-extra "^8.1.0" + glob "^7.1.4" + iconv-lite "^0.5.0" + klaw-sync "^6.0.0" + lodash "~4.17.15" + semver "~6.3.0" + +apidoc-markdown@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/apidoc-markdown/-/apidoc-markdown-5.0.0.tgz#e2d59d7cbbaa10402b09cec3e8ec17a03a27be59" + integrity sha512-gp4I4MvtgJvZPikEd7lwn149jjnC454CanPhm5demROdHCuakY+3YtIKEgVrJOqnS2iwbeeF+u4riB9CoO11+A== + dependencies: + ejs "^3.0.1" + semver "^7.1.3" + yargs "^15.1.0" + +apidoc@^0.20.1: + version "0.20.1" + resolved "https://registry.yarnpkg.com/apidoc/-/apidoc-0.20.1.tgz#b29a2e2ae47e2df6a29e1f1527b94edf91853072" + integrity sha512-V54vkZ2lDFBiGn0qusZmHbMi4svuFBq0rjZAIe3nwYvBY7iztW78vKOyHyTr9ASaTB7EGe8hhLbpEnYAIO31TQ== + dependencies: + apidoc-core "^0.11.1" + commander "^2.20.0" + fs-extra "^8.1.0" + lodash "^4.17.15" + markdown-it "^10.0.0" + nodemon "^2.0.2" + winston "^3.2.1" + apollo-cache-control@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.1.1.tgz#173d14ceb3eb9e7cb53de7eb8b61bee6159d4171" @@ -7470,18 +7475,13 @@ binaryextensions@2: resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935" integrity sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA== -bindings@1, bindings@^1.5.0: +bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== dependencies: file-uri-to-path "1.0.0" -bindings@~1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" - integrity sha1-FK1hE4EtLTfXLme0ystLtyZQXxE= - bit-twiddle@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bit-twiddle/-/bit-twiddle-1.0.2.tgz#0c6c1fabe2b23d17173d9a61b7b7093eb9e1769e" @@ -7706,6 +7706,20 @@ boxen@^3.0.0: type-fest "^0.3.0" widest-line "^2.0.0" +boxen@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" + integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^3.0.0" + cli-boxes "^2.2.0" + string-width "^4.1.0" + term-size "^2.1.0" + type-fest "^0.8.1" + widest-line "^3.1.0" + brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -8015,7 +8029,7 @@ buffer@^5.1.0, buffer@^5.2.0: base64-js "^1.0.2" ieee754 "^1.1.4" -builtin-modules@^1.0.0, builtin-modules@^1.1.1: +builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= @@ -8162,13 +8176,6 @@ cache-loader@^4.1.0: neo-async "^2.6.1" schema-utils "^2.0.0" -cacheable-lookup@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.0.tgz#33b1e56f17507f5cf9bb46075112d65473fb7713" - integrity sha512-s2piO6LvA7xnL1AR03wuEdSx3BZT3tIJpZ56/lcJwzO/6DTJZlTs7X3lrvPxk6d1PlDe6PrVe2TjlUIZNFglAQ== - dependencies: - keyv "^4.0.0" - cacheable-request@^2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" @@ -8182,18 +8189,18 @@ cacheable-request@^2.1.1: normalize-url "2.0.1" responselike "1.0.2" -cacheable-request@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" - integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== dependencies: clone-response "^1.0.2" get-stream "^5.1.0" http-cache-semantics "^4.0.0" - keyv "^4.0.0" + keyv "^3.0.0" lowercase-keys "^2.0.0" normalize-url "^4.1.0" - responselike "^2.0.0" + responselike "^1.0.2" cachedir@2.3.0: version "2.3.0" @@ -8701,6 +8708,21 @@ chokidar@^2.0.0, chokidar@^2.1.2, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.2.2: + version "3.3.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" + integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.3.0" + optionalDependencies: + fsevents "~2.1.2" + chownr@^1.0.1, chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" @@ -9287,16 +9309,16 @@ commander@4.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83" integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw== -commander@^2.12.1, commander@^2.20.0, commander@^2.7.1: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== +commander@^2.20.0, commander@^2.7.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@^2.8.1: version "2.18.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" @@ -9489,6 +9511,18 @@ configstore@^3.1.2: write-file-atomic "^2.0.0" xdg-basedir "^3.0.0" +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + connect-history-api-fallback@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" @@ -10087,6 +10121,11 @@ crypto-random-string@^1.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + cson-parser@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/cson-parser/-/cson-parser-1.3.5.tgz#7ec675e039145533bf2a6a856073f1599d9c2d24" @@ -10701,7 +10740,7 @@ debug-fabulous@1.X: memoizee "0.4.X" object-assign "4.X" -debug@2, debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -10773,13 +10812,6 @@ decompress-response@^4.2.0: dependencies: mimic-response "^2.0.0" -decompress-response@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-5.0.0.tgz#7849396e80e3d1eba8cb2f75ef4930f76461cb0f" - integrity sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw== - dependencies: - mimic-response "^2.0.0" - decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" @@ -10931,10 +10963,10 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" -defer-to-connect@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" - integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" @@ -11484,6 +11516,13 @@ dot-prop@^4.1.0, dot-prop@^4.1.1: dependencies: is-obj "^1.0.0" +dot-prop@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" + integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== + dependencies: + is-obj "^2.0.0" + dotenv-defaults@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-1.0.2.tgz#441cf5f067653fca4bbdce9dd3b803f6f84c585d" @@ -11681,6 +11720,11 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== +ejs@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.0.2.tgz#745b01cdcfe38c1c6a2da3bbb2d9957060a31226" + integrity sha512-IncmUpn1yN84hy2shb0POJ80FWrfGNY0cxO9f4v+/sG7qcBvAtVWUA1IdzY/8EYUmOVhoKJVdJjNd3AZcnxOjA== + elastic-apm-http-client@^9.2.0: version "9.2.1" resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-9.2.1.tgz#e0e980ceb9975ff770bdbf2f5cdaac39fd70e8e6" @@ -12215,6 +12259,11 @@ es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: es6-iterator "^2.0.1" es6-symbol "^3.1.1" +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -13158,7 +13207,7 @@ extract-zip@1.6.7: mkdirp "0.5.1" yauzl "2.4.1" -extract-zip@^1.6.6, extract-zip@^1.6.7, extract-zip@^1.7.0: +extract-zip@^1.6.6, extract-zip@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== @@ -13388,17 +13437,6 @@ fetch-mock@^7.3.9: path-to-regexp "^2.2.1" whatwg-url "^6.5.0" -ffi@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/ffi/-/ffi-2.3.0.tgz#fa1a7b3d85c0fa8c83d96947a64b5192bc47f858" - integrity sha512-vkPA9Hf9CVuQ5HeMZykYvrZF2QNJ/iKGLkyDkisBnoOOFeFXZQhUPxBARPBIZMJVulvBI2R+jgofW03gyPpJcQ== - dependencies: - bindings "~1.2.0" - debug "2" - nan "2" - ref "1" - ref-struct "1" - figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -14054,7 +14092,7 @@ fs-exists-sync@^0.1.0: resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= -fs-extra@8.1.0, fs-extra@^8.0.1: +fs-extra@8.1.0, fs-extra@^8.0.1, fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== @@ -14151,7 +14189,7 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.1.0, fsevents@~2.1.1: +fsevents@~2.1.0, fsevents@~2.1.1, fsevents@~2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== @@ -14383,7 +14421,7 @@ get-stream@^2.2.0: object-assign "^4.0.1" pinkie-promise "^2.0.0" -get-stream@^4.0.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== @@ -14637,6 +14675,13 @@ global-dirs@^0.1.0: dependencies: ini "^1.3.4" +global-dirs@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" + integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== + dependencies: + ini "^1.3.5" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -14881,27 +14926,6 @@ got@5.6.0: unzip-response "^1.0.0" url-parse-lax "^1.0.0" -got@^10.6.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-10.6.0.tgz#ac3876261a4d8e5fc4f81186f79955ce7b0501dc" - integrity sha512-3LIdJNTdCFbbJc+h/EH0V5lpNpbJ6Bfwykk21lcQvQsEcrzdi/ltCyQehFHLzJ/ka0UMH4Slg0hkYvAZN9qUDg== - dependencies: - "@sindresorhus/is" "^2.0.0" - "@szmarczak/http-timer" "^4.0.0" - "@types/cacheable-request" "^6.0.1" - cacheable-lookup "^2.0.0" - cacheable-request "^7.0.1" - decompress-response "^5.0.0" - duplexer3 "^0.1.4" - get-stream "^5.0.0" - lowercase-keys "^2.0.0" - mimic-response "^2.1.0" - p-cancelable "^2.0.0" - p-event "^4.0.0" - responselike "^2.0.0" - to-readable-stream "^2.0.0" - type-fest "^0.10.0" - got@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/got/-/got-3.3.1.tgz#e5d0ed4af55fc3eef4d56007769d98192bcb2eca" @@ -14978,6 +15002,23 @@ got@^8.3.1, got@^8.3.2: url-parse-lax "^3.0.0" url-to-options "^1.0.1" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.4: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -15682,6 +15723,11 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.1, has@^1.0.3, has@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -16277,6 +16323,11 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + ignore@^3.1.2, ignore@^3.3.5: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" @@ -17051,11 +17102,6 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.0.0.tgz#038c31b774709641bda678b1f06a4e3227c10b3e" integrity sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g== -is-generator-function@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" - integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw== - is-glob@4.0.0, is-glob@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" @@ -17097,6 +17143,14 @@ is-installed-globally@0.1.0, is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" +is-installed-globally@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + dependencies: + global-dirs "^2.0.1" + is-path-inside "^3.0.1" + is-integer@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/is-integer/-/is-integer-1.0.7.tgz#6bde81aacddf78b659b6629d629cadc51a886d5c" @@ -17167,6 +17221,11 @@ is-npm@^1.0.0: resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= +is-npm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" + integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== + is-number-object@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" @@ -17199,6 +17258,11 @@ is-obj@^1.0.0, is-obj@^1.0.1: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + is-object@^1.0.1, is-object@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" @@ -17463,6 +17527,11 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + is2@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is2/-/is2-2.0.1.tgz#8ac355644840921ce435d94f05d3a94634d3481a" @@ -18345,11 +18414,6 @@ json-buffer@3.0.0: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - json-parse-better-errors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz#50183cd1b2d25275de069e9e71b467ac9eab973a" @@ -18585,7 +18649,7 @@ jsx-to-string@^1.4.0: json-stringify-pretty-compact "^1.0.1" react "^0.14.0" -jszip@^3.2.2: +jszip@^3.1.5: version "3.2.2" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d" integrity sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA== @@ -18761,12 +18825,12 @@ keyv@3.0.0: dependencies: json-buffer "3.0.0" -keyv@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.0.tgz#2d1dab694926b2d427e4c74804a10850be44c12f" - integrity sha512-U7ioE8AimvRVLfw4LffyOIRhL2xVgmE8T22L6i0BucSnBUyv4w+I7VN/zVZwRKHOI6ZRUcdMdWHQ8KSUvGpEog== +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== dependencies: - json-buffer "3.0.1" + json-buffer "3.0.0" killable@^1.0.1: version "1.0.1" @@ -18804,6 +18868,13 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + klaw@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" @@ -18864,6 +18935,13 @@ latest-version@^3.0.0, latest-version@^3.1.0: dependencies: package-json "^4.0.0" +latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + lazy-ass@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -19731,7 +19809,7 @@ lowercase-keys@1.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= -lowercase-keys@^1.0.0: +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== @@ -20384,7 +20462,7 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-response@^1.0.0: +mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== @@ -20394,11 +20472,6 @@ mimic-response@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== -mimic-response@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== - mimos@4.x.x: version "4.0.0" resolved "https://registry.yarnpkg.com/mimos/-/mimos-4.0.0.tgz#76e3d27128431cb6482fd15b20475719ad626a5a" @@ -20827,19 +20900,6 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" -ms-chromium-edge-driver@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/ms-chromium-edge-driver/-/ms-chromium-edge-driver-0.2.0.tgz#0e0c6fd9fd1d1d36db97b2b3d7e9d4ba4d2de456" - integrity sha512-RkDsBPnMLjRna7q4LlvtLb+CHPei9gZapnlxm3ayWKk3Ab6HmDsz/17xG2eyqkKX5UcKeo04YlLZ345tO7OolA== - dependencies: - extract-zip "^1.6.7" - got "^10.6.0" - lodash "4.17.15" - tslint "^6.1.0" - tslint-config-prettier "^1.18.0" - util "^0.12.2" - windows-registry "^0.1.5" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -20952,7 +21012,7 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@2, nan@^2.12.1, nan@^2.13.2: +nan@^2.12.1, nan@^2.13.2: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -21379,6 +21439,22 @@ nodemailer@^4.7.0: resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.7.0.tgz#4420e06abfffd77d0618f184ea49047db84f4ad8" integrity sha512-IludxDypFpYw4xpzKdMAozBSkzKHmNBvGanUREjJItgJ2NYcK/s8+PggVhj7c2yGFQykKsnnmv1+Aqo0ZfjHmw== +nodemon@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.3.tgz#e9c64df8740ceaef1cb00e1f3da57c0a93ef3714" + integrity sha512-lLQLPS90Lqwc99IHe0U94rDgvjo+G9I4uEIxRG3evSLROcqQ9hwc0AxlSHKS4T1JW/IMj/7N5mthiN58NL/5kw== + dependencies: + chokidar "^3.2.2" + debug "^3.2.6" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.7" + semver "^5.7.1" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.2" + update-notifier "^4.0.0" + "nomnom@>= 1.5.x": version "1.8.1" resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7" @@ -21401,6 +21477,13 @@ nopt@^2.2.0: dependencies: abbrev "1" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -22153,10 +22236,10 @@ p-cancelable@^0.4.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== -p-cancelable@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" - integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== p-defer@^1.0.0: version "1.0.0" @@ -22170,7 +22253,7 @@ p-each-series@^1.0.0: dependencies: p-reduce "^1.0.0" -p-event@^4.0.0, p-event@^4.1.0: +p-event@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.1.0.tgz#e92bb866d7e8e5b732293b1c8269d38e9982bf8e" integrity sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA== @@ -22340,6 +22423,16 @@ package-json@^5.0.0: registry-url "^3.1.0" semver "^5.5.0" +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + pad-component@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/pad-component/-/pad-component-0.0.1.tgz#ad1f22ce1bf0fdc0d6ddd908af17f351a404b8ac" @@ -22843,6 +22936,11 @@ picomatch@^2.0.4, picomatch@^2.0.5: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== +picomatch@^2.0.7: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -23487,6 +23585,11 @@ psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw== +pstree.remy@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" + integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A== + public-encrypt@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" @@ -23674,6 +23777,13 @@ punycode@^1.2.4, punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +pupa@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" + integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== + dependencies: + escape-goat "^2.0.0" + puppeteer-core@^1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-1.19.0.tgz#3c3f98edb5862583e3a9c19cbc0da57ccc63ba5c" @@ -23931,7 +24041,7 @@ raw-loader@~0.5.1: resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" integrity sha1-DD0L6u2KAclm2Xh793goElKpeao= -rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -25015,6 +25125,13 @@ readdirp@~3.2.0: dependencies: picomatch "^2.0.4" +readdirp@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" + integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== + dependencies: + picomatch "^2.0.7" + readline2@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" @@ -25226,31 +25343,6 @@ redux@^4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" -ref-struct@1, ref-struct@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ref-struct/-/ref-struct-1.1.0.tgz#5d5ee65ad41cefc3a5c5feb40587261e479edc13" - integrity sha1-XV7mWtQc78Olxf60BYcmHkee3BM= - dependencies: - debug "2" - ref "1" - -ref-union@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ref-union/-/ref-union-1.0.1.tgz#3a2397f862f1e75171d687268f43b3f17729f120" - integrity sha1-OiOX+GLx51Fx1ocmj0Oz8Xcp8SA= - dependencies: - debug "2" - ref "1" - -ref@1, ref@^1.2.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ref/-/ref-1.3.5.tgz#0e33f080cdb94a3d95312b2b3b1fd0f82044ca0f" - integrity sha512-2cBCniTtxcGUjDpvFfVpw323a83/0RLSGJJY5l5lcomZWhYpU2cuLdsvYqMixvsdLJ9+sTdzEkju8J8ZHDM2nA== - dependencies: - bindings "1" - debug "2" - nan "2" - reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" @@ -25377,6 +25469,13 @@ registry-auth-token@^3.0.1, registry-auth-token@^3.3.2: rc "^1.1.6" safe-buffer "^5.0.1" +registry-auth-token@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479" + integrity sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA== + dependencies: + rc "^1.2.8" + registry-url@^3.0.0, registry-url@^3.0.3, registry-url@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" @@ -25384,6 +25483,13 @@ registry-url@^3.0.0, registry-url@^3.0.3, registry-url@^3.1.0: dependencies: rc "^1.0.1" +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + regjsgen@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" @@ -25983,20 +26089,13 @@ resolve@~1.10.1: dependencies: path-parse "^1.0.6" -responselike@1.0.2: +responselike@1.0.2, responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= dependencies: lowercase-keys "^1.0.0" -responselike@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" - integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== - dependencies: - lowercase-keys "^2.0.0" - restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -26555,14 +26654,15 @@ select@^1.1.2: resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= -selenium-webdriver@^4.0.0-alpha.7: - version "4.0.0-alpha.7" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.7.tgz#e3879d8457fd7ad8e4424094b7dc0540d99e6797" - integrity sha512-D4qnTsyTr91jT8f7MfN+OwY0IlU5+5FmlO5xlgRUV6hDEV8JyYx2NerdTEqDDkNq7RZDYc4VoPALk8l578RBHw== +selenium-webdriver@^4.0.0-alpha.5: + version "4.0.0-alpha.5" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.5.tgz#e4683b3dbf827d70df09a7e43bf02ebad20fa7c1" + integrity sha512-hktl3DSrhzM59yLhWzDGHIX9o56DvA+cVK7Dw6FcJR6qQ4CGzkaHeXQPcdrslkWMTeq0Ci9AmCxq0EMOvm2Rkg== dependencies: - jszip "^3.2.2" - rimraf "^2.7.1" + jszip "^3.1.5" + rimraf "^2.6.3" tmp "0.0.30" + xml2js "^0.4.19" selfsigned@^1.10.7: version "1.10.7" @@ -26578,6 +26678,13 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -26627,7 +26734,7 @@ semver@^5.5.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0, semver@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -26637,6 +26744,11 @@ semver@^6.1.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b" integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ== +semver@^7.1.3: + version "7.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.0.tgz#91f7c70ec944a63e5dc7a74cde2da375d8e0853c" + integrity sha512-uyvgU/igkrMgNHwLgXvlpD9jEADbJhB0+JXSywoO47JgJ6c16iau9F9cjtc/E5o0PoqRYTiTIAPRKaYe84z6eQ== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -27790,7 +27902,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== @@ -28568,6 +28680,11 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" +term-size@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" + integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== + terser-webpack-plugin@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" @@ -28962,10 +29079,10 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" -to-readable-stream@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-2.1.0.tgz#82880316121bea662cdc226adb30addb50cb06e8" - integrity sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w== +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== to-regex-range@^2.1.0: version "2.1.1" @@ -29051,6 +29168,13 @@ topojson-client@3.0.0, topojson-client@^3.0.0: dependencies: commander "2" +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tough-cookie@>=2.3.3, tough-cookie@^2.0.0, tough-cookie@^2.3.3, tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -29246,37 +29370,6 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== -tslint-config-prettier@^1.18.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37" - integrity sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg== - -tslint@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.0.tgz#c6c611b8ba0eed1549bf5a59ba05a7732133d851" - integrity sha512-fXjYd/61vU6da04E505OZQGb2VCN2Mq3doeWcOIryuG+eqdmFUXTYVwdhnbEu2k46LNLgUYt9bI5icQze/j0bQ== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.1" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.10.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -29781,11 +29874,6 @@ type-detect@^1.0.0: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" integrity sha1-diIXzAbbJY7EiQihKY6LlRIejqI= -type-fest@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.10.0.tgz#7f06b2b9fbfc581068d1341ffabd0349ceafc642" - integrity sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw== - type-fest@^0.3.0, type-fest@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" @@ -29965,6 +30053,13 @@ unc-path-regex@^0.1.2: resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= +undefsafe@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" + integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== + dependencies: + debug "^2.2.0" + underscore.string@~3.3.4: version "3.3.5" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023" @@ -30152,6 +30247,13 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + unist-util-is@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.1.tgz#0c312629e3f960c66e931e812d3d80e77010947b" @@ -30321,6 +30423,25 @@ update-notifier@^2.5.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" +update-notifier@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" + integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== + dependencies: + boxen "^4.2.0" + chalk "^3.0.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.3.1" + is-npm "^4.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + pupa "^2.0.1" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + upper-case-first@^1.1.0, upper-case-first@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115" @@ -30483,16 +30604,6 @@ util@^0.11.0: dependencies: inherits "2.0.3" -util@^0.12.2: - version "0.12.2" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.2.tgz#54adb634c9e7c748707af2bf5a8c7ab640cbba2b" - integrity sha512-XE+MkWQvglYa+IOfBt5UFG93EmncEMP23UqpgDvVZVFBPxwmkK10QRp6pgU4xICPnWRf/t0zPv4noYSUq9gqUQ== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - safe-buffer "^5.1.2" - utila@^0.4.0, utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -31632,6 +31743,13 @@ widest-line@^2.0.1: dependencies: string-width "^2.1.1" +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + win-release@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/win-release/-/win-release-1.1.1.tgz#5fa55e02be7ca934edfc12665632e849b72e5209" @@ -31654,17 +31772,6 @@ window-size@^0.2.0: resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" integrity sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU= -windows-registry@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/windows-registry/-/windows-registry-0.1.5.tgz#92c25c960884b0d215e69395f52d8dfaa0ba4ad0" - integrity sha512-gMN3ets1fbdP+TApEbbX2TIfBK3MIH5+p9GMvIFS3CNLr7U0Khe5mRj/T5zvwo/pKdhJgDrCLYyaNSs7HYiBCw== - dependencies: - debug "^2.2.0" - ffi "^2.0.0" - ref "^1.2.0" - ref-struct "^1.0.2" - ref-union "^1.0.0" - windows-release@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" @@ -31940,6 +32047,11 @@ xdg-basedir@^3.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + xhr@^2.0.1: version "2.4.1" resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.4.1.tgz#ba982cced205ae5eec387169ac9dc77ca4853d38" @@ -31973,6 +32085,14 @@ xml-parse-from-string@^1.0.0: resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28" integrity sha1-qQKekp09vN7RafPG4oI42VpdWig= +xml2js@^0.4.19, xml2js@^0.4.5: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + xml2js@^0.4.22: version "0.4.22" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.22.tgz#4fa2d846ec803237de86f30aa9b5f70b6600de02" @@ -31982,14 +32102,6 @@ xml2js@^0.4.22: util.promisify "~1.0.0" xmlbuilder "~11.0.0" -xml2js@^0.4.5: - version "0.4.19" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" - integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== - dependencies: - sax ">=0.6.0" - xmlbuilder "~9.0.1" - xml@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" @@ -32261,7 +32373,7 @@ yargs@^13.2.2, yargs@^13.3.0: y18n "^4.0.0" yargs-parser "^13.1.1" -yargs@^15.0.2, yargs@^15.3.1: +yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1: version "15.3.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==