diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc index 815e731760785..1f064c1cad3fd 100644 --- a/docs/apm/advanced-queries.asciidoc +++ b/docs/apm/advanced-queries.asciidoc @@ -10,7 +10,7 @@ so it's easy to share a specific query or view with others. In the screenshot below, you can begin to see some of the transaction fields available for filtering on: [role="screenshot"] -image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM UI in Kibana] +image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] [float] ==== Example queries diff --git a/docs/apm/agent-configuration.asciidoc b/docs/apm/agent-configuration.asciidoc index 4d2ae5d01688c..6f147d0e3223a 100644 --- a/docs/apm/agent-configuration.asciidoc +++ b/docs/apm/agent-configuration.asciidoc @@ -2,18 +2,15 @@ [[agent-configuration]] === APM Agent configuration -beta[] APM Agent configuration allows you to fine-tune your agent configuration directly in Kibana. +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. -To get started, simply choose the service and environment you wish to configure. +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. [role="screenshot"] image::apm/images/apm-agent-configuration.png[APM Agent configuration in Kibana] -IMPORTANT: As this feature is in Beta, a limited number of configuration settings are supported. -We recommend you watch your agent logs to confirm that configuration has been applied. -If you have feedback, please reach out in our https://discuss.elastic.co/c/apm[Discuss forum]. - [float] ==== Precedence @@ -34,6 +31,26 @@ Kibana communicates any changed settings to APM Server so that your agents only [float] ==== Supported configurations +[float] +===== `CAPTURE_BODY` + +added[7.5.0] Can be `"off"`, `"errors"`, `"transactions"`, or `"all"`. Defaults to `"off"`. + +For transactions that are HTTP requests, the Agent can optionally capture the request body, e.g., POST variables. +Remember, request bodies often contain sensitive values like passwords, credit card numbers, etc. +If your service handles sensitive data, enable this feature with care. +Turning on body capturing can also significantly increase the overhead the overhead of the Agent, +and the Elasticsearch index size. + +[float] +===== `TRANSACTION_MAX_SPANS` + +added[7.5.0] A number between `0` and `32000`. Defaults to `500`. + +Limit the number of spans that are recorded per transaction. +This is helpful in cases where a transaction creates a very high amount of spans, e.g., thousands of SQL queries. +Setting an upper limit will help prevent the Agent and the APM Server from being overloaded. + [float] ===== `TRANSACTION_SAMPLE_RATE` diff --git a/docs/apm/errors.asciidoc b/docs/apm/errors.asciidoc index e80438975cba0..689fa1fffa89e 100644 --- a/docs/apm/errors.asciidoc +++ b/docs/apm/errors.asciidoc @@ -10,12 +10,12 @@ This makes it very easy to quickly see which errors are affecting your services, and to take actions to rectify them. [role="screenshot"] -image::apm/images/apm-errors-overview.png[Example view of the errors overview in the APM UI in Kibana] +image::apm/images/apm-errors-overview.png[Example view of the errors overview in the APM app in Kibana] Selecting an error group ID or error message brings you to the *Error group*. [role="screenshot"] -image::apm/images/apm-error-group.png[Example view of the error group page in the APM UI in Kibana] +image::apm/images/apm-error-group.png[Example view of the error group page in the APM app in Kibana] Here, you'll see the error message, culprit, and the number of occurrences over time. @@ -43,4 +43,4 @@ 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 UI in Kibana] \ No newline at end of file +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 diff --git a/docs/apm/getting-started.asciidoc b/docs/apm/getting-started.asciidoc index 8f1265b73a8f4..4a391f1a49672 100644 --- a/docs/apm/getting-started.asciidoc +++ b/docs/apm/getting-started.asciidoc @@ -10,7 +10,7 @@ image::apm/images/apm-setup.png[Installation instructions on the APM page in Kib Index patterns tell Kibana which Elasticsearch indices you want to explore. -An APM index pattern is necessary for certain features in the APM UI, like the query bar. +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. diff --git a/docs/apm/images/apm-metrics.png b/docs/apm/images/apm-metrics.png index a75702ad570d0..6a9789b5a6ecd 100644 Binary files a/docs/apm/images/apm-metrics.png and b/docs/apm/images/apm-metrics.png differ diff --git a/docs/apm/images/jvm-metrics.png b/docs/apm/images/jvm-metrics.png new file mode 100644 index 0000000000000..ffeab27e10246 Binary files /dev/null and b/docs/apm/images/jvm-metrics.png differ diff --git a/docs/apm/metrics.asciidoc b/docs/apm/metrics.asciidoc index 3fc63d6a1344a..ab394b785ef84 100644 --- a/docs/apm/metrics.asciidoc +++ b/docs/apm/metrics.asciidoc @@ -2,13 +2,21 @@ === Metrics overview The *Metrics* overview provides agent-specific metrics, -which lets you perform more in-depth root cause analysis investigations within the APM UI. +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. [role="screenshot"] -image::apm/images/apm-metrics.png[Example view of the Metrics overview in APM UI in Kibana] +image::apm/images/apm-metrics.png[Example view of the Metrics overview in APM app in Kibana] + +If you're using the Java Agent, the metrics view focuses on JVMs. +A detailed view of metrics per JVM makes it much easier to analyze the provided metrics: +CPU usage, memory usage, heap or non-heap memory, +thread count, garbage collection rate, and garbage collection time spent per minute. + +[role="screenshot"] +image::apm/images/jvm-metrics.png[Example view of the Metrics overview for the Java Agent] [[machine-learning-integration]] === Machine Learning integration @@ -17,7 +25,7 @@ The Machine Learning integration will initiate a new job predefined to calculate 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 UI in Kibana] +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*. diff --git a/docs/apm/services.asciidoc b/docs/apm/services.asciidoc index a6620b5cea7b7..9af3e74562dab 100644 --- a/docs/apm/services.asciidoc +++ b/docs/apm/services.asciidoc @@ -6,4 +6,4 @@ The *Services* overview gives you quick insights into the health and general per You can add services by setting the `service.name` configuration in each of the {apm-agents-ref}[APM agents] you’re instrumenting. [role="screenshot"] -image::apm/images/apm-services-overview.png[Example view of services table the APM UI in Kibana] \ No newline at end of file +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/spans.asciidoc b/docs/apm/spans.asciidoc index d23c4f5f4caea..75eae61b4cf12 100644 --- a/docs/apm/spans.asciidoc +++ b/docs/apm/spans.asciidoc @@ -9,7 +9,7 @@ The span timeline visualization is a bird's-eye view of what your application wa This makes it useful for visualizing where the selected transaction spent most of its time. [role="screenshot"] -image::apm/images/apm-distributed-tracing.png[Example view of the distributed tracing in APM UI in Kibana] +image::apm/images/apm-distributed-tracing.png[Example view of the distributed tracing in APM app in Kibana] View a span in detail by clicking on it in the timeline waterfall. For example, in the below screenshot we've clicked on an SQL Select database query. @@ -20,13 +20,13 @@ Finally, APM knows which files are your code and which are just modules or libra These library frames will be minimized by default in order to show you the most relevant stack trace. [role="screenshot"] -image::apm/images/apm-span-detail.png[Example view of a span detail in the APM UI in Kibana] +image::apm/images/apm-span-detail.png[Example view of a span detail in the APM app in Kibana] If your span timeline is colorful, it's indicative of a <>. Services in a distributed trace are separated by color and listed in the order they occur. [role="screenshot"] -image::apm/images/apm-services-trace.png[Example of distributed trace colors in the APM UI in Kibana] +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. When viewing these distributed traces in the timeline waterfall, you'll see this image:apm/images/transaction-icon.png[APM icon] icon, @@ -37,4 +37,4 @@ After exploring these traces, you can return to the full trace by clicking *View full trace* in the upper right hand corner of the page. [role="screenshot"] -image::apm/images/apm-transaction-sample.png[Example of distributed trace colors in the APM UI in Kibana] +image::apm/images/apm-transaction-sample.png[Example of distributed trace colors in the APM app in Kibana] diff --git a/docs/apm/traces.asciidoc b/docs/apm/traces.asciidoc index 214b997818503..09d8f52b92840 100644 --- a/docs/apm/traces.asciidoc +++ b/docs/apm/traces.asciidoc @@ -11,7 +11,7 @@ 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 <>. [role="screenshot"] -image::apm/images/apm-traces.png[Example view of the Traces overview in APM UI in Kibana] +image::apm/images/apm-traces.png[Example view of the Traces overview in APM app in Kibana] [float] [[distributed-tracing]] @@ -22,7 +22,7 @@ Distributed tracing is a key feature of modern application performance monitorin service-based architectures. Distributed tracing allows APM users to automatically trace requests all the way through the service architecture, -and visualize those traces in one single view in the APM UI. +and visualize those traces in one single view in the APM app. This is accomplished by tracing all of the requests, from the initial web request to your front-end service, to queries made to your back-end services. This makes finding possible bottlenecks throughout your application much easier and faster. @@ -31,6 +31,6 @@ By definition, a distributed trace includes more than one transaction. You can use the <> to view a waterfall display of all of the transactions from individual services that are connected in a trace. [role="screenshot"] -image::apm/images/apm-distributed-tracing.png[Example view of the distributed tracing in APM UI in Kibana] +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 diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index e70587baa65ea..33f61adc8be63 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -10,9 +10,9 @@ The *Transactions* table, however, provides only a list of _transaction groups_ In other words, this view groups all transactions of the same name together, and only displays one transaction for each group. [role="screenshot"] -image::apm/images/apm-transactions-overview.png[Example view of transactions table in the APM UI in Kibana] +image::apm/images/apm-transactions-overview.png[Example view of transactions table in the APM app in Kibana] -*Time spent by span type* -- beta[] Certain agents support breakdown graphs in the APM UI. +*Time spent by span type* -- Most agents support breakdown graphs in the APM app. This graph is an easy way to visualize where your application is spending most of its time. For example, is your app spending time in external calls, database processing, or application code execution? @@ -22,8 +22,6 @@ This could be a sign that the agent does not have auto-instrumentation for whate It's important to note that if you have asynchronous spans, the sum of all span times may exceed the duration of the transaction. -TIP: If the *Time spent by span type* chart is missing in the APM UI, it means your agent does not support this feature yet. - *Transaction duration* shows the response times for this service and is broken down into average, 95th, and 99th percentile. If there's a weird spike that you'd like to investigate, you can simply zoom in on the graph - this will adjust the specific time range, diff --git a/docs/developer/plugin/development-uiexports.asciidoc b/docs/developer/plugin/development-uiexports.asciidoc index de713416ae2cd..6368446f7fb43 100644 --- a/docs/developer/plugin/development-uiexports.asciidoc +++ b/docs/developer/plugin/development-uiexports.asciidoc @@ -8,7 +8,6 @@ An aggregate list of available UiExport types: | Type | Purpose | hacks | Any module that should be included in every application | visTypes | Modules that register providers with the `ui/registry/vis_types` registry. -| fieldFormats | Modules that register providers with the `ui/registry/field_formats` registry. | inspectorViews | Modules that register custom inspector views via the `viewRegistry` in `ui/inspector`. | chromeNavControls | Modules that register providers with the `ui/registry/chrome_nav_controls` registry. | navbarExtensions | Modules that register providers with the `ui/registry/navbar_extensions` registry. diff --git a/docs/discover/kuery.asciidoc b/docs/discover/kuery.asciidoc index abf3e05fb7819..c835c15028074 100644 --- a/docs/discover/kuery.asciidoc +++ b/docs/discover/kuery.asciidoc @@ -19,14 +19,13 @@ they appear. This means documents with "quick brown fox" will match, but so will to search for a phrase. The query parser will no longer split on whitespace. Multiple search terms must be separated by explicit -boolean operators. Note that boolean operators are not case sensitive. +boolean operators. Lucene will combine search terms with an `or` by default, so `response:200 extension:php` would +become `response:200 or extension:php` in KQL. This will match documents where response matches 200, extension matches php, or both. +Note that boolean operators are not case sensitive. -`response:200 extension:php` in lucene would become `response:200 and extension:php`. - This will match documents where response matches 200 and extension matches php. +We can make terms required by using `and`. -We can make terms optional by using `or`. - -`response:200 or extension:php` will match documents where response matches 200, extension matches php, or both. +`response:200 and extension:php` will match documents where response matches 200 and extension matches php. By default, `and` has a higher precedence than `or`. @@ -73,7 +72,7 @@ set these terms will be matched against all fields. For example, a query for `re in the response field, but a query for just `200` will search for 200 across all fields in your index. ============ -===== Nested Field Support +==== Nested Field Support KQL supports querying on {ref}/nested.html[nested fields] through a special syntax. You can query nested fields in subtly different ways, depending on the results you want, so crafting nested queries requires extra thought. @@ -85,7 +84,8 @@ There are two main approaches to take: * *Parts of the query can match different nested documents.* This is how a regular object field works. Although generally less useful, there might be occasions where you want to query a nested field in this way. -Let's take a look at the first approach. In the following document, `items` is a nested field: +Let's take a look at the first approach. In the following document, `items` is a nested field. Each document in the nested +field contains a name, stock, and category. [source,json] ---------------------------------- @@ -116,21 +116,38 @@ Let's take a look at the first approach. In the following document, `items` is a } ---------------------------------- +===== Match a single nested document + To find stores that have more than 10 bananas in stock, you would write a query like this: `items:{ name:banana and stock > 10 }` -`items` is the "nested path". Everything inside the curly braces (the "nested group") must match a single document. -For example, `items:{ name:banana and stock:9 }` does not match because there isn't a single nested document that -matches the entire query in the nested group. +`items` is the "nested path". Everything inside the curly braces (the "nested group") must match a single nested document. + +The following example returns no matches because no single nested document has bananas with a stock of 9. + +`items:{ name:banana and stock:9 }` + +==== Match different nested documents -What if you want to find a store with more than 10 bananas that *also* stocks vegetables? This is the second way of querying a nested field, and you can do it like this: +The subqueries in this example are in separate nested groups and can match different nested documents. + +`items:{ name:banana } and items:{ stock:9 }` + +`name:banana` matches the first document in the array and `stock:9` matches the third document in the array. + +==== Combine approaches + +You can combine these two approaches to create complex queries. What if you wanted to find a store with more than 10 +bananas that *also* stocks vegetables? You could do this: `items:{ name:banana and stock > 10 } and items:{ category:vegetable }` The first nested group (`name:banana and stock > 10`) must still match a single document, but the `category:vegetables` subquery can match a different nested document because it is in a separate group. +==== Nested fields inside other nested fields + KQL's syntax also supports nested fields inside of other nested fields—you simply have to specify the full path. Suppose you have a document where `level1` and `level2` are both nested fields: diff --git a/docs/images/lens_data_info.gif b/docs/images/lens_data_info.gif new file mode 100644 index 0000000000000..e2c565de9f6a7 Binary files /dev/null and b/docs/images/lens_data_info.gif differ diff --git a/docs/images/lens_drag_drop.gif b/docs/images/lens_drag_drop.gif new file mode 100644 index 0000000000000..39cde64fb97eb Binary files /dev/null and b/docs/images/lens_drag_drop.gif differ diff --git a/docs/images/lens_remove_layer.png b/docs/images/lens_remove_layer.png new file mode 100644 index 0000000000000..4184e5b846870 Binary files /dev/null and b/docs/images/lens_remove_layer.png differ diff --git a/docs/images/lens_suggestions.gif b/docs/images/lens_suggestions.gif new file mode 100644 index 0000000000000..0452207b86456 Binary files /dev/null and b/docs/images/lens_suggestions.gif differ diff --git a/docs/images/lens_tutorial_1.png b/docs/images/lens_tutorial_1.png new file mode 100644 index 0000000000000..7992276c833e7 Binary files /dev/null and b/docs/images/lens_tutorial_1.png differ diff --git a/docs/images/lens_tutorial_2.png b/docs/images/lens_tutorial_2.png new file mode 100644 index 0000000000000..b47e7feff3b9f Binary files /dev/null and b/docs/images/lens_tutorial_2.png differ diff --git a/docs/images/lens_tutorial_3.png b/docs/images/lens_tutorial_3.png new file mode 100644 index 0000000000000..ea40b458202b7 Binary files /dev/null and b/docs/images/lens_tutorial_3.png differ diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 38fceeb47d6fd..977a65f62202d 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -46,6 +46,7 @@ adapt to the interval between measurements. Keys are http://en.wikipedia.org/wik `dateFormat:tz`:: The timezone that Kibana uses. The default value of `Browser` uses the timezone detected by the browser. `dateNanosFormat`:: The format to use for displaying https://momentjs.com/docs/#/displaying/format/[pretty formatted dates] of {ref}/date_nanos.html[Elasticsearch date_nanos type]. `defaultIndex`:: The index to access if no index is set. The default is `null`. +`defaultRoute`:: The default route when opening Kibana. Use this setting to route users to a specific dashboard, application, or saved object as they enter each space. `fields:popularLimit`:: The top N most popular fields to show. `filterEditor:suggestValues`:: Set this property to `false` to prevent the filter editor from suggesting values for fields. `filters:pinnedByDefault`:: Set this property to `true` to make filters have a global state (be pinned) by default. diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 68dd9a8b3cefb..2fc74d2ffee32 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -45,14 +45,26 @@ production cluster as well as monitor data sent to a dedicated monitoring cluster. `xpack.monitoring.elasticsearch.username`:: -Specifies the user ID that {kib} uses for authentication when it retrieves data -from the monitoring cluster. If not set, {kib} uses the value of the -`elasticsearch.username` setting. +Specifies the username used by {kib} monitoring to establish a persistent connection +in {kib} to the {es} monitoring cluster and to verify licensing status on the {es} +monitoring cluster. + +Every other request performed by the Stack Monitoring UI to the monitoring {es} +cluster uses the authenticated user's credentials, which must be the same on +both the {es} monitoring cluster and the {es} production cluster. + +If not set, {kib} uses the value of the `elasticsearch.username` setting. `xpack.monitoring.elasticsearch.password`:: -Specifies the password that {kib} uses for authentication when it retrieves data -from the monitoring cluster. If not set, {kib} uses the value of the -`elasticsearch.password` setting. +Specifies the password used by {kib} monitoring to establish a persistent connection +in {kib} to the {es} monitoring cluster and to verify licensing status on the {es} +monitoring cluster. + +Every other request performed by the Stack Monitoring UI to the monitoring {es} +cluster uses the authenticated user's credentials, which must be the same on +both the {es} monitoring cluster and the {es} production cluster. + +If not set, {kib} uses the value of the `elasticsearch.password` setting. `telemetry.enabled`:: Set to `true` (default) to send cluster statistics to Elastic. Reporting your diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index fc858eb6d86ef..69655aac521e7 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -22,6 +22,7 @@ Kibana supports spaces in several ways. You can: * <> * <> * <> +* <> * <> [float] @@ -108,6 +109,13 @@ interface. {kib} also has beta <> and <> APIs if you want to automate this process. +[float] +[[spaces-default-route]] +=== Configure a Space-level landing page + +You can create a custom experience for users by configuring the {kib} landing page on a per-space basis. +The landing page can route users to a specific dashboard, application, or saved object as they enter each space. +To configure the landing page, use the `defaultRoute` setting in < Advanced settings>>. [float] [[spaces-delete-started]] diff --git a/docs/user/visualize.asciidoc b/docs/user/visualize.asciidoc index eec9ef65cba90..ed74525d22e7c 100644 --- a/docs/user/visualize.asciidoc +++ b/docs/user/visualize.asciidoc @@ -24,9 +24,10 @@ To create a visualization: . Click on *Visualize* in the side navigation. . Click the *Create new visualization* button or the **+** button. . Choose the visualization type: -+ + * *Basic charts* [horizontal] +<>:: Quickly build several types of basic visualizations by simply dragging and dropping the data fields you want to display. <>:: Compare different series in X/Y charts. <>:: Shade cells within a matrix. <>:: Display each source's contribution to a total. @@ -142,6 +143,8 @@ include::{kib-repo-dir}/visualize/saving.asciidoc[] include::{kib-repo-dir}/visualize/visualize_rollup_data.asciidoc[] +include::{kib-repo-dir}/visualize/lens.asciidoc[] + include::{kib-repo-dir}/visualize/xychart.asciidoc[] include::{kib-repo-dir}/visualize/controls.asciidoc[] diff --git a/docs/visualize/lens.asciidoc b/docs/visualize/lens.asciidoc new file mode 100644 index 0000000000000..086f88c636c81 --- /dev/null +++ b/docs/visualize/lens.asciidoc @@ -0,0 +1,186 @@ +[role="xpack"] +[[lens]] +== Lens + +beta[] + +*Lens* provides you with a simple and fast way to create visualizations from your Elasticsearch data. With Lens, you can: + +* Quickly build visualizations by dragging and dropping data fields. + +* Understand your data with a summary view on each field. + +* Easily change the visualization type by selecting the automatically generated visualization suggestions. + +* Save your visualization for use in a dashboard. + +[float] +[[drag-drop]] +=== Drag and drop + +The data panel in the left column shows the data fields for the selected time period. When +you drag a field from the data panel, Lens highlights where you can drop that field. The first time you drag a data field, +you'll see two places highlighted in green: + +* The visualization builder pane + +* The *X-axis* or *Y-axis* fields in the right column + +You can incorporate many fields into your visualization, and Lens uses heuristics to decide how +to apply each one to the visualization. + +[role="screenshot"] +image::images/lens_drag_drop.gif[] + +TIP: Drag-and-drop capabilities are available only when Lens knows how to use the data. You can still customize +your visualization if Lens is unable to make a suggestion. + +[float] +[[apply-lens-filters]] +==== Find the right data + +Lens shows you fields based on the <> you have defined in +{kib}, and the current time range. When you change the index pattern or time filter, +the list of fields are updated. + +To narrow the list of fields you see in the left panel, you can: + +* Enter the field name in *Search field names*. + +* Click *Filter by type*, then select the filter. You can also select *Only show fields with data* +to show the full list of fields from the index pattern. + +[float] +[[view-data-summaries]] +==== Data summaries + +To help you decide exactly the data you want to display, get a quick summary of each data field. +The summary shows the distribution of values in the time range. + +To view the data information, navigate to a data field, then click *i*. + +[role="screenshot"] +image::images/lens_data_info.gif[] + +[float] +[[change-the-visualization-type]] +==== Change the visualization type + +With Lens, you are no longer required to build each visualization from scratch. Lens allows +you to switch between any supported chart type at any time. Lens also provides +suggestions, which are shortcuts to alternate visualizations based on the data you have. + +You can switch between suggestions without losing your previous state: + +[role="screenshot"] +image::images/lens_suggestions.gif[] + +If you want to switch to a chart type that is not suggested, click the chart type in the +top right, then select a chart type. When there is an exclamation point (!) +next to a chart type, Lens is unable to transfer your current data, but +still allows you to make the change. + +[float] +[[customize-operation]] +==== Customize the data for your visualization + +Lens allows some customizations of the data for each visualization. + +. Change the index pattern. + +.. In the left column, click the index pattern name. + +.. Select the new index pattern. ++ +If there is a match, Lens displays the new data. All fields that do not match the index pattern are removed. + +. Change the data field options, such as the aggregation or label. + +.. Click *Drop a field here* or the field name in the right column. + +.. Change the options that appear depending on the type of field. + +[float] +[[layers]] +==== Layers in bar, line, and area charts + +The bar, line, and area charts allow you to layer two different series. To add a layer, click *+*. + +To remove a layer, click the chart icon next to the index name: + +[role="screenshot"] +image::images/lens_remove_layer.png[] + +[float] +[[lens-tutorial]] +=== Lens tutorial + +Ready to create your own visualization with Lens? Use the following tutorial to create a visualization that +lets you compare sales over time. + +[float] +[[lens-before-begin]] +==== Before you begin + +To start, you'll need to add the <>. + +[float] +==== Build the visualization + +Drag and drop your data onto the visualization builder pane. + +. Open *Visualize*, then click *Create visualization*. + +. On the *New Visualization* window, click *Lens*. + +. In the left column, select the *kibana_sample_data_ecommerce* index. + +. Click image:images/time-filter-calendar.png[], then click *Last 7 days*. The list of data fields are updated. + +. Drag and drop the *taxful_total_price* data field to the visualization builder pane. ++ +[role="screenshot"] +image::images/lens_tutorial_1.png[Lens tutorial] + +Lens has taken your intent to see *taxful_total_price* and added in the *order_date* field to show +average order prices over time. + +To break down your data, drag the *category.keyword* field to the visualization builder pane. Lens +understands that you want to show the top categories and compare them across the dates, +and creates a chart that compares the sales for each of the top 3 categories: + +[role="screenshot"] +image::images/lens_tutorial_2.png[Lens tutorial] + +[float] +[[customize-lens-visualization]] +==== Further customization + +Customize your visualization to look exactly how you want. + +. In the right column, click *Average of taxful_total_price*. + +.. Change the *Label* to `Sales`, or a name that you prefer for the data. + +. Click *Top values of category.keyword*. + +.. Increase *Number of values* to `10`. The visualization updates in the background to show there are only +six available categories. + +. Look at the suggestions. None of them show an area chart, but for sales data, a stacked area chart +might make sense. To switch the chart type: + +.. Click *Stacked bar chart* in the right column. + +.. Click *Stacked area*. ++ +[role="screenshot"] +image::images/lens_tutorial_3.png[Lens tutorial] + +[float] +[[lens-tutorial-next-steps]] +==== Next steps + +Now that you've created your visualization in Lens, you can add it to a Dashboard. + +For more information, see <>. diff --git a/packages/kbn-es/src/custom_snapshots.js b/packages/kbn-es/src/custom_snapshots.js index be6bbeca538ff..74de3c2c792fd 100644 --- a/packages/kbn-es/src/custom_snapshots.js +++ b/packages/kbn-es/src/custom_snapshots.js @@ -24,10 +24,9 @@ function isVersionFlag(a) { } function getCustomSnapshotUrl() { - // force use of manually created snapshots until live ones are available + // force use of manually created snapshots until ReindexPutMappings fix if (!process.env.KBN_ES_SNAPSHOT_URL && !process.argv.some(isVersionFlag)) { - // return 'https://storage.googleapis.com/kibana-ci-tmp-artifacts/{name}-{version}-{os}-x86_64.{ext}'; - return; + return 'https://storage.googleapis.com/kibana-ci-tmp-artifacts/{name}-{version}-{os}-x86_64.{ext}'; } if (process.env.KBN_ES_SNAPSHOT_URL && process.env.KBN_ES_SNAPSHOT_URL !== 'false') { diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 6989c2159dce3..a849068b9dde1 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -9,6 +9,7 @@ - [Challenges on the server](#challenges-on-the-server) - [Challenges in the browser](#challenges-in-the-browser) - [Plan of action](#plan-of-action) + - [Shared application plugins](#shared-application-plugins) - [Server-side plan of action](#server-side-plan-of-action) - [De-couple from hapi.js server and request objects](#de-couple-from-hapijs-server-and-request-objects) - [Introduce new plugin definition shim](#introduce-new-plugin-definition-shim) @@ -314,6 +315,43 @@ First, decouple your plugin's business logic from the dependencies that are not Once those things are finished for any given plugin, it can officially be switched to the new plugin system. +### Shared application plugins + +Some services have been already moved to the new platform. + +Below you can find their new locations: + +| Service | Old place | New place in the NP | +| --------------- | ----------------------------------------- | --------------------------------------------------- | +| *FieldFormats* | ui/registry/field_formats | plugins/data/public | + +The `FieldFormats` service has been moved to the `data` plugin in the New Platform. If your plugin has any imports from `ui/registry/field_formats`, you'll need to update your imports as follows: + +Use it in your New Platform plugin: + +```ts +class MyPlugin { + setup (core, { data }) { + data.fieldFormats.register(myFieldFormat); + // ... + } + start (core, { data }) { + data.fieldFormats.getType(myFieldFormatId); + // ... + } +} +``` + +Or, in your legacy platform plugin, consume it through the `ui/new_platform` module: + +```ts +import { npSetup, npStart } from 'ui/new_platform'; + +npSetup.plugins.data.fieldFormats.register(myFieldFormat); +npStart.plugins.data.fieldFormats.getType(myFieldFormatId); +// ... +``` + ## Server-side plan of action Legacy server-side plugins access functionality from core and other plugins at runtime via function arguments, which is similar to how they must be architected to use the new plugin system. This greatly simplifies the plan of action for migrating server-side plugins. @@ -1139,7 +1177,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; | `ui/embeddable` | `embeddables` | still in progress | | `ui/filter_manager` | `data.filter` | -- | | `ui/index_patterns` | `data.indexPatterns` | still in progress | -| `ui/registry/feature_catalogue | `feature_catalogue.register` | Must add `feature_catalogue` as a dependency in your kibana.json. | +| `ui/registry/feature_catalogue` | `home.featureCatalogue.register` | Must add `home` as a dependency in your kibana.json. | | `ui/registry/vis_types` | `visualizations.types` | -- | | `ui/vis` | `visualizations.types` | -- | | `ui/share` | `share` | `showShareContextMenu` is now called `toggleShareContextMenu`, `ShareContextMenuExtensionsRegistryProvider` is now called `register` | @@ -1182,7 +1220,7 @@ This table shows where these uiExports have moved to in the New Platform. In mos | `fieldFormatEditors` | | | | `fieldFormats` | | | | `hacks` | n/a | Just run the code in your plugin's `start` method. | -| `home` | [`plugins.feature_catalogue.register`](./src/plugins/feature_catalogue) | Must add `feature_catalogue` as a dependency in your kibana.json. | +| `home` | [`plugins.home.featureCatalogue.register`](./src/plugins/home/public/feature_catalogue) | Must add `home` as a dependency in your kibana.json. | | `indexManagement` | | Should be an API on the indexManagement plugin. | | `injectDefaultVars` | n/a | Plugins will only be able to "whitelist" config values for the frontend. See [#41990](https://github.com/elastic/kibana/issues/41990) | | `inspectorViews` | | Should be an API on the data (?) plugin. | diff --git a/src/core/server/saved_objects/service/lib/filter_utils.test.ts b/src/core/server/saved_objects/service/lib/filter_utils.test.ts index 80a6a96aeaf2b..4c4f321695d70 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.test.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.test.ts @@ -405,5 +405,29 @@ describe('Filter Utils', () => { }, ]); }); + + test('Return Error if filter is using an non-existing key null key', () => { + const validationObject = validateFilterKueryNode( + fromKueryExpression('foo.attributes.description: hello AND bye'), + ['foo'], + mockMappings + ); + expect(validationObject).toEqual([ + { + astPath: 'arguments.0', + error: null, + isSavedObjectAttr: false, + key: 'foo.attributes.description', + type: 'foo', + }, + { + astPath: 'arguments.1', + error: 'The key is empty and needs to be wrapped by a saved object type like foo', + isSavedObjectAttr: false, + key: null, + type: null, + }, + ]); + }); }); }); diff --git a/src/core/server/saved_objects/service/lib/filter_utils.ts b/src/core/server/saved_objects/service/lib/filter_utils.ts index 64abf268cacd6..e331d3eff990f 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.ts @@ -128,7 +128,8 @@ export const validateFilterKueryNode = ( }, []); }; -const getType = (key: string) => (key.includes('.') ? key.split('.')[0] : null); +const getType = (key: string | undefined | null) => + key != null && key.includes('.') ? key.split('.')[0] : null; /** * Is this filter key referring to a a top-level SavedObject attribute such as @@ -137,8 +138,8 @@ const getType = (key: string) => (key.includes('.') ? key.split('.')[0] : null); * @param key * @param indexMapping */ -export const isSavedObjectAttr = (key: string, indexMapping: IndexMapping) => { - const keySplit = key.split('.'); +export const isSavedObjectAttr = (key: string | null | undefined, indexMapping: IndexMapping) => { + const keySplit = key != null ? key.split('.') : []; if (keySplit.length === 1 && fieldDefined(indexMapping, keySplit[0])) { return true; } else if (keySplit.length === 2 && fieldDefined(indexMapping, keySplit[1])) { @@ -149,10 +150,13 @@ export const isSavedObjectAttr = (key: string, indexMapping: IndexMapping) => { }; export const hasFilterKeyError = ( - key: string, + key: string | null | undefined, types: string[], indexMapping: IndexMapping ): string | null => { + if (key == null) { + return `The key is empty and needs to be wrapped by a saved object type like ${types.join()}`; + } if (!key.includes('.')) { return `This key '${key}' need to be wrapped by a saved object type like ${types.join()}`; } else if (key.includes('.')) { diff --git a/src/fixtures/stubbed_logstash_index_pattern.js b/src/fixtures/stubbed_logstash_index_pattern.js index e1fa5db8b7140..9f6d648477d29 100644 --- a/src/fixtures/stubbed_logstash_index_pattern.js +++ b/src/fixtures/stubbed_logstash_index_pattern.js @@ -21,6 +21,7 @@ import StubIndexPattern from 'test_utils/stub_index_pattern'; import stubbedLogstashFields from 'fixtures/logstash_fields'; import { getKbnFieldType } from '../plugins/data/common'; +import { mockUiSettings } from '../legacy/ui/public/new_platform/new_platform.karma_mock'; export default function stubbedLogstashIndexPatternService() { const mockLogstashFields = stubbedLogstashFields(); @@ -40,7 +41,7 @@ export default function stubbedLogstashIndexPatternService() { }; }); - const indexPattern = new StubIndexPattern('logstash-*', cfg => cfg, 'time', fields); + const indexPattern = new StubIndexPattern('logstash-*', cfg => cfg, 'time', fields, mockUiSettings); indexPattern.id = 'logstash-*'; indexPattern.isTimeNanosBased = () => false; diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts index 463aac74da944..b610cf7e6a3bb 100644 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/console/np_ready/public/legacy.ts @@ -30,7 +30,7 @@ import { I18nContext } from 'ui/i18n'; export interface XPluginSet { devTools: DevToolsSetup; - feature_catalogue: FeatureCatalogueSetup; + home: HomePublicPluginSetup; __LEGACY: { I18nContext: any; }; @@ -38,7 +38,7 @@ export interface XPluginSet { import { plugin } from '.'; import { DevToolsSetup } from '../../../../../plugins/dev_tools/public'; -import { FeatureCatalogueSetup } from '../../../../../plugins/feature_catalogue/public'; +import { HomePublicPluginSetup } from '../../../../../plugins/home/public'; const pluginInstance = plugin({} as any); diff --git a/src/legacy/core_plugins/console/np_ready/public/plugin.ts b/src/legacy/core_plugins/console/np_ready/public/plugin.ts index 301b85b6e7395..4050f20a4fb07 100644 --- a/src/legacy/core_plugins/console/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/console/np_ready/public/plugin.ts @@ -32,10 +32,10 @@ export class ConsoleUIPlugin implements Plugin { const { __LEGACY: { I18nContext }, devTools, - feature_catalogue, + home, } = pluginSet; - feature_catalogue.register({ + home.featureCatalogue.register({ id: 'console', title: i18n.translate('console.devToolsTitle', { defaultMessage: 'Console', diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx index 5b389f5b98aba..23c9c9ffc94bb 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx @@ -21,13 +21,12 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { useState } from 'react'; -import { CoreStart } from 'src/core/public'; import { IndexPattern } from '../../index_patterns'; import { FilterEditor } from './filter_editor'; import { FilterItem } from './filter_item'; import { FilterOptions } from './filter_options'; -import { useKibana, KibanaContextProvider } from '../../../../../../plugins/kibana_react/public'; -import { DataPublicPluginStart, esFilters } from '../../../../../../plugins/data/public'; +import { useKibana } from '../../../../../../plugins/kibana_react/public'; +import { esFilters } from '../../../../../../plugins/data/public'; interface Props { filters: esFilters.Filter[]; @@ -35,48 +34,15 @@ interface Props { className: string; indexPatterns: IndexPattern[]; intl: InjectedIntl; - - // TODO: Only for filter-bar directive! - uiSettings?: CoreStart['uiSettings']; - docLinks?: CoreStart['docLinks']; - pluginDataStart?: DataPublicPluginStart; } function FilterBarUI(props: Props) { const [isAddFilterPopoverOpen, setIsAddFilterPopoverOpen] = useState(false); const kibana = useKibana(); - const uiSettings = kibana.services.uiSettings || props.uiSettings; + const uiSettings = kibana.services.uiSettings; if (!uiSettings) return null; - function hasContext() { - return Boolean(kibana.services.uiSettings); - } - - function wrapInContextIfMissing(content: JSX.Element) { - // TODO: Relevant only as long as directives are used! - if (!hasContext()) { - if (props.docLinks && props.uiSettings && props.pluginDataStart) { - return ( - - {content} - - ); - } else { - throw new Error( - 'Rending filter bar requires providing sufficient context: uiSettings, docLinks and NP data plugin' - ); - } - } - return content; - } - function onFiltersUpdated(filters: esFilters.Filter[]) { if (props.onFiltersUpdated) { props.onFiltersUpdated(filters); @@ -119,7 +85,7 @@ function FilterBarUI(props: Props) { ); - return wrapInContextIfMissing( + return ( ({ - fieldFormats: { - getDefaultInstance: jest.fn(), - }, -})); +jest.mock('ui/new_platform'); jest.mock('../../../../../../plugins/kibana_utils/public', () => { const originalModule = jest.requireActual('../../../../../../plugins/kibana_utils/public'); @@ -142,6 +139,9 @@ describe('IndexPattern', () => { // create an indexPattern instance for each test beforeEach(() => { setNotifications(notifications); + setFieldFormats(({ + getDefaultInstance: jest.fn(), + } as unknown) as FieldFormatRegisty); return create(indexPatternId).then((pattern: IndexPattern) => { indexPattern = pattern; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 12aa3c2fb0d51..f77342c7bc274 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -19,8 +19,6 @@ import _, { each, reject } from 'lodash'; import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { fieldFormats } from 'ui/registry/field_formats'; import { SavedObjectsClientContract } from 'src/core/public'; import { DuplicateField, @@ -30,6 +28,12 @@ import { MappingObject, } from '../../../../../../plugins/kibana_utils/public'; +import { + ES_FIELD_TYPES, + KBN_FIELD_TYPES, + IIndexPattern, +} from '../../../../../../plugins/data/public'; + import { findIndexPatternByTitle, getRoutes } from '../utils'; import { IndexPatternMissingIndices } from '../errors'; import { Field, FieldList, FieldListInterface, FieldType } from '../fields'; @@ -37,8 +41,7 @@ import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; import { IIndexPatternsApiClient } from './index_patterns_api_client'; -import { ES_FIELD_TYPES, IIndexPattern } from '../../../../../../plugins/data/public'; -import { getNotifications } from '../services'; +import { getNotifications, getFieldFormats } from '../services'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const type = 'index-pattern'; @@ -114,7 +117,10 @@ export class IndexPattern implements IIndexPattern { this.fields = new FieldList(this, [], this.shortDotsEnable); this.fieldsFetcher = createFieldsFetcher(this, apiClient, this.getConfig('metaFields')); this.flattenHit = flattenHitWrapper(this, this.getConfig('metaFields')); - this.formatHit = formatHitProvider(this, fieldFormats.getDefaultInstance('string')); + this.formatHit = formatHitProvider( + this, + getFieldFormats().getDefaultInstance(KBN_FIELD_TYPES.STRING) + ); this.formatField = this.formatHit.formatField; } @@ -125,12 +131,14 @@ export class IndexPattern implements IIndexPattern { } private deserializeFieldFormatMap(mapping: any) { - const FieldFormat = fieldFormats.getType(mapping.id); + const FieldFormat = getFieldFormats().getType(mapping.id); + return FieldFormat && new FieldFormat(mapping.params, this.getConfig); } private initFields(input?: any) { const newValue = input || this.fields; + this.fields = new FieldList(this, newValue, this.shortDotsEnable); } @@ -451,6 +459,7 @@ export class IndexPattern implements IIndexPattern { const { toasts } = getNotifications(); toasts.addDanger(message); + throw err; } @@ -492,6 +501,7 @@ export class IndexPattern implements IIndexPattern { if (err instanceof IndexPatternMissingIndices) { toasts.addDanger((err as any).message); + return []; } diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts index 8a5c78d13c251..0a5d1bfcae21f 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts @@ -29,12 +29,6 @@ jest.mock('../errors', () => ({ IndexPatternMissingIndices: jest.fn(), })); -jest.mock('ui/registry/field_formats', () => ({ - fieldFormats: { - getDefaultInstance: jest.fn(), - }, -})); - jest.mock('./index_pattern', () => { class IndexPattern { init = async () => { diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts index 2c58af9deaf49..c8e80b3aede20 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -24,8 +24,6 @@ import { UiSettingsClientContract, HttpServiceBase, } from 'src/core/public'; -// @ts-ignore -import { fieldFormats } from 'ui/registry/field_formats'; import { createIndexPatternCache } from './_pattern_cache'; import { IndexPattern } from './index_pattern'; @@ -34,8 +32,6 @@ import { IndexPatternsApiClient, GetFieldsOptions } from './index_patterns_api_c const indexPatternCache = createIndexPatternCache(); export class IndexPatterns { - fieldFormats: fieldFormats; - private config: UiSettingsClientContract; private savedObjectsClient: SavedObjectsClientContract; private savedObjectsCache?: Array>> | null; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts index 9ce1b5f2e4a20..c9c52400b1f19 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts @@ -23,9 +23,10 @@ import { HttpServiceBase, NotificationsStart, } from 'src/core/public'; +import { FieldFormatsStart } from '../../../../../plugins/data/public'; import { Field, FieldList, FieldListInterface, FieldType } from './fields'; import { createIndexPatternSelect } from './components'; -import { setNotifications } from './services'; +import { setNotifications, setFieldFormats } from './services'; import { createFlattenHitWrapper, @@ -40,6 +41,7 @@ export interface IndexPatternDependencies { savedObjectsClient: SavedObjectsClientContract; http: HttpServiceBase; notifications: NotificationsStart; + fieldFormats: FieldFormatsStart; } /** @@ -64,8 +66,15 @@ export class IndexPatternsService { return this.setupApi; } - public start({ uiSettings, savedObjectsClient, http, notifications }: IndexPatternDependencies) { + public start({ + uiSettings, + savedObjectsClient, + http, + notifications, + fieldFormats, + }: IndexPatternDependencies) { setNotifications(notifications); + setFieldFormats(fieldFormats); return { ...this.setupApi, diff --git a/src/legacy/core_plugins/data/public/index_patterns/services.ts b/src/legacy/core_plugins/data/public/index_patterns/services.ts index 5cc087548d6fb..ecd898b28de63 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/services.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/services.ts @@ -19,7 +19,12 @@ import { NotificationsStart } from 'src/core/public'; import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; +import { FieldFormatsStart } from '../../../../../plugins/data/public'; export const [getNotifications, setNotifications] = createGetterSetter( 'Notifications' ); + +export const [getFieldFormats, setFieldFormats] = createGetterSetter( + 'FieldFormats' +); diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 2059f61fde59e..da24576655d2b 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -94,6 +94,7 @@ export class DataPlugin implements Plugin void; onChange: (payload: { dateRange: TimeRange; query?: Query }) => void; onRefresh?: (payload: { dateRange: TimeRange }) => void; + dataTestSubj?: string; disableAutoFocus?: boolean; screenTitle?: string; indexPatterns?: Array; @@ -189,6 +190,7 @@ function QueryBarTopRowUI(props: Props) { onChange={onQueryChange} onSubmit={onInputSubmit} persistedLog={persistedLog} + dataTestSubj={props.dataTestSubj} /> ); diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx index e97c06ace1579..e29908bf53764 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx @@ -64,7 +64,7 @@ export interface SearchBarOwnProps { isLoading?: boolean; customSubmitButton?: React.ReactNode; screenTitle?: string; - + dataTestSubj?: string; // Togglers showQueryBar?: boolean; showQueryInput?: boolean; @@ -415,6 +415,7 @@ class SearchBarUI extends Component { customSubmitButton={ this.props.customSubmitButton ? this.props.customSubmitButton : undefined } + dataTestSubj={this.props.dataTestSubj} /> ); } diff --git a/src/legacy/core_plugins/data/public/shim/legacy_module.ts b/src/legacy/core_plugins/data/public/shim/legacy_module.ts index edc389b411971..06c5caa04ba9a 100644 --- a/src/legacy/core_plugins/data/public/shim/legacy_module.ts +++ b/src/legacy/core_plugins/data/public/shim/legacy_module.ts @@ -19,58 +19,11 @@ import { once } from 'lodash'; -import { wrapInI18nContext } from 'ui/i18n'; - // @ts-ignore import { uiModules } from 'ui/modules'; -import { npStart } from 'ui/new_platform'; -import { FilterBar } from '../filter'; import { IndexPatterns } from '../index_patterns/index_patterns'; /** @internal */ export const initLegacyModule = once((indexPatterns: IndexPatterns): void => { - uiModules - .get('app/kibana', ['react']) - .directive('filterBar', () => { - return { - restrict: 'E', - template: '', - compile: (elem: any) => { - const child = document.createElement('filter-bar-helper'); - - // Copy attributes to the child directive - for (const attr of elem[0].attributes) { - child.setAttribute(attr.name, attr.value); - } - - child.setAttribute('ui-settings', 'uiSettings'); - child.setAttribute('doc-links', 'docLinks'); - child.setAttribute('plugin-data-start', 'pluginDataStart'); - - // Append helper directive - elem.append(child); - - const linkFn = ($scope: any) => { - $scope.uiSettings = npStart.core.uiSettings; - $scope.docLinks = npStart.core.docLinks; - $scope.pluginDataStart = npStart.plugins.data; - }; - - return linkFn; - }, - }; - }) - .directive('filterBarHelper', (reactDirective: any) => { - return reactDirective(wrapInI18nContext(FilterBar), [ - ['uiSettings', { watchDepth: 'reference' }], - ['docLinks', { watchDepth: 'reference' }], - ['onFiltersUpdated', { watchDepth: 'reference' }], - ['indexPatterns', { watchDepth: 'collection' }], - ['filters', { watchDepth: 'collection' }], - ['className', { watchDepth: 'reference' }], - ['pluginDataStart', { watchDepth: 'reference' }], - ]); - }); - uiModules.get('kibana/index_patterns').value('indexPatterns', indexPatterns); }); diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index c7cda8aec0165..dcb7d7998ff1a 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -64,7 +64,6 @@ export default function (kibana) { hacks: [ 'plugins/kibana/dev_tools', ], - fieldFormats: ['plugins/kibana/field_formats/register'], savedObjectTypes: [ 'plugins/kibana/visualize/saved_visualizations/saved_visualization_register', 'plugins/kibana/discover/saved_searches/saved_search_register', diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts index 1f2094d68063d..d9dea35a8a1c0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { AppStateClass } from 'ui/state_management/app_state'; +import { AppStateClass } from '../legacy_imports'; /** * A poor excuse for a mock just to get some basic tests to run in jest without requiring the injector. diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index eebfad5979d68..14c35759d70a9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -1,7 +1,7 @@ .dshAppContainer { - flex: 1; display: flex; flex-direction: column; + height: 100%; } .dshStartScreen { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts new file mode 100644 index 0000000000000..d507d547d9ba9 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -0,0 +1,228 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { EuiConfirmModal, EuiIcon } from '@elastic/eui'; +import angular, { IModule } from 'angular'; +import { IPrivate } from 'ui/private'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; +import { + AppMountContext, + ChromeStart, + LegacyCoreStart, + SavedObjectsClientContract, + UiSettingsClientContract, +} from 'kibana/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { + GlobalStateProvider, + StateManagementConfigProvider, + AppStateProvider, + PrivateProvider, + EventsProvider, + PersistedState, + createTopNavDirective, + createTopNavHelper, + PromiseServiceCreator, + KbnUrlProvider, + RedirectWhenMissingProvider, + confirmModalFactory, + configureAppAngularModule, +} from './legacy_imports'; + +// @ts-ignore +import { initDashboardApp } from './legacy_app'; +import { DataStart } from '../../../data/public'; +import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { NavigationStart } from '../../../navigation/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; + +export interface RenderDeps { + core: LegacyCoreStart; + indexPatterns: DataStart['indexPatterns']['indexPatterns']; + dataStart: DataStart; + npDataStart: NpDataStart; + navigation: NavigationStart; + savedObjectsClient: SavedObjectsClientContract; + savedObjectRegistry: any; + dashboardConfig: any; + savedDashboards: any; + dashboardCapabilities: any; + uiSettings: UiSettingsClientContract; + chrome: ChromeStart; + addBasePath: (path: string) => string; + savedQueryService: DataStart['search']['services']['savedQueryService']; + embeddables: ReturnType; + localStorage: Storage; + share: SharePluginStart; +} + +let angularModuleInstance: IModule | null = null; + +export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { + if (!angularModuleInstance) { + angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation); + // global routing stuff + configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true); + // custom routing stuff + initDashboardApp(angularModuleInstance, deps); + } + const $injector = mountDashboardApp(appBasePath, element); + return () => { + $injector.get('$rootScope').$destroy(); + }; +}; + +const mainTemplate = (basePath: string) => `
+ +
+
+`; + +const moduleName = 'app/dashboard'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; + +function mountDashboardApp(appBasePath: string, element: HTMLElement) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%'); + // eslint-disable-next-line + mountpoint.innerHTML = mainTemplate(appBasePath); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + // initialize global state handler + element.appendChild(mountpoint); + return $injector; +} + +function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalConfigModule(core); + createLocalKbnUrlModule(); + createLocalStateModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(navigation); + createLocalConfirmModalModule(); + createLocalIconModule(); + + const dashboardAngularModule = angular.module(moduleName, [ + ...thirdPartyAngularDependencies, + 'app/dashboard/Config', + 'app/dashboard/I18n', + 'app/dashboard/Private', + 'app/dashboard/PersistedState', + 'app/dashboard/TopNav', + 'app/dashboard/State', + 'app/dashboard/ConfirmModal', + 'app/dashboard/icon', + ]); + return dashboardAngularModule; +} + +function createLocalIconModule() { + angular + .module('app/dashboard/icon', ['react']) + .directive('icon', reactDirective => reactDirective(EuiIcon)); +} + +function createLocalConfirmModalModule() { + angular + .module('app/dashboard/ConfirmModal', ['react']) + .factory('confirmModal', confirmModalFactory) + .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); +} + +function createLocalStateModule() { + angular + .module('app/dashboard/State', [ + 'app/dashboard/Private', + 'app/dashboard/Config', + 'app/dashboard/KbnUrl', + 'app/dashboard/Promise', + 'app/dashboard/PersistedState', + ]) + .factory('AppState', function(Private: any) { + return Private(AppStateProvider); + }) + .service('getAppState', function(Private: any) { + return Private(AppStateProvider).getAppState; + }) + .service('globalState', function(Private: any) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule() { + angular + .module('app/dashboard/PersistedState', ['app/dashboard/Private', 'app/dashboard/Promise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: any) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule() { + angular + .module('app/dashboard/KbnUrl', ['app/dashboard/Private', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(core: AppMountContext['core']) { + angular + .module('app/dashboard/Config', ['app/dashboard/Private']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: core.uiSettings.get.bind(core.uiSettings), + }), + }; + }); +} + +function createLocalPromiseModule() { + angular.module('app/dashboard/Promise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalPrivateModule() { + angular.module('app/dashboard/Private', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule(navigation: NavigationStart) { + angular + .module('app/dashboard/TopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); +} + +function createLocalI18nModule() { + angular + .module('app/dashboard/I18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index f644f3811e3e0..b645bb408300f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -4,11 +4,11 @@ >
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index d5da4ba51e55b..0ce8f2ef59fc0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -17,26 +17,16 @@ * under the License. */ -import _ from 'lodash'; - -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { IInjector } from 'ui/chrome'; - -// @ts-ignore -import * as filterActions from 'plugins/kibana/discover/doc_table/actions/filter'; +import { StaticIndexPattern, SavedQuery } from 'plugins/data'; +import moment from 'moment'; +import { Subscription } from 'rxjs'; import { AppStateClass as TAppStateClass, AppState as TAppState, -} from 'ui/state_management/app_state'; - -import { KbnUrl } from 'ui/url/kbn_url'; -import { IndexPattern } from 'ui/index_patterns'; -import { IPrivate } from 'ui/private'; -import { StaticIndexPattern, SavedQuery } from 'plugins/data'; -import moment from 'moment'; -import { Subscription } from 'rxjs'; + IInjector, + KbnUrl, +} from './legacy_imports'; import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; @@ -44,6 +34,7 @@ import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types' import { TimeRange, Query, esFilters } from '../../../../../../src/plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; +import { RenderDeps } from './application'; export interface DashboardAppScope extends ng.IScope { dash: SavedObjectDashboard; @@ -90,54 +81,40 @@ export interface DashboardAppScope extends ng.IScope { kbnTopNav: any; enterEditMode: () => void; timefilterSubscriptions$: Subscription; + isVisible: boolean; } -const app = uiModules.get('app/dashboard', ['elasticsearch', 'ngRoute', 'react', 'kibana/config']); - -app.directive('dashboardApp', function($injector: IInjector) { - const AppState = $injector.get>('AppState'); - const kbnUrl = $injector.get('kbnUrl'); - const confirmModal = $injector.get('confirmModal'); - const config = $injector.get('config'); - - const Private = $injector.get('Private'); +export function initDashboardAppDirective(app: any, deps: RenderDeps) { + app.directive('dashboardApp', function($injector: IInjector) { + const AppState = $injector.get>('AppState'); + const kbnUrl = $injector.get('kbnUrl'); + const confirmModal = $injector.get('confirmModal'); + const config = deps.uiSettings; - const indexPatterns = $injector.get<{ - getDefault: () => Promise; - }>('indexPatterns'); - - return { - restrict: 'E', - controllerAs: 'dashboardApp', - controller: ( - $scope: DashboardAppScope, - $route: any, - $routeParams: { - id?: string; - }, - getAppState: { - previouslyStored: () => TAppState | undefined; - }, - dashboardConfig: { - getHideWriteControls: () => boolean; - }, - localStorage: { - get: (prop: string) => unknown; - } - ) => - new DashboardAppController({ - $route, - $scope, - $routeParams, - getAppState, - dashboardConfig, - localStorage, - Private, - kbnUrl, - AppStateClass: AppState, - indexPatterns, - config, - confirmModal, - }), - }; -}); + return { + restrict: 'E', + controllerAs: 'dashboardApp', + controller: ( + $scope: DashboardAppScope, + $route: any, + $routeParams: { + id?: string; + }, + getAppState: any, + globalState: any + ) => + new DashboardAppController({ + $route, + $scope, + $routeParams, + getAppState, + globalState, + kbnUrl, + AppStateClass: AppState, + config, + confirmModal, + ...deps, + }), + }; + }); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 457d8972876ae..16c0e4437c344 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -23,41 +23,23 @@ import React from 'react'; import angular from 'angular'; import { uniq } from 'lodash'; -import chrome from 'ui/chrome'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -import { toastNotifications } from 'ui/notify'; - -// @ts-ignore -import { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; - -import { docTitle } from 'ui/doc_title/doc_title'; - -import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; - -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; - -import { timefilter } from 'ui/timefilter'; - -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; +import { Subscription } from 'rxjs'; import { + subscribeWithScope, + ConfirmationButtonTypes, + showSaveModal, + SaveResult, + migrateLegacyQuery, + State, AppStateClass as TAppStateClass, - AppState as TAppState, -} from 'ui/state_management/app_state'; - -import { KbnUrl } from 'ui/url/kbn_url'; -import { IndexPattern } from 'ui/index_patterns'; -import { IPrivate } from 'ui/private'; -import { SavedQuery } from 'src/legacy/core_plugins/data/public'; -import { SaveOptions } from 'ui/saved_objects/saved_object'; -import { capabilities } from 'ui/capabilities'; -import { Subscription } from 'rxjs'; -import { npStart } from 'ui/new_platform'; -import { unhashUrl } from 'ui/state_management/state_hashing'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; + KbnUrl, + SaveOptions, + SavedObjectFinder, + unhashUrl, +} from './legacy_imports'; +import { FilterStateManager, IndexPattern, SavedQuery } from '../../../data/public'; import { Query } from '../../../../../plugins/data/public'; -import { start as data } from '../../../data/public/legacy'; import { DashboardContainer, @@ -72,7 +54,6 @@ import { ViewMode, openAddPanelFlyout, } from '../../../embeddable_api/public/np_ready/public'; -import { start } from '../../../embeddable_api/public/np_ready/public/legacy'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; import { showOptionsPopover } from './top_nav/show_options_popover'; @@ -87,8 +68,23 @@ import { getDashboardTitle } from './dashboard_strings'; import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; - -const { savedQueryService } = data.search.services; +import { RenderDeps } from './application'; + +export interface DashboardAppControllerDependencies extends RenderDeps { + $scope: DashboardAppScope; + $route: any; + $routeParams: any; + getAppState: any; + globalState: State; + indexPatterns: { + getDefault: () => Promise; + }; + dashboardConfig: any; + kbnUrl: KbnUrl; + AppStateClass: TAppStateClass; + config: any; + confirmModal: ConfirmModalFn; +} export class DashboardAppController { // Part of the exposed plugin API - do not remove without careful consideration. @@ -101,58 +97,55 @@ export class DashboardAppController { $route, $routeParams, getAppState, + globalState, dashboardConfig, localStorage, - Private, kbnUrl, AppStateClass, indexPatterns, config, confirmModal, - }: { - $scope: DashboardAppScope; - $route: any; - $routeParams: any; - getAppState: { - previouslyStored: () => TAppState | undefined; - }; - indexPatterns: { - getDefault: () => Promise; - }; - dashboardConfig: any; - localStorage: { - get: (prop: string) => unknown; - }; - Private: IPrivate; - kbnUrl: KbnUrl; - AppStateClass: TAppStateClass; - config: any; - confirmModal: ConfirmModalFn; - }) { - const queryFilter = Private(FilterBarQueryFilterProvider); - const getUnhashableStates = Private(getUnhashableStatesProvider); + savedQueryService, + embeddables, + share, + dashboardCapabilities, + npDataStart: { + query: { + filterManager, + timefilter: { timefilter }, + }, + }, + core: { notifications, overlays, chrome, injectedMetadata }, + }: DashboardAppControllerDependencies) { + new FilterStateManager(globalState, getAppState, filterManager); + const queryFilter = filterManager; + + function getUnhashableStates(): State[] { + return [getAppState(), globalState].filter(Boolean); + } let lastReloadRequestTime = 0; const dash = ($scope.dash = $route.current.locals.dash); if (dash.id) { - docTitle.change(dash.title); + chrome.docTitle.change(dash.title); } const dashboardStateManager = new DashboardStateManager({ savedDashboard: dash, AppStateClass, hideWriteControls: dashboardConfig.getHideWriteControls(), + kibanaVersion: injectedMetadata.getKibanaVersion(), }); $scope.appState = dashboardStateManager.getAppState(); - // The 'previouslyStored' check is so we only update the time filter on dashboard open, not during + // The hash check is so we only update the time filter on dashboard open, not during // normal cross app navigation. - if (dashboardStateManager.getIsTimeSavedWithDashboard() && !getAppState.previouslyStored()) { + if (dashboardStateManager.getIsTimeSavedWithDashboard() && !globalState.$inheritedGlobalState) { dashboardStateManager.syncTimefilterWithDashboard(timefilter); } - $scope.showSaveQuery = capabilities.get().dashboard.saveQuery as boolean; + $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; const updateIndexPatterns = (container?: DashboardContainer) => { if (!container || isErrorEmbeddable(container)) { @@ -187,10 +180,7 @@ export class DashboardAppController { [key: string]: DashboardPanelState; } = {}; dashboardStateManager.getPanels().forEach((panel: SavedDashboardPanel) => { - embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState( - panel, - dashboardStateManager.getUseMargins() - ); + embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel); }); let expandedPanelId; if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { @@ -239,7 +229,7 @@ export class DashboardAppController { let outputSubscription: Subscription | undefined; const dashboardDom = document.getElementById('dashboardViewport'); - const dashboardFactory = start.getEmbeddableFactory( + const dashboardFactory = embeddables.getEmbeddableFactory( DASHBOARD_CONTAINER_TYPE ) as DashboardContainerFactory; dashboardFactory @@ -334,7 +324,7 @@ export class DashboardAppController { // Push breadcrumbs to new header navigation const updateBreadcrumbs = () => { - chrome.breadcrumbs.set([ + chrome.setBreadcrumbs([ { text: i18n.translate('kbn.dashboard.dashboardAppBreadcrumbsTitle', { defaultMessage: 'Dashboard', @@ -495,7 +485,7 @@ export class DashboardAppController { }); $scope.$watch( - () => capabilities.get().dashboard.saveQuery, + () => dashboardCapabilities.saveQuery, newCapability => { $scope.showSaveQuery = newCapability as boolean; } @@ -595,7 +585,7 @@ export class DashboardAppController { return saveDashboard(angular.toJson, timefilter, dashboardStateManager, saveOptions) .then(function(id) { if (id) { - toastNotifications.addSuccess({ + notifications.toasts.addSuccess({ title: i18n.translate('kbn.dashboard.dashboardWasSavedSuccessMessage', { defaultMessage: `Dashboard '{dashTitle}' was saved`, values: { dashTitle: dash.title }, @@ -606,14 +596,14 @@ export class DashboardAppController { if (dash.id !== $routeParams.id) { kbnUrl.change(createDashboardEditUrl(dash.id)); } else { - docTitle.change(dash.lastSavedTitle); + chrome.docTitle.change(dash.lastSavedTitle); updateViewMode(ViewMode.VIEW); } } return { id }; }) .catch(error => { - toastNotifications.addDanger({ + notifications.toasts.addDanger({ title: i18n.translate('kbn.dashboard.dashboardWasNotSavedDangerMessage', { defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`, values: { @@ -734,10 +724,10 @@ export class DashboardAppController { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { openAddPanelFlyout({ embeddable: dashboardContainer, - getAllFactories: start.getEmbeddableFactories, - getFactory: start.getEmbeddableFactory, - notifications: npStart.core.notifications, - overlays: npStart.core.overlays, + getAllFactories: embeddables.getEmbeddableFactories, + getFactory: embeddables.getEmbeddableFactory, + notifications, + overlays, SavedObjectFinder, }); } @@ -757,7 +747,7 @@ export class DashboardAppController { }); }; navActions[TopNavIds.SHARE] = anchorElement => { - npStart.plugins.share.toggleShareContextMenu({ + share.toggleShareContextMenu({ anchorElement, allowEmbed: true, allowShortUrl: !dashboardConfig.getHideWriteControls(), @@ -784,8 +774,15 @@ export class DashboardAppController { }, }); + const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => { + $scope.$evalAsync(() => { + $scope.isVisible = isVisible; + }); + }); + $scope.$on('$destroy', () => { updateSubscription.unsubscribe(); + visibleSubscription.unsubscribe(); $scope.timefilterSubscriptions$.unsubscribe(); dashboardStateManager.destroy(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index 5e81373001bf5..c236ac7843c03 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -21,16 +21,13 @@ import './np_core.test.mocks'; import { DashboardStateManager } from './dashboard_state_manager'; import { getAppStateMock, getSavedDashboardMock } from './__tests__'; -import { AppStateClass } from 'ui/state_management/app_state'; +import { AppStateClass } from './legacy_imports'; import { DashboardAppState } from './types'; -import { TimeRange, TimefilterContract } from 'src/plugins/data/public'; +import { TimeRange, TimefilterContract, InputTimeRange } from 'src/plugins/data/public'; import { ViewMode } from 'src/plugins/embeddable/public'; -import { InputTimeRange } from 'ui/timefilter'; -jest.mock('ui/registry/field_formats', () => ({ - fieldFormats: { - getDefaultInstance: jest.fn(), - }, +jest.mock('ui/state_management/state', () => ({ + State: {}, })); describe('DashboardState', function() { @@ -52,6 +49,7 @@ describe('DashboardState', function() { savedDashboard, AppStateClass: getAppStateMock() as AppStateClass, hideWriteControls: false, + kibanaVersion: '7.0.0', }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index d5af4c93d0e0c..ac8628ec2a9d9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -20,15 +20,21 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; -import { Timefilter } from 'ui/timefilter'; -import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { Moment } from 'moment'; import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; -import { Query, esFilters } from '../../../../../../src/plugins/data/public'; +import { + stateMonitorFactory, + StateMonitor, + AppStateClass as TAppStateClass, + migrateLegacyQuery, +} from './legacy_imports'; +import { + Query, + esFilters, + TimefilterContract as Timefilter, +} from '../../../../../../src/plugins/data/public'; import { getAppStateDefaults, migrateAppState } from './lib'; import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters'; @@ -54,6 +60,7 @@ export class DashboardStateManager { }; private stateDefaults: DashboardAppStateDefaults; private hideWriteControls: boolean; + private kibanaVersion: string; public isDirty: boolean; private changeListeners: Array<(status: { dirty: boolean }) => void>; private stateMonitor: StateMonitor; @@ -68,11 +75,14 @@ export class DashboardStateManager { savedDashboard, AppStateClass, hideWriteControls, + kibanaVersion, }: { savedDashboard: SavedObjectDashboard; AppStateClass: TAppStateClass; hideWriteControls: boolean; + kibanaVersion: string; }) { + this.kibanaVersion = kibanaVersion; this.savedDashboard = savedDashboard; this.hideWriteControls = hideWriteControls; @@ -84,7 +94,7 @@ export class DashboardStateManager { // appState based on the URL (the url trumps the defaults). This means if we update the state format at all and // want to handle BWC, we must not only migrate the data stored with saved Dashboard, but also any old state in the // url. - migrateAppState(this.appState); + migrateAppState(this.appState, kibanaVersion); this.isDirty = false; @@ -146,7 +156,8 @@ export class DashboardStateManager { } convertedPanelStateMap[panelState.explicitInput.id] = convertPanelStateToSavedDashboardPanel( - panelState + panelState, + this.kibanaVersion ); if ( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts new file mode 100644 index 0000000000000..8a733f940734b --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { State } from './legacy_imports'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; + +/** + * Helper function to sync the global state with the various state providers + * when a local angular application mounts. There are three different ways + * global state can be passed into the application: + * * parameter in the URL hash - e.g. shared link + * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values + * + * This function looks up the three sources (earlier in the list means it takes precedence), + * puts it into the globalState object and syncs it with the url. + * + * Currently the legacy chrome takes care of restoring the global state when navigating from + * one app to another - to migrate away from that it will become necessary to also write the current + * state to local storage + */ +export function syncOnMount( + globalState: State, + { + query: { + filterManager, + timefilter: { timefilter }, + }, + }: NpDataStart +) { + // pull in global state information from the URL + globalState.fetch(); + // remember whether there were info in the URL + const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); + + // sync kibana platform state with the angular global state + if (!globalState.time) { + globalState.time = timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = timefilter.getRefreshInterval(); + } + if (!globalState.filters && filterManager.getGlobalFilters().length > 0) { + globalState.filters = filterManager.getGlobalFilters(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (hasGlobalURLState) { + // set flag the global state is set from the URL + globalState.$inheritedGlobalState = true; + } + globalState.save(); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js index 56b2bd253381c..1b1a7f84c8131 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js @@ -17,26 +17,30 @@ * under the License. */ -import React, { Fragment, PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; export class HelpMenu extends PureComponent { render() { return ( - - - - - - - + + <> + + + + + + + ); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js index aeabff2d97007..2dc8ce523a7da 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js @@ -21,9 +21,9 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { HelpMenu } from './help_menu'; -export function addHelpMenuToAppChrome(chrome) { - chrome.helpExtension.set(domElement => { - render(, domElement); +export function addHelpMenuToAppChrome(chrome, docLinks) { + chrome.setHelpExtension(domElement => { + render(, domElement); return () => { unmountComponentAtNode(domElement); }; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.js b/src/legacy/core_plugins/kibana/public/dashboard/index.js deleted file mode 100644 index 712e05c92e5e8..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.js +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import './dashboard_app'; -import { i18n } from '@kbn/i18n'; -import './saved_dashboard/saved_dashboards'; -import './dashboard_config'; -import uiRoutes from 'ui/routes'; -import chrome from 'ui/chrome'; -import { wrapInI18nContext } from 'ui/i18n'; -import { toastNotifications } from 'ui/notify'; - -import dashboardTemplate from './dashboard_app.html'; -import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; - -import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; -import { InvalidJSONProperty, SavedObjectNotFound } from '../../../../../plugins/kibana_utils/public'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; -import { uiModules } from 'ui/modules'; -import 'ui/capabilities/route_setup'; -import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; - -import { npStart } from 'ui/new_platform'; - -// load directives -import '../../../data/public'; - -const app = uiModules.get('app/dashboard', [ - 'ngRoute', - 'react', -]); - -app.directive('dashboardListing', function (reactDirective) { - return reactDirective(wrapInI18nContext(DashboardListing)); -}); - -function createNewDashboardCtrl($scope) { - $scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', { - defaultMessage: 'visit the Visualize app', - }); - addHelpMenuToAppChrome(chrome); -} - -uiRoutes - .defaults(/dashboard/, { - requireDefaultIndex: true, - requireUICapability: 'dashboard.show', - badge: uiCapabilities => { - if (uiCapabilities.dashboard.showWriteControls) { - return undefined; - } - - return { - text: i18n.translate('kbn.dashboard.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.dashboard.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save dashboards', - }), - iconType: 'glasses' - }; - } - }) - .when(DashboardConstants.LANDING_PAGE_PATH, { - template: dashboardListingTemplate, - controller($injector, $location, $scope, Private, config) { - const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; - const kbnUrl = $injector.get('kbnUrl'); - const dashboardConfig = $injector.get('dashboardConfig'); - - $scope.listingLimit = config.get('savedObjects:listingLimit'); - $scope.create = () => { - kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL); - }; - $scope.find = (search) => { - return services.dashboards.find(search, $scope.listingLimit); - }; - $scope.editItem = ({ id }) => { - kbnUrl.redirect(`${createDashboardEditUrl(id)}?_a=(viewMode:edit)`); - }; - $scope.getViewUrl = ({ id }) => { - return chrome.addBasePath(`#${createDashboardEditUrl(id)}`); - }; - $scope.delete = (dashboards) => { - return services.dashboards.delete(dashboards.map(d => d.id)); - }; - $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); - $scope.initialFilter = ($location.search()).filter || EMPTY_FILTER; - chrome.breadcrumbs.set([{ - text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', { - defaultMessage: 'Dashboards', - }), - }]); - addHelpMenuToAppChrome(chrome); - }, - resolve: { - dash: function ($route, Private, redirectWhenMissing, kbnUrl) { - const savedObjectsClient = Private(SavedObjectsClientProvider); - const title = $route.current.params.title; - if (title) { - return savedObjectsClient.find({ - search: `"${title}"`, - search_fields: 'title', - type: 'dashboard', - }).then(results => { - // The search isn't an exact match, lets see if we can find a single exact match to use - const matchingDashboards = results.savedObjects.filter( - dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase()); - if (matchingDashboards.length === 1) { - kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id)); - } else { - kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`); - } - throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN; - }).catch(redirectWhenMissing({ - 'dashboard': DashboardConstants.LANDING_PAGE_PATH - })); - } - } - } - }) - .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, { - template: dashboardTemplate, - controller: createNewDashboardCtrl, - requireUICapability: 'dashboard.createNew', - resolve: { - dash: function (savedDashboards, redirectWhenMissing) { - return savedDashboards.get() - .catch(redirectWhenMissing({ - 'dashboard': DashboardConstants.LANDING_PAGE_PATH - })); - } - } - }) - .when(createDashboardEditUrl(':id'), { - template: dashboardTemplate, - controller: createNewDashboardCtrl, - resolve: { - dash: function (savedDashboards, $route, redirectWhenMissing, kbnUrl, AppState) { - const id = $route.current.params.id; - - return savedDashboards.get(id) - .then((savedDashboard) => { - npStart.core.chrome.recentlyAccessed.add(savedDashboard.getFullPath(), savedDashboard.title, id); - return savedDashboard; - }) - .catch((error) => { - // A corrupt dashboard was detected (e.g. with invalid JSON properties) - if (error instanceof InvalidJSONProperty) { - toastNotifications.addDanger(error.message); - kbnUrl.redirect(DashboardConstants.LANDING_PAGE_PATH); - return; - } - - // Preserve BWC of v5.3.0 links for new, unsaved dashboards. - // See https://github.com/elastic/kibana/issues/10951 for more context. - if (error instanceof SavedObjectNotFound && id === 'create') { - // Note "new AppState" is necessary so the state in the url is preserved through the redirect. - kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); - toastNotifications.addWarning(i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', - { defaultMessage: 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.' } - )); - } else { - throw error; - } - }) - .catch(redirectWhenMissing({ - 'dashboard': DashboardConstants.LANDING_PAGE_PATH - })); - } - } - }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'dashboard', - title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { - defaultMessage: 'Dashboard', - }), - description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', { - defaultMessage: 'Display and share a collection of visualizations and saved searches.', - }), - icon: 'dashboardApp', - path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts new file mode 100644 index 0000000000000..d134739aa24c2 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -0,0 +1,69 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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, + SavedObjectRegistryProvider, + legacyChrome, + IPrivate, +} from './legacy_imports'; +import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; +import { start as data } from '../../../data/public/legacy'; +import { localApplicationService } from '../local_application_service'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as navigation } from '../../../navigation/public/legacy'; +import './saved_dashboard/saved_dashboards'; +import './dashboard_config'; + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularDependencies(): Promise { + const injector = await legacyChrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const savedObjectRegistry = Private(SavedObjectRegistryProvider); + + return { + dashboardConfig: injector.get('dashboardConfig'), + savedObjectRegistry, + savedDashboards: injector.get('savedDashboards'), + }; +} + +(async () => { + const instance = new DashboardPlugin(); + instance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + localApplicationService, + getAngularDependencies, + }, + }); + instance.start(npStart.core, { + ...npStart.plugins, + data, + npData: npStart.plugins.data, + embeddables, + navigation, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js new file mode 100644 index 0000000000000..c7f2adb4b875b --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js @@ -0,0 +1,224 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 dashboardTemplate from './dashboard_app.html'; +import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; + +import { ensureDefaultIndexPattern } from './legacy_imports'; +import { initDashboardAppDirective } from './dashboard_app'; +import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; +import { + InvalidJSONProperty, + SavedObjectNotFound, +} from '../../../../../plugins/kibana_utils/public'; +import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; +import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; +import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router'; +import { syncOnMount } from './global_state_sync'; + +export function initDashboardApp(app, deps) { + initDashboardAppDirective(app, deps); + + app.directive('dashboardListing', function (reactDirective) { + return reactDirective(DashboardListing); + }); + + function createNewDashboardCtrl($scope) { + $scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', { + defaultMessage: 'visit the Visualize app', + }); + addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); + } + + app.run(globalState => { + syncOnMount(globalState, deps.npDataStart); + }); + + app.run((globalState, $rootScope) => { + registerTimefilterWithGlobalStateFactory( + deps.npDataStart.query.timefilter.timefilter, + globalState, + $rootScope + ); + }); + + app.config(function ($routeProvider) { + const defaults = { + reloadOnSearch: false, + requireUICapability: 'dashboard.show', + badge: () => { + if (deps.dashboardCapabilities.showWriteControls) { + return undefined; + } + + return { + text: i18n.translate('kbn.dashboard.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.dashboard.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save dashboards', + }), + iconType: 'glasses', + }; + }, + }; + + $routeProvider + .when(DashboardConstants.LANDING_PAGE_PATH, { + ...defaults, + template: dashboardListingTemplate, + controller($injector, $location, $scope) { + const services = deps.savedObjectRegistry.byLoaderPropertiesName; + const kbnUrl = $injector.get('kbnUrl'); + const dashboardConfig = deps.dashboardConfig; + + $scope.listingLimit = deps.uiSettings.get('savedObjects:listingLimit'); + $scope.create = () => { + kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL); + }; + $scope.find = search => { + return services.dashboards.find(search, $scope.listingLimit); + }; + $scope.editItem = ({ id }) => { + kbnUrl.redirect(`${createDashboardEditUrl(id)}?_a=(viewMode:edit)`); + }; + $scope.getViewUrl = ({ id }) => { + return deps.addBasePath(`#${createDashboardEditUrl(id)}`); + }; + $scope.delete = dashboards => { + return services.dashboards.delete(dashboards.map(d => d.id)); + }; + $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); + $scope.initialFilter = $location.search().filter || EMPTY_FILTER; + deps.chrome.setBreadcrumbs([ + { + text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', { + defaultMessage: 'Dashboards', + }), + }, + ]); + addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); + }, + resolve: { + dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl) { + return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl).then(() => { + const savedObjectsClient = deps.savedObjectsClient; + const title = $route.current.params.title; + if (title) { + return savedObjectsClient + .find({ + search: `"${title}"`, + search_fields: 'title', + type: 'dashboard', + }) + .then(results => { + // The search isn't an exact match, lets see if we can find a single exact match to use + const matchingDashboards = results.savedObjects.filter( + dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase() + ); + if (matchingDashboards.length === 1) { + kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id)); + } else { + kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`); + } + $rootScope.$digest(); + return new Promise(() => {}); + }); + } + }); + }, + }, + }) + .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, { + ...defaults, + template: dashboardTemplate, + controller: createNewDashboardCtrl, + requireUICapability: 'dashboard.createNew', + resolve: { + dash: function (redirectWhenMissing, $rootScope, kbnUrl) { + return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) + .then(() => { + return deps.savedDashboards.get(); + }) + .catch( + redirectWhenMissing({ + dashboard: DashboardConstants.LANDING_PAGE_PATH, + }) + ); + }, + }, + }) + .when(createDashboardEditUrl(':id'), { + ...defaults, + template: dashboardTemplate, + controller: createNewDashboardCtrl, + resolve: { + dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { + const id = $route.current.params.id; + + return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) + .then(() => { + return deps.savedDashboards.get(id); + }) + .then(savedDashboard => { + deps.chrome.recentlyAccessed.add( + savedDashboard.getFullPath(), + savedDashboard.title, + id + ); + return savedDashboard; + }) + .catch(error => { + // A corrupt dashboard was detected (e.g. with invalid JSON properties) + if (error instanceof InvalidJSONProperty) { + deps.toastNotifications.addDanger(error.message); + kbnUrl.redirect(DashboardConstants.LANDING_PAGE_PATH); + return; + } + + // Preserve BWC of v5.3.0 links for new, unsaved dashboards. + // See https://github.com/elastic/kibana/issues/10951 for more context. + if (error instanceof SavedObjectNotFound && id === 'create') { + // Note "new AppState" is necessary so the state in the url is preserved through the redirect. + kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); + deps.toastNotifications.addWarning( + i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', { + defaultMessage: + 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.', + }) + ); + return new Promise(() => {}); + } else { + throw error; + } + }) + .catch( + redirectWhenMissing({ + dashboard: DashboardConstants.LANDING_PAGE_PATH, + }) + ); + }, + }, + }) + .when(`dashboard/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` }) + .when(`dashboards/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` }); + }); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts new file mode 100644 index 0000000000000..7c3c389330887 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * The imports in this file are static functions and types which still live in legacy folders and are used + * within dashboard. To consolidate them all in one place, they are re-exported from this file. Eventually + * this list should become empty. Imports from the top level of shimmed or moved plugins can be imported + * directly where they are needed. + */ + +import chrome from 'ui/chrome'; + +export const legacyChrome = chrome; +export { State } from 'ui/state_management/state'; +export { AppState } from 'ui/state_management/app_state'; +export { AppStateClass } from 'ui/state_management/app_state'; +export { SaveOptions } from 'ui/saved_objects/saved_object'; +export { npSetup, npStart } from 'ui/new_platform'; +export { SavedObjectRegistryProvider } from 'ui/saved_objects'; +export { IPrivate } from 'ui/private'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +// @ts-ignore +export { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; +export { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +export { KbnUrl } from 'ui/url/kbn_url'; +// @ts-ignore +export { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +// @ts-ignore +export { AppStateProvider } from 'ui/state_management/app_state'; +// @ts-ignore +export { PrivateProvider } from 'ui/private/private'; +// @ts-ignore +export { EventsProvider } from 'ui/events'; +export { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +export { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +// @ts-ignore +export { confirmModalFactory } from 'ui/modals/confirm_modal'; +export { configureAppAngularModule } from 'ui/legacy_compat'; +export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; +export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; +export { unhashUrl } from 'ui/state_management/state_hashing'; +export { IInjector } from 'ui/chrome'; +export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts index 99bb6b115b985..3f04cad4f322b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts @@ -48,7 +48,7 @@ test('convertSavedDashboardPanelToPanelState', () => { version: '7.0.0', }; - expect(convertSavedDashboardPanelToPanelState(savedDashboardPanel, true)).toEqual({ + expect(convertSavedDashboardPanelToPanelState(savedDashboardPanel)).toEqual({ gridData: { x: 0, y: 0, @@ -82,7 +82,7 @@ test('convertSavedDashboardPanelToPanelState does not include undefined id', () version: '7.0.0', }; - const converted = convertSavedDashboardPanelToPanelState(savedDashboardPanel, false); + const converted = convertSavedDashboardPanelToPanelState(savedDashboardPanel); expect(converted.hasOwnProperty('savedObjectId')).toBe(false); }); @@ -103,7 +103,7 @@ test('convertPanelStateToSavedDashboardPanel', () => { type: 'search', }; - expect(convertPanelStateToSavedDashboardPanel(dashboardPanel)).toEqual({ + expect(convertPanelStateToSavedDashboardPanel(dashboardPanel, '6.3.0')).toEqual({ type: 'search', embeddableConfig: { something: 'hi!', @@ -137,6 +137,6 @@ test('convertPanelStateToSavedDashboardPanel will not add an undefined id when n type: 'search', }; - const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel); + const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel, '8.0.0'); expect(converted.hasOwnProperty('id')).toBe(false); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts index 4a3bc3b228106..2d42609e1e25f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts @@ -18,12 +18,10 @@ */ import { omit } from 'lodash'; import { DashboardPanelState } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; -import chrome from 'ui/chrome'; import { SavedDashboardPanel } from '../types'; export function convertSavedDashboardPanelToPanelState( - savedDashboardPanel: SavedDashboardPanel, - useMargins: boolean + savedDashboardPanel: SavedDashboardPanel ): DashboardPanelState { return { type: savedDashboardPanel.type, @@ -38,13 +36,14 @@ export function convertSavedDashboardPanelToPanelState( } export function convertPanelStateToSavedDashboardPanel( - panelState: DashboardPanelState + panelState: DashboardPanelState, + version: string ): SavedDashboardPanel { const customTitle: string | undefined = panelState.explicitInput.title ? (panelState.explicitInput.title as string) : undefined; return { - version: chrome.getKibanaVersion(), + version, type: panelState.type, gridData: panelState.gridData, panelIndex: panelState.explicitInput.id, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts index 10c27226300a5..4aa2461bb6593 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts @@ -43,7 +43,7 @@ test('migrate app state from 6.0', async () => { getQueryParamName: () => 'a', save: mockSave, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -58,6 +58,7 @@ test('migrate app state from 6.0', async () => { }); test('migrate sort from 6.1', async () => { + const TARGET_VERSION = '8.0'; const mockSave = jest.fn(); const appState = { uiState: { @@ -80,7 +81,7 @@ test('migrate sort from 6.1', async () => { save: mockSave, useMargins: false, }; - migrateAppState(appState); + migrateAppState(appState, TARGET_VERSION); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -112,7 +113,7 @@ test('migrates 6.0 even when uiState does not exist', async () => { getQueryParamName: () => 'a', save: mockSave, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -147,7 +148,7 @@ test('6.2 migration adjusts w & h without margins', async () => { save: mockSave, useMargins: false, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -184,7 +185,7 @@ test('6.2 migration adjusts w & h with margins', async () => { save: mockSave, useMargins: true, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts index 9bd93029f06d8..c4ad754548459 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts @@ -18,7 +18,6 @@ */ import semver from 'semver'; -import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { @@ -37,7 +36,10 @@ import { migratePanelsTo730 } from '../migrations/migrate_to_730_panels'; * * Once we hit a major version, we can remove support for older style URLs and get rid of this logic. */ -export function migrateAppState(appState: { [key: string]: unknown } | DashboardAppState) { +export function migrateAppState( + appState: { [key: string]: unknown } | DashboardAppState, + kibanaVersion: string +) { if (!appState.panels) { throw new Error( i18n.translate('kbn.dashboard.panel.invalidData', { @@ -73,7 +75,7 @@ export function migrateAppState(appState: { [key: string]: unknown } | Dashboard | SavedDashboardPanel630 | SavedDashboardPanel640To720 >, - chrome.getKibanaVersion(), + kibanaVersion, appState.useMargins, appState.uiState ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts index 168f320b5ea7e..e0d82373d3ad9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts @@ -17,8 +17,8 @@ * under the License. */ -import { SaveOptions } from 'ui/saved_objects/saved_object'; -import { Timefilter } from 'ui/timefilter'; +import { TimefilterContract } from 'src/plugins/data/public'; +import { SaveOptions } from '../legacy_imports'; import { updateSavedDashboard } from './update_saved_dashboard'; import { DashboardStateManager } from '../dashboard_state_manager'; @@ -32,7 +32,7 @@ import { DashboardStateManager } from '../dashboard_state_manager'; */ export function saveDashboard( toJson: (obj: any) => string, - timeFilter: Timefilter, + timeFilter: TimefilterContract, dashboardStateManager: DashboardStateManager, saveOptions: SaveOptions ): Promise { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts index 707b5a0f5f5f5..ce9096b3a56f0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts @@ -18,16 +18,15 @@ */ import _ from 'lodash'; -import { AppState } from 'ui/state_management/app_state'; -import { Timefilter } from 'ui/timefilter'; -import { RefreshInterval } from 'src/plugins/data/public'; +import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public'; +import { AppState } from '../legacy_imports'; import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; export function updateSavedDashboard( savedDashboard: SavedObjectDashboard, appState: AppState, - timeFilter: Timefilter, + timeFilter: TimefilterContract, toJson: (object: T) => string ) { savedDashboard.title = appState.title; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap b/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap index 1ed05035f5f4c..b2f004568841a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap @@ -1,533 +1,545 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`after fetch hideWriteControls 1`] = ` - - - - - } - /> -
- } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, + + + + + + } + /> +
+ } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch initialFilter 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch renders call to action when no dashboards exist 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch renders table rows 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`renders empty page in before initial fetch to avoid flickering 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js index c222fcd3c928c..98581223afa46 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js @@ -19,7 +19,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; @@ -41,27 +41,29 @@ export class DashboardListing extends React.Component { render() { return ( - + + + ); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts new file mode 100644 index 0000000000000..780fa6571e4e7 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -0,0 +1,151 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { + App, + CoreSetup, + CoreStart, + LegacyCoreStart, + Plugin, + SavedObjectsClientContract, +} from 'kibana/public'; +import { i18n } from '@kbn/i18n'; +import { RenderDeps } from './application'; +import { LocalApplicationService } from '../local_application_service'; +import { DataStart } from '../../../data/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; +import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { NavigationStart } from '../../../navigation/public'; +import { DashboardConstants } from './dashboard_constants'; +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../../plugins/home/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; + +export interface LegacyAngularInjectedDependencies { + dashboardConfig: any; + savedObjectRegistry: any; + savedDashboards: any; +} + +export interface DashboardPluginStartDependencies { + data: DataStart; + npData: NpDataStart; + embeddables: ReturnType; + navigation: NavigationStart; + share: SharePluginStart; +} + +export interface DashboardPluginSetupDependencies { + __LEGACY: { + getAngularDependencies: () => Promise; + localApplicationService: LocalApplicationService; + }; + home: HomePublicPluginSetup; +} + +export class DashboardPlugin implements Plugin { + private startDependencies: { + dataStart: DataStart; + npDataStart: NpDataStart; + savedObjectsClient: SavedObjectsClientContract; + embeddables: ReturnType; + navigation: NavigationStart; + share: SharePluginStart; + } | null = null; + + public setup( + core: CoreSetup, + { + __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, + home, + }: DashboardPluginSetupDependencies + ) { + const app: App = { + id: '', + title: 'Dashboards', + mount: async ({ core: contextCore }, params) => { + if (this.startDependencies === null) { + throw new Error('not started yet'); + } + const { + dataStart, + savedObjectsClient, + embeddables, + navigation, + share, + npDataStart, + } = this.startDependencies; + const angularDependencies = await getAngularDependencies(); + const deps: RenderDeps = { + core: contextCore as LegacyCoreStart, + ...legacyServices, + ...angularDependencies, + navigation, + dataStart, + share, + npDataStart, + indexPatterns: dataStart.indexPatterns.indexPatterns, + savedObjectsClient, + chrome: contextCore.chrome, + addBasePath: contextCore.http.basePath.prepend, + uiSettings: contextCore.uiSettings, + savedQueryService: dataStart.search.services.savedQueryService, + embeddables, + dashboardCapabilities: contextCore.application.capabilities.dashboard, + localStorage: new Storage(localStorage), + }; + const { renderApp } = await import('./application'); + return renderApp(params.element, params.appBasePath, deps); + }, + }; + localApplicationService.register({ ...app, id: 'dashboard' }); + localApplicationService.register({ ...app, id: 'dashboards' }); + + home.featureCatalogue.register({ + id: 'dashboard', + title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { + defaultMessage: 'Dashboard', + }), + description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', { + defaultMessage: 'Display and share a collection of visualizations and saved searches.', + }), + icon: 'dashboardApp', + path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }); + } + + start( + { savedObjects: { client: savedObjectsClient } }: CoreStart, + { data: dataStart, embeddables, navigation, npData, share }: DashboardPluginStartDependencies + ) { + this.startDependencies = { + dataStart, + npDataStart: npData, + savedObjectsClient, + embeddables, + navigation, + share, + }; + } +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js index 153a049276cee..aa7e219d75963 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js @@ -17,9 +17,16 @@ * under the License. */ + import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +jest.mock('../legacy_imports', () => ({ + SavedObjectSaveModal: () => null +})); + +jest.mock('ui/new_platform'); + import { DashboardSaveModal } from './save_modal'; test('renders DashboardSaveModal', () => { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx index 47455f04ba809..0640b2be431be 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx @@ -19,10 +19,10 @@ import React, { Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; - -import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { EuiFormRow, EuiTextArea, EuiSwitch } from '@elastic/eui'; +import { SavedObjectSaveModal } from '../legacy_imports'; + interface SaveOptions { newTitle: string; newDescription: string; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx index c3cd5621b2c88..af1020e01e0c5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx @@ -17,10 +17,10 @@ * under the License. */ -import { I18nContext } from 'ui/i18n'; import React from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; import { DashboardCloneModal } from './clone_modal'; export function showCloneModal( @@ -54,7 +54,7 @@ export function showCloneModal( }; document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx index 8640d7dbc6bdc..7c23e4808fbea 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx @@ -19,9 +19,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; - +import { I18nProvider } from '@kbn/i18n/react'; import { EuiWrappingPopover } from '@elastic/eui'; + import { OptionsMenu } from './options'; let isOpen = false; @@ -55,7 +55,7 @@ export function showOptionsPopover({ document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/types.ts index 3c2c87a502da4..371274401739e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/types.ts @@ -17,9 +17,8 @@ * under the License. */ -import { AppState } from 'ui/state_management/app_state'; -import { AppState as TAppState } from 'ui/state_management/app_state'; import { ViewMode } from 'src/plugins/embeddable/public'; +import { AppState } from './legacy_imports'; import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel610, @@ -153,5 +152,5 @@ export type AddFilterFn = ( operator: string; index: string; }, - appState: TAppState + appState: AppState ) => void; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js index a5b55e50eb90e..bac56f008233c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js @@ -241,7 +241,6 @@ describe('discover field chooser directives', function () { $scope.computeDetails(field); expect(field.details.buckets).to.not.be(undefined); expect(field.details.buckets[0].value).to.be(40.141592); - expect(field.details.buckets[0].display).to.be('40.142'); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context_app.html b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.html index 68e1d536a91ce..3e0f8a8329154 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context_app.html +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.html @@ -1,9 +1,16 @@ - +> + +
$injector.invoke( @@ -119,50 +119,53 @@ uiRoutes template: indexTemplate, reloadOnSearch: false, resolve: { - ip: function (Promise, indexPatterns, config, Private) { + savedObjects: function (Promise, indexPatterns, config, Private, $rootScope, kbnUrl, redirectWhenMissing, savedSearches, $route) { const State = Private(StateProvider); - return indexPatterns.getCache().then((savedObjects)=> { - /** - * In making the indexPattern modifiable it was placed in appState. Unfortunately, - * the load order of AppState conflicts with the load order of many other things - * so in order to get the name of the index we should use, and to switch to the - * default if necessary, we parse the appState with a temporary State object and - * then destroy it immediatly after we're done - * - * @type {State} - */ - const state = new State('_a', {}); - - const specified = !!state.index; - const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1; - const id = exists ? state.index : config.get('defaultIndex'); - state.destroy(); + const savedSearchId = $route.current.params.id; + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => { return Promise.props({ - list: savedObjects, - loaded: indexPatterns.get(id), - stateVal: state.index, - stateValFound: specified && exists + ip: indexPatterns.getCache().then((savedObjects) => { + /** + * In making the indexPattern modifiable it was placed in appState. Unfortunately, + * the load order of AppState conflicts with the load order of many other things + * so in order to get the name of the index we should use, and to switch to the + * default if necessary, we parse the appState with a temporary State object and + * then destroy it immediatly after we're done + * + * @type {State} + */ + const state = new State('_a', {}); + + const specified = !!state.index; + const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1; + const id = exists ? state.index : config.get('defaultIndex'); + state.destroy(); + + return Promise.props({ + list: savedObjects, + loaded: indexPatterns.get(id), + stateVal: state.index, + stateValFound: specified && exists + }); + }), + savedSearch: savedSearches.get(savedSearchId) + .then((savedSearch) => { + if (savedSearchId) { + chrome.recentlyAccessed.add( + savedSearch.getFullPath(), + savedSearch.title, + savedSearchId); + } + return savedSearch; + }) + .catch(redirectWhenMissing({ + 'search': '/discover', + 'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id + })) }); }); }, - savedSearch: function (redirectWhenMissing, savedSearches, $route) { - const savedSearchId = $route.current.params.id; - return savedSearches.get(savedSearchId) - .then((savedSearch) => { - if (savedSearchId) { - chrome.recentlyAccessed.add( - savedSearch.getFullPath(), - savedSearch.title, - savedSearchId); - } - return savedSearch; - }) - .catch(redirectWhenMissing({ - 'search': '/discover', - 'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id - })); - } } }); @@ -224,7 +227,7 @@ function discoverController( }; // the saved savedSearch - const savedSearch = $route.current.locals.savedSearch; + const savedSearch = $route.current.locals.savedObjects.savedSearch; let abortController; $scope.$on('$destroy', () => { @@ -417,20 +420,6 @@ function discoverController( queryFilter.setFilters(filters); }; - $scope.applyFilters = filters => { - const { timeRangeFilter, restOfFilters } = extractTimeFilter($scope.indexPattern.timeFieldName, filters); - queryFilter.addFilters(restOfFilters); - if (timeRangeFilter) changeTimeFilter(timefilter, timeRangeFilter); - - $scope.state.$newFilters = []; - }; - - $scope.$watch('state.$newFilters', (filters = []) => { - if (filters.length === 1) { - $scope.applyFilters(filters); - } - }); - const getFieldCounts = async () => { // the field counts aren't set until we have the data back, // so we wait for the fetch to be done before proceeding @@ -539,7 +528,7 @@ function discoverController( sampleSize: config.get('discover:sampleSize'), timefield: isDefaultTypeIndexPattern($scope.indexPattern) && $scope.indexPattern.timeFieldName, savedSearch: savedSearch, - indexPatternList: $route.current.locals.ip.list, + indexPatternList: $route.current.locals.savedObjects.ip.list, }; const shouldSearchOnPageLoad = () => { @@ -1055,7 +1044,7 @@ function discoverController( loaded: loadedIndexPattern, stateVal, stateValFound, - } = $route.current.locals.ip; + } = $route.current.locals.savedObjects.ip; const ownIndexPattern = $scope.searchSource.getOwnField('index'); @@ -1103,12 +1092,12 @@ function discoverController( // Block the UI from loading if the user has loaded a rollup index pattern but it isn't // supported. $scope.isUnsupportedIndexPattern = ( - !isDefaultTypeIndexPattern($route.current.locals.ip.loaded) - && !hasSearchStategyForIndexPattern($route.current.locals.ip.loaded) + !isDefaultTypeIndexPattern($route.current.locals.savedObjects.ip.loaded) + && !hasSearchStategyForIndexPattern($route.current.locals.savedObjects.ip.loaded) ); if ($scope.isUnsupportedIndexPattern) { - $scope.unsupportedIndexPatternType = $route.current.locals.ip.loaded.type; + $scope.unsupportedIndexPatternType = $route.current.locals.savedObjects.ip.loaded.type; return; } diff --git a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts index 51e0dcba1cad0..6c3856932c96c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts +++ b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts @@ -34,7 +34,7 @@ export function getSavedSearchBreadcrumbs($route: any) { return [ ...getRootBreadcrumbs(), { - text: $route.current.locals.savedSearch.id, + text: $route.current.locals.savedObjects.savedSearch.id, }, ]; } diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 61d7933464e7f..02b08d7fa4b61 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -46,6 +46,7 @@ import * as docViewsRegistry from 'ui/registry/doc_views'; const services = { // new plattform + core: npStart.core, addBasePath: npStart.core.http.basePath.prepend, capabilities: npStart.core.application.capabilities, chrome: npStart.core.chrome, @@ -108,6 +109,7 @@ export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; export { tabifyAggResponse } from 'ui/agg_response/tabify'; // @ts-ignore export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; +export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { unhashUrl } from 'ui/state_management/state_hashing'; // EXPORT types diff --git a/src/legacy/core_plugins/kibana/public/field_formats/__tests__/_conformance.js b/src/legacy/core_plugins/kibana/public/field_formats/__tests__/_conformance.js deleted file mode 100644 index 1c63d2efc7e0b..0000000000000 --- a/src/legacy/core_plugins/kibana/public/field_formats/__tests__/_conformance.js +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { fieldFormats } from 'ui/registry/field_formats'; -import { npStart } from 'ui/new_platform'; -import { FieldFormat } from '../../../../../../plugins/data/public'; - -const config = npStart.core.uiSettings; - -const formatIds = [ - 'bytes', - 'date', - 'date_nanos', - 'duration', - 'ip', - 'number', - 'percent', - 'color', - 'string', - 'url', - '_source', - 'truncate', - 'boolean', - 'relative_date', - 'static_lookup' -]; - -// eslint-disable-next-line import/no-default-export -export default describe('conformance', function () { - - const getConfig = (...args) => config.get(...args); - - formatIds.forEach(function (id) { - let instance; - let Type; - - beforeEach(function () { - Type = fieldFormats.getType(id); - instance = fieldFormats.getInstance(id); - }); - - describe(id + ' Type', function () { - it('has an id', function () { - expect(Type.id).to.be.a('string'); - }); - - it('has a title', function () { - expect(Type.title).to.be.a('string'); - }); - - it('declares compatible field formats as a string or array', function () { - expect(Type.fieldType).to.be.ok(); - expect(_.isString(Type.fieldType) || Array.isArray(Type.fieldType)).to.be(true); - }); - }); - - describe(id + ' Instance', function () { - it('extends FieldFormat', function () { - expect(instance).to.be.a(FieldFormat); - }); - }); - }); - - it('registers all of the fieldFormats', function () { - expect(_.difference(fieldFormats.raw, formatIds.map(fieldFormats.getType))).to.eql([]); - }); - - describe('Bytes format', basicPatternTests('bytes', require('numeral'))); - describe('Percent Format', basicPatternTests('percent', require('numeral'))); - describe('Date Format', basicPatternTests('date', require('moment'))); - - describe('Number Format', function () { - basicPatternTests('number', require('numeral'))(); - - it('tries to parse strings', function () { - const number = new (fieldFormats.getType('number'))({ pattern: '0.0b' }, getConfig); - expect(number.convert(123.456)).to.be('123.5B'); - expect(number.convert('123.456')).to.be('123.5B'); - }); - - }); - - function basicPatternTests(id, lib) { - const confKey = id === 'date' ? 'dateFormat' : 'format:' + id + ':defaultPattern'; - - return function () { - it('converts using the format:' + id + ':defaultPattern config', function () { - const inst = fieldFormats.getInstance(id); - [ - '0b', - '0 b', - '0.[000] b', - '0.[000]b', - '0.[0]b' - ].forEach(function (pattern) { - const original = config.get(confKey); - const num = _.random(-10000, 10000, true); - config.set(confKey, pattern); - expect(inst.convert(num)).to.be(lib(num).format(pattern)); - config.set(confKey, original); - }); - }); - - it('uses the pattern param if available', function () { - const original = config.get(confKey); - const num = _.random(-10000, 10000, true); - const defFormat = '0b'; - const customFormat = '0.00000%'; - - config.set(confKey, defFormat); - const defInst = fieldFormats.getInstance(id); - - const Type = fieldFormats.getType(id); - const customInst = new Type({ pattern: customFormat }, getConfig); - - expect(defInst.convert(num)).to.not.be(customInst.convert(num)); - expect(defInst.convert(num)).to.be(lib(num).format(defFormat)); - expect(customInst.convert(num)).to.be(lib(num).format(customFormat)); - - config.set(confKey, original); - }); - }; - } -}); diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index 4ebf719b86233..b1c03507c9a2d 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -64,11 +64,11 @@ let copiedLegacyCatalogue = false; const Private = injector.get('Private'); // Merge legacy registry with new registry (Private(FeatureCatalogueRegistryProvider as any) as any).inTitleOrder.map( - npSetup.plugins.feature_catalogue.register + npSetup.plugins.home.featureCatalogue.register ); copiedLegacyCatalogue = true; } - return npStart.plugins.feature_catalogue.get(); + return npStart.plugins.home.featureCatalogue.get(); }, getAngularDependencies, localApplicationService, diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 6189204ee4cfc..5ef6e019db042 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -29,7 +29,7 @@ import { UiSettingsState, } from 'kibana/public'; import { UiStatsMetricType } from '@kbn/analytics'; -import { FeatureCatalogueEntry } from '../../../../../plugins/feature_catalogue/public'; +import { FeatureCatalogueEntry } from '../../../../../plugins/home/public'; export interface HomeKibanaServices { indexPatternService: any; diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index 2a2ea371d7f3b..18e101fc58d51 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -23,7 +23,7 @@ import { UiStatsMetricType } from '@kbn/analytics'; import { DataStart } from '../../../data/public'; import { LocalApplicationService } from '../local_application_service'; import { setServices } from './kibana_services'; -import { FeatureCatalogueEntry } from '../../../../../plugins/feature_catalogue/public'; +import { FeatureCatalogueEntry } from '../../../../../plugins/home/public'; export interface LegacyAngularInjectedDependencies { telemetryOptInProvider: any; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 14fc2ec6ead00..98def2252b75c 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -31,7 +31,6 @@ import 'uiExports/visTypes'; import 'uiExports/visEditorTypes'; import 'uiExports/visualize'; import 'uiExports/savedObjectTypes'; -import 'uiExports/fieldFormats'; import 'uiExports/fieldFormatEditors'; import 'uiExports/navbarExtensions'; import 'uiExports/contextMenuActions'; diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js index c0949318e9253..83fc8e4db9b55 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ b/src/legacy/core_plugins/kibana/public/management/index.js @@ -28,7 +28,6 @@ import { I18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; import appTemplate from './app.html'; import landingTemplate from './landing.html'; -import { capabilities } from 'ui/capabilities'; import { management, SidebarNav, MANAGEMENT_BREADCRUMB } from 'ui/management'; import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { timefilter } from 'ui/timefilter'; @@ -50,13 +49,6 @@ uiRoutes redirectTo: '/management' }); -require('./route_setup/load_default')({ - whenMissingRedirectTo: () => { - const canManageIndexPatterns = capabilities.get().management.kibana.index_patterns; - return canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home'; - } -}); - export function updateLandingPage(version) { const node = document.getElementById(LANDING_ID); if (!node) { diff --git a/src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js b/src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js deleted file mode 100644 index f797acbe8888e..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import React from 'react'; -import { banners } from 'ui/notify'; -import { NoDefaultIndexPattern } from 'ui/index_patterns'; -import uiRoutes from 'ui/routes'; -import { - EuiCallOut, -} from '@elastic/eui'; -import { clearTimeout } from 'timers'; -import { i18n } from '@kbn/i18n'; - -let bannerId; -let timeoutId; - -function displayBanner() { - clearTimeout(timeoutId); - - // Avoid being hostile to new users who don't have an index pattern setup yet - // give them a friendly info message instead of a terse error message - bannerId = banners.set({ - id: bannerId, // initially undefined, but reused after first set - component: ( - - ) - }); - - // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around - timeoutId = setTimeout(() => { - banners.remove(bannerId); - timeoutId = undefined; - }, 15000); -} - -// eslint-disable-next-line import/no-default-export -export default function (opts) { - opts = opts || {}; - const whenMissingRedirectTo = opts.whenMissingRedirectTo || null; - - uiRoutes - .addSetupWork(function loadDefaultIndexPattern(Promise, $route, config, indexPatterns) { - const route = _.get($route, 'current.$$route'); - - if (!route.requireDefaultIndex) { - return; - } - - return indexPatterns.getIds() - .then(function (patterns) { - let defaultId = config.get('defaultIndex'); - let defined = !!defaultId; - const exists = _.contains(patterns, defaultId); - - if (defined && !exists) { - config.remove('defaultIndex'); - defaultId = defined = false; - } - - if (!defined) { - // If there is any index pattern created, set the first as default - if (patterns.length >= 1) { - defaultId = patterns[0]; - config.set('defaultIndex', defaultId); - } else { - throw new NoDefaultIndexPattern(); - } - } - }); - }) - .afterWork( - // success - null, - - // failure - function (err, kbnUrl) { - const hasDefault = !(err instanceof NoDefaultIndexPattern); - if (hasDefault || !whenMissingRedirectTo) throw err; // rethrow - - kbnUrl.change(whenMissingRedirectTo()); - - displayBanner(); - } - ); -} diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html index 0ef3cce832bc7..bf9ac9b9bbe36 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html @@ -1,7 +1,7 @@
@@ -33,6 +33,7 @@ show-query-bar is set to "true". -->
savedVisualizations.get($route.current.params)) .then(savedVis => { if (savedVis.vis.type.setup) { return savedVis.vis.type.setup(savedVis) @@ -102,28 +104,33 @@ uiRoutes template: editorTemplate, k7Breadcrumbs: getEditBreadcrumbs, resolve: { - savedVis: function (savedVisualizations, redirectWhenMissing, $route) { - return savedVisualizations.get($route.current.params.id) + savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) { + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl) + .then(() => savedVisualizations.get($route.current.params.id)) .then((savedVis) => { chrome.recentlyAccessed.add( savedVis.getFullPath(), savedVis.title, - savedVis.id); + savedVis.id + ); return savedVis; }) .then(savedVis => { if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis) - .catch(() => savedVis); + return savedVis.vis.type.setup(savedVis).catch(() => savedVis); } return savedVis; }) - .catch(redirectWhenMissing({ - 'visualization': '/visualize', - 'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id - })); + .catch( + redirectWhenMissing({ + visualization: '/visualize', + search: '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern': + '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern-field': + '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + }) + ); } } }); @@ -418,6 +425,12 @@ function VisEditor( next: updateTimeRange })); + subscriptions.add(chrome.getIsVisible$().subscribe(isVisible => { + $scope.$evalAsync(() => { + $scope.isVisible = isVisible; + }); + })); + // update the searchSource when query updates $scope.fetch = function () { $state.save(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js index 592a355a71b0d..57707f6321376 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/index.js @@ -17,6 +17,7 @@ * under the License. */ +import { ensureDefaultIndexPattern } from 'ui/legacy_compat'; import './editor/editor'; import { i18n } from '@kbn/i18n'; import './saved_visualizations/_saved_vis'; @@ -32,7 +33,6 @@ const { FeatureCatalogueRegistryProvider, uiRoutes } = getServices(); uiRoutes .defaults(/visualize/, { - requireDefaultIndex: true, requireUICapability: 'visualize.show', badge: uiCapabilities => { if (uiCapabilities.visualize.save) { @@ -57,6 +57,7 @@ uiRoutes controllerAs: 'listingController', resolve: { createNewVis: () => false, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) }, }) .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { @@ -66,6 +67,7 @@ uiRoutes controllerAs: 'listingController', resolve: { createNewVis: () => true, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) }, }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 3be49971cf4c9..e2201cdca9a57 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -60,6 +60,7 @@ const services = { savedObjectsClient: npStart.core.savedObjects.client, toastNotifications: npStart.core.notifications.toasts, uiSettings: npStart.core.uiSettings, + core: npStart.core, share: npStart.plugins.share, data, diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts index a6ca444de6d4c..af142973a535d 100644 --- a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts +++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts @@ -25,7 +25,7 @@ describe('getNotifyUserAboutOptInDefault: get a flag that describes if the user getNotifyUserAboutOptInDefault({ allowChangingOptInStatus: true, telemetrySavedObject: { userHasSeenNotice: false }, - telemetryOptedIn: null, + telemetryOptedIn: true, configTelemetryOptIn: true, }) ).toBe(true); @@ -40,50 +40,37 @@ describe('getNotifyUserAboutOptInDefault: get a flag that describes if the user configTelemetryOptIn: false, }) ).toBe(false); - }); - it('should return false if user has seen notice', () => { expect( getNotifyUserAboutOptInDefault({ - allowChangingOptInStatus: true, - telemetrySavedObject: { userHasSeenNotice: true }, - telemetryOptedIn: false, + allowChangingOptInStatus: false, + telemetrySavedObject: null, + telemetryOptedIn: true, configTelemetryOptIn: true, }) ).toBe(false); + }); + it('should return false if user has seen notice', () => { expect( getNotifyUserAboutOptInDefault({ allowChangingOptInStatus: true, telemetrySavedObject: { userHasSeenNotice: true }, - telemetryOptedIn: true, - configTelemetryOptIn: true, + telemetryOptedIn: false, + configTelemetryOptIn: false, }) ).toBe(false); - }); - it('not show notice for users already opted in and has not seen notice yet', () => { expect( getNotifyUserAboutOptInDefault({ allowChangingOptInStatus: true, - telemetrySavedObject: { userHasSeenNotice: false }, + telemetrySavedObject: { userHasSeenNotice: true }, telemetryOptedIn: true, configTelemetryOptIn: true, }) ).toBe(false); }); - it('should see notice if they are merely opted in by default and have not yet seen the notice', () => { - expect( - getNotifyUserAboutOptInDefault({ - allowChangingOptInStatus: true, - telemetrySavedObject: { userHasSeenNotice: false }, - telemetryOptedIn: null, - configTelemetryOptIn: true, - }) - ).toBe(true); - }); - it('should return false if user is opted out', () => { expect( getNotifyUserAboutOptInDefault({ diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts index eb95aff6392e0..8ef3bd8388ecb 100644 --- a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts +++ b/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts @@ -41,9 +41,5 @@ export function getNotifyUserAboutOptInDefault({ return false; } - if (telemetryOptedIn !== null) { - return false; // they were not defaulted in - } - - return configTelemetryOptIn; + return telemetryOptedIn === true && configTelemetryOptIn === true; } diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index 5bc5355d7c061..04edfc5b61141 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -30,8 +30,6 @@ import { timefilter } from 'ui/timefilter'; import { npStart } from 'ui/new_platform'; import { getSavedSheetBreadcrumbs, getCreateBreadcrumbs } from './breadcrumbs'; -// import the uiExports that we want to "use" -import 'uiExports/fieldFormats'; import 'uiExports/savedObjectTypes'; require('ui/autoload/all'); diff --git a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js index 8dd2b093c6f91..9f58d00d38271 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js +++ b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js @@ -51,7 +51,7 @@ describe('metric_vis - controller', function () { expect(metrics.length).to.be(1); expect(metrics[0].label).to.be('Count'); - expect(metrics[0].value).to.be(4301021); + expect(metrics[0].value).to.be('4301021'); }); it('should support multi-value metrics', function () { @@ -66,8 +66,8 @@ describe('metric_vis - controller', function () { expect(metrics.length).to.be(2); expect(metrics[0].label).to.be('1st percentile of bytes'); - expect(metrics[0].value).to.be(182); + expect(metrics[0].value).to.be('182'); expect(metrics[1].label).to.be('99th percentile of bytes'); - expect(metrics[1].value).to.be(445842.4634666484); + expect(metrics[1].value).to.be('445842.4634666484'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js index 0459d11c74ef0..1192b678ae148 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js @@ -19,10 +19,14 @@ import handlebars from 'handlebars/dist/handlebars'; import { isNumber } from 'lodash'; -import { fieldFormats } from 'ui/registry/field_formats'; +import { npStart } from 'ui/new_platform'; import { inputFormats, outputFormats, isDuration } from '../lib/durations'; + + export const createTickFormatter = (format = '0,0.[00]', template, getConfig = null) => { + const fieldFormats = npStart.plugins.data.fieldFormats; + if (!template) template = '{{value}}'; const render = handlebars.compile(template, { knownHelpersOnly: true }); let formatter; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js index 8af175d116556..10fc34fccd2cc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js @@ -20,18 +20,17 @@ import _, { isArray, last, get } from 'lodash'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { fieldFormats } from 'ui/registry/field_formats'; +import { npStart } from 'ui/new_platform'; import { createTickFormatter } from '../../lib/tick_formatter'; import { calculateLabel } from '../../../../common/calculate_label'; import { isSortable } from './is_sortable'; import { EuiToolTip, EuiIcon } from '@elastic/eui'; import { replaceVars } from '../../lib/replace_vars'; +import { FIELD_FORMAT_IDS } from '../../../../../../../plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; import { METRIC_TYPES } from '../../../../common/metric_types'; -const DateFormat = fieldFormats.getType('date'); - function getColor(rules, colorKey, value) { let color; if (rules) { @@ -49,6 +48,10 @@ function getColor(rules, colorKey, value) { export class TableVis extends Component { constructor(props) { super(props); + + const fieldFormats = npStart.plugins.data.fieldFormats; + const DateFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE); + this.dateFormatter = new DateFormat({}, this.props.getConfig); } diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts index 97d3a4352c2f8..d987260b099bd 100644 --- a/src/legacy/plugin_discovery/types.ts +++ b/src/legacy/plugin_discovery/types.ts @@ -62,6 +62,8 @@ export interface LegacyPluginOptions { }>; apps: any; hacks: string[]; + visualize: string[]; + devTools: string[]; styleSheetPaths: string; injectDefaultVars: (server: Server) => Record; noParse: string[]; diff --git a/src/legacy/server/config/transform_deprecations.js b/src/legacy/server/config/transform_deprecations.js index 7cac17a88fe64..b23a1de2c0773 100644 --- a/src/legacy/server/config/transform_deprecations.js +++ b/src/legacy/server/config/transform_deprecations.js @@ -102,6 +102,10 @@ const deprecations = [ rename('optimize.lazyHost', 'optimize.watchHost'), rename('optimize.lazyPrebuild', 'optimize.watchPrebuild'), rename('optimize.lazyProxyTimeout', 'optimize.watchProxyTimeout'), + rename('xpack.telemetry.enabled', 'telemetry.enabled'), + rename('xpack.telemetry.config', 'telemetry.config'), + rename('xpack.telemetry.banner', 'telemetry.banner'), + rename('xpack.telemetry.url', 'telemetry.url'), savedObjectsIndexCheckTimeout, rewriteBasePath, configPath, diff --git a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts index 66466b96abe83..370c312706242 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts +++ b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts @@ -20,10 +20,10 @@ import { has } from 'lodash'; import { Legacy } from 'kibana'; import { FieldFormatsService } from './field_formats_service'; -import { FieldFormat } from '../../../../plugins/data/public'; +import { IFieldFormatType } from '../../../../plugins/data/public'; export function fieldFormatsMixin(kbnServer: any, server: Legacy.Server) { - const fieldFormatClasses: Array = []; + const fieldFormatClasses: IFieldFormatType[] = []; // for use outside of the request context, for special cases server.decorate('server', 'fieldFormatServiceFactory', async function(uiSettings) { diff --git a/src/legacy/ui/field_formats/mixin/field_formats_service.ts b/src/legacy/ui/field_formats/mixin/field_formats_service.ts index c0800fcd4162b..c5bc25333985b 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_service.ts +++ b/src/legacy/ui/field_formats/mixin/field_formats_service.ts @@ -18,7 +18,7 @@ */ import { indexBy, Dictionary } from 'lodash'; -import { FieldFormat } from '../../../../plugins/data/public'; +import { FieldFormat, IFieldFormatType } from '../../../../plugins/data/common'; interface FieldFormatConfig { id: string; @@ -27,9 +27,9 @@ interface FieldFormatConfig { export class FieldFormatsService { getConfig: any; - _fieldFormats: Dictionary; + _fieldFormats: Dictionary; - constructor(fieldFormatClasses: Array, getConfig: Function) { + constructor(fieldFormatClasses: IFieldFormatType[], getConfig: Function) { this._fieldFormats = indexBy(fieldFormatClasses, 'id'); this.getConfig = getConfig; } diff --git a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js b/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js index ce7d87c228fbd..49d814c33209c 100644 --- a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js +++ b/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js @@ -20,16 +20,7 @@ import { buildHierarchicalData } from './build_hierarchical_data'; import { legacyResponseHandlerProvider } from '../../vis/response_handlers/legacy'; -jest.mock('../../registry/field_formats', () => { - return { fieldFormats: { - getType: id => { - if(id === '1') { return jest.fn(); } - if(id === 'agg_1') { return jest.fn(); } - } - } - }; -} -); +jest.mock('ui/new_platform'); jest.mock('../../chrome', () => ({ getUiSettingsClient: jest.fn() diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/ui/public/agg_types/agg_config.ts index becfaf8c89e27..de1a6059774e7 100644 --- a/src/legacy/ui/public/agg_types/agg_config.ts +++ b/src/legacy/ui/public/agg_types/agg_config.ts @@ -26,16 +26,14 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { AggType } from './agg_type'; import { FieldParamType } from './param_types/field'; import { AggGroupNames } from '../vis/editors/default/agg_groups'; import { writeParams } from './agg_params'; import { AggConfigs } from './agg_configs'; import { Schema } from '../vis/editors/default/schemas'; -import { ContentType } from '../../../../plugins/data/public'; - -// @ts-ignore -import { fieldFormats } from '../registry/field_formats'; +import { ContentType, KBN_FIELD_TYPES } from '../../../../plugins/data/public'; export interface AggConfigOptions { enabled: boolean; @@ -378,14 +376,16 @@ export class AggConfig { if (format) { return format.getConverterFor(contentType); } + return this.fieldOwnFormatter(contentType, defaultFormat); } fieldOwnFormatter(contentType?: ContentType, defaultFormat?: any) { + const fieldFormats = npStart.plugins.data.fieldFormats; const field = this.getField(); let format = field && field.format; if (!format) format = defaultFormat; - if (!format) format = fieldFormats.getDefaultInstance('string'); + if (!format) format = fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING); return format.getConverterFor(contentType); } diff --git a/src/legacy/ui/public/agg_types/agg_type.test.ts b/src/legacy/ui/public/agg_types/agg_type.test.ts index 1c1453b74fe98..9b34910e81e88 100644 --- a/src/legacy/ui/public/agg_types/agg_type.test.ts +++ b/src/legacy/ui/public/agg_types/agg_type.test.ts @@ -19,15 +19,10 @@ import { AggType, AggTypeConfig } from './agg_type'; import { AggConfig } from './agg_config'; +import { npStart } from 'ui/new_platform'; jest.mock('ui/new_platform'); -jest.mock('ui/registry/field_formats', () => ({ - fieldFormats: { - getDefaultInstance: jest.fn(() => 'default'), - }, -})); - describe('AggType Class', () => { describe('constructor', () => { it("requires a valid config object as it's first param", () => { @@ -158,6 +153,8 @@ describe('AggType Class', () => { }); it('returns default formatter', () => { + npStart.plugins.data.fieldFormats.getDefaultInstance = jest.fn(() => 'default') as any; + const aggType = new AggType({ name: 'name', title: 'title', diff --git a/src/legacy/ui/public/agg_types/agg_type.ts b/src/legacy/ui/public/agg_types/agg_type.ts index 7be8ec1406d3c..5216affb3e52d 100644 --- a/src/legacy/ui/public/agg_types/agg_type.ts +++ b/src/legacy/ui/public/agg_types/agg_type.ts @@ -19,6 +19,7 @@ import { constant, noop, identity } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { AggParam, initParams } from './agg_params'; import { AggConfig } from '../vis'; @@ -27,8 +28,7 @@ import { SearchSource } from '../courier'; import { Adapters } from '../inspector'; import { BaseParamType } from './param_types/base'; -// @ts-ignore -import { FieldFormat, fieldFormats } from '../registry/field_formats'; +import { KBN_FIELD_TYPES, FieldFormat } from '../../../../plugins/data/public'; export interface AggTypeConfig< TAggConfig extends AggConfig = AggConfig, @@ -62,7 +62,9 @@ export interface AggTypeConfig< const getFormat = (agg: AggConfig) => { const field = agg.getField(); - return field ? field.format : fieldFormats.getDefaultInstance('string'); + const fieldFormats = npStart.plugins.data.fieldFormats; + + return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING); }; export class AggType { diff --git a/src/legacy/ui/public/agg_types/buckets/date_range.ts b/src/legacy/ui/public/agg_types/buckets/date_range.ts index 908d921d12313..860d76ff2aa7b 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/date_range.ts @@ -23,13 +23,15 @@ import { npStart } from 'ui/new_platform'; import { BUCKET_TYPES } from './bucket_agg_types'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; -import { FieldFormat, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { DateRangesParamEditor } from '../../vis/editors/default/controls/date_ranges'; -// @ts-ignore -import { fieldFormats } from '../../registry/field_formats'; // @ts-ignore import { dateRange } from '../../utils/date_range'; +import { + KBN_FIELD_TYPES, + TEXT_CONTEXT_TYPE, + FieldFormat, +} from '../../../../../plugins/data/public'; const dateRangeTitle = i18n.translate('common.ui.aggTypes.buckets.dateRangeTitle', { defaultMessage: 'Date Range', @@ -48,7 +50,12 @@ export const dateRangeBucketAgg = new BucketAggType({ return { from, to }; }, getFormat(agg) { - const formatter = agg.fieldOwnFormatter('text', fieldFormats.getDefaultInstance('date')); + const fieldFormats = npStart.plugins.data.fieldFormats; + + const formatter = agg.fieldOwnFormatter( + TEXT_CONTEXT_TYPE, + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.DATE) + ); const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { return dateRange.toString(range, formatter); }); diff --git a/src/legacy/ui/public/agg_types/buckets/ip_range.ts b/src/legacy/ui/public/agg_types/buckets/ip_range.ts index 7ef415ff8d0c4..35155a482734c 100644 --- a/src/legacy/ui/public/agg_types/buckets/ip_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/ip_range.ts @@ -19,17 +19,20 @@ import { noop, map, omit, isNull } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { IpRangeTypeParamEditor } from '../../vis/editors/default/controls/ip_range_type'; import { IpRangesParamEditor } from '../../vis/editors/default/controls/ip_ranges'; -// @ts-ignore -import { fieldFormats } from '../../registry/field_formats'; -import { FieldFormat, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { ipRange } from '../../utils/ip_range'; import { BUCKET_TYPES } from './bucket_agg_types'; // @ts-ignore import { createFilterIpRange } from './create_filter/ip_range'; +import { + KBN_FIELD_TYPES, + TEXT_CONTEXT_TYPE, + FieldFormat, +} from '../../../../../plugins/data/public'; const ipRangeTitle = i18n.translate('common.ui.aggTypes.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', @@ -50,7 +53,11 @@ export const ipRangeBucketAgg = new BucketAggType({ return { type: 'range', from: bucket.from, to: bucket.to }; }, getFormat(agg) { - const formatter = agg.fieldOwnFormatter('text', fieldFormats.getDefaultInstance('ip')); + const fieldFormats = npStart.plugins.data.fieldFormats; + const formatter = agg.fieldOwnFormatter( + TEXT_CONTEXT_TYPE, + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.IP) + ); const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { return ipRange.toString(range, formatter); }); diff --git a/src/legacy/ui/public/agg_types/buckets/terms.ts b/src/legacy/ui/public/agg_types/buckets/terms.ts index c0f870c27f10d..89e33784fb5fb 100644 --- a/src/legacy/ui/public/agg_types/buckets/terms.ts +++ b/src/legacy/ui/public/agg_types/buckets/terms.ts @@ -41,7 +41,7 @@ import { OtherBucketParamEditor } from '../../vis/editors/default/controls/other import { AggConfigs } from '../agg_configs'; import { Adapters } from '../../../../../plugins/inspector/public'; -import { ContentType, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { ContentType, FieldFormat, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; // @ts-ignore import { Schemas } from '../../vis/editors/default/schemas'; @@ -75,7 +75,7 @@ export const termsBucketAgg = new BucketAggType({ const params = agg.params; return agg.getFieldDisplayName() + ': ' + params.order.text; }, - getFormat(bucket) { + getFormat(bucket): FieldFormat { return { getConverterFor: (type: ContentType) => { return (val: any) => { @@ -91,10 +91,11 @@ export const termsBucketAgg = new BucketAggType({ basePath: chrome.getBasePath(), }; const converter = bucket.params.field.format.getConverterFor(type); + return converter(val, undefined, undefined, parsedUrl); }; }, - }; + } as FieldFormat; }, createFilter: createFilterTerms, postFlightRequest: async ( diff --git a/src/legacy/ui/public/agg_types/metrics/cardinality.ts b/src/legacy/ui/public/agg_types/metrics/cardinality.ts index 221e1c6d6b083..301ae2c80116c 100644 --- a/src/legacy/ui/public/agg_types/metrics/cardinality.ts +++ b/src/legacy/ui/public/agg_types/metrics/cardinality.ts @@ -18,10 +18,10 @@ */ import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { MetricAggType } from './metric_agg_type'; -// @ts-ignore -import { fieldFormats } from '../../registry/field_formats'; import { METRIC_TYPES } from './metric_agg_types'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const uniqueCountTitle = i18n.translate('common.ui.aggTypes.metrics.uniqueCountTitle', { defaultMessage: 'Unique Count', @@ -37,7 +37,9 @@ export const cardinalityMetricAgg = new MetricAggType({ }); }, getFormat() { - return fieldFormats.getDefaultInstance('number'); + const fieldFormats = npStart.plugins.data.fieldFormats; + + return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }, params: [ { diff --git a/src/legacy/ui/public/agg_types/metrics/count.ts b/src/legacy/ui/public/agg_types/metrics/count.ts index 12964c8873e97..b5b844e8658d6 100644 --- a/src/legacy/ui/public/agg_types/metrics/count.ts +++ b/src/legacy/ui/public/agg_types/metrics/count.ts @@ -18,12 +18,11 @@ */ import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -// @ts-ignore -import { fieldFormats } from '../../registry/field_formats'; - export const countMetricAgg = new MetricAggType({ name: METRIC_TYPES.COUNT, title: i18n.translate('common.ui.aggTypes.metrics.countTitle', { @@ -36,7 +35,9 @@ export const countMetricAgg = new MetricAggType({ }); }, getFormat() { - return fieldFormats.getDefaultInstance('number'); + const fieldFormats = npStart.plugins.data.fieldFormats; + + return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }, getValue(agg, bucket) { return bucket.doc_count; diff --git a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts b/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts index 7428bd6caa22d..a466b9f852607 100644 --- a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts +++ b/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts @@ -18,6 +18,7 @@ */ import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../agg_config'; @@ -72,8 +73,9 @@ export class MetricAggType< this.getFormat = config.getFormat || (agg => { + const registeredFormats = npStart.plugins.data.fieldFormats; const field = agg.getField(); - return field ? field.format : fieldFormats.getDefaultInstance('number'); + return field ? field.format : registeredFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }); this.subtype = diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts index 4fabe137f1bc8..ead5122278b5a 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts @@ -18,24 +18,27 @@ */ import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { PercentileRanksEditor } from '../../vis/editors/default/controls/percentile_ranks'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; -// @ts-ignore -import { fieldFormats } from '../../registry/field_formats'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; // required by the values editor export type IPercentileRanksAggConfig = IResponseAggConfig; +const getFieldFormats = () => npStart.plugins.data.fieldFormats; + const valueProps = { makeLabel(this: IPercentileRanksAggConfig) { + const fieldFormats = getFieldFormats(); const field = this.getField(); - const format = (field && field.format) || fieldFormats.getDefaultInstance('number'); + const format = + (field && field.format) || fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); const customLabel = this.getParam('customLabel'); const label = customLabel || this.getFieldDisplayName(); @@ -81,7 +84,11 @@ export const percentileRanksMetricAgg = new MetricAggType new ValueAggConfig(value)); }, getFormat() { - return fieldFormats.getInstance('percent') || fieldFormats.getDefaultInstance('number'); + const fieldFormats = getFieldFormats(); + return ( + fieldFormats.getInstance(FIELD_FORMAT_IDS.PERCENT) || + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER) + ); }, getValue(agg, bucket) { return getPercentileValue(agg, bucket) / 100; diff --git a/src/legacy/ui/public/chrome/api/angular.js b/src/legacy/ui/public/chrome/api/angular.js index e6457fec93633..73d50a83e11a5 100644 --- a/src/legacy/ui/public/chrome/api/angular.js +++ b/src/legacy/ui/public/chrome/api/angular.js @@ -21,13 +21,15 @@ import { uiModules } from '../../modules'; import { directivesProvider } from '../directives'; import { registerSubUrlHooks } from './sub_url_hooks'; +import { start as data } from '../../../../core_plugins/data/public/legacy'; import { configureAppAngularModule } from 'ui/legacy_compat'; +import { npStart } from '../../new_platform/new_platform'; export function initAngularApi(chrome, internals) { chrome.setupAngular = function () { const kibana = uiModules.get('kibana'); - configureAppAngularModule(kibana); + configureAppAngularModule(kibana, npStart.core, data, false); kibana.value('chrome', chrome); diff --git a/src/legacy/ui/public/field_editor/field_editor.js b/src/legacy/ui/public/field_editor/field_editor.js index 896fb8fc5ddd8..f3c5990caae64 100644 --- a/src/legacy/ui/public/field_editor/field_editor.js +++ b/src/legacy/ui/public/field_editor/field_editor.js @@ -27,10 +27,6 @@ import { getSupportedScriptingLanguages, } from 'ui/scripting_languages'; -import { - fieldFormats -} from 'ui/registry/field_formats'; - import { getDocLink } from 'ui/documentation_links'; @@ -39,6 +35,8 @@ import { toastNotifications } from 'ui/notify'; +import { npStart } from 'ui/new_platform'; + import { EuiBasicTable, EuiButton, @@ -84,7 +82,10 @@ import { FormattedMessage } from '@kbn/i18n/react'; // This loads Ace editor's "groovy" mode, used below to highlight the script. import 'brace/mode/groovy'; +const getFieldFormats = () => npStart.plugins.data.fieldFormats; + export class FieldEditor extends PureComponent { + static propTypes = { indexPattern: PropTypes.object.isRequired, field: PropTypes.object.isRequired, @@ -141,9 +142,10 @@ export class FieldEditor extends PureComponent { const fieldTypes = get(FIELD_TYPES_BY_LANG, field.lang, DEFAULT_FIELD_TYPES); field.type = fieldTypes.includes(field.type) ? field.type : fieldTypes[0]; - const DefaultFieldFormat = fieldFormats.getDefaultType(field.type, field.esTypes); + const fieldFormats = getFieldFormats(); + const fieldTypeFormats = [ - getDefaultFormat(DefaultFieldFormat), + getDefaultFormat(fieldFormats.getDefaultType(field.type, field.esTypes)), ...fieldFormats.getByFieldType(field.type), ]; @@ -169,12 +171,14 @@ export class FieldEditor extends PureComponent { onTypeChange = (type) => { const { getConfig } = this.props.helpers; const { field } = this.state; + const fieldFormats = getFieldFormats(); const DefaultFieldFormat = fieldFormats.getDefaultType(type); + field.type = type; const fieldTypeFormats = [ getDefaultFormat(DefaultFieldFormat), - ...fieldFormats.getByFieldType(field.type), + ...getFieldFormats().getByFieldType(field.type), ]; const FieldFormat = fieldTypeFormats[0]; @@ -202,6 +206,7 @@ export class FieldEditor extends PureComponent { const { getConfig } = this.props.helpers; const { field, fieldTypeFormats } = this.state; const FieldFormat = fieldTypeFormats.find((format) => format.id === formatId) || fieldTypeFormats[0]; + field.format = new FieldFormat(params, getConfig); this.setState({ @@ -684,6 +689,7 @@ export class FieldEditor extends PureComponent { } saveField = async () => { + const fieldFormat = this.state.field.format; const field = this.state.field.toActualField(); const { indexPattern } = this.props; const { fieldFormatId } = this.state; @@ -721,7 +727,7 @@ export class FieldEditor extends PureComponent { if (!fieldFormatId) { indexPattern.fieldFormatMap[field.name] = undefined; } else { - indexPattern.fieldFormatMap[field.name] = field.format; + indexPattern.fieldFormatMap[field.name] = fieldFormat; } return indexPattern.save() diff --git a/src/legacy/ui/public/field_editor/field_editor.test.js b/src/legacy/ui/public/field_editor/field_editor.test.js index 34503238f437d..72eebee960b52 100644 --- a/src/legacy/ui/public/field_editor/field_editor.test.js +++ b/src/legacy/ui/public/field_editor/field_editor.test.js @@ -20,9 +20,12 @@ jest.mock('ui/kfetch', () => ({})); import React from 'react'; + +import { npStart } from 'ui/new_platform'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; jest.mock('brace/mode/groovy', () => ({})); +jest.mock('ui/new_platform'); import { FieldEditor } from './field_editor'; @@ -46,6 +49,10 @@ jest.mock('@elastic/eui', () => ({ EuiSpacer: 'eui-spacer', EuiText: 'eui-text', EuiTextArea: 'eui-textArea', + htmlIdGenerator: () => 42, + palettes: { + euiPaletteColorBlind: { colors: ['red'] } + } })); jest.mock('ui/scripting_languages', () => ({ @@ -54,26 +61,6 @@ jest.mock('ui/scripting_languages', () => ({ getDeprecatedScriptingLanguages: () => ['testlang'], })); -jest.mock('ui/registry/field_formats', () => { - class Format { - static id = 'test_format'; static title = 'Test format'; - params() {} - } - - return { - fieldFormats: { - getDefaultType: () => { - return Format; - }, - getByFieldType: (fieldType) => { - if(fieldType === 'number') { - return [Format]; - } - } - }, - }; -}); - jest.mock('ui/documentation_links', () => ({ getDocLink: (doc) => `(docLink for ${doc})`, })); @@ -133,6 +120,13 @@ describe('FieldEditor', () => { indexPattern = { fields, }; + + npStart.plugins.data.fieldFormats.getDefaultType = jest.fn(() => Format); + npStart.plugins.data.fieldFormats.getByFieldType = jest.fn((fieldType) => { + if(fieldType === 'number') { + return [Format]; + } + }); }); it('should render create new scripted field correctly', async () => { diff --git a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js index 9c4cee6b05db0..a1d48caf3f489 100644 --- a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js @@ -75,8 +75,7 @@ export function createTopNavDirective() { module.directive('kbnTopNav', createTopNavDirective); -export function createTopNavHelper(reactDirective) { - const { TopNavMenu } = navigation.ui; +export const createTopNavHelper = ({ TopNavMenu }) => (reactDirective) => { return reactDirective( wrapInI18nContext(TopNavMenu), [ @@ -116,6 +115,6 @@ export function createTopNavHelper(reactDirective) { 'showAutoRefreshOnly', ], ); -} +}; -module.directive('kbnTopNavHelper', createTopNavHelper); +module.directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index 788718e848430..6e9f5c85aa1b2 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -28,7 +28,7 @@ import { IRootScopeService, } from 'angular'; import $ from 'jquery'; -import { cloneDeep, forOwn, set } from 'lodash'; +import _, { cloneDeep, forOwn, get, set } from 'lodash'; import React, { Fragment } from 'react'; import * as Rx from 'rxjs'; @@ -37,27 +37,43 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart, LegacyCoreStart } from 'kibana/public'; import { fatalError } from 'ui/notify'; -import { capabilities } from 'ui/capabilities'; +import { RouteConfiguration } from 'ui/routes/route_manager'; // @ts-ignore import { modifyUrl } from 'ui/url'; import { toMountPoint } from '../../../../plugins/kibana_react/public'; // @ts-ignore import { UrlOverflowService } from '../error_url_overflow'; -import { npStart } from '../new_platform'; -import { toastNotifications } from '../notify'; // @ts-ignore import { isSystemApiRequest } from '../system_api'; const URL_LIMIT_WARN_WITHIN = 1000; -function isDummyWrapperRoute($route: any) { +/** + * Detects whether a given angular route is a dummy route that doesn't + * require any action. There are two ways this can happen: + * If `outerAngularWrapperRoute` is set on the route config object, + * it means the local application service set up this route on the outer angular + * and the internal routes will handle the hooks. + * + * If angular did not detect a route and it is the local angular, we are currently + * navigating away from a URL controlled by a local angular router and the + * application will get unmounted. In this case the outer router will handle + * the hooks. + * @param $route Injected $route dependency + * @param isLocalAngular Flag whether this is the local angular router + */ +function isDummyRoute($route: any, isLocalAngular: boolean) { return ( - $route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute + ($route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute) || + (!$route.current && isLocalAngular) ); } -export const configureAppAngularModule = (angularModule: IModule) => { - const newPlatform = npStart.core; +export const configureAppAngularModule = ( + angularModule: IModule, + newPlatform: LegacyCoreStart, + isLocalAngular: boolean +) => { const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { @@ -73,15 +89,16 @@ export const configureAppAngularModule = (angularModule: IModule) => { .value('buildSha', legacyMetadata.buildSha) .value('serverName', legacyMetadata.serverName) .value('esUrl', getEsUrl(newPlatform)) - .value('uiCapabilities', capabilities.get()) + .value('uiCapabilities', newPlatform.application.capabilities) .config(setupCompileProvider(newPlatform)) .config(setupLocationProvider(newPlatform)) .config($setupXsrfRequestInterceptor(newPlatform)) .run(capture$httpLoadingCount(newPlatform)) - .run($setupBreadcrumbsAutoClear(newPlatform)) - .run($setupBadgeAutoClear(newPlatform)) - .run($setupHelpExtensionAutoClear(newPlatform)) - .run($setupUrlOverflowHandling(newPlatform)); + .run($setupBreadcrumbsAutoClear(newPlatform, isLocalAngular)) + .run($setupBadgeAutoClear(newPlatform, isLocalAngular)) + .run($setupHelpExtensionAutoClear(newPlatform, isLocalAngular)) + .run($setupUrlOverflowHandling(newPlatform, isLocalAngular)) + .run($setupUICapabilityRedirect(newPlatform)); }; const getEsUrl = (newPlatform: CoreStart) => { @@ -168,12 +185,42 @@ const capture$httpLoadingCount = (newPlatform: CoreStart) => ( ); }; +/** + * integrates with angular to automatically redirect to home if required + * capability is not met + */ +const $setupUICapabilityRedirect = (newPlatform: CoreStart) => ( + $rootScope: IRootScopeService, + $injector: any +) => { + const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana'); + // this feature only works within kibana app for now after everything is + // switched to the application service, this can be changed to handle all + // apps. + if (!isKibanaAppRoute) { + return; + } + $rootScope.$on( + '$routeChangeStart', + (event, { $$route: route }: { $$route?: RouteConfiguration } = {}) => { + if (!route || !route.requireUICapability) { + return; + } + + if (!get(newPlatform.application.capabilities, route.requireUICapability)) { + $injector.get('kbnUrl').change('/home'); + event.preventDefault(); + } + } + ); +}; + /** * internal angular run function that will be called when angular bootstraps and * lets us integrate with the angular router so that we can automatically clear * the breadcrumbs if we switch to a Kibana app that does not use breadcrumbs correctly */ -const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( +const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -195,7 +242,7 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } const current = $route.current || {}; @@ -223,7 +270,7 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( * lets us integrate with the angular router so that we can automatically clear * the badge if we switch to a Kibana app that does not use the badge correctly */ -const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( +const $setupBadgeAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -237,7 +284,7 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } const current = $route.current || {}; @@ -266,7 +313,7 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( * the helpExtension if we switch to a Kibana app that does not set its own * helpExtension */ -const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( +const $setupHelpExtensionAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -284,14 +331,14 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $route = $injector.has('$route') ? $injector.get('$route') : {}; $rootScope.$on('$routeChangeStart', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } helpExtensionSetSinceRouteChange = false; }); $rootScope.$on('$routeChangeSuccess', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } const current = $route.current || {}; @@ -304,7 +351,7 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( }); }; -const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( +const $setupUrlOverflowHandling = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $location: ILocationService, $rootScope: IRootScopeService, $injector: auto.IInjectorService @@ -312,7 +359,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( const $route = $injector.has('$route') ? $injector.get('$route') : {}; const urlOverflow = new UrlOverflowService(); const check = () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } // disable long url checks when storing state in session storage @@ -326,7 +373,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( try { if (urlOverflow.check($location.absUrl()) <= URL_LIMIT_WARN_WITHIN) { - toastNotifications.addWarning({ + newPlatform.notifications.toasts.addWarning({ title: i18n.translate('common.ui.chrome.bigUrlWarningNotificationTitle', { defaultMessage: 'The URL is big and Kibana might stop working', }), diff --git a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx new file mode 100644 index 0000000000000..98e95865d7325 --- /dev/null +++ b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx @@ -0,0 +1,105 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { contains } from 'lodash'; +import { IRootScopeService } from 'angular'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; +import { EuiCallOut } from '@elastic/eui'; +import { CoreStart } from 'kibana/public'; +import { DataStart } from '../../../core_plugins/data/public'; + +let bannerId: string; +let timeoutId: NodeJS.Timeout | undefined; + +/** + * Checks whether a default index pattern is set and exists and defines + * one otherwise. + * + * If there are no index patterns, redirect to management page and show + * banner. In this case the promise returned from this function will never + * resolve to wait for the URL change to happen. + */ +export async function ensureDefaultIndexPattern( + newPlatform: CoreStart, + data: DataStart, + $rootScope: IRootScopeService, + kbnUrl: any +) { + const patterns = await data.indexPatterns.indexPatterns.getIds(); + let defaultId = newPlatform.uiSettings.get('defaultIndex'); + let defined = !!defaultId; + const exists = contains(patterns, defaultId); + + if (defined && !exists) { + newPlatform.uiSettings.remove('defaultIndex'); + defaultId = defined = false; + } + + if (defined) { + return; + } + + // If there is any index pattern created, set the first as default + if (patterns.length >= 1) { + defaultId = patterns[0]; + newPlatform.uiSettings.set('defaultIndex', defaultId); + } else { + const canManageIndexPatterns = + newPlatform.application.capabilities.management.kibana.index_patterns; + const redirectTarget = canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home'; + + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Avoid being hostile to new users who don't have an index pattern setup yet + // give them a friendly info message instead of a terse error message + bannerId = newPlatform.overlays.banners.replace(bannerId, (element: HTMLElement) => { + ReactDOM.render( + + + , + element + ); + return () => ReactDOM.unmountComponentAtNode(element); + }); + + // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around + timeoutId = setTimeout(() => { + newPlatform.overlays.banners.remove(bannerId); + timeoutId = undefined; + }, 15000); + + kbnUrl.change(redirectTarget); + $rootScope.$digest(); + + // return never-resolving promise to stop resolving and wait for the url change + return new Promise(() => {}); + } +} diff --git a/src/legacy/ui/public/legacy_compat/index.ts b/src/legacy/ui/public/legacy_compat/index.ts index b29056954051b..ea8932114118e 100644 --- a/src/legacy/ui/public/legacy_compat/index.ts +++ b/src/legacy/ui/public/legacy_compat/index.ts @@ -18,3 +18,4 @@ */ export { configureAppAngularModule } from './angular_config'; +export { ensureDefaultIndexPattern } from './ensure_default_index_pattern'; 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 5c269c7b019aa..8703e8750fda4 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 @@ -18,6 +18,7 @@ */ import sinon from 'sinon'; +import { getFieldFormatsRegistry } from '../../../../test_utils/public/stub_field_formats'; const mockObservable = () => { return { @@ -25,9 +26,24 @@ const mockObservable = () => { }; }; +export const mockUiSettings = { + get: (item) => { + return mockUiSettings[item]; + }, + getUpdate$: () => ({ + subscribe: sinon.fake(), + }), + 'query:allowLeadingWildcards': true, + 'query:queryString:options': {}, + 'courier:ignoreFilterIfFieldNotInIndex': true, + 'dateFormat:tz': 'Browser', + 'format:defaultTypeMap': {}, +}; + export const npSetup = { core: { - chrome: {} + chrome: {}, + uiSettings: mockUiSettings, }, plugins: { embeddable: { @@ -61,6 +77,7 @@ export const npSetup = { history: sinon.fake(), } }, + fieldFormats: getFieldFormatsRegistry(mockUiSettings), }, share: { register: () => {}, @@ -81,6 +98,9 @@ export const npSetup = { registerAction: sinon.fake(), registerTrigger: sinon.fake(), }, + feature_catalogue: { + register: sinon.fake(), + }, }, }; @@ -164,6 +184,7 @@ export const npStart = { history: sinon.fake(), }, }, + fieldFormats: getFieldFormatsRegistry(mockUiSettings), }, share: { toggleShareContextMenu: () => {}, @@ -185,6 +206,9 @@ export const npStart = { getTriggerActions: sinon.fake(), getTriggerCompatibleActions: sinon.fake(), }, + feature_catalogue: { + register: sinon.fake(), + }, }, }; diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index 6e71d36877895..1db360749c714 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -29,17 +29,14 @@ import { } from '../../../../plugins/inspector/public'; import { EuiUtilsStart } from '../../../../plugins/eui_utils/public'; import { DevToolsSetup, DevToolsStart } from '../../../../plugins/dev_tools/public'; -import { - FeatureCatalogueSetup, - FeatureCatalogueStart, -} from '../../../../plugins/feature_catalogue/public'; +import { HomePublicPluginSetup, HomePublicPluginStart } from '../../../../plugins/home/public'; import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/public'; export interface PluginsSetup { data: ReturnType; embeddable: EmbeddableSetup; expressions: ReturnType; - feature_catalogue: FeatureCatalogueSetup; + home: HomePublicPluginSetup; inspector: InspectorSetup; uiActions: IUiActionsSetup; share: SharePluginSetup; @@ -51,7 +48,7 @@ export interface PluginsStart { embeddable: EmbeddableStart; eui_utils: EuiUtilsStart; expressions: ReturnType; - feature_catalogue: FeatureCatalogueStart; + home: HomePublicPluginStart; inspector: InspectorStart; uiActions: IUiActionsStart; share: SharePluginStart; diff --git a/src/legacy/ui/public/registry/feature_catalogue.js b/src/legacy/ui/public/registry/feature_catalogue.js index 8905a15106953..475705ff39e48 100644 --- a/src/legacy/ui/public/registry/feature_catalogue.js +++ b/src/legacy/ui/public/registry/feature_catalogue.js @@ -19,7 +19,7 @@ import { uiRegistry } from './_registry'; import { capabilities } from '../capabilities'; -export { FeatureCatalogueCategory } from '../../../../plugins/feature_catalogue/public'; +export { FeatureCatalogueCategory } from '../../../../plugins/home/public'; export const FeatureCatalogueRegistryProvider = uiRegistry({ name: 'featureCatalogue', diff --git a/src/legacy/ui/public/registry/field_formats.js b/src/legacy/ui/public/registry/field_formats.js deleted file mode 100644 index 9e03ef2e3cdd9..0000000000000 --- a/src/legacy/ui/public/registry/field_formats.js +++ /dev/null @@ -1,186 +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 { memoize, forOwn, isFunction } from 'lodash'; -import { npStart } from 'ui/new_platform'; - -class FieldFormatRegistry { - constructor() { - this.fieldFormats = new Map(); - this._uiSettings = npStart.core.uiSettings; - this.getConfig = (...args) => this._uiSettings.get(...args); - this._defaultMap = []; - this.init(); - } - - init() { - this.parseDefaultTypeMap(this._uiSettings.get('format:defaultTypeMap')); - - this._uiSettings.getUpdate$().subscribe(({ key, newValue }) => { - if (key === 'format:defaultTypeMap') { - this.parseDefaultTypeMap(newValue); - } - }); - } - - /** - * Get the id of the default type for this field type - * using the format:defaultTypeMap config map - * - * @param {String} fieldType - the field type - * @param {String[]} esTypes - Array of ES data types - * @return {Object} - */ - getDefaultConfig = (fieldType, esTypes) => { - const type = this.getDefaultTypeName(fieldType, esTypes); - return this._defaultMap[type] || this._defaultMap._default_; - }; - - /** - * Get a FieldFormat type (class) by it's id. - * - * @param {String} formatId - the format id - * @return {Function} - */ - getType = (formatId) => { - return this.fieldFormats.get(formatId); - }; - /** - * Get the default FieldFormat type (class) for - * a field type, using the format:defaultTypeMap. - * used by the field editor - * - * @param {String} fieldType - * @param {String} esTypes - Array of ES data types - * @return {Function} - */ - getDefaultType = (fieldType, esTypes) => { - const config = this.getDefaultConfig(fieldType, esTypes); - return this.getType(config.id); - }; - - /** - * Get the name of the default type for ES types like date_nanos - * using the format:defaultTypeMap config map - * - * @param {String[]} esTypes - Array of ES data types - * @return {String|undefined} - */ - getTypeNameByEsTypes = (esTypes) => { - if(!Array.isArray(esTypes)) { - return; - } - return esTypes.find(type => this._defaultMap[type] && this._defaultMap[type].es); - }; - /** - * Get the default FieldFormat type name for - * a field type, using the format:defaultTypeMap. - * - * @param {String} fieldType - * @param {String[]} esTypes - * @return {string} - */ - getDefaultTypeName = (fieldType, esTypes) => { - return this.getTypeNameByEsTypes(esTypes) || fieldType; - }; - - /** - * Get the singleton instance of the FieldFormat type by it's id. - * - * @param {String} formatId - * @return {FieldFormat} - */ - getInstance = memoize(function (formatId) { - const FieldFormat = this.getType(formatId); - if (!FieldFormat) { - throw new Error(`Field Format '${formatId}' not found!`); - } - return new FieldFormat(null, this.getConfig); - }); - - /** - * Get the default fieldFormat instance for a field format. - * - * @param {String} fieldType - * @param {String[]} esTypes - * @return {FieldFormat} - */ - getDefaultInstancePlain(fieldType, esTypes) { - const conf = this.getDefaultConfig(fieldType, esTypes); - - const FieldFormat = this.getType(conf.id); - return new FieldFormat(conf.params, this.getConfig); - } - /** - * Returns a cache key built by the given variables for caching in memoized - * Where esType contains fieldType, fieldType is returned - * -> kibana types have a higher priority in that case - * -> would lead to failing tests that match e.g. date format with/without esTypes - * https://lodash.com/docs#memoize - * - * @param {String} fieldType - * @param {String[]} esTypes - * @return {string} - */ - getDefaultInstanceCacheResolver(fieldType, esTypes) { - return Array.isArray(esTypes) && esTypes.indexOf(fieldType) === -1 - ? [fieldType, ...esTypes].join('-') - : fieldType; - } - - /** - * Get filtered list of field formats by format type - * - * @param {String} fieldType - * @return {FieldFormat[]} - */ - - getByFieldType(fieldType) { - return [ ...this.fieldFormats.values()] - .filter(format => format.fieldType.indexOf(fieldType) !== -1); - } - - /** - * Get the default fieldFormat instance for a field format. - * It's a memoized function that builds and reads a cache - * - * @param {String} fieldType - * @param {String[]} esTypes - * @return {FieldFormat} - */ - getDefaultInstance = memoize(this.getDefaultInstancePlain, this.getDefaultInstanceCacheResolver); - - parseDefaultTypeMap(value) { - this._defaultMap = value; - forOwn(this, function (fn) { - if (isFunction(fn) && fn.cache) { - // clear all memoize caches - fn.cache = new memoize.Cache(); - } - }); - } - - register = derivedFieldFormat => { - this.fieldFormats.set(derivedFieldFormat.id, derivedFieldFormat); - - return this; - }; -} - -export const fieldFormats = new FieldFormatRegistry(); diff --git a/src/legacy/ui/public/routes/__tests__/_route_manager.js b/src/legacy/ui/public/routes/__tests__/_route_manager.js index d6d4c869b4b7e..450bb51f0b0c6 100644 --- a/src/legacy/ui/public/routes/__tests__/_route_manager.js +++ b/src/legacy/ui/public/routes/__tests__/_route_manager.js @@ -119,18 +119,6 @@ describe('routes/route_manager', function () { expect($rp.when.secondCall.args[1]).to.have.property('reloadOnSearch', false); expect($rp.when.lastCall.args[1]).to.have.property('reloadOnSearch', true); }); - - it('sets route.requireDefaultIndex to false by default', function () { - routes.when('/nothing-set'); - routes.when('/no-index-required', { requireDefaultIndex: false }); - routes.when('/index-required', { requireDefaultIndex: true }); - routes.config($rp); - - expect($rp.when.callCount).to.be(3); - expect($rp.when.firstCall.args[1]).to.have.property('requireDefaultIndex', false); - expect($rp.when.secondCall.args[1]).to.have.property('requireDefaultIndex', false); - expect($rp.when.lastCall.args[1]).to.have.property('requireDefaultIndex', true); - }); }); describe('#defaults()', () => { diff --git a/src/legacy/ui/public/routes/route_manager.d.ts b/src/legacy/ui/public/routes/route_manager.d.ts index 56203354f3c20..a5261a7c8ee3a 100644 --- a/src/legacy/ui/public/routes/route_manager.d.ts +++ b/src/legacy/ui/public/routes/route_manager.d.ts @@ -23,7 +23,7 @@ import { ChromeBreadcrumb } from '../../../../core/public'; -interface RouteConfiguration { +export interface RouteConfiguration { controller?: string | ((...args: any[]) => void); redirectTo?: string; resolveRedirectTo?: (...args: any[]) => void; diff --git a/src/legacy/ui/public/routes/route_manager.js b/src/legacy/ui/public/routes/route_manager.js index ba48984bb45b9..6444ef66fbe47 100644 --- a/src/legacy/ui/public/routes/route_manager.js +++ b/src/legacy/ui/public/routes/route_manager.js @@ -46,10 +46,6 @@ export default function RouteManager() { route.reloadOnSearch = false; } - if (route.requireDefaultIndex == null) { - route.requireDefaultIndex = false; - } - wrapRouteWithPrep(route, setup); $routeProvider.when(path, route); }); diff --git a/src/legacy/ui/public/saved_objects/__tests__/saved_object.js b/src/legacy/ui/public/saved_objects/__tests__/saved_object.js index ff53d48de758a..56124a047ba6d 100644 --- a/src/legacy/ui/public/saved_objects/__tests__/saved_object.js +++ b/src/legacy/ui/public/saved_objects/__tests__/saved_object.js @@ -26,6 +26,7 @@ import { SavedObjectProvider } from '../saved_object'; import StubIndexPattern from 'test_utils/stub_index_pattern'; import { SavedObjectsClientProvider } from '../saved_objects_client_provider'; import { InvalidJSONProperty } from '../../../../../plugins/kibana_utils/public'; +import { mockUiSettings } from '../../new_platform/new_platform.karma_mock'; const getConfig = cfg => cfg; @@ -337,7 +338,7 @@ describe('Saved Object', function () { type: 'dashboard', }); }); - const indexPattern = new StubIndexPattern('my-index', getConfig, null, []); + const indexPattern = new StubIndexPattern('my-index', getConfig, null, [], mockUiSettings); indexPattern.title = indexPattern.id; savedObject.searchSource.setField('index', indexPattern); return savedObject @@ -725,7 +726,7 @@ describe('Saved Object', function () { const savedObject = new SavedObject(config); sinon.stub(savedObject, 'hydrateIndexPattern').callsFake(() => { - const indexPattern = new StubIndexPattern(indexPatternId, getConfig, null, []); + const indexPattern = new StubIndexPattern(indexPatternId, getConfig, null, [], mockUiSettings); indexPattern.title = indexPattern.id; savedObject.searchSource.setField('index', indexPattern); return Promise.resolve(indexPattern); diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index b7623ab0fc5a5..8d55a6929a617 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -42,9 +42,14 @@ import { isStateHash, } from './state_storage'; -export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl) { +export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl, $injector) { const Events = Private(EventsProvider); + const isDummyRoute = () => + $injector.has('$route') && + $injector.get('$route').current && + $injector.get('$route').current.outerAngularWrapperRoute; + createLegacyClass(State).inherits(Events); function State( urlParam, @@ -137,7 +142,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon let stash = this._readFromURL(); - // nothing to read from the url? save if ordered to persist + // nothing to read from the url? save if ordered to persist, but only if it's not on a wrapper route if (stash === null) { if (this._persistAcrossApps) { return this.save(); @@ -150,7 +155,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon // apply diff to state from stash, will change state in place via side effect const diffResults = applyDiff(this, stash); - if (diffResults.keys.length) { + if (!isDummyRoute() && diffResults.keys.length) { this.emit('fetch_with_changes', diffResults.keys); } }; @@ -164,6 +169,10 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return; } + if (isDummyRoute()) { + return; + } + let stash = this._readFromURL(); const state = this.toObject(); replace = replace || false; diff --git a/src/legacy/ui/public/time_buckets/time_buckets.js b/src/legacy/ui/public/time_buckets/time_buckets.js index bcd5cd161edd3..c875d7820373e 100644 --- a/src/legacy/ui/public/time_buckets/time_buckets.js +++ b/src/legacy/ui/public/time_buckets/time_buckets.js @@ -20,13 +20,14 @@ import _ from 'lodash'; import moment from 'moment'; import chrome from '../chrome'; +import { npStart } from 'ui/new_platform'; import { parseInterval } from '../utils/parse_interval'; import { calcAutoIntervalLessThan, calcAutoIntervalNear } from './calc_auto_interval'; import { convertDurationToNormalizedEsInterval, convertIntervalToEsInterval, } from './calc_es_interval'; -import { fieldFormats } from '../registry/field_formats'; +import { FIELD_FORMAT_IDS } from '../../../../plugins/data/public'; const config = chrome.getUiSettingsClient(); @@ -311,7 +312,9 @@ TimeBuckets.prototype.getScaledDateFormat = function () { }; TimeBuckets.prototype.getScaledDateFormatter = function () { - const DateFieldFormat = fieldFormats.getType('date'); + const fieldFormats = npStart.plugins.data.fieldFormats; + const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE); + return new DateFieldFormat({ pattern: this.getScaledDateFormat() }, getConfig); diff --git a/src/legacy/ui/public/timefilter/setup_router.test.js b/src/legacy/ui/public/timefilter/setup_router.test.js index 4bc797e5eff00..f229937c3b435 100644 --- a/src/legacy/ui/public/timefilter/setup_router.test.js +++ b/src/legacy/ui/public/timefilter/setup_router.test.js @@ -42,9 +42,14 @@ describe('registerTimefilterWithGlobalState()', () => { } }; + const rootScope = { + $on: jest.fn() + }; + registerTimefilterWithGlobalState( timefilter, - globalState + globalState, + rootScope, ); expect(setTime.mock.calls.length).toBe(2); diff --git a/src/legacy/ui/public/timefilter/setup_router.ts b/src/legacy/ui/public/timefilter/setup_router.ts index 0a73378f99cd7..64105b016fb44 100644 --- a/src/legacy/ui/public/timefilter/setup_router.ts +++ b/src/legacy/ui/public/timefilter/setup_router.ts @@ -23,6 +23,7 @@ import moment from 'moment'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import chrome from 'ui/chrome'; import { RefreshInterval, TimeRange, TimefilterContract } from 'src/plugins/data/public'; +import { Subscription } from 'rxjs'; // TODO // remove everything underneath once globalState is no longer an angular service @@ -40,49 +41,62 @@ export function getTimefilterConfig() { }; } -// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter -// and require it to be executed to properly function. -// This function is exposed for applications that do not use uiRoutes like APM -// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter -export const registerTimefilterWithGlobalState = _.once( - (timefilter: TimefilterContract, globalState: any, $rootScope: IScope) => { - // settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account. - const config = getTimefilterConfig(); - timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults)); - timefilter.setRefreshInterval( - _.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults) - ); +export const registerTimefilterWithGlobalStateFactory = ( + timefilter: TimefilterContract, + globalState: any, + $rootScope: IScope +) => { + // settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account. + const config = getTimefilterConfig(); + timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults)); + timefilter.setRefreshInterval( + _.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults) + ); - globalState.on('fetch_with_changes', () => { - // clone and default to {} in one - const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults); - const newRefreshInterval: RefreshInterval = _.defaults( - {}, - globalState.refreshInterval, - config.refreshIntervalDefaults - ); + globalState.on('fetch_with_changes', () => { + // clone and default to {} in one + const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults); + const newRefreshInterval: RefreshInterval = _.defaults( + {}, + globalState.refreshInterval, + config.refreshIntervalDefaults + ); - if (newTime) { - if (newTime.to) newTime.to = convertISO8601(newTime.to); - if (newTime.from) newTime.from = convertISO8601(newTime.from); - } + if (newTime) { + if (newTime.to) newTime.to = convertISO8601(newTime.to); + if (newTime.from) newTime.from = convertISO8601(newTime.from); + } - timefilter.setTime(newTime); - timefilter.setRefreshInterval(newRefreshInterval); - }); + timefilter.setTime(newTime); + timefilter.setRefreshInterval(newRefreshInterval); + }); - const updateGlobalStateWithTime = () => { - globalState.time = timefilter.getTime(); - globalState.refreshInterval = timefilter.getRefreshInterval(); - globalState.save(); - }; + const updateGlobalStateWithTime = () => { + globalState.time = timefilter.getTime(); + globalState.refreshInterval = timefilter.getRefreshInterval(); + globalState.save(); + }; + const subscriptions = new Subscription(); + subscriptions.add( subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), { next: updateGlobalStateWithTime, - }); + }) + ); + subscriptions.add( subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), { next: updateGlobalStateWithTime, - }); - } -); + }) + ); + + $rootScope.$on('$destroy', () => { + subscriptions.unsubscribe(); + }); +}; + +// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter +// and require it to be executed to properly function. +// This function is exposed for applications that do not use uiRoutes like APM +// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter +export const registerTimefilterWithGlobalState = _.once(registerTimefilterWithGlobalStateFactory); diff --git a/src/legacy/ui/public/vis/__tests__/_agg_config.js b/src/legacy/ui/public/vis/__tests__/_agg_config.js index 2e2e0c31bdb8a..9b0398cf8853e 100644 --- a/src/legacy/ui/public/vis/__tests__/_agg_config.js +++ b/src/legacy/ui/public/vis/__tests__/_agg_config.js @@ -24,7 +24,6 @@ import { Vis } from '..'; import { AggType } from '../../agg_types/agg_type'; import { AggConfig } from '../../agg_types/agg_config'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { fieldFormats } from '../../registry/field_formats'; describe('AggConfig', function () { @@ -439,12 +438,15 @@ describe('AggConfig', function () { } ] }); - expect(vis.aggs.aggs[0].fieldFormatter()).to.be(fieldFormats.getDefaultInstance('number').getConverterFor()); + + const fieldFormatter = vis.aggs.aggs[0].fieldFormatter(); + + expect(fieldFormatter).to.be.defined; + expect(fieldFormatter('text')).to.be('text'); }); }); describe('#fieldFormatter - no custom getFormat handler', function () { - const visStateAggWithoutCustomGetFormat = { aggs: [ { @@ -467,13 +469,17 @@ describe('AggConfig', function () { it('returns the string format if the field does not have a format', function () { const agg = vis.aggs.aggs[0]; agg.params.field = { type: 'number', format: null }; - expect(agg.fieldFormatter()).to.be(fieldFormats.getDefaultInstance('string').getConverterFor()); + const fieldFormatter = agg.fieldFormatter(); + expect(fieldFormatter).to.be.defined; + expect(fieldFormatter('text')).to.be('text'); }); it('returns the string format if their is no field', function () { const agg = vis.aggs.aggs[0]; delete agg.params.field; - expect(agg.fieldFormatter()).to.be(fieldFormats.getDefaultInstance('string').getConverterFor()); + const fieldFormatter = agg.fieldFormatter(); + expect(fieldFormatter).to.be.defined; + expect(fieldFormatter('text')).to.be('text'); }); it('returns the html converter if "html" is passed in', function () { diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index e879d040125f1..18d633e1b5fb2 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -115,7 +115,6 @@ const VisFiltersProvider = (getAppState, $timeout) => { } }; - return { pushFilters, }; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index 6598da76f60ba..f49e0f08e8732 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -20,14 +20,13 @@ import { i18n } from '@kbn/i18n'; import { identity } from 'lodash'; import { AggConfig, Vis } from 'ui/vis'; +import { npStart } from 'ui/new_platform'; import { SerializedFieldFormat } from 'src/plugins/expressions/public'; -import { FieldFormat } from '../../../../../../plugins/data/public'; +import { IFieldFormatId, FieldFormat } from '../../../../../../plugins/data/public'; import { tabifyGetColumns } from '../../../agg_response/tabify/_get_columns'; import chrome from '../../../chrome'; -// @ts-ignore -import { fieldFormats } from '../../../registry/field_formats'; import { dateRange } from '../../../utils/date_range'; import { ipRange } from '../../../utils/ip_range'; import { DateRangeKey } from '../../../agg_types/buckets/date_range'; @@ -45,18 +44,22 @@ function isTermsFieldFormat( return serializedFieldFormat.id === 'terms'; } -const config = chrome.getUiSettingsClient(); +const getConfig = (key: string, defaultOverride?: any): any => + npStart.core.uiSettings.get(key, defaultOverride); +const DefaultFieldFormat = FieldFormat.from(identity); -const getConfig = (...args: any[]): any => config.get(...args); -const getDefaultFieldFormat = () => ({ convert: identity }); +const getFieldFormat = (id?: IFieldFormatId, params: object = {}): FieldFormat => { + const fieldFormats = npStart.plugins.data.fieldFormats; -const getFieldFormat = (id: string | undefined, params: object = {}) => { - const Format = fieldFormats.getType(id); - if (Format) { - return new Format(params, getConfig); - } else { - return getDefaultFieldFormat(); + if (id) { + const Format = fieldFormats.getType(id); + + if (Format) { + return new Format(params, getConfig); + } } + + return new DefaultFieldFormat(); }; export const createFormat = (agg: AggConfig): SerializedFieldFormat => { @@ -93,9 +96,9 @@ export const createFormat = (agg: AggConfig): SerializedFieldFormat => { export type FormatFactory = (mapping?: SerializedFieldFormat) => FieldFormat; -export const getFormat: FormatFactory = (mapping = {}) => { +export const getFormat: FormatFactory = mapping => { if (!mapping) { - return getDefaultFieldFormat(); + return new DefaultFieldFormat(); } const { id } = mapping; if (id === 'range') { @@ -145,6 +148,7 @@ export const getFormat: FormatFactory = (mapping = {}) => { pathname: window.location.pathname, basePath: chrome.getBasePath(), }; + // @ts-ignore return format.convert(val, undefined, undefined, parsedUrl); }; }, @@ -161,9 +165,10 @@ export const getFormat: FormatFactory = (mapping = {}) => { pathname: window.location.pathname, basePath: chrome.getBasePath(), }; + // @ts-ignore return format.convert(val, type, undefined, parsedUrl); }, - }; + } as FieldFormat; } else { return getFieldFormat(id, mapping.params); } diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss index 7cbe135115877..9575908146d1d 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss @@ -1,8 +1,10 @@ .dshDashboardViewport { + height: 100%; width: 100%; background-color: $euiColorEmptyShade; } .dshDashboardViewport-withMargins { width: 100%; + height: 100%; } diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.ts b/src/plugins/data/common/es_query/filters/phrase_filter.ts index 8b8c5f8915269..167bb3f840350 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrase_filter.ts @@ -25,6 +25,12 @@ export type PhraseFilterMeta = FilterMeta & { params?: { query: string; // The unformatted value }; + field?: any; + index?: any; +}; + +export type PhraseFilter = Filter & { + meta: PhraseFilterMeta; script?: { script: { source?: any; @@ -32,12 +38,6 @@ export type PhraseFilterMeta = FilterMeta & { params: any; }; }; - field?: any; - index?: any; -}; - -export type PhraseFilter = Filter & { - meta: PhraseFilterMeta; }; type PhraseFilterValue = string | number | boolean; @@ -79,7 +79,7 @@ export const buildPhraseFilter = ( return { meta: { index: indexPattern.id, field: field.name } as PhraseFilterMeta, script: getPhraseScript(field, value), - } as PhraseFilter; + }; } else { return { meta: { index: indexPattern.id }, diff --git a/src/plugins/data/common/field_formats/converters/boolean.test.ts b/src/plugins/data/common/field_formats/converters/boolean.test.ts index 2a548a6c1b179..3650df6517611 100644 --- a/src/plugins/data/common/field_formats/converters/boolean.test.ts +++ b/src/plugins/data/common/field_formats/converters/boolean.test.ts @@ -23,7 +23,7 @@ describe('Boolean Format', () => { let boolean: Record; beforeEach(() => { - boolean = new BoolFormat(); + boolean = new BoolFormat({}, jest.fn()); }); [ diff --git a/src/plugins/data/common/field_formats/converters/boolean.ts b/src/plugins/data/common/field_formats/converters/boolean.ts index 96e353592d676..6cc6c71465d50 100644 --- a/src/plugins/data/common/field_formats/converters/boolean.ts +++ b/src/plugins/data/common/field_formats/converters/boolean.ts @@ -19,11 +19,11 @@ import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; import { asPrettyString } from '../utils'; export class BoolFormat extends FieldFormat { - static id = 'boolean'; + static id = FIELD_FORMAT_IDS.BOOLEAN; static title = 'Boolean'; static fieldType = [KBN_FIELD_TYPES.BOOLEAN, KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING]; diff --git a/src/plugins/data/common/field_formats/converters/bytes.ts b/src/plugins/data/common/field_formats/converters/bytes.ts index 146f7afe74ccb..6c6df5eb7367d 100644 --- a/src/plugins/data/common/field_formats/converters/bytes.ts +++ b/src/plugins/data/common/field_formats/converters/bytes.ts @@ -18,9 +18,10 @@ */ import { NumeralFormat } from './numeral'; +import { FIELD_FORMAT_IDS } from '../types'; export class BytesFormat extends NumeralFormat { - static id = 'bytes'; + static id = FIELD_FORMAT_IDS.BYTES; static title = 'Bytes'; id = BytesFormat.id; diff --git a/src/plugins/data/common/field_formats/converters/color.test.ts b/src/plugins/data/common/field_formats/converters/color.test.ts index b7fcbf61227eb..f7aa26d449c7b 100644 --- a/src/plugins/data/common/field_formats/converters/color.test.ts +++ b/src/plugins/data/common/field_formats/converters/color.test.ts @@ -23,16 +23,19 @@ import { HTML_CONTEXT_TYPE } from '../content_types'; describe('Color Format', () => { describe('field is a number', () => { test('should add colors if the value is in range', () => { - const colorer = new ColorFormat({ - fieldType: 'number', - colors: [ - { - range: '100:150', - text: 'blue', - background: 'yellow', - }, - ], - }); + const colorer = new ColorFormat( + { + fieldType: 'number', + colors: [ + { + range: '100:150', + text: 'blue', + background: 'yellow', + }, + ], + }, + jest.fn() + ); expect(colorer.convert(99, HTML_CONTEXT_TYPE)).toBe('99'); expect(colorer.convert(100, HTML_CONTEXT_TYPE)).toBe( @@ -45,16 +48,19 @@ describe('Color Format', () => { }); test('should not convert invalid ranges', () => { - const colorer = new ColorFormat({ - fieldType: 'number', - colors: [ - { - range: '100150', - text: 'blue', - background: 'yellow', - }, - ], - }); + const colorer = new ColorFormat( + { + fieldType: 'number', + colors: [ + { + range: '100150', + text: 'blue', + background: 'yellow', + }, + ], + }, + jest.fn() + ); expect(colorer.convert(99, HTML_CONTEXT_TYPE)).toBe('99'); }); @@ -62,16 +68,19 @@ describe('Color Format', () => { describe('field is a string', () => { test('should add colors if the regex matches', () => { - const colorer = new ColorFormat({ - fieldType: 'string', - colors: [ - { - regex: 'A.*', - text: 'blue', - background: 'yellow', - }, - ], - }); + const colorer = new ColorFormat( + { + fieldType: 'string', + colors: [ + { + regex: 'A.*', + text: 'blue', + background: 'yellow', + }, + ], + }, + jest.fn() + ); const converter = colorer.getConverterFor(HTML_CONTEXT_TYPE) as Function; expect(converter('B', HTML_CONTEXT_TYPE)).toBe('B'); @@ -97,16 +106,19 @@ describe('Color Format', () => { }); test('returns original value (escaped) when regex is invalid', () => { - const colorer = new ColorFormat({ - fieldType: 'string', - colors: [ - { - regex: 'A.*', - text: 'blue', - background: 'yellow', - }, - ], - }); + const colorer = new ColorFormat( + { + fieldType: 'string', + colors: [ + { + regex: 'A.*', + text: 'blue', + background: 'yellow', + }, + ], + }, + jest.fn() + ); const converter = colorer.getConverterFor(HTML_CONTEXT_TYPE) as Function; expect(converter('<', HTML_CONTEXT_TYPE)).toBe('<'); diff --git a/src/plugins/data/common/field_formats/converters/color.ts b/src/plugins/data/common/field_formats/converters/color.ts index 6ba8bb97332e8..ffc72ba9a2c30 100644 --- a/src/plugins/data/common/field_formats/converters/color.ts +++ b/src/plugins/data/common/field_formats/converters/color.ts @@ -20,14 +20,14 @@ import { findLast, cloneDeep, template, escape } from 'lodash'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { HtmlContextTypeConvert } from '../types'; +import { HtmlContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; import { asPrettyString } from '../utils'; import { DEFAULT_CONVERTER_COLOR } from '../constants'; const convertTemplate = template('<%- val %>'); export class ColorFormat extends FieldFormat { - static id = 'color'; + static id = FIELD_FORMAT_IDS.COLOR; static title = 'Color'; static fieldType = [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING]; diff --git a/src/plugins/data/common/field_formats/converters/custom.ts b/src/plugins/data/common/field_formats/converters/custom.ts index 8ab31e5784566..687870306c873 100644 --- a/src/plugins/data/common/field_formats/converters/custom.ts +++ b/src/plugins/data/common/field_formats/converters/custom.ts @@ -18,13 +18,11 @@ */ import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; - -const ID = 'custom'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export const createCustomFieldFormat = (convert: TextContextTypeConvert) => class CustomFieldFormat extends FieldFormat { - static id = ID; + static id = FIELD_FORMAT_IDS.CUSTOM; textConvert = convert; }; diff --git a/src/plugins/data/common/field_formats/converters/date.ts b/src/plugins/data/common/field_formats/converters/date.ts index 0017d5afb0608..06af64d9c17c2 100644 --- a/src/plugins/data/common/field_formats/converters/date.ts +++ b/src/plugins/data/common/field_formats/converters/date.ts @@ -19,28 +19,23 @@ import { memoize, noop } from 'lodash'; import moment from 'moment'; -import { FieldFormat, KBN_FIELD_TYPES, TextContextTypeConvert } from '../../index'; +import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; +import { FieldFormat } from '../field_format'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export class DateFormat extends FieldFormat { - static id = 'date'; + static id = FIELD_FORMAT_IDS.DATE; static title = 'Date'; static fieldType = KBN_FIELD_TYPES.DATE; - private getConfig: Function; private memoizedConverter: Function = noop; private memoizedPattern: string = ''; private timeZone: string = ''; - constructor(params: Record, getConfig: Function) { - super(params); - - this.getConfig = getConfig; - } - getParamDefaults() { return { - pattern: this.getConfig('dateFormat'), - timezone: this.getConfig('dateFormat:tz'), + pattern: this.getConfig!('dateFormat'), + timezone: this.getConfig!('dateFormat:tz'), }; } diff --git a/src/plugins/data/common/field_formats/converters/date_nanos.ts b/src/plugins/data/common/field_formats/converters/date_nanos.ts index aef47f362bc97..8b0f8b111694e 100644 --- a/src/plugins/data/common/field_formats/converters/date_nanos.ts +++ b/src/plugins/data/common/field_formats/converters/date_nanos.ts @@ -21,7 +21,7 @@ import moment, { Moment } from 'moment'; import { memoize, noop } from 'lodash'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; /** * Analyse the given moment.js format pattern for the fractional sec part (S,SS,SSS...) @@ -69,26 +69,19 @@ export function formatWithNanos( } export class DateNanosFormat extends FieldFormat { - static id = 'date_nanos'; + static id = FIELD_FORMAT_IDS.DATE_NANOS; static title = 'Date Nanos'; static fieldType = KBN_FIELD_TYPES.DATE; - private getConfig: Function; private memoizedConverter: Function = noop; private memoizedPattern: string = ''; private timeZone: string = ''; - constructor(params: Record, getConfig: Function) { - super(params); - - this.getConfig = getConfig; - } - getParamDefaults() { return { - pattern: this.getConfig('dateNanosFormat'), - fallbackPattern: this.getConfig('dateFormat'), - timezone: this.getConfig('dateFormat:tz'), + pattern: this.getConfig!('dateNanosFormat'), + fallbackPattern: this.getConfig!('dateFormat'), + timezone: this.getConfig!('dateFormat:tz'), }; } diff --git a/src/plugins/data/common/field_formats/converters/date_server.ts b/src/plugins/data/common/field_formats/converters/date_server.ts index 7ed2745a256c4..0c214e424f163 100644 --- a/src/plugins/data/common/field_formats/converters/date_server.ts +++ b/src/plugins/data/common/field_formats/converters/date_server.ts @@ -21,22 +21,20 @@ import { memoize, noop } from 'lodash'; import moment from 'moment-timezone'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export class DateFormat extends FieldFormat { - static id = 'date'; + static id = FIELD_FORMAT_IDS.DATE; static title = 'Date'; static fieldType = KBN_FIELD_TYPES.DATE; - private getConfig: Function; private memoizedConverter: Function = noop; private memoizedPattern: string = ''; private timeZone: string = ''; constructor(params: Record, getConfig: Function) { - super(params); + super(params, getConfig); - this.getConfig = getConfig; this.memoizedConverter = memoize((val: any) => { if (val == null) { return '-'; @@ -66,8 +64,8 @@ export class DateFormat extends FieldFormat { getParamDefaults() { return { - pattern: this.getConfig('dateFormat'), - timezone: this.getConfig('dateFormat:tz'), + pattern: this.getConfig!('dateFormat'), + timezone: this.getConfig!('dateFormat:tz'), }; } diff --git a/src/plugins/data/common/field_formats/converters/duration.test.ts b/src/plugins/data/common/field_formats/converters/duration.test.ts index b892884475eec..d6205d54bd702 100644 --- a/src/plugins/data/common/field_formats/converters/duration.test.ts +++ b/src/plugins/data/common/field_formats/converters/duration.test.ts @@ -142,7 +142,10 @@ describe('Duration Format', () => { test(`should format ${input} ${inputFormat} through ${outputFormat}${ outputPrecision ? `, ${outputPrecision} decimals` : '' }`, () => { - const duration = new DurationFormat({ inputFormat, outputFormat, outputPrecision }); + const duration = new DurationFormat( + { inputFormat, outputFormat, outputPrecision }, + jest.fn() + ); expect(duration.convert(input)).toBe(output); }); }); diff --git a/src/plugins/data/common/field_formats/converters/duration.ts b/src/plugins/data/common/field_formats/converters/duration.ts index 5eed523214ab3..d02de1a2fd889 100644 --- a/src/plugins/data/common/field_formats/converters/duration.ts +++ b/src/plugins/data/common/field_formats/converters/duration.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import moment, { unitOfTime, Duration } from 'moment'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; const ratioToSeconds: Record = { picoseconds: 0.000000000001, @@ -166,7 +166,7 @@ function parseInputAsDuration(val: number, inputFormat: string) { } export class DurationFormat extends FieldFormat { - static id = 'duration'; + static id = FIELD_FORMAT_IDS.DURATION; static title = 'Duration'; static fieldType = KBN_FIELD_TYPES.NUMBER; static inputFormats = inputFormats; diff --git a/src/plugins/data/common/field_formats/converters/ip.test.ts b/src/plugins/data/common/field_formats/converters/ip.test.ts index cc42d41adc4a1..a9a02d1a43ea8 100644 --- a/src/plugins/data/common/field_formats/converters/ip.test.ts +++ b/src/plugins/data/common/field_formats/converters/ip.test.ts @@ -23,7 +23,7 @@ describe('IP Address Format', () => { let ip: Record; beforeEach(() => { - ip = new IpFormat(); + ip = new IpFormat({}, jest.fn()); }); test('converts a value from a decimal to a string', () => { diff --git a/src/plugins/data/common/field_formats/converters/ip.ts b/src/plugins/data/common/field_formats/converters/ip.ts index 669f7d1b605d7..3e011e8d7dde8 100644 --- a/src/plugins/data/common/field_formats/converters/ip.ts +++ b/src/plugins/data/common/field_formats/converters/ip.ts @@ -19,10 +19,10 @@ import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export class IpFormat extends FieldFormat { - static id = 'ip'; + static id = FIELD_FORMAT_IDS.IP; static title = 'IP Address'; static fieldType = KBN_FIELD_TYPES.IP; diff --git a/src/plugins/data/common/field_formats/converters/number.ts b/src/plugins/data/common/field_formats/converters/number.ts index e0c22f5350716..6969c1551e1cc 100644 --- a/src/plugins/data/common/field_formats/converters/number.ts +++ b/src/plugins/data/common/field_formats/converters/number.ts @@ -18,9 +18,10 @@ */ import { NumeralFormat } from './numeral'; +import { FIELD_FORMAT_IDS } from '../types'; export class NumberFormat extends NumeralFormat { - static id = 'number'; + static id = FIELD_FORMAT_IDS.NUMBER; static title = 'Number'; id = NumberFormat.id; diff --git a/src/plugins/data/common/field_formats/converters/numeral.ts b/src/plugins/data/common/field_formats/converters/numeral.ts index f7bf7ddfd1701..d8e46a480294f 100644 --- a/src/plugins/data/common/field_formats/converters/numeral.ts +++ b/src/plugins/data/common/field_formats/converters/numeral.ts @@ -37,15 +37,8 @@ export abstract class NumeralFormat extends FieldFormat { abstract id: string; abstract title: string; - protected getConfig: Function; - - constructor(params: Record, getConfig: Function) { - super(params); - this.getConfig = getConfig; - } - getParamDefaults = () => ({ - pattern: this.getConfig(`format:${this.id}:defaultPattern`), + pattern: this.getConfig!(`format:${this.id}:defaultPattern`), }); protected getConvertedValue(val: any): string { diff --git a/src/plugins/data/common/field_formats/converters/percent.ts b/src/plugins/data/common/field_formats/converters/percent.ts index f810f12377362..2ae32c7c77f07 100644 --- a/src/plugins/data/common/field_formats/converters/percent.ts +++ b/src/plugins/data/common/field_formats/converters/percent.ts @@ -18,17 +18,17 @@ */ import { NumeralFormat } from './numeral'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export class PercentFormat extends NumeralFormat { - static id = 'percent'; + static id = FIELD_FORMAT_IDS.PERCENT; static title = 'Percentage'; id = PercentFormat.id; title = PercentFormat.title; getParamDefaults = () => ({ - pattern: this.getConfig('format:percent:defaultPattern'), + pattern: this.getConfig!('format:percent:defaultPattern'), fractional: true, }); diff --git a/src/plugins/data/common/field_formats/converters/relative_date.test.ts b/src/plugins/data/common/field_formats/converters/relative_date.test.ts index bde5aec0a5ab5..6311402a34b46 100644 --- a/src/plugins/data/common/field_formats/converters/relative_date.test.ts +++ b/src/plugins/data/common/field_formats/converters/relative_date.test.ts @@ -24,7 +24,7 @@ describe('Relative Date Format', () => { let convert: Function; beforeEach(() => { - const relativeDate = new RelativeDateFormat({}); + const relativeDate = new RelativeDateFormat({}, jest.fn()); convert = relativeDate.convert.bind(relativeDate); }); diff --git a/src/plugins/data/common/field_formats/converters/relative_date.ts b/src/plugins/data/common/field_formats/converters/relative_date.ts index caab8c3a2d7da..273b2cef28a03 100644 --- a/src/plugins/data/common/field_formats/converters/relative_date.ts +++ b/src/plugins/data/common/field_formats/converters/relative_date.ts @@ -20,17 +20,13 @@ import moment from 'moment'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export class RelativeDateFormat extends FieldFormat { - static id = 'relative_date'; + static id = FIELD_FORMAT_IDS.RELATIVE_DATE; static title = 'Relative Date'; static fieldType = KBN_FIELD_TYPES.DATE; - constructor(params: Record) { - super(params); - } - textConvert: TextContextTypeConvert = val => { if (val === null || val === undefined) { return '-'; diff --git a/src/plugins/data/common/field_formats/converters/source.ts b/src/plugins/data/common/field_formats/converters/source.ts index 35eb14ca59ebb..54977c7e66976 100644 --- a/src/plugins/data/common/field_formats/converters/source.ts +++ b/src/plugins/data/common/field_formats/converters/source.ts @@ -24,7 +24,7 @@ import { noWhiteSpace } from '../../../../../legacy/core_plugins/kibana/common/u import { shortenDottedString } from '../../../../../legacy/core_plugins/kibana/common/utils/shorten_dotted_string'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert, HtmlContextTypeConvert } from '../types'; +import { TextContextTypeConvert, HtmlContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; const templateHtml = `
@@ -37,18 +37,10 @@ const templateHtml = ` const doTemplate = template(noWhiteSpace(templateHtml)); export class SourceFormat extends FieldFormat { - static id = '_source'; + static id = FIELD_FORMAT_IDS._SOURCE; static title = '_source'; static fieldType = KBN_FIELD_TYPES._SOURCE; - private getConfig: Function; - - constructor(params: Record, getConfig: Function) { - super(params); - - this.getConfig = getConfig; - } - textConvert: TextContextTypeConvert = value => JSON.stringify(value); htmlConvert: HtmlContextTypeConvert = (value, field, hit) => { @@ -62,7 +54,7 @@ export class SourceFormat extends FieldFormat { const formatted = field.indexPattern.formatHit(hit); const highlightPairs: any[] = []; const sourcePairs: any[] = []; - const isShortDots = this.getConfig('shortDots:enable'); + const isShortDots = this.getConfig!('shortDots:enable'); keys(formatted).forEach(key => { const pairs = highlights[key] ? highlightPairs : sourcePairs; diff --git a/src/plugins/data/common/field_formats/converters/static_lookup.ts b/src/plugins/data/common/field_formats/converters/static_lookup.ts index 29c64fd4f4046..419e7c786640b 100644 --- a/src/plugins/data/common/field_formats/converters/static_lookup.ts +++ b/src/plugins/data/common/field_formats/converters/static_lookup.ts @@ -19,7 +19,7 @@ import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; function convertLookupEntriesToMap(lookupEntries: any[]) { return lookupEntries.reduce( @@ -32,7 +32,7 @@ function convertLookupEntriesToMap(lookupEntries: any[]) { } export class StaticLookupFormat extends FieldFormat { - static id = 'static_lookup'; + static id = FIELD_FORMAT_IDS.STATIC_LOOKUP; static title = 'Static Lookup'; static fieldType = [ KBN_FIELD_TYPES.STRING, diff --git a/src/plugins/data/common/field_formats/converters/string.test.ts b/src/plugins/data/common/field_formats/converters/string.test.ts index bb0fd5cc011b6..b2d96ed5c0308 100644 --- a/src/plugins/data/common/field_formats/converters/string.test.ts +++ b/src/plugins/data/common/field_formats/converters/string.test.ts @@ -21,30 +21,42 @@ import { StringFormat } from './string'; describe('String Format', () => { test('convert a string to lower case', () => { - const string = new StringFormat({ - transform: 'lower', - }); + const string = new StringFormat( + { + transform: 'lower', + }, + jest.fn() + ); expect(string.convert('Kibana')).toBe('kibana'); }); test('convert a string to upper case', () => { - const string = new StringFormat({ - transform: 'upper', - }); + const string = new StringFormat( + { + transform: 'upper', + }, + jest.fn() + ); expect(string.convert('Kibana')).toBe('KIBANA'); }); test('decode a base64 string', () => { - const string = new StringFormat({ - transform: 'base64', - }); + const string = new StringFormat( + { + transform: 'base64', + }, + jest.fn() + ); expect(string.convert('Zm9vYmFy')).toBe('foobar'); }); test('convert a string to title case', () => { - const string = new StringFormat({ - transform: 'title', - }); + const string = new StringFormat( + { + transform: 'title', + }, + jest.fn() + ); expect(string.convert('PLEASE DO NOT SHOUT')).toBe('Please Do Not Shout'); expect(string.convert('Mean, variance and standard_deviation.')).toBe( 'Mean, Variance And Standard_deviation.' @@ -53,24 +65,33 @@ describe('String Format', () => { }); test('convert a string to short case', () => { - const string = new StringFormat({ - transform: 'short', - }); + const string = new StringFormat( + { + transform: 'short', + }, + jest.fn() + ); expect(string.convert('dot.notated.string')).toBe('d.n.string'); }); test('convert a string to unknown transform case', () => { - const string = new StringFormat({ - transform: 'unknown_transform', - }); + const string = new StringFormat( + { + transform: 'unknown_transform', + }, + jest.fn() + ); const value = 'test test test'; expect(string.convert(value)).toBe(value); }); test('decode a URL Param string', () => { - const string = new StringFormat({ - transform: 'urlparam', - }); + const string = new StringFormat( + { + transform: 'urlparam', + }, + jest.fn() + ); expect(string.convert('%EC%95%88%EB%85%95%20%ED%82%A4%EB%B0%94%EB%82%98')).toBe('안녕 키바나'); }); }); diff --git a/src/plugins/data/common/field_formats/converters/string.ts b/src/plugins/data/common/field_formats/converters/string.ts index 82547c0b0dee5..0edd219ca60f9 100644 --- a/src/plugins/data/common/field_formats/converters/string.ts +++ b/src/plugins/data/common/field_formats/converters/string.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import { asPrettyString } from '../index'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; // @ts-ignore import { shortenDottedString } from '../../../../../legacy/core_plugins/kibana/common/utils/shorten_dotted_string'; @@ -72,7 +72,7 @@ const TRANSFORM_OPTIONS = [ const DEFAULT_TRANSFORM_OPTION = false; export class StringFormat extends FieldFormat { - static id = 'string'; + static id = FIELD_FORMAT_IDS.STRING; static title = 'String'; static fieldType = [ KBN_FIELD_TYPES.NUMBER, diff --git a/src/plugins/data/common/field_formats/converters/truncate.test.ts b/src/plugins/data/common/field_formats/converters/truncate.test.ts index 7de4bdb3dedfe..472d9673346d7 100644 --- a/src/plugins/data/common/field_formats/converters/truncate.test.ts +++ b/src/plugins/data/common/field_formats/converters/truncate.test.ts @@ -21,25 +21,25 @@ import { TruncateFormat } from './truncate'; describe('String TruncateFormat', () => { test('truncate large string', () => { - const truncate = new TruncateFormat({ fieldLength: 4 }); + const truncate = new TruncateFormat({ fieldLength: 4 }, jest.fn()); expect(truncate.convert('This is some text')).toBe('This...'); }); test('does not truncate large string when field length is not a string', () => { - const truncate = new TruncateFormat({ fieldLength: 'not number' }); + const truncate = new TruncateFormat({ fieldLength: 'not number' }, jest.fn()); expect(truncate.convert('This is some text')).toBe('This is some text'); }); test('does not truncate large string when field length is null', () => { - const truncate = new TruncateFormat({ fieldLength: null }); + const truncate = new TruncateFormat({ fieldLength: null }, jest.fn()); expect(truncate.convert('This is some text')).toBe('This is some text'); }); test('does not truncate large string when field length larger than the text', () => { - const truncate = new TruncateFormat({ fieldLength: 100000 }); + const truncate = new TruncateFormat({ fieldLength: 100000 }, jest.fn()); expect(truncate.convert('This is some text')).toBe('This is some text'); }); diff --git a/src/plugins/data/common/field_formats/converters/truncate.ts b/src/plugins/data/common/field_formats/converters/truncate.ts index acccf2a20c69a..dc25d71ec95d7 100644 --- a/src/plugins/data/common/field_formats/converters/truncate.ts +++ b/src/plugins/data/common/field_formats/converters/truncate.ts @@ -20,12 +20,12 @@ import { trunc } from 'lodash'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; const omission = '...'; export class TruncateFormat extends FieldFormat { - static id = 'truncate'; + static id = FIELD_FORMAT_IDS.TRUNCATE; static title = 'Truncated String'; static fieldType = KBN_FIELD_TYPES.STRING; diff --git a/src/plugins/data/common/field_formats/converters/url.ts b/src/plugins/data/common/field_formats/converters/url.ts index 6c00f11a408dc..bd68dedf38a67 100644 --- a/src/plugins/data/common/field_formats/converters/url.ts +++ b/src/plugins/data/common/field_formats/converters/url.ts @@ -22,7 +22,7 @@ import { escape, memoize } from 'lodash'; import { getHighlightHtml } from '../utils'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert, HtmlContextTypeConvert } from '../types'; +import { TextContextTypeConvert, HtmlContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; const templateMatchRE = /{{([\s\S]+?)}}/g; const whitelistUrlSchemes = ['http://', 'https://']; @@ -50,7 +50,7 @@ const URL_TYPES = [ const DEFAULT_URL_TYPE = 'a'; export class UrlFormat extends FieldFormat { - static id = 'url'; + static id = FIELD_FORMAT_IDS.URL; static title = 'Url'; static fieldType = [ KBN_FIELD_TYPES.NUMBER, diff --git a/src/plugins/data/common/field_formats/field_format.test.ts b/src/plugins/data/common/field_formats/field_format.test.ts index d4789f12bdee9..2229601994496 100644 --- a/src/plugins/data/common/field_formats/field_format.test.ts +++ b/src/plugins/data/common/field_formats/field_format.test.ts @@ -32,7 +32,7 @@ const getTestFormat = ( textConvert = textConvert; htmlConvert = htmlConvert; - })(_params); + })(_params, jest.fn()); describe('FieldFormat class', () => { describe('params', () => { diff --git a/src/plugins/data/common/field_formats/field_format.ts b/src/plugins/data/common/field_formats/field_format.ts index e99894ca56167..6b5f665c6e20e 100644 --- a/src/plugins/data/common/field_formats/field_format.ts +++ b/src/plugins/data/common/field_formats/field_format.ts @@ -19,7 +19,12 @@ import { transform, size, cloneDeep, get, defaults } from 'lodash'; import { createCustomFieldFormat } from './converters/custom'; -import { ContentType, FieldFormatConvert, FieldFormatConvertFunction } from './types'; +import { + ContentType, + FIELD_FORMAT_IDS, + FieldFormatConvert, + FieldFormatConvertFunction, +} from './types'; import { htmlContentTypeSetup, textContentTypeSetup, @@ -68,7 +73,16 @@ export abstract class FieldFormat { */ public type: any = this.constructor; - constructor(public _params: any = {}) {} + private readonly _params: any; + protected getConfig: Function | undefined; + + constructor(_params: any = {}, getConfig?: Function) { + this._params = _params; + + if (getConfig) { + this.getConfig = getConfig; + } + } /** * Convert a raw value to a formatted string @@ -170,7 +184,7 @@ export abstract class FieldFormat { }; } - static from(convertFn: FieldFormatConvertFunction): ReturnType { + static from(convertFn: FieldFormatConvertFunction): IFieldFormatType { return createCustomFieldFormat(convertFn); } @@ -183,3 +197,11 @@ export abstract class FieldFormat { } export type IFieldFormat = PublicMethodsOf; +/** + * @string id type is needed for creating custom converters. + */ +export type IFieldFormatId = FIELD_FORMAT_IDS | string; +export type IFieldFormatType = (new (params?: any, getConfig?: Function) => FieldFormat) & { + id: IFieldFormatId; + fieldType: string | string[]; +}; diff --git a/src/plugins/data/common/field_formats/index.ts b/src/plugins/data/common/field_formats/index.ts index 5d04a69e4dc50..b751b097b5ed2 100644 --- a/src/plugins/data/common/field_formats/index.ts +++ b/src/plugins/data/common/field_formats/index.ts @@ -18,7 +18,7 @@ */ export { HTML_CONTEXT_TYPE, TEXT_CONTEXT_TYPE } from './content_types'; -export { FieldFormat } from './field_format'; +export { FieldFormat, IFieldFormatType, IFieldFormatId } from './field_format'; export { getHighlightRequest, asPrettyString, getHighlightHtml } from './utils'; export * from './converters'; export * from './constants'; diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts index 626bab297392b..fc8e6e20a1a96 100644 --- a/src/plugins/data/common/field_formats/types.ts +++ b/src/plugins/data/common/field_formats/types.ts @@ -42,3 +42,23 @@ export interface FieldFormatConvert { text: TextContextTypeConvert; html: HtmlContextTypeConvert; } + +/** @public **/ +export enum FIELD_FORMAT_IDS { + _SOURCE = '_source', + BOOLEAN = 'boolean', + BYTES = 'bytes', + COLOR = 'color', + CUSTOM = 'custom', + DATE = 'date', + DATE_NANOS = 'date_nanos', + DURATION = 'duration', + IP = 'ip', + NUMBER = 'number', + PERCENT = 'percent', + RELATIVE_DATE = 'relative_date', + STATIC_LOOKUP = 'static_lookup', + STRING = 'string', + TRUNCATE = 'truncate', + URL = 'url', +} diff --git a/src/plugins/data/common/kbn_field_types/types.ts b/src/plugins/data/common/kbn_field_types/types.ts index 21d58bcd0f78c..11c62e8f86dce 100644 --- a/src/plugins/data/common/kbn_field_types/types.ts +++ b/src/plugins/data/common/kbn_field_types/types.ts @@ -27,9 +27,10 @@ export interface KbnFieldTypeOptions { /** @public **/ export enum ES_FIELD_TYPES { - _TYPE = '_type', _ID = '_id', + _INDEX = '_index', _SOURCE = '_source', + _TYPE = '_type', STRING = 'string', TEXT = 'text', diff --git a/src/plugins/data/public/field_formats_provider/field_formats.ts b/src/plugins/data/public/field_formats_provider/field_formats.ts new file mode 100644 index 0000000000000..f46994c209ded --- /dev/null +++ b/src/plugins/data/public/field_formats_provider/field_formats.ts @@ -0,0 +1,226 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { forOwn, isFunction, memoize } from 'lodash'; +import { UiSettingsClientContract } from 'kibana/public'; +import { + ES_FIELD_TYPES, + KBN_FIELD_TYPES, + FIELD_FORMAT_IDS, + IFieldFormatType, + IFieldFormatId, + FieldFormat, +} from '../../common'; +import { FieldType } from './types'; + +export class FieldFormatRegisty { + private fieldFormats: Map; + private uiSettings!: UiSettingsClientContract; + private defaultMap: Record; + + constructor() { + this.fieldFormats = new Map(); + this.defaultMap = {}; + } + + getConfig = (key: string, override?: any) => this.uiSettings.get(key, override); + + init(uiSettings: UiSettingsClientContract) { + this.uiSettings = uiSettings; + + this.parseDefaultTypeMap(this.uiSettings.get('format:defaultTypeMap')); + + this.uiSettings.getUpdate$().subscribe(({ key, newValue }) => { + if (key === 'format:defaultTypeMap') { + this.parseDefaultTypeMap(newValue); + } + }); + } + + /** + * Get the id of the default type for this field type + * using the format:defaultTypeMap config map + * + * @param {KBN_FIELD_TYPES} fieldType - the field type + * @param {ES_FIELD_TYPES[]} esTypes - Array of ES data types + * @return {FieldType} + */ + getDefaultConfig = (fieldType: KBN_FIELD_TYPES, esTypes?: ES_FIELD_TYPES[]): FieldType => { + const type = this.getDefaultTypeName(fieldType, esTypes); + + return ( + (this.defaultMap && this.defaultMap[type]) || { id: FIELD_FORMAT_IDS.STRING, params: {} } + ); + }; + + /** + * Get a derived FieldFormat class by its id. + * + * @param {IFieldFormatId} formatId - the format id + * @return {FieldFormat} + */ + getType = (formatId: IFieldFormatId): IFieldFormatType | undefined => { + return this.fieldFormats.get(formatId); + }; + + /** + * Get the default FieldFormat type (class) for + * a field type, using the format:defaultTypeMap. + * used by the field editor + * + * @param {KBN_FIELD_TYPES} fieldType + * @param {ES_FIELD_TYPES[]} esTypes - Array of ES data types + * @return {FieldFormat} + */ + getDefaultType = ( + fieldType: KBN_FIELD_TYPES, + esTypes: ES_FIELD_TYPES[] + ): IFieldFormatType | undefined => { + const config = this.getDefaultConfig(fieldType, esTypes); + + return this.getType(config.id); + }; + + /** + * Get the name of the default type for ES types like date_nanos + * using the format:defaultTypeMap config map + * + * @param {ES_FIELD_TYPES[]} esTypes - Array of ES data types + * @return {ES_FIELD_TYPES} + */ + getTypeNameByEsTypes = (esTypes: ES_FIELD_TYPES[] | undefined): ES_FIELD_TYPES | undefined => { + if (!Array.isArray(esTypes)) { + return; + } + + return esTypes.find(type => this.defaultMap[type] && this.defaultMap[type].es); + }; + + /** + * Get the default FieldFormat type name for + * a field type, using the format:defaultTypeMap. + * + * @param {KBN_FIELD_TYPES} fieldType + * @param {ES_FIELD_TYPES[]} esTypes + * @return {ES_FIELD_TYPES | String} + */ + getDefaultTypeName = ( + fieldType: KBN_FIELD_TYPES, + esTypes?: ES_FIELD_TYPES[] + ): ES_FIELD_TYPES | KBN_FIELD_TYPES => { + const esType = this.getTypeNameByEsTypes(esTypes); + + return esType || fieldType; + }; + + /** + * Get the singleton instance of the FieldFormat type by its id. + * + * @param {IFieldFormatId} formatId + * @return {FIELD_FORMATS_INSTANCES[number]} + */ + getInstance = memoize( + (formatId: IFieldFormatId): FieldFormat => { + const DerivedFieldFormat = this.getType(formatId); + + if (!DerivedFieldFormat) { + throw new Error(`Field Format '${formatId}' not found!`); + } + + return new DerivedFieldFormat({}, this.getConfig); + } + ); + + /** + * Get the default fieldFormat instance for a field format. + * + * @param {KBN_FIELD_TYPES} fieldType + * @param {ES_FIELD_TYPES[]} esTypes + * @return {FieldFormat} + */ + getDefaultInstancePlain(fieldType: KBN_FIELD_TYPES, esTypes?: ES_FIELD_TYPES[]): FieldFormat { + const conf = this.getDefaultConfig(fieldType, esTypes); + + const DerivedFieldFormat = this.getType(conf.id); + + if (!DerivedFieldFormat) { + throw new Error(`Field Format '${conf.id}' not found!`); + } + + return new DerivedFieldFormat(conf.params, this.getConfig); + } + /** + * Returns a cache key built by the given variables for caching in memoized + * Where esType contains fieldType, fieldType is returned + * -> kibana types have a higher priority in that case + * -> would lead to failing tests that match e.g. date format with/without esTypes + * https://lodash.com/docs#memoize + * + * @param {KBN_FIELD_TYPES} fieldType + * @param {ES_FIELD_TYPES[]} esTypes + * @return {String} + */ + getDefaultInstanceCacheResolver(fieldType: KBN_FIELD_TYPES, esTypes: ES_FIELD_TYPES[]): string { + // @ts-ignore + return Array.isArray(esTypes) && esTypes.indexOf(fieldType) === -1 + ? [fieldType, ...esTypes].join('-') + : fieldType; + } + + /** + * Get filtered list of field formats by format type + * + * @param {KBN_FIELD_TYPES} fieldType + * @return {FieldFormat[]} + */ + getByFieldType(fieldType: KBN_FIELD_TYPES): IFieldFormatType[] { + return [...this.fieldFormats.values()].filter( + (format: IFieldFormatType) => format.fieldType.indexOf(fieldType) !== -1 + ); + } + + /** + * Get the default fieldFormat instance for a field format. + * It's a memoized function that builds and reads a cache + * + * @param {KBN_FIELD_TYPES} fieldType + * @param {ES_FIELD_TYPES[]} esTypes + * @return {FieldFormat} + */ + getDefaultInstance = memoize(this.getDefaultInstancePlain, this.getDefaultInstanceCacheResolver); + + parseDefaultTypeMap(value: any) { + this.defaultMap = value; + forOwn(this, fn => { + if (isFunction(fn) && fn.cache) { + // clear all memoize caches + // @ts-ignore + fn.cache = new memoize.Cache(); + } + }); + } + + register = (fieldFormats: IFieldFormatType[]) => { + fieldFormats.forEach(fieldFormat => { + this.fieldFormats.set(fieldFormat.id, fieldFormat); + }); + + return this; + }; +} diff --git a/src/plugins/data/public/field_formats_provider/field_formats_service.ts b/src/plugins/data/public/field_formats_provider/field_formats_service.ts new file mode 100644 index 0000000000000..b144ea7ec2530 --- /dev/null +++ b/src/plugins/data/public/field_formats_provider/field_formats_service.ts @@ -0,0 +1,87 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { UiSettingsClientContract } from 'src/core/public'; +import { FieldFormatRegisty } from './field_formats'; + +import { + BoolFormat, + BytesFormat, + ColorFormat, + DateFormat, + DateNanosFormat, + DurationFormat, + IpFormat, + NumberFormat, + PercentFormat, + RelativeDateFormat, + SourceFormat, + StaticLookupFormat, + StringFormat, + TruncateFormat, + UrlFormat, +} from '../../common/'; + +/** + * Field Format Service + * @internal + */ +interface FieldFormatsServiceDependencies { + uiSettings: UiSettingsClientContract; +} + +export class FieldFormatsService { + private readonly fieldFormats: FieldFormatRegisty = new FieldFormatRegisty(); + + public setup({ uiSettings }: FieldFormatsServiceDependencies) { + this.fieldFormats.init(uiSettings); + + this.fieldFormats.register([ + BoolFormat, + BytesFormat, + ColorFormat, + DateFormat, + DateNanosFormat, + DurationFormat, + IpFormat, + NumberFormat, + PercentFormat, + RelativeDateFormat, + SourceFormat, + StaticLookupFormat, + StringFormat, + TruncateFormat, + UrlFormat, + ]); + + return this.fieldFormats as FieldFormatsSetup; + } + + public start() { + return this.fieldFormats as FieldFormatsStart; + } + + public stop() { + // nothing to do here yet + } +} + +/** @public */ +export type FieldFormatsSetup = Omit; +export type FieldFormatsStart = Omit; diff --git a/src/plugins/data/public/field_formats_provider/index.ts b/src/plugins/data/public/field_formats_provider/index.ts new file mode 100644 index 0000000000000..442d877c5316a --- /dev/null +++ b/src/plugins/data/public/field_formats_provider/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { FieldFormatRegisty } from './field_formats'; // TODO: Try to remove +export { FieldFormatsService, FieldFormatsSetup, FieldFormatsStart } from './field_formats_service'; diff --git a/src/plugins/feature_catalogue/public/services/index.ts b/src/plugins/data/public/field_formats_provider/types.ts similarity index 84% rename from src/plugins/feature_catalogue/public/services/index.ts rename to src/plugins/data/public/field_formats_provider/types.ts index 17433264f5a42..fc33bf4d38f85 100644 --- a/src/plugins/feature_catalogue/public/services/index.ts +++ b/src/plugins/data/public/field_formats_provider/types.ts @@ -17,4 +17,10 @@ * under the License. */ -export * from './feature_catalogue_registry'; +import { IFieldFormatId } from '../../common'; + +export interface FieldType { + id: IFieldFormatId; + params: Record; + es?: boolean; +} diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 4477c6defbc81..6a2df6a61d136 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -29,6 +29,8 @@ export { DataPublicPlugin as Plugin }; export * from '../common'; export * from './autocomplete_provider'; +export * from './field_formats_provider'; + export * from './types'; export { IRequestTypesMap, IResponseTypesMap } from './search'; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 4aae63c24d7fc..ff5c96c2d89ed 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Plugin } from '.'; +import { FieldFormatRegisty, Plugin, FieldFormatsStart, FieldFormatsSetup } from '.'; import { searchSetupMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; @@ -29,11 +29,29 @@ const autocompleteMock: any = { clearProviders: jest.fn(), }; +const fieldFormatsMock: PublicMethodsOf = { + getByFieldType: jest.fn(), + getConfig: jest.fn(), + getDefaultConfig: jest.fn(), + getDefaultInstance: jest.fn() as any, + getDefaultInstanceCacheResolver: jest.fn(), + getDefaultInstancePlain: jest.fn(), + getDefaultType: jest.fn(), + getDefaultTypeName: jest.fn(), + getInstance: jest.fn() as any, + getType: jest.fn(), + getTypeNameByEsTypes: jest.fn(), + init: jest.fn(), + register: jest.fn(), + parseDefaultTypeMap: jest.fn(), +}; + const createSetupContract = (): Setup => { const querySetupMock = queryServiceMock.createSetupContract(); const setupContract = { autocomplete: autocompleteMock, search: searchSetupMock, + fieldFormats: fieldFormatsMock as FieldFormatsSetup, query: querySetupMock, }; @@ -46,6 +64,7 @@ const createStartContract = (): Start => { autocomplete: autocompleteMock, getSuggestions: jest.fn(), search: { search: jest.fn() }, + fieldFormats: fieldFormatsMock as FieldFormatsStart, query: queryStartMock, }; return startContract; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 79db34c022b39..3aa9cd9a0bcb4 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -17,29 +17,34 @@ * under the License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { Storage } from '../../kibana_utils/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from './types'; import { AutocompleteProviderRegister } from './autocomplete_provider'; import { getSuggestionsProvider } from './suggestions_provider'; import { SearchService } from './search/search_service'; +import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; export class DataPublicPlugin implements Plugin { private readonly autocomplete = new AutocompleteProviderRegister(); private readonly searchService: SearchService; + private readonly fieldFormatsService: FieldFormatsService; private readonly queryService: QueryService; constructor(initializerContext: PluginInitializerContext) { this.searchService = new SearchService(initializerContext); this.queryService = new QueryService(); + this.fieldFormatsService = new FieldFormatsService(); } public setup(core: CoreSetup): DataPublicPluginSetup { const storage = new Storage(window.localStorage); + return { autocomplete: this.autocomplete, search: this.searchService.setup(core), + fieldFormats: this.fieldFormatsService.setup(core), query: this.queryService.setup({ uiSettings: core.uiSettings, storage, @@ -52,6 +57,7 @@ export class DataPublicPlugin implements Plugin new FeatureCataloguePlugin(); +export const plugin = () => new HomePublicPlugin(); diff --git a/src/plugins/feature_catalogue/public/plugin.test.mocks.ts b/src/plugins/home/public/plugin.test.mocks.ts similarity index 95% rename from src/plugins/feature_catalogue/public/plugin.test.mocks.ts rename to src/plugins/home/public/plugin.test.mocks.ts index c0da6a179204b..a48ea8f795136 100644 --- a/src/plugins/feature_catalogue/public/plugin.test.mocks.ts +++ b/src/plugins/home/public/plugin.test.mocks.ts @@ -17,7 +17,7 @@ * under the License. */ -import { featureCatalogueRegistryMock } from './services/feature_catalogue_registry.mock'; +import { featureCatalogueRegistryMock } from './services/feature_catalogue/feature_catalogue_registry.mock'; export const registryMock = featureCatalogueRegistryMock.create(); jest.doMock('./services', () => ({ diff --git a/src/plugins/feature_catalogue/public/plugin.test.ts b/src/plugins/home/public/plugin.test.ts similarity index 79% rename from src/plugins/feature_catalogue/public/plugin.test.ts rename to src/plugins/home/public/plugin.test.ts index 8bbbb973b459e..fad6e8cf47bfe 100644 --- a/src/plugins/feature_catalogue/public/plugin.test.ts +++ b/src/plugins/home/public/plugin.test.ts @@ -18,9 +18,9 @@ */ import { registryMock } from './plugin.test.mocks'; -import { FeatureCataloguePlugin } from './plugin'; +import { HomePublicPlugin } from './plugin'; -describe('FeatureCataloguePlugin', () => { +describe('HomePublicPlugin', () => { beforeEach(() => { registryMock.setup.mockClear(); registryMock.start.mockClear(); @@ -28,22 +28,22 @@ describe('FeatureCataloguePlugin', () => { describe('setup', () => { test('wires up and returns registry', async () => { - const setup = await new FeatureCataloguePlugin().setup(); - expect(registryMock.setup).toHaveBeenCalledWith(); - expect(setup.register).toBeDefined(); + const setup = await new HomePublicPlugin().setup(); + expect(setup).toHaveProperty('featureCatalogue'); + expect(setup.featureCatalogue).toHaveProperty('register'); }); }); describe('start', () => { test('wires up and returns registry', async () => { - const service = new FeatureCataloguePlugin(); + const service = new HomePublicPlugin(); await service.setup(); const core = { application: { capabilities: { catalogue: {} } } } as any; const start = await service.start(core); expect(registryMock.start).toHaveBeenCalledWith({ capabilities: core.application.capabilities, }); - expect(start.get).toBeDefined(); + expect(start.featureCatalogue.get).toBeDefined(); }); }); }); diff --git a/src/plugins/feature_catalogue/public/plugin.ts b/src/plugins/home/public/plugin.ts similarity index 71% rename from src/plugins/feature_catalogue/public/plugin.ts rename to src/plugins/home/public/plugin.ts index 46a70baff488a..40f2047ef0016 100644 --- a/src/plugins/feature_catalogue/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -24,21 +24,22 @@ import { FeatureCatalogueRegistryStart, } from './services'; -export class FeatureCataloguePlugin - implements Plugin { +export class HomePublicPlugin implements Plugin { private readonly featuresCatalogueRegistry = new FeatureCatalogueRegistry(); public async setup() { return { - ...this.featuresCatalogueRegistry.setup(), + featureCatalogue: { ...this.featuresCatalogueRegistry.setup() }, }; } public async start(core: CoreStart) { return { - ...this.featuresCatalogueRegistry.start({ - capabilities: core.application.capabilities, - }), + featureCatalogue: { + ...this.featuresCatalogueRegistry.start({ + capabilities: core.application.capabilities, + }), + }, }; } } @@ -48,3 +49,13 @@ export type FeatureCatalogueSetup = FeatureCatalogueRegistrySetup; /** @public */ export type FeatureCatalogueStart = FeatureCatalogueRegistryStart; + +/** @public */ +export interface HomePublicPluginSetup { + featureCatalogue: FeatureCatalogueSetup; +} + +/** @public */ +export interface HomePublicPluginStart { + featureCatalogue: FeatureCatalogueStart; +} diff --git a/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.mock.ts b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts similarity index 100% rename from src/plugins/feature_catalogue/public/services/feature_catalogue_registry.mock.ts rename to src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts diff --git a/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.test.ts b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.test.ts similarity index 100% rename from src/plugins/feature_catalogue/public/services/feature_catalogue_registry.test.ts rename to src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.test.ts diff --git a/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.ts b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts similarity index 100% rename from src/plugins/feature_catalogue/public/services/feature_catalogue_registry.ts rename to src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts diff --git a/src/plugins/home/public/services/feature_catalogue/index.ts b/src/plugins/home/public/services/feature_catalogue/index.ts new file mode 100644 index 0000000000000..eae01271e8559 --- /dev/null +++ b/src/plugins/home/public/services/feature_catalogue/index.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { + FeatureCatalogueCategory, + FeatureCatalogueEntry, + FeatureCatalogueRegistry, + FeatureCatalogueRegistrySetup, + FeatureCatalogueRegistryStart, +} from './feature_catalogue_registry'; diff --git a/src/legacy/ui/public/registry/field_formats.d.ts b/src/plugins/home/public/services/index.ts similarity index 95% rename from src/legacy/ui/public/registry/field_formats.d.ts rename to src/plugins/home/public/services/index.ts index 79eec7a5a4e74..3621b0912393a 100644 --- a/src/legacy/ui/public/registry/field_formats.d.ts +++ b/src/plugins/home/public/services/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export type FieldFormat = any; +export * from './feature_catalogue'; diff --git a/src/plugins/home/server/index.ts b/src/plugins/home/server/index.ts index 178a77dc85ca9..be4e20ab63d3c 100644 --- a/src/plugins/home/server/index.ts +++ b/src/plugins/home/server/index.ts @@ -17,8 +17,8 @@ * under the License. */ -export { HomePluginSetup, HomePluginStart } from './plugin'; +export { HomeServerPluginSetup, HomeServerPluginStart } from './plugin'; export { TutorialProvider } from './services'; -import { HomePlugin } from './plugin'; +import { HomeServerPlugin } from './plugin'; -export const plugin = () => new HomePlugin(); +export const plugin = () => new HomeServerPlugin(); diff --git a/src/plugins/home/server/plugin.test.mocks.ts b/src/plugins/home/server/plugin.test.mocks.ts index df63b467d8656..a5640de579b15 100644 --- a/src/plugins/home/server/plugin.test.mocks.ts +++ b/src/plugins/home/server/plugin.test.mocks.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { tutorialsRegistryMock } from './services/tutorials_registry.mock'; +import { tutorialsRegistryMock } from './services/tutorials/tutorials_registry.mock'; export const registryMock = tutorialsRegistryMock.create(); jest.doMock('./services', () => ({ diff --git a/src/plugins/home/server/plugin.test.ts b/src/plugins/home/server/plugin.test.ts index e86a2d807109f..eec6501436bf4 100644 --- a/src/plugins/home/server/plugin.test.ts +++ b/src/plugins/home/server/plugin.test.ts @@ -18,13 +18,13 @@ */ import { registryMock } from './plugin.test.mocks'; -import { HomePlugin } from './plugin'; +import { HomeServerPlugin } from './plugin'; import { coreMock } from '../../../core/server/mocks'; import { CoreSetup } from '../../../core/server'; type MockedKeys = { [P in keyof T]: jest.Mocked }; -describe('HomePlugin', () => { +describe('HomeServerPlugin', () => { beforeEach(() => { registryMock.setup.mockClear(); registryMock.start.mockClear(); @@ -34,7 +34,7 @@ describe('HomePlugin', () => { const mockCoreSetup: MockedKeys = coreMock.createSetup(); test('wires up and returns registerTutorial and addScopedTutorialContextFactory', () => { - const setup = new HomePlugin().setup(mockCoreSetup); + const setup = new HomeServerPlugin().setup(mockCoreSetup); expect(setup).toHaveProperty('tutorials'); expect(setup.tutorials).toHaveProperty('registerTutorial'); expect(setup.tutorials).toHaveProperty('addScopedTutorialContextFactory'); @@ -43,7 +43,7 @@ describe('HomePlugin', () => { describe('start', () => { test('is defined', () => { - const start = new HomePlugin().start(); + const start = new HomeServerPlugin().start(); expect(start).toBeDefined(); expect(start).toHaveProperty('tutorials'); }); diff --git a/src/plugins/home/server/plugin.ts b/src/plugins/home/server/plugin.ts index d5a3f235f8490..89dda8205ce02 100644 --- a/src/plugins/home/server/plugin.ts +++ b/src/plugins/home/server/plugin.ts @@ -19,7 +19,7 @@ import { CoreSetup, Plugin } from 'src/core/server'; import { TutorialsRegistry, TutorialsRegistrySetup, TutorialsRegistryStart } from './services'; -export class HomePlugin implements Plugin { +export class HomeServerPlugin implements Plugin { private readonly tutorialsRegistry = new TutorialsRegistry(); public setup(core: CoreSetup) { @@ -36,11 +36,11 @@ export class HomePlugin implements Plugin { } /** @public */ -export interface HomePluginSetup { +export interface HomeServerPluginSetup { tutorials: TutorialsRegistrySetup; } /** @public */ -export interface HomePluginStart { +export interface HomeServerPluginStart { tutorials: TutorialsRegistryStart; } diff --git a/src/plugins/home/server/services/index.ts b/src/plugins/home/server/services/index.ts index 5fe5cb0ba4760..9bfbe4079c6be 100644 --- a/src/plugins/home/server/services/index.ts +++ b/src/plugins/home/server/services/index.ts @@ -19,9 +19,17 @@ // provided to other plugins as APIs // should model the plugin lifecycle +export { TutorialsRegistry, TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials'; export { - TutorialsRegistry, - TutorialsRegistrySetup, - TutorialsRegistryStart, -} from './tutorials_registry'; -export * from '../lib/tutorials_registry_types'; + TutorialsCategory, + ParamTypes, + InstructionSetSchema, + ParamsSchema, + InstructionsSchema, + DashboardSchema, + ArtifactsSchema, + TutorialSchema, + TutorialProvider, + TutorialContextFactory, + ScopedTutorialContextFactory, +} from './tutorials'; diff --git a/src/plugins/home/server/services/tutorials/index.ts b/src/plugins/home/server/services/tutorials/index.ts new file mode 100644 index 0000000000000..d481a94516163 --- /dev/null +++ b/src/plugins/home/server/services/tutorials/index.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +export { + TutorialsRegistry, + TutorialsRegistrySetup, + TutorialsRegistryStart, +} from './tutorials_registry'; +export { + TutorialsCategory, + ParamTypes, + InstructionSetSchema, + ParamsSchema, + InstructionsSchema, + DashboardSchema, + ArtifactsSchema, + TutorialSchema, + TutorialProvider, + TutorialContextFactory, + ScopedTutorialContextFactory, +} from './lib/tutorials_registry_types'; diff --git a/src/plugins/home/server/lib/tutorial_schema.ts b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts similarity index 100% rename from src/plugins/home/server/lib/tutorial_schema.ts rename to src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts diff --git a/src/plugins/home/server/lib/tutorials_registry_types.ts b/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts similarity index 100% rename from src/plugins/home/server/lib/tutorials_registry_types.ts rename to src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts diff --git a/src/plugins/home/server/services/tutorials_registry.mock.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.mock.ts similarity index 100% rename from src/plugins/home/server/services/tutorials_registry.mock.ts rename to src/plugins/home/server/services/tutorials/tutorials_registry.mock.ts diff --git a/src/plugins/home/server/services/tutorials_registry.test.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts similarity index 95% rename from src/plugins/home/server/services/tutorials_registry.test.ts rename to src/plugins/home/server/services/tutorials/tutorials_registry.test.ts index 04c26bab1f065..8144fef2d92e4 100644 --- a/src/plugins/home/server/services/tutorials_registry.test.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts @@ -18,16 +18,16 @@ */ import { TutorialsRegistry } from './tutorials_registry'; -import { coreMock } from '../../../../core/server/mocks'; -import { CoreSetup } from '../../../../core/server'; -import { httpServerMock } from '../../../../../src/core/server/mocks'; +import { coreMock } from '../../../../../core/server/mocks'; +import { CoreSetup } from '../../../../../core/server'; +import { httpServerMock } from '../../../../../core/server/mocks'; import { TutorialProvider, TutorialSchema, TutorialsCategory, ScopedTutorialContextFactory, -} from '../lib/tutorials_registry_types'; +} from './lib/tutorials_registry_types'; const INVALID_TUTORIAL: TutorialSchema = { id: 'test', diff --git a/src/plugins/home/server/services/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts similarity index 96% rename from src/plugins/home/server/services/tutorials_registry.ts rename to src/plugins/home/server/services/tutorials/tutorials_registry.ts index 40692d8558656..be0302cbd8188 100644 --- a/src/plugins/home/server/services/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -23,8 +23,8 @@ import { TutorialProvider, TutorialContextFactory, ScopedTutorialContextFactory, -} from '../lib/tutorials_registry_types'; -import { tutorialSchema } from '../lib/tutorial_schema'; +} from './lib/tutorials_registry_types'; +import { tutorialSchema } from './lib/tutorial_schema'; export class TutorialsRegistry { private readonly tutorialProviders: TutorialProvider[] = []; // pre-register all the tutorials we know we want in here diff --git a/src/legacy/core_plugins/kibana/public/field_formats/register.js b/src/test_utils/public/stub_field_formats.ts similarity index 60% rename from src/legacy/core_plugins/kibana/public/field_formats/register.js rename to src/test_utils/public/stub_field_formats.ts index 9709b56fc8c3c..39c6fb2f6d10e 100644 --- a/src/legacy/core_plugins/kibana/public/field_formats/register.js +++ b/src/test_utils/public/stub_field_formats.ts @@ -16,38 +16,49 @@ * specific language governing permissions and limitations * under the License. */ +import { UiSettingsClientContract } from 'kibana/public'; -import { fieldFormats } from 'ui/registry/field_formats'; import { - UrlFormat, - StringFormat, - NumberFormat, + FieldFormatRegisty, + BoolFormat, BytesFormat, - TruncateFormat, - RelativeDateFormat, - PercentFormat, - IpFormat, - DurationFormat, - DateNanosFormat, - DateFormat, ColorFormat, - BoolFormat, + DateFormat, + DateNanosFormat, + DurationFormat, + IpFormat, + NumberFormat, + PercentFormat, + RelativeDateFormat, SourceFormat, - StaticLookupFormat -} from '../../../../../plugins/data/public'; + StaticLookupFormat, + StringFormat, + TruncateFormat, + UrlFormat, +} from '../../plugins/data/public/'; + +export const getFieldFormatsRegistry = (uiSettings: UiSettingsClientContract) => { + const fieldFormats = new FieldFormatRegisty(); + + fieldFormats.register([ + BoolFormat, + BytesFormat, + ColorFormat, + DateFormat, + DateNanosFormat, + DurationFormat, + IpFormat, + NumberFormat, + PercentFormat, + RelativeDateFormat, + SourceFormat, + StaticLookupFormat, + StringFormat, + TruncateFormat, + UrlFormat, + ]); + + fieldFormats.init(uiSettings); -fieldFormats.register(UrlFormat); -fieldFormats.register(BytesFormat); -fieldFormats.register(DateFormat); -fieldFormats.register(DateNanosFormat); -fieldFormats.register(RelativeDateFormat); -fieldFormats.register(DurationFormat); -fieldFormats.register(IpFormat); -fieldFormats.register(NumberFormat); -fieldFormats.register(PercentFormat); -fieldFormats.register(StringFormat); -fieldFormats.register(SourceFormat); -fieldFormats.register(ColorFormat); -fieldFormats.register(TruncateFormat); -fieldFormats.register(BoolFormat); -fieldFormats.register(StaticLookupFormat); + return fieldFormats; +}; diff --git a/src/test_utils/public/stub_index_pattern.js b/src/test_utils/public/stub_index_pattern.js index a6a0eb386d32e..b41ebe3e61861 100644 --- a/src/test_utils/public/stub_index_pattern.js +++ b/src/test_utils/public/stub_index_pattern.js @@ -28,9 +28,15 @@ import { formatHitProvider, flattenHitWrapper, } from 'ui/index_patterns'; -import { fieldFormats } from 'ui/registry/field_formats'; +import { + FIELD_FORMAT_IDS, +} from '../../plugins/data/public'; + +import { getFieldFormatsRegistry } from './stub_field_formats'; + +export default function StubIndexPattern(pattern, getConfig, timeField, fields, uiSettings) { + const registeredFieldFormats = getFieldFormatsRegistry(uiSettings); -export default function StubIndexPattern(pattern, getConfig, timeField, fields) { this.id = pattern; this.title = pattern; this.popularizeField = sinon.stub(); @@ -47,7 +53,7 @@ export default function StubIndexPattern(pattern, getConfig, timeField, fields) this.getComputedFields = IndexPattern.prototype.getComputedFields.bind(this); this.flattenHit = flattenHitWrapper(this, this.metaFields); - this.formatHit = formatHitProvider(this, fieldFormats.getDefaultInstance('string')); + this.formatHit = formatHitProvider(this, registeredFieldFormats.getDefaultInstance(FIELD_FORMAT_IDS.STRING)); this.fieldsFetcher = { apiClient: { baseUrl: '' } }; this.formatField = this.formatHit.formatField; @@ -56,7 +62,7 @@ export default function StubIndexPattern(pattern, getConfig, timeField, fields) }; this.stubSetFieldFormat = function (fieldName, id, params) { - const FieldFormat = fieldFormats.getType(id); + const FieldFormat = registeredFieldFormats.getType(id); this.fieldFormatMap[fieldName] = new FieldFormat(params); this._reindexFields(); }; diff --git a/test/api_integration/apis/home/sample_data.js b/test/api_integration/apis/home/sample_data.js index 7ca66e87a9325..042f490768af0 100644 --- a/test/api_integration/apis/home/sample_data.js +++ b/test/api_integration/apis/home/sample_data.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const MILLISECOND_IN_WEEK = 1000 * 60 * 60 * 24 * 7; diff --git a/test/api_integration/apis/index_patterns/es_errors/errors.js b/test/api_integration/apis/index_patterns/es_errors/errors.js index 4e50b965211c2..77e024c9d20cc 100644 --- a/test/api_integration/apis/index_patterns/es_errors/errors.js +++ b/test/api_integration/apis/index_patterns/es_errors/errors.js @@ -34,7 +34,7 @@ import { } from './lib'; export default function ({ getService }) { - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); describe('index_patterns/* error handler', () => { diff --git a/test/api_integration/apis/kql_telemetry/kql_telemetry.js b/test/api_integration/apis/kql_telemetry/kql_telemetry.js index 87d06ee9458d6..25a68bb4bb2b6 100644 --- a/test/api_integration/apis/kql_telemetry/kql_telemetry.js +++ b/test/api_integration/apis/kql_telemetry/kql_telemetry.js @@ -24,7 +24,7 @@ import { get } from 'lodash'; export default function ({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); describe('telemetry API', () => { before(() => esArchiver.load('saved_objects/basic')); diff --git a/test/api_integration/apis/management/saved_objects/find.js b/test/api_integration/apis/management/saved_objects/find.js index 0e7cf08fa5c26..6bb3c0cebddbf 100644 --- a/test/api_integration/apis/management/saved_objects/find.js +++ b/test/api_integration/apis/management/saved_objects/find.js @@ -20,7 +20,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { - const es = getService('es'); + const es = getService('legacyEs'); const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); diff --git a/test/api_integration/apis/saved_objects/bulk_create.js b/test/api_integration/apis/saved_objects/bulk_create.js index e77e08d949f2b..3a520d369120d 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.js +++ b/test/api_integration/apis/saved_objects/bulk_create.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); const BULK_REQUESTS = [ diff --git a/test/api_integration/apis/saved_objects/bulk_get.js b/test/api_integration/apis/saved_objects/bulk_get.js index 8c5b589a12a2e..52733aa70200b 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.js +++ b/test/api_integration/apis/saved_objects/bulk_get.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); const BULK_REQUESTS = [ diff --git a/test/api_integration/apis/saved_objects/bulk_update.js b/test/api_integration/apis/saved_objects/bulk_update.js index 4bdf257ceef02..b38934aecd0fb 100644 --- a/test/api_integration/apis/saved_objects/bulk_update.js +++ b/test/api_integration/apis/saved_objects/bulk_update.js @@ -22,7 +22,7 @@ import _ from 'lodash'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); diff --git a/test/api_integration/apis/saved_objects/create.js b/test/api_integration/apis/saved_objects/create.js index eefd9e8ab1a95..363aa9d30c08d 100644 --- a/test/api_integration/apis/saved_objects/create.js +++ b/test/api_integration/apis/saved_objects/create.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); describe('create', () => { diff --git a/test/api_integration/apis/saved_objects/delete.js b/test/api_integration/apis/saved_objects/delete.js index a9037bf697406..8aa76e99312b0 100644 --- a/test/api_integration/apis/saved_objects/delete.js +++ b/test/api_integration/apis/saved_objects/delete.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); describe('delete', () => { diff --git a/test/api_integration/apis/saved_objects/export.js b/test/api_integration/apis/saved_objects/export.js index 9ab7a09309952..59c9afe24b802 100644 --- a/test/api_integration/apis/saved_objects/export.js +++ b/test/api_integration/apis/saved_objects/export.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); describe('export', () => { diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index 7b2b15d298ce0..e1bec19cf8e6a 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); describe('find', () => { diff --git a/test/api_integration/apis/saved_objects/get.js b/test/api_integration/apis/saved_objects/get.js index fb4b6f91b8bb8..9034bc5d84a72 100644 --- a/test/api_integration/apis/saved_objects/get.js +++ b/test/api_integration/apis/saved_objects/get.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); describe('get', () => { diff --git a/test/api_integration/apis/saved_objects/migrations.js b/test/api_integration/apis/saved_objects/migrations.js index 5c492951ec938..c4b258d47e24b 100644 --- a/test/api_integration/apis/saved_objects/migrations.js +++ b/test/api_integration/apis/saved_objects/migrations.js @@ -31,7 +31,7 @@ import { SavedObjectsSerializer } from '../../../../src/core/server/saved_object import { SavedObjectsSchema } from '../../../../src/core/server/saved_objects/schema'; export default ({ getService }) => { - const es = getService('es'); + const es = getService('legacyEs'); const callCluster = (path, ...args) => _.get(es, path).call(es, ...args); describe('Kibana index migration', () => { diff --git a/test/api_integration/apis/saved_objects/update.js b/test/api_integration/apis/saved_objects/update.js index e6ad6b2b781da..c73ed0ecc6424 100644 --- a/test/api_integration/apis/saved_objects/update.js +++ b/test/api_integration/apis/saved_objects/update.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); describe('update', () => { diff --git a/test/api_integration/apis/ui_metric/ui_metric.js b/test/api_integration/apis/ui_metric/ui_metric.js index f0c86f2904638..51959bf5f7fda 100644 --- a/test/api_integration/apis/ui_metric/ui_metric.js +++ b/test/api_integration/apis/ui_metric/ui_metric.js @@ -22,7 +22,7 @@ import { ReportManager, METRIC_TYPE } from '@kbn/analytics'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const createStatsMetric = (eventName) => ({ key: ReportManager.createMetricKey({ appName: 'myApp', type: METRIC_TYPE.CLICK, eventName }), diff --git a/test/api_integration/services/index.ts b/test/api_integration/services/index.ts index 573450cae534a..782ea271869ba 100644 --- a/test/api_integration/services/index.ts +++ b/test/api_integration/services/index.ts @@ -23,10 +23,7 @@ import { services as commonServices } from '../../common/services'; import { KibanaSupertestProvider, ElasticsearchSupertestProvider } from './supertest'; export const services = { - es: commonServices.es, - esArchiver: commonServices.esArchiver, - retry: commonServices.retry, + ...commonServices, supertest: KibanaSupertestProvider, esSupertest: ElasticsearchSupertestProvider, - randomness: commonServices.randomness, }; diff --git a/src/legacy/ui/public/capabilities/route_setup.ts b/test/common/services/elasticsearch.ts similarity index 58% rename from src/legacy/ui/public/capabilities/route_setup.ts rename to test/common/services/elasticsearch.ts index c7817b8cc5748..63c4bfeeb4ce7 100644 --- a/src/legacy/ui/public/capabilities/route_setup.ts +++ b/test/common/services/elasticsearch.ts @@ -17,22 +17,17 @@ * under the License. */ -import { get } from 'lodash'; -import chrome from 'ui/chrome'; -import uiRoutes from 'ui/routes'; -import { UICapabilities } from '.'; +import { format as formatUrl } from 'url'; -uiRoutes.addSetupWork( - (uiCapabilities: UICapabilities, kbnBaseUrl: string, $route: any, kbnUrl: any) => { - const route = get($route, 'current.$$route') as any; - if (!route.requireUICapability) { - return; - } +import { Client } from '@elastic/elasticsearch'; - if (!get(uiCapabilities, route.requireUICapability)) { - const url = chrome.addBasePath(`${kbnBaseUrl}#/home`); - kbnUrl.redirect(url); - throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN; - } - } -); +import { FtrProviderContext } from '../ftr_provider_context'; + +export function ElasticsearchProvider({ getService }: FtrProviderContext) { + const config = getService('config'); + + return new Client({ + nodes: [formatUrl(config.get('servers.elasticsearch'))], + requestTimeout: config.get('timeouts.esRequestTimeout'), + }); +} diff --git a/test/common/services/es_archiver.ts b/test/common/services/es_archiver.ts index e72bb49a76c0d..cfe0610414b4f 100644 --- a/test/common/services/es_archiver.ts +++ b/test/common/services/es_archiver.ts @@ -26,7 +26,7 @@ import * as KibanaServer from './kibana_server'; export function EsArchiverProvider({ getService, hasService }: FtrProviderContext): EsArchiver { const config = getService('config'); - const client = getService('es'); + const client = getService('legacyEs'); const log = getService('log'); if (!config.get('esArchiver')) { diff --git a/test/common/services/index.ts b/test/common/services/index.ts index 225aacc1c9895..3454964f35e07 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -18,13 +18,15 @@ */ import { LegacyEsProvider } from './legacy_es'; +import { ElasticsearchProvider } from './elasticsearch'; import { EsArchiverProvider } from './es_archiver'; import { KibanaServerProvider } from './kibana_server'; import { RetryProvider } from './retry'; import { RandomnessProvider } from './randomness'; export const services = { - es: LegacyEsProvider, + legacyEs: LegacyEsProvider, + es: ElasticsearchProvider, esArchiver: EsArchiverProvider, kibanaServer: KibanaServerProvider, retry: RetryProvider, diff --git a/test/functional/apps/dashboard/embed_mode.js b/test/functional/apps/dashboard/embed_mode.js index 7122d9ff8ca25..9eb5b2c9352d8 100644 --- a/test/functional/apps/dashboard/embed_mode.js +++ b/test/functional/apps/dashboard/embed_mode.js @@ -25,6 +25,7 @@ export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['dashboard', 'common']); const browser = getService('browser'); + const globalNav = getService('globalNav'); describe('embed mode', () => { before(async () => { @@ -38,8 +39,8 @@ export default function ({ getService, getPageObjects }) { }); it('hides the chrome', async () => { - const isChromeVisible = await PageObjects.common.isChromeVisible(); - expect(isChromeVisible).to.be(true); + const globalNavShown = await globalNav.exists(); + expect(globalNavShown).to.be(true); const currentUrl = await browser.getCurrentUrl(); const newUrl = currentUrl + '&embed=true'; @@ -48,8 +49,8 @@ export default function ({ getService, getPageObjects }) { await browser.get(newUrl.toString(), useTimeStamp); await retry.try(async () => { - const isChromeHidden = await PageObjects.common.isChromeHidden(); - expect(isChromeHidden).to.be(true); + const globalNavHidden = !(await globalNav.exists()); + expect(globalNavHidden).to.be(true); }); }); diff --git a/test/functional/apps/management/_handle_alias.js b/test/functional/apps/management/_handle_alias.js index 1556897d69387..a5994fc5dd1b4 100644 --- a/test/functional/apps/management/_handle_alias.js +++ b/test/functional/apps/management/_handle_alias.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover', 'timePicker']); diff --git a/test/functional/apps/management/_handle_version_conflict.js b/test/functional/apps/management/_handle_version_conflict.js index 217e6d4c1a8d3..e6c0825136ba1 100644 --- a/test/functional/apps/management/_handle_version_conflict.js +++ b/test/functional/apps/management/_handle_version_conflict.js @@ -32,7 +32,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - const es = getService('es'); + const es = getService('legacyEs'); const retry = getService('retry'); const scriptedFiledName = 'versionConflictScript'; const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover', 'header']); diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index b30a0e50886d1..380c33e93ad90 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -38,6 +38,17 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { const coveragePrefix = 'coveragejson:'; const coverageDir = resolve(__dirname, '../../../../target/kibana-coverage/functional'); let logSubscription: undefined | Rx.Subscription; + type BrowserStorage = 'sessionStorage' | 'localStorage'; + + const clearBrowserStorage = async (storageType: BrowserStorage) => { + try { + await driver.executeScript(`window.${storageType}.clear();`); + } catch (error) { + if (!error.message.includes(`Failed to read the '${storageType}' property from 'Window'`)) { + throw error; + } + } + }; const { driver, By, until, consoleLog$ } = await initWebDriver( log, @@ -128,8 +139,8 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { .manage() .window() .setRect({ width, height }); - await driver.executeScript('window.sessionStorage.clear();'); - await driver.executeScript('window.localStorage.clear();'); + await clearBrowserStorage('sessionStorage'); + await clearBrowserStorage('localStorage'); }); lifecycle.on('cleanup', async () => { diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js index b0db26c0c6743..fe004b79ba45c 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js @@ -37,7 +37,6 @@ import 'uiExports/visRequestHandlers'; import 'uiExports/visEditorTypes'; import 'uiExports/visualize'; import 'uiExports/savedObjectTypes'; -import 'uiExports/fieldFormats'; import 'uiExports/search'; import { Main } from './components/main'; diff --git a/test/server_integration/config.js b/test/server_integration/config.js index 18575fcb5fcff..77aeaa8af3b9f 100644 --- a/test/server_integration/config.js +++ b/test/server_integration/config.js @@ -29,9 +29,7 @@ export default async function ({ readConfigFile }) { return { services: { - es: commonConfig.get('services.es'), - esArchiver: commonConfig.get('services.esArchiver'), - retry: commonConfig.get('services.retry'), + ...commonConfig.get('services'), supertest: KibanaSupertestProvider, supertestWithoutAuth: KibanaSupertestWithoutAuthProvider, esSupertest: ElasticsearchSupertestProvider, diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index fbdf496ebaec4..c260a754e4594 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -32,7 +32,7 @@ interface ConstructorOptions { createAPIKey: () => Promise; } -interface FindOptions { +export interface FindOptions { options?: { perPage?: number; page?: number; @@ -40,6 +40,7 @@ interface FindOptions { defaultSearchOperator?: 'AND' | 'OR'; searchFields?: string[]; sortField?: string; + sortOrder?: string; hasReference?: { type: string; id: string; diff --git a/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 0521270a7ba74..9d82cd6b5455c 100644 --- a/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -102,6 +102,8 @@ exports[`Error TRANSACTION_TYPE 1`] = `"request"`; exports[`Error URL_FULL 1`] = `undefined`; +exports[`Error USER_AGENT_NAME 1`] = `undefined`; + exports[`Error USER_ID 1`] = `undefined`; exports[`Span CLIENT_GEO_COUNTRY_ISO_CODE 1`] = `undefined`; @@ -206,6 +208,8 @@ exports[`Span TRANSACTION_TYPE 1`] = `undefined`; exports[`Span URL_FULL 1`] = `undefined`; +exports[`Span USER_AGENT_NAME 1`] = `undefined`; + exports[`Span USER_ID 1`] = `undefined`; exports[`Transaction CLIENT_GEO_COUNTRY_ISO_CODE 1`] = `undefined`; @@ -310,4 +314,6 @@ exports[`Transaction TRANSACTION_TYPE 1`] = `"transaction type"`; exports[`Transaction URL_FULL 1`] = `"http://www.elastic.co"`; +exports[`Transaction USER_AGENT_NAME 1`] = `"Other"`; + exports[`Transaction USER_ID 1`] = `"1337"`; diff --git a/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.test.ts b/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.test.ts index 52471e08b1b4d..82a679ccdd32e 100644 --- a/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.test.ts +++ b/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.test.ts @@ -34,6 +34,7 @@ describe('Transaction', () => { timestamp: { us: 1337 }, trace: { id: 'trace id' }, user: { id: '1337' }, + user_agent: { name: 'Other', original: 'test original' }, parent: { id: 'parentId' }, diff --git a/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts index 552c149ce6214..d0830337e0d35 100644 --- a/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts @@ -10,6 +10,7 @@ export const SERVICE_NODE_NAME = 'service.node.name'; export const URL_FULL = 'url.full'; export const HTTP_REQUEST_METHOD = 'http.request.method'; export const USER_ID = 'user.id'; +export const USER_AGENT_NAME = 'user_agent.name'; export const OBSERVER_VERSION_MAJOR = 'observer.version_major'; export const OBSERVER_LISTENING = 'observer.listening'; diff --git a/x-pack/legacy/plugins/apm/common/processor_event.ts b/x-pack/legacy/plugins/apm/common/processor_event.ts index a513f62092767..83dadfc21da90 100644 --- a/x-pack/legacy/plugins/apm/common/processor_event.ts +++ b/x-pack/legacy/plugins/apm/common/processor_event.ts @@ -4,4 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export type ProcessorEvent = 'transaction' | 'error' | 'metric'; +export enum ProcessorEvent { + transaction = 'transaction', + error = 'error', + metric = 'metric' +} diff --git a/x-pack/legacy/plugins/apm/common/transaction_types.ts b/x-pack/legacy/plugins/apm/common/transaction_types.ts index 4dd59af63047d..1226e926b1ee3 100644 --- a/x-pack/legacy/plugins/apm/common/transaction_types.ts +++ b/x-pack/legacy/plugins/apm/common/transaction_types.ts @@ -5,5 +5,5 @@ */ export const TRANSACTION_PAGE_LOAD = 'page-load'; -export const TRANSACTION_ROUTE_CHANGE = 'route-change'; export const TRANSACTION_REQUEST = 'request'; +export const TRANSACTION_ROUTE_CHANGE = 'route-change'; diff --git a/x-pack/legacy/plugins/apm/common/viz_colors.ts b/x-pack/legacy/plugins/apm/common/viz_colors.ts new file mode 100644 index 0000000000000..cc070005409b6 --- /dev/null +++ b/x-pack/legacy/plugins/apm/common/viz_colors.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 lightTheme from '@elastic/eui/dist/eui_theme_light.json'; + +function getVizColorsForTheme(theme = lightTheme) { + return [ + theme.euiColorVis0, + theme.euiColorVis1, + theme.euiColorVis2, + theme.euiColorVis3, + theme.euiColorVis4, + theme.euiColorVis5, + theme.euiColorVis6, + theme.euiColorVis7, + theme.euiColorVis8, + theme.euiColorVis9 + ]; +} + +export function getVizColorForIndex(index = 0, theme = lightTheme) { + const colors = getVizColorsForTheme(theme); + return colors[index % colors.length]; +} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx new file mode 100644 index 0000000000000..e95f733fb4bc8 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.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 React from 'react'; +import { shallow } from 'enzyme'; +import { BrowserLineChart } from './BrowserLineChart'; + +describe('BrowserLineChart', () => { + describe('render', () => { + it('renders', () => { + expect(() => shallow()).not.toThrowError(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.tsx new file mode 100644 index 0000000000000..58bc4655f730c --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiTitle } from '@elastic/eui'; +import { TransactionLineChart } from './TransactionLineChart'; +import { + getMaxY, + getResponseTimeTickFormatter, + getResponseTimeTooltipFormatter +} from '.'; +import { getDurationFormatter } from '../../../../utils/formatters'; +import { useAvgDurationByBrowser } from '../../../../hooks/useAvgDurationByBrowser'; + +export function BrowserLineChart() { + const { data } = useAvgDurationByBrowser(); + const maxY = getMaxY(data); + const formatter = getDurationFormatter(maxY); + const formatTooltipValue = getResponseTimeTooltipFormatter(formatter); + const tickFormatY = getResponseTimeTickFormatter(formatter); + + return ( + <> + + + {i18n.translate( + 'xpack.apm.metrics.pageLoadCharts.avgPageLoadByBrowser', + { + defaultMessage: 'Avg. page load duration distribution by browser' + } + )} + + + + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx index fabc4c695d86e..0761cec53fc2e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx @@ -4,33 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGrid, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; +import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { useAvgDurationByCountry } from '../../../../../hooks/useAvgDurationByCountry'; + import { ChoroplethMap } from '../ChoroplethMap'; export const DurationByCountryMap: React.FC = () => { const { data } = useAvgDurationByCountry(); return ( - - - - - - {i18n.translate( - 'xpack.apm.metrics.durationByCountryMap.avgPageLoadByCountryLabel', - { - defaultMessage: - 'Avg. page load duration distribution by country' - } - )} - - - - - - + <> + {' '} + + + {i18n.translate( + 'xpack.apm.metrics.durationByCountryMap.avgPageLoadByCountryLabel', + { + defaultMessage: 'Avg. page load duration distribution by country' + } + )} + + + + ); }; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index c032d60359903..97794bf66687b 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -33,6 +33,7 @@ import { MLJobLink } from '../../Links/MachineLearningLinks/MLJobLink'; import { LicenseContext } from '../../../../context/LicenseContext'; import { TransactionLineChart } from './TransactionLineChart'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; +import { BrowserLineChart } from './BrowserLineChart'; import { DurationByCountryMap } from './DurationByCountryMap'; import { TRANSACTION_PAGE_LOAD, @@ -59,31 +60,29 @@ const ShiftedEuiText = styled(EuiText)` top: 5px; `; -export class TransactionCharts extends Component { - public getMaxY = (responseTimeSeries: TimeSeries[]) => { - const coordinates = flatten( - responseTimeSeries.map((serie: TimeSeries) => serie.data as Coordinate[]) - ); - - const numbers: number[] = coordinates.map((c: Coordinate) => - c.y ? c.y : 0 - ); +export function getResponseTimeTickFormatter(formatter: TimeFormatter) { + return (t: number) => formatter(t).formatted; +} - return Math.max(...numbers, 0); +export function getResponseTimeTooltipFormatter(formatter: TimeFormatter) { + return (p: Coordinate) => { + return isValidCoordinateValue(p.y) + ? formatter(p.y).formatted + : NOT_AVAILABLE_LABEL; }; +} - public getResponseTimeTickFormatter = (formatter: TimeFormatter) => { - return (t: number) => formatter(t).formatted; - }; +export function getMaxY(responseTimeSeries: TimeSeries[]) { + const coordinates = flatten( + responseTimeSeries.map((serie: TimeSeries) => serie.data as Coordinate[]) + ); - public getResponseTimeTooltipFormatter = (formatter: TimeFormatter) => { - return (p: Coordinate) => { - return isValidCoordinateValue(p.y) - ? formatter(p.y).formatted - : NOT_AVAILABLE_LABEL; - }; - }; + const numbers: number[] = coordinates.map((c: Coordinate) => (c.y ? c.y : 0)); + return Math.max(...numbers, 0); +} + +export class TransactionCharts extends Component { public getTPMFormatter = (t: number) => { const { urlParams } = this.props; const unit = tpmUnit(urlParams.transactionType); @@ -154,7 +153,7 @@ export class TransactionCharts extends Component { const { charts, urlParams } = this.props; const { responseTimeSeries, tpmSeries } = charts; const { transactionType } = urlParams; - const maxY = this.getMaxY(responseTimeSeries); + const maxY = getMaxY(responseTimeSeries); const formatter = getDurationFormatter(maxY); return ( @@ -177,8 +176,8 @@ export class TransactionCharts extends Component { @@ -205,7 +204,18 @@ export class TransactionCharts extends Component { {transactionType === TRANSACTION_PAGE_LOAD && ( <> - + + + + + + + + + + + + )} diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts index 5fa9294a95dfd..1806e7395a8cc 100644 --- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts +++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts @@ -83,24 +83,24 @@ export function getPathParams(pathname: string = ''): PathParams { switch (servicePageName) { case 'transactions': return { - processorEvent: 'transaction', + processorEvent: ProcessorEvent.transaction, serviceName }; case 'errors': return { - processorEvent: 'error', + processorEvent: ProcessorEvent.error, serviceName, errorGroupId: paths[3] }; case 'metrics': return { - processorEvent: 'metric', + processorEvent: ProcessorEvent.metric, serviceName, serviceNodeName }; case 'nodes': return { - processorEvent: 'metric', + processorEvent: ProcessorEvent.metric, serviceName }; case 'service-map': @@ -113,7 +113,7 @@ export function getPathParams(pathname: string = ''): PathParams { case 'traces': return { - processorEvent: 'transaction' + processorEvent: ProcessorEvent.transaction }; default: return {}; diff --git a/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts new file mode 100644 index 0000000000000..38f26c2ba9fbd --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts @@ -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 { renderHook } from 'react-hooks-testing-library'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import * as useFetcherModule from './useFetcher'; +import { useAvgDurationByBrowser } from './useAvgDurationByBrowser'; + +describe('useAvgDurationByBrowser', () => { + it('returns data', () => { + const data = [ + { title: 'Other', data: [{ x: 1572530100000, y: 130010.8947368421 }] } + ]; + jest.spyOn(useFetcherModule, 'useFetcher').mockReturnValueOnce({ + data, + refetch: () => {}, + status: 'success' as useFetcherModule.FETCH_STATUS + }); + const { result } = renderHook(() => useAvgDurationByBrowser()); + + expect(result.current.data).toEqual([ + { + color: theme.euiColorVis0, + data: [{ x: 1572530100000, y: 130010.8947368421 }], + title: 'Other', + type: 'linemark' + } + ]); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.ts b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.ts new file mode 100644 index 0000000000000..a1e9294455d54 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.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 theme from '@elastic/eui/dist/eui_theme_light.json'; +import { useFetcher } from './useFetcher'; +import { useUrlParams } from './useUrlParams'; +import { AvgDurationByBrowserAPIResponse } from '../../server/lib/transactions/avg_duration_by_browser'; +import { TimeSeries } from '../../typings/timeseries'; +import { getVizColorForIndex } from '../../common/viz_colors'; + +function toTimeSeries(data?: AvgDurationByBrowserAPIResponse): TimeSeries[] { + if (!data) { + return []; + } + + return data.map((item, index) => { + return { + ...item, + color: getVizColorForIndex(index, theme), + type: 'linemark' + }; + }); +} + +export function useAvgDurationByBrowser() { + const { + urlParams: { serviceName, start, end, transactionName }, + uiFilters + } = useUrlParams(); + + const { data, error, status } = useFetcher( + callApmApi => { + if (serviceName && start && end) { + return callApmApi({ + pathname: + '/api/apm/services/{serviceName}/transaction_groups/avg_duration_by_browser', + params: { + path: { serviceName }, + query: { + start, + end, + transactionName, + uiFilters: JSON.stringify(uiFilters) + } + } + }); + } + }, + [serviceName, start, end, transactionName, uiFilters] + ); + + return { + data: toTimeSeries(data), + status, + error + }; +} diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts index 180537d68a2a2..8cff6e5d3aa80 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts @@ -23,16 +23,7 @@ import { METRIC_JAVA_GC_TIME } from '../../../../../../common/elasticsearch_fieldnames'; import { getBucketSize } from '../../../../helpers/get_bucket_size'; - -const colors = [ - theme.euiColorVis0, - theme.euiColorVis1, - theme.euiColorVis2, - theme.euiColorVis3, - theme.euiColorVis4, - theme.euiColorVis5, - theme.euiColorVis6 -]; +import { getVizColorForIndex } from '../../../../../../common/viz_colors'; export async function fetchAndTransformGcMetrics({ setup, @@ -148,7 +139,7 @@ export async function fetchAndTransformGcMetrics({ title: label, key: label, type: chartBase.type, - color: colors[i], + color: getVizColorForIndex(i, theme), overallValue, data }; diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts index 1e7f197435a67..03f21e4f26e7b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts @@ -11,16 +11,7 @@ import { ESSearchRequest } from '../../../typings/elasticsearch'; import { AggregationOptionsByType } from '../../../typings/elasticsearch/aggregations'; - -const colors = [ - theme.euiColorVis0, - theme.euiColorVis1, - theme.euiColorVis2, - theme.euiColorVis3, - theme.euiColorVis4, - theme.euiColorVis5, - theme.euiColorVis6 -]; +import { getVizColorForIndex } from '../../../common/viz_colors'; export type GenericMetricsChart = ReturnType< typeof transformDataToMetricsChart @@ -66,7 +57,8 @@ export function transformDataToMetricsChart( title: chartBase.series[seriesKey].title, key: seriesKey, type: chartBase.type, - color: chartBase.series[seriesKey].color || colors[i], + color: + chartBase.series[seriesKey].color || getVizColorForIndex(i, theme), overallValue, data: timeseriesData?.buckets.map(bucket => { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/__fixtures__/responses.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/__fixtures__/responses.ts new file mode 100644 index 0000000000000..3f0f8a84dc62f --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/__fixtures__/responses.ts @@ -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 { + ESSearchResponse, + ESSearchRequest +} from '../../../../../typings/elasticsearch'; + +export const response = ({ + hits: { + total: 599, + max_score: 0, + hits: [] + }, + took: 4, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0 + }, + aggregations: { + user_agent_keys: { + buckets: [{ key: 'Firefox' }, { key: 'Other' }] + }, + browsers: { + buckets: [ + { + key_as_string: '2019-10-21T04:38:20.000-05:00', + key: 1571650700000, + doc_count: 0, + user_agent: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [] + } + }, + { + key_as_string: '2019-10-21T04:40:00.000-05:00', + key: 1571650800000, + doc_count: 1, + user_agent: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'Other', + doc_count: 1, + avg_duration: { + value: 860425.0 + } + }, + { + key: 'Firefox', + doc_count: 10, + avg_duration: { + value: 86425.1 + } + } + ] + } + } + ] + } + } +} as unknown) as ESSearchResponse< + unknown, + ESSearchRequest, + { restTotalHitsAsInt: false } +>; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts new file mode 100644 index 0000000000000..f2227524db081 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts @@ -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 { Setup } from '../../helpers/setup_request'; +import { fetcher } from './fetcher'; + +describe('fetcher', () => { + it('performs a search', async () => { + const search = jest.fn(); + const setup = ({ + client: { search }, + indices: {}, + uiFiltersES: [] + } as unknown) as Setup; + + await fetcher({ serviceName: 'testServiceName', setup }); + + expect(search).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts new file mode 100644 index 0000000000000..8a96a25aef50e --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESFilter } from '../../../../typings/elasticsearch'; +import { PromiseReturnType } from '../../../../typings/common'; +import { + PROCESSOR_EVENT, + SERVICE_NAME, + TRANSACTION_TYPE, + USER_AGENT_NAME, + TRANSACTION_DURATION +} from '../../../../common/elasticsearch_fieldnames'; +import { rangeFilter } from '../../helpers/range_filter'; +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { Options } from '.'; +import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; +import { ProcessorEvent } from '../../../../common/processor_event'; + +export type ESResponse = PromiseReturnType; + +export function fetcher(options: Options) { + const { end, client, indices, start, uiFiltersES } = options.setup; + const { serviceName } = options; + const { intervalString } = getBucketSize(start, end, 'auto'); + + const filter: ESFilter[] = [ + { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, + { range: rangeFilter(start, end) }, + ...uiFiltersES + ]; + + const params = { + index: indices['apm_oss.transactionIndices'], + body: { + size: 0, + query: { bool: { filter } }, + aggs: { + user_agent_keys: { + terms: { + field: USER_AGENT_NAME + } + }, + browsers: { + date_histogram: { + extended_bounds: { + max: end, + min: start + }, + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0 + }, + aggs: { + user_agent: { + terms: { + field: USER_AGENT_NAME + }, + aggs: { + avg_duration: { + avg: { + field: TRANSACTION_DURATION + } + } + } + } + } + } + } + } + }; + + return client.search(params); +} diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.test.ts new file mode 100644 index 0000000000000..fe103ade24161 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.test.ts @@ -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 { + getTransactionAvgDurationByBrowser, + Options, + AvgDurationByBrowserAPIResponse +} from '.'; +import * as transformerModule from './transformer'; +import * as fetcherModule from './fetcher'; +import { response } from './__fixtures__/responses'; + +describe('getAvgDurationByBrowser', () => { + it('returns a transformed response', async () => { + const transformer = jest + .spyOn(transformerModule, 'transformer') + .mockReturnValueOnce(({} as unknown) as AvgDurationByBrowserAPIResponse); + const search = () => {}; + const options = ({ + setup: { client: { search }, indices: {}, uiFiltersES: [] } + } as unknown) as Options; + jest + .spyOn<{ fetcher: any }, 'fetcher'>(fetcherModule, 'fetcher') + .mockResolvedValueOnce(response); + + await getTransactionAvgDurationByBrowser(options); + + expect(transformer).toHaveBeenCalledWith({ response }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts new file mode 100644 index 0000000000000..57b3c8cbe9f93 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Coordinate } from '../../../../typings/timeseries'; +import { Setup } from '../../helpers/setup_request'; +import { fetcher } from './fetcher'; +import { transformer } from './transformer'; + +export interface Options { + serviceName: string; + setup: Setup; +} + +export type AvgDurationByBrowserAPIResponse = Array<{ + data: Coordinate[]; + title: string; +}>; + +export async function getTransactionAvgDurationByBrowser(options: Options) { + return transformer({ response: await fetcher(options) }); +} diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.test.ts new file mode 100644 index 0000000000000..5caec12c81d5d --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { transformer } from './transformer'; +import { response } from './__fixtures__/responses'; + +describe('transformer', () => { + it('transforms', () => { + expect(transformer({ response })).toEqual([ + { + data: [ + { x: 1571650700000, y: undefined }, + { x: 1571650800000, y: 86425.1 } + ], + title: 'Firefox' + }, + { + data: [ + { x: 1571650700000, y: undefined }, + { x: 1571650800000, y: 860425.0 } + ], + title: 'Other' + } + ]); + }); +}); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts new file mode 100644 index 0000000000000..805f8f192bdb1 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESResponse } from './fetcher'; +import { AvgDurationByBrowserAPIResponse } from '.'; +import { Coordinate } from '../../../../typings/timeseries'; + +export function transformer({ + response +}: { + response: ESResponse; +}): AvgDurationByBrowserAPIResponse { + const allUserAgentKeys = new Set( + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + (response.aggregations?.user_agent_keys?.buckets ?? []).map(({ key }) => + key.toString() + ) + ); + const buckets = response.aggregations?.browsers?.buckets ?? []; + + const series = buckets.reduce<{ [key: string]: Coordinate[] }>( + (acc, next) => { + const userAgentBuckets = next.user_agent?.buckets ?? []; + const x = next.key; + const seenUserAgentKeys = new Set(); + + userAgentBuckets.map(userAgentBucket => { + const key = userAgentBucket.key; + const y = userAgentBucket.avg_duration?.value; + + seenUserAgentKeys.add(key.toString()); + acc[key] = (acc[key] || []).concat({ x, y }); + }); + + const emptyUserAgents = new Set( + [...allUserAgentKeys].filter(key => !seenUserAgentKeys.has(key)) + ); + + // If no user agent requests exist for this bucked, fill in the data with + // undefined + [...emptyUserAgents].map(key => { + acc[key] = (acc[key] || []).concat({ x, y: undefined }); + }); + + return acc; + }, + {} + ); + + return Object.entries(series).map(([title, data]) => ({ title, data })); +} diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/constants.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/constants.ts index 0e288de1e4600..dcf6e8e07c45b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/constants.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/constants.ts @@ -3,19 +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. */ -import theme from '@elastic/eui/dist/eui_theme_light.json'; export const MAX_KPIS = 20; - -export const COLORS = [ - theme.euiColorVis0, - theme.euiColorVis1, - theme.euiColorVis2, - theme.euiColorVis3, - theme.euiColorVis4, - theme.euiColorVis5, - theme.euiColorVis6, - theme.euiColorVis7, - theme.euiColorVis8, - theme.euiColorVis9 -]; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts index 3166938090d8f..12f6694116950 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -18,7 +18,8 @@ import { import { Setup } from '../../helpers/setup_request'; import { rangeFilter } from '../../helpers/range_filter'; import { getMetricsDateHistogramParams } from '../../helpers/metrics'; -import { MAX_KPIS, COLORS } from './constants'; +import { MAX_KPIS } from './constants'; +import { getVizColorForIndex } from '../../../../common/viz_colors'; export async function getTransactionBreakdown({ setup, @@ -142,7 +143,7 @@ export async function getTransactionBreakdown({ const kpis = sortByOrder(visibleKpis, 'name').map((kpi, index) => { return { ...kpi, - color: COLORS[index % COLORS.length] + color: getVizColorForIndex(index) }; }); diff --git a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts index c35b66b453634..1735aa9da7dca 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts @@ -42,7 +42,8 @@ import { transactionGroupsChartsRoute, transactionGroupsDistributionRoute, transactionGroupsRoute, - transactionGroupsAvgDurationByCountry + transactionGroupsAvgDurationByCountry, + transactionGroupsAvgDurationByBrowser } from './transaction_groups'; import { errorGroupsLocalFiltersRoute, @@ -102,6 +103,7 @@ const createApmApi = () => { .add(transactionGroupsChartsRoute) .add(transactionGroupsDistributionRoute) .add(transactionGroupsRoute) + .add(transactionGroupsAvgDurationByBrowser) .add(transactionGroupsAvgDurationByCountry) // UI filters diff --git a/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts b/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts index 0b5c29fc29857..269f5fee9738c 100644 --- a/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts +++ b/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts @@ -12,6 +12,7 @@ import { getTransactionBreakdown } from '../lib/transactions/breakdown'; import { getTransactionGroupList } from '../lib/transaction_groups'; import { createRoute } from './create_route'; import { uiFiltersRt, rangeRt } from './default_api_types'; +import { getTransactionAvgDurationByBrowser } from '../lib/transactions/avg_duration_by_browser'; import { getTransactionAvgDurationByCountry } from '../lib/transactions/avg_duration_by_country'; export const transactionGroupsRoute = createRoute(() => ({ @@ -144,6 +145,32 @@ export const transactionGroupsBreakdownRoute = createRoute(() => ({ } })); +export const transactionGroupsAvgDurationByBrowser = createRoute(() => ({ + path: `/api/apm/services/{serviceName}/transaction_groups/avg_duration_by_browser`, + params: { + path: t.type({ + serviceName: t.string + }), + query: t.intersection([ + t.partial({ + transactionType: t.string, + transactionName: t.string + }), + uiFiltersRt, + rangeRt + ]) + }, + handler: async (req, { path }) => { + const setup = await setupRequest(req); + const { serviceName } = path; + + return getTransactionAvgDurationByBrowser({ + serviceName, + setup + }); + } +})); + export const transactionGroupsAvgDurationByCountry = createRoute(() => ({ path: `/api/apm/services/{serviceName}/transaction_groups/avg_duration_by_country`, params: { diff --git a/x-pack/legacy/plugins/canvas/public/app.js b/x-pack/legacy/plugins/canvas/public/app.js index 0ba7385cf7a9e..9faa92a02a9ef 100644 --- a/x-pack/legacy/plugins/canvas/public/app.js +++ b/x-pack/legacy/plugins/canvas/public/app.js @@ -19,7 +19,6 @@ import 'uiExports/visRequestHandlers'; import 'uiExports/visEditorTypes'; import 'uiExports/savedObjectTypes'; import 'uiExports/spyModes'; -import 'uiExports/fieldFormats'; import 'uiExports/embeddableFactories'; import 'uiExports/interpreter'; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx b/x-pack/legacy/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx index 9cf2ddc3a22e3..2ec3cfde8bd68 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx @@ -19,8 +19,6 @@ import { getScrubber as scrubber, getScrubberSlideContainer as scrubberContainer, getPageControlsCenter as center, - getSettingsTrigger as trigger, - getContextMenuItems as menuItems, // getAutoplayTextField as autoplayText, // getAutoplayCheckbox as autoplayCheck, // getAutoplaySubmit as autoplaySubmit, @@ -30,6 +28,7 @@ import { getPageControlsPrevious as previous, getPageControlsNext as next, } from '../../test/selectors'; +import { openSettings, selectMenuItem } from '../../test/interactions'; // Mock the renderers jest.mock('../../supported_renderers'); @@ -102,13 +101,9 @@ describe('', () => { test('autohide footer functions on mouseEnter + Leave', async () => { const wrapper = getWrapper(); - trigger(wrapper).simulate('click'); - await tick(20); - menuItems(wrapper) - .at(1) - .simulate('click'); - await tick(20); - wrapper.update(); + await openSettings(wrapper); + await selectMenuItem(wrapper, 1); + expect(footer(wrapper).prop('isHidden')).toEqual(false); expect(footer(wrapper).prop('isAutohide')).toEqual(false); toolbarCheck(wrapper).simulate('click'); @@ -125,13 +120,9 @@ describe('', () => { expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(true); // Open the menu and activate toolbar hiding. - trigger(wrapper).simulate('click'); - await tick(20); - menuItems(wrapper) - .at(1) - .simulate('click'); - await tick(20); - wrapper.update(); + await openSettings(wrapper); + await selectMenuItem(wrapper, 1); + toolbarCheck(wrapper).simulate('click'); await tick(20); diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap index 670f31f018f71..367041dd1d5db 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap @@ -56,26 +56,35 @@ exports[` can navigate Autoplay Settings 1`] = ` class="euiContextMenu__itemLayout" > + > + + Auto Play + > + +
@@ -498,26 +516,35 @@ exports[` can navigate Toolbar Settings, closes when activated 1`] = class="euiContextMenu__itemLayout" > + > + + Auto Play + > + +
@@ -566,6 +602,257 @@ exports[` can navigate Toolbar Settings, closes when activated 1`] =
`; -exports[` can navigate Toolbar Settings, closes when activated 2`] = `"
Settings

Hide Toolbar

Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 2`] = ` +
+
+
+
+
+ +
+
+
+`; exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings

Hide Toolbar

Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx index 0667674b6a7dd..66515eb3421d5 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx @@ -7,7 +7,8 @@ import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; import { JestContext } from '../../../../test/context_jest'; -import { takeMountedSnapshot, tick } from '../../../../test'; +import { takeMountedSnapshot } from '../../../../test'; +import { openSettings, selectMenuItem } from '../../../../test/interactions'; import { getSettingsTrigger as trigger, getPopover as popover, @@ -60,36 +61,26 @@ describe('', () => { expect(popover(wrapper).prop('isOpen')).toEqual(false); }); - test.skip('can navigate Autoplay Settings', async () => { - trigger(wrapper).simulate('click'); + test('can navigate Autoplay Settings', async () => { + await openSettings(wrapper); expect(takeMountedSnapshot(portal(wrapper))).toMatchSnapshot(); - await tick(20); - menuItems(wrapper) - .at(0) - .simulate('click'); - await tick(20); + + await selectMenuItem(wrapper, 0); expect(takeMountedSnapshot(portal(wrapper))).toMatchSnapshot(); }); - test.skip('can navigate Toolbar Settings, closes when activated', async () => { - trigger(wrapper).simulate('click'); + test('can navigate Toolbar Settings, closes when activated', async () => { + await openSettings(wrapper); expect(takeMountedSnapshot(portal(wrapper))).toMatchSnapshot(); - menuItems(wrapper) - .at(1) - .simulate('click'); - // Wait for the animation and DOM update - await tick(40); - portal(wrapper).update(); - expect(portal(wrapper).html()).toMatchSnapshot(); + await selectMenuItem(wrapper, 1); + expect(takeMountedSnapshot(portal(wrapper))).toMatchSnapshot(); // Click the Hide Toolbar switch portal(wrapper) .find('button[data-test-subj="hideToolbarSwitch"]') .simulate('click'); - // Wait for the animation and DOM update - await tick(20); portal(wrapper).update(); // The Portal should not be open. diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/test/interactions.ts b/x-pack/legacy/plugins/canvas/shareable_runtime/test/interactions.ts new file mode 100644 index 0000000000000..1c5b78929aaa5 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/test/interactions.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ReactWrapper } from 'enzyme'; +import { getSettingsTrigger, getPortal, getContextMenuItems } from './selectors'; +import { waitFor } from './utils'; + +export const openSettings = async function(wrapper: ReactWrapper) { + getSettingsTrigger(wrapper).simulate('click'); + + try { + // Wait for EuiPanel to be visible + await waitFor(() => { + wrapper.update(); + + return getPortal(wrapper) + .find('EuiPanel') + .exists(); + }); + } catch (e) { + throw new Error('Settings Panel did not open in given time'); + } +}; + +export const selectMenuItem = async function(wrapper: ReactWrapper, menuItemIndex: number) { + getContextMenuItems(wrapper) + .at(menuItemIndex) + .simulate('click'); + + try { + // When the menu item is clicked, wait for all of the context menus to be there + await waitFor(() => { + wrapper.update(); + return getPortal(wrapper).find('EuiContextMenuPanel').length === 2; + }); + } catch (e) { + throw new Error('Context menu did not transition'); + } +}; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/test/utils.ts b/x-pack/legacy/plugins/canvas/shareable_runtime/test/utils.ts index 2e7bc4b262b52..4e18f2af1b06a 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/test/utils.ts +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/test/utils.ts @@ -6,7 +6,6 @@ import { ReactWrapper } from 'enzyme'; import { Component } from 'react'; -import { setTimeout } from 'timers'; export const tick = (ms = 0) => new Promise(resolve => { @@ -19,3 +18,25 @@ export const takeMountedSnapshot = (mountedComponent: ReactWrapper<{}, {}, Compo template.innerHTML = html; return template.content.firstChild; }; + +export const waitFor = (fn: () => boolean, stepMs = 100, failAfterMs = 1000) => { + return new Promise((resolve, reject) => { + let waitForTimeout: NodeJS.Timeout; + + const tryCondition = () => { + if (fn()) { + clearTimeout(failTimeout); + resolve(); + } else { + waitForTimeout = setTimeout(tryCondition, stepMs); + } + }; + + const failTimeout = setTimeout(() => { + clearTimeout(waitForTimeout); + reject('wait for condition was never met'); + }, failAfterMs); + + tryCondition(); + }); +}; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 5f7ac218e1b98..8093c57d2631a 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -26,7 +26,6 @@ import 'uiExports/embeddableActions'; import 'uiExports/embeddableFactories'; import 'uiExports/navbarExtensions'; import 'uiExports/docViews'; -import 'uiExports/fieldFormats'; import 'uiExports/search'; import 'uiExports/shareContextMenuExtensions'; import _ from 'lodash'; @@ -38,6 +37,8 @@ import 'ui/agg_response'; import 'ui/agg_types'; import 'leaflet'; import { npStart } from 'ui/new_platform'; +import { localApplicationService } from 'plugins/kibana/local_application_service'; + import { showAppRedirectNotification } from 'ui/notify'; import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard/dashboard_constants'; @@ -45,6 +46,8 @@ import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashb uiModules.get('kibana') .config(dashboardConfigProvider => dashboardConfigProvider.turnHideWriteControlsOn()); +localApplicationService.attachToAngular(routes); + routes.enable(); routes.otherwise({ redirectTo: defaultUrl() }); diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts index 48420d403653f..988aa78695095 100644 --- a/x-pack/legacy/plugins/graph/public/index.ts +++ b/x-pack/legacy/plugins/graph/public/index.ts @@ -6,7 +6,6 @@ // legacy imports currently necessary to power Graph // for a cutover all of these have to be resolved -import 'uiExports/fieldFormats'; import 'uiExports/savedObjectTypes'; import 'uiExports/autocompleteProviders'; import 'ui/autoload/all'; @@ -20,6 +19,7 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis import { npSetup, npStart } from 'ui/new_platform'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; +import { start as navigation } from '../../../../../src/legacy/core_plugins/navigation/public/legacy'; import { GraphPlugin } from './plugin'; // @ts-ignore @@ -53,6 +53,7 @@ async function getAngularInjectedDependencies(): Promise; + navigation: NavigationStart; } export interface GraphPluginSetupDependencies { @@ -30,6 +32,7 @@ export interface GraphPluginStartDependencies { export class GraphPlugin implements Plugin { private dataStart: DataStart | null = null; + private navigationStart: NavigationStart | null = null; private npDataStart: ReturnType | null = null; private savedObjectsClient: SavedObjectsClientContract | null = null; private angularDependencies: LegacyAngularInjectedDependencies | null = null; @@ -42,6 +45,7 @@ export class GraphPlugin implements Plugin { const { renderApp } = await import('./render_app'); return renderApp({ ...params, + navigation: this.navigationStart!, npData: this.npDataStart!, savedObjectsClient: this.savedObjectsClient!, xpackInfo, @@ -66,9 +70,9 @@ export class GraphPlugin implements Plugin { start( core: CoreStart, - { data, npData, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies + { data, npData, navigation, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies ) { - // TODO is this really the right way? I though the app context would give us those + this.navigationStart = navigation; this.dataStart = data; this.npDataStart = npData; this.angularDependencies = angularDependencies; diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/render_app.ts index a8a86f4d1f850..18cdf0ddd81b2 100644 --- a/x-pack/legacy/plugins/graph/public/render_app.ts +++ b/x-pack/legacy/plugins/graph/public/render_app.ts @@ -25,6 +25,7 @@ import { DataStart } from 'src/legacy/core_plugins/data/public'; import { AppMountContext, ChromeStart, + LegacyCoreStart, SavedObjectsClientContract, ToastsStart, UiSettingsClientContract, @@ -32,6 +33,7 @@ import { // @ts-ignore import { initGraphApp } from './app'; import { Plugin as DataPlugin } from '../../../../../src/plugins/data/public'; +import { NavigationStart } from '../../../../../src/legacy/core_plugins/navigation/public'; /** * These are dependencies of the Graph app besides the base dependencies @@ -44,6 +46,7 @@ export interface GraphDependencies extends LegacyAngularInjectedDependencies { appBasePath: string; capabilities: Record>; coreStart: AppMountContext['core']; + navigation: NavigationStart; chrome: ChromeStart; config: UiSettingsClientContract; toastNotifications: ToastsStart; @@ -75,8 +78,8 @@ export interface LegacyAngularInjectedDependencies { } export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { - const graphAngularModule = createLocalAngularModule(deps.coreStart); - configureAppAngularModule(graphAngularModule); + const graphAngularModule = createLocalAngularModule(deps.navigation); + configureAppAngularModule(graphAngularModule, deps.coreStart as LegacyCoreStart, true); initGraphApp(graphAngularModule, deps); const $injector = mountGraphApp(appBasePath, element); return () => $injector.get('$rootScope').$destroy(); @@ -104,9 +107,9 @@ function mountGraphApp(appBasePath: string, element: HTMLElement) { return $injector; } -function createLocalAngularModule(core: AppMountContext['core']) { +function createLocalAngularModule(navigation: NavigationStart) { createLocalI18nModule(); - createLocalTopNavModule(); + createLocalTopNavModule(navigation); createLocalConfirmModalModule(); const graphAngularModule = angular.module(moduleName, [ @@ -125,11 +128,11 @@ function createLocalConfirmModalModule() { .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); } -function createLocalTopNavModule() { +function createLocalTopNavModule(navigation: NavigationStart) { angular .module('graphTopNav', ['react']) .directive('kbnTopNav', createTopNavDirective) - .directive('kbnTopNavHelper', createTopNavHelper); + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); } function createLocalI18nModule() { diff --git a/x-pack/legacy/plugins/lens/common/constants.ts b/x-pack/legacy/plugins/lens/common/constants.ts index 787a348a788b8..c2eed1940fa1a 100644 --- a/x-pack/legacy/plugins/lens/common/constants.ts +++ b/x-pack/legacy/plugins/lens/common/constants.ts @@ -6,9 +6,13 @@ export const PLUGIN_ID = 'lens'; -export const BASE_APP_URL = '/app/lens'; +export const BASE_APP_URL = '/app/kibana'; export const BASE_API_URL = '/api/lens'; +export function getBasePath() { + return `${BASE_APP_URL}#/lens`; +} + export function getEditPath(id: string) { - return `${BASE_APP_URL}#/edit/${encodeURIComponent(id)}`; + return `${BASE_APP_URL}#/lens/edit/${encodeURIComponent(id)}`; } diff --git a/x-pack/legacy/plugins/lens/index.ts b/x-pack/legacy/plugins/lens/index.ts index 20f92ebbe0654..d4cea28d14085 100644 --- a/x-pack/legacy/plugins/lens/index.ts +++ b/x-pack/legacy/plugins/lens/index.ts @@ -12,7 +12,7 @@ import mappings from './mappings.json'; import { PLUGIN_ID, getEditPath } from './common'; import { lensServerPlugin } from './server'; -const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; +export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; export const lens: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ @@ -26,10 +26,11 @@ export const lens: LegacyPluginInitializer = kibana => { app: { title: NOT_INTERNATIONALIZED_PRODUCT_NAME, description: 'Explore and visualize data.', - main: `plugins/${PLUGIN_ID}/index`, + main: `plugins/${PLUGIN_ID}/redirect`, listed: false, }, - embeddableFactories: ['plugins/lens/register_embeddable'], + visualize: [`plugins/${PLUGIN_ID}/legacy`], + embeddableFactories: [`plugins/${PLUGIN_ID}/legacy`], styleSheetPaths: resolve(__dirname, 'public/index.scss'), mappings, visTypes: ['plugins/lens/register_vis_type_alias'], diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx index 3fcb6609f28f1..ce05af46ade66 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx @@ -99,6 +99,12 @@ describe('Lens App', () => { data: { query: { filterManager: createMockFilterManager(), + timefilter: { + timefilter: { + getTime: jest.fn(() => ({ from: 'now-7d', to: 'now' })), + setTime: jest.fn(), + }, + }, }, }, dataShim: { @@ -109,7 +115,6 @@ describe('Lens App', () => { }), }, }, - timefilter: { history: {} }, }, storage: { get: jest.fn(), diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index fc5088c1271ad..553b98643f8a5 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -62,33 +62,35 @@ export function App({ docStorage: SavedObjectStore; redirectTo: (id?: string) => void; }) { - const timeDefaults = core.uiSettings.get('timepicker:timeDefaults'); const language = storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage'); - const [state, setState] = useState({ - isLoading: !!docId, - isSaveModalVisible: false, - indexPatternsForTopNav: [], - query: { query: '', language }, - dateRange: { - fromDate: timeDefaults.from, - toDate: timeDefaults.to, - }, - filters: [], + const [state, setState] = useState(() => { + const currentRange = data.query.timefilter.timefilter.getTime(); + return { + isLoading: !!docId, + isSaveModalVisible: false, + indexPatternsForTopNav: [], + query: { query: '', language }, + dateRange: { + fromDate: currentRange.from, + toDate: currentRange.to, + }, + filters: [], + }; }); const { lastKnownDoc } = state; useEffect(() => { - const subscription = data.query.filterManager.getUpdates$().subscribe({ + const filterSubscription = data.query.filterManager.getUpdates$().subscribe({ next: () => { setState(s => ({ ...s, filters: data.query.filterManager.getFilters() })); trackUiEvent('app_filters_updated'); }, }); return () => { - subscription.unsubscribe(); + filterSubscription.unsubscribe(); }; }, []); @@ -199,6 +201,7 @@ export function App({ dateRange.from !== state.dateRange.fromDate || dateRange.to !== state.dateRange.toDate ) { + data.query.timefilter.timefilter.setTime(dateRange); trackUiEvent('app_date_change'); } else { trackUiEvent('app_query_change'); diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index 56c19ea2bb9f2..60a375f696f7b 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -4,18 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import 'ui/autoload/all'; +// Used to run esaggs queries +import 'uiExports/fieldFormats'; +import 'uiExports/search'; +import 'uiExports/visRequestHandlers'; +import 'uiExports/visResponseHandlers'; +// Used for kibana_context function +import 'uiExports/savedObjectTypes'; + import React from 'react'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; import { HashRouter, Switch, Route, RouteComponentProps } from 'react-router-dom'; +import { render, unmountComponentAtNode } from 'react-dom'; import chrome from 'ui/chrome'; import { CoreSetup, CoreStart } from 'src/core/public'; -import { npSetup, npStart } from 'ui/new_platform'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { DataStart } from '../../../../../../src/legacy/core_plugins/data/public'; -import { start as dataShimStart } from '../../../../../../src/legacy/core_plugins/data/public/legacy'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { editorFrameSetup, editorFrameStart, editorFrameStop } from '../editor_frame_plugin'; import { indexPatternDatasourceSetup, indexPatternDatasourceStop } from '../indexpattern_plugin'; +import { addHelpMenuToAppChrome } from '../help_menu_util'; import { SavedObjectIndexStore } from '../persistence'; import { xyVisualizationSetup, xyVisualizationStop } from '../xy_visualization_plugin'; import { metricVisualizationSetup, metricVisualizationStop } from '../metric_visualization_plugin'; @@ -31,10 +40,15 @@ import { stopReportManager, trackUiEvent, } from '../lens_ui_telemetry'; +import { LocalApplicationService } from '../../../../../../src/legacy/core_plugins/kibana/public/local_application_service'; +import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../index'; export interface LensPluginStartDependencies { data: DataPublicPluginStart; dataShim: DataStart; + __LEGACY: { + localApplicationService: LocalApplicationService; + }; } export class AppPlugin { private instance: EditorFrameInstance | null = null; @@ -58,11 +72,16 @@ export class AppPlugin { editorFrameSetupInterface.registerDatasource('indexpattern', indexPattern); } - start(core: CoreStart, { data, dataShim }: LensPluginStartDependencies) { + start( + core: CoreStart, + { data, dataShim, __LEGACY: { localApplicationService } }: LensPluginStartDependencies + ) { if (this.store === null) { throw new Error('Start lifecycle called before setup lifecycle'); } + addHelpMenuToAppChrome(core.chrome); + const store = this.store; const editorFrameStartInterface = editorFrameStart(); @@ -89,9 +108,9 @@ export class AppPlugin { docStorage={store} redirectTo={id => { if (!id) { - routeProps.history.push('/'); + routeProps.history.push('/lens'); } else { - routeProps.history.push(`/edit/${id}`); + routeProps.history.push(`/lens/edit/${id}`); } }} /> @@ -103,17 +122,27 @@ export class AppPlugin { return ; } - return ( - - - - - - - - - - ); + localApplicationService.register({ + id: 'lens', + title: NOT_INTERNATIONALIZED_PRODUCT_NAME, + mount: async (context, params) => { + render( + + + + + + + + + , + params.element + ); + return () => { + unmountComponentAtNode(params.element); + }; + }, + }); } stop() { @@ -131,10 +160,3 @@ export class AppPlugin { editorFrameStop(); } } - -const app = new AppPlugin(); - -export const appSetup = () => app.setup(npSetup.core, {}); -export const appStart = () => - app.start(npStart.core, { dataShim: dataShimStart, data: npStart.plugins.data }); -export const appStop = () => app.stop(); diff --git a/x-pack/legacy/plugins/lens/public/help_menu_util.tsx b/x-pack/legacy/plugins/lens/public/help_menu_util.tsx index 30a05dbc38537..fec7175ea48eb 100644 --- a/x-pack/legacy/plugins/lens/public/help_menu_util.tsx +++ b/x-pack/legacy/plugins/lens/public/help_menu_util.tsx @@ -10,12 +10,12 @@ import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { Chrome } from 'ui/chrome'; +import { ChromeStart } from 'kibana/public'; const docsPage = 'lens'; -export function addHelpMenuToAppChrome(chrome: Chrome) { - chrome.helpExtension.set(domElement => { +export function addHelpMenuToAppChrome(chrome: ChromeStart) { + chrome.setHelpExtension(domElement => { render(, domElement); return () => { unmountComponentAtNode(domElement); diff --git a/x-pack/legacy/plugins/lens/public/index.ts b/x-pack/legacy/plugins/lens/public/index.ts index 2a5422d4bb8a1..9f4141dbcae7d 100644 --- a/x-pack/legacy/plugins/lens/public/index.ts +++ b/x-pack/legacy/plugins/lens/public/index.ts @@ -5,36 +5,3 @@ */ export * from './types'; - -import 'ui/autoload/all'; -// Used to run esaggs queries -import 'uiExports/fieldFormats'; -import 'uiExports/search'; -import 'uiExports/visRequestHandlers'; -import 'uiExports/visResponseHandlers'; -import 'uiExports/interpreter'; -// Used for kibana_context function -import 'uiExports/savedObjectTypes'; - -import { render, unmountComponentAtNode } from 'react-dom'; -import { IScope } from 'angular'; -import chrome from 'ui/chrome'; -import { appStart, appSetup, appStop } from './app_plugin'; -import { PLUGIN_ID } from '../common'; -import { addHelpMenuToAppChrome } from './help_menu_util'; - -// TODO: Convert this to the "new platform" way of doing UI -function Root($scope: IScope, $element: JQLite) { - const el = $element[0]; - $scope.$on('$destroy', () => { - unmountComponentAtNode(el); - appStop(); - }); - - appSetup(); - addHelpMenuToAppChrome(chrome); - - return render(appStart(), el); -} - -chrome.setRootController(PLUGIN_ID, Root); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx index affb1accbbef4..dc23df250ebd4 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx @@ -17,7 +17,6 @@ import { EuiProgress } from '@elastic/eui'; import { documentField } from './document_field'; jest.mock('ui/new_platform'); -jest.mock('../../../../../../src/legacy/ui/public/registry/field_formats'); const initialState: IndexPatternPrivateState = { indexPatternRefs: [], diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx index 9956c0ec33061..1b49eb6bca7fa 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx @@ -10,63 +10,58 @@ import { EuiLoadingSpinner, EuiPopover } from '@elastic/eui'; import { FieldItem, FieldItemProps } from './field_item'; import { coreMock } from 'src/core/public/mocks'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { npStart } from 'ui/new_platform'; +import { FieldFormatsStart } from '../../../../../../src/plugins/data/public'; +import { IndexPattern } from './types'; jest.mock('ui/new_platform'); -// Formatter must be mocked to return a string, or the rendering will fail -jest.mock('../../../../../../src/legacy/ui/public/registry/field_formats', () => ({ - fieldFormats: { - getDefaultInstance: jest.fn().mockReturnValue({ - convert: jest.fn().mockReturnValue((s: unknown) => JSON.stringify(s)), - }), - }, -})); - const waitForPromises = () => new Promise(resolve => setTimeout(resolve)); -const indexPattern = { - id: '1', - title: 'my-fake-index-pattern', - timeFieldName: 'timestamp', - fields: [ - { - name: 'timestamp', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'memory', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'unsupported', - type: 'geo', - aggregatable: true, - searchable: true, - }, - { - name: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }, - ], -}; - describe('IndexPattern Field Item', () => { let defaultProps: FieldItemProps; + let indexPattern: IndexPattern; let core: ReturnType; beforeEach(() => { + indexPattern = { + id: '1', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + fields: [ + { + name: 'timestamp', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'unsupported', + type: 'geo', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + ], + } as IndexPattern; + core = coreMock.createSetup(); core.http.post.mockClear(); defaultProps = { @@ -87,6 +82,12 @@ describe('IndexPattern Field Item', () => { }, exists: true, }; + + npStart.plugins.data.fieldFormats = ({ + getDefaultInstance: jest.fn(() => ({ + convert: jest.fn((s: unknown) => JSON.stringify(s)), + })), + } as unknown) as FieldFormatsStart; }); it('should request field stats every time the button is clicked', async () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx index 3536ad8053891..20505107be122 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx @@ -7,48 +7,49 @@ import React, { useState } from 'react'; import DateMath from '@elastic/datemath'; import { + EuiButtonGroup, EuiFlexGroup, EuiFlexItem, - EuiProgress, - EuiPopover, - EuiLoadingSpinner, + EuiIconTip, EuiKeyboardAccessible, - EuiText, - EuiToolTip, - EuiButtonGroup, + EuiLoadingSpinner, + EuiPopover, EuiPopoverFooter, EuiPopoverTitle, - EuiIconTip, + EuiProgress, + 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 { - Chart, Axis, + BarSeries, + Chart, + DataSeriesColorsValues, getAxisId, getSpecId, - BarSeries, + niceTimeFormatter, Position, ScaleType, Settings, - DataSeriesColorsValues, TooltipType, - niceTimeFormatter, } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import { Query, + KBN_FIELD_TYPES, + ES_FIELD_TYPES, esFilters, esQuery, IIndexPattern, } from '../../../../../../src/plugins/data/public'; -// @ts-ignore -import { fieldFormats } from '../../../../../../src/legacy/ui/public/registry/field_formats'; import { DraggedField } from './indexpattern'; import { DragDrop } from '../drag_drop'; import { DatasourceDataPanelProps, DataType } from '../types'; import { BucketedAggregation, FieldStatsResponse } from '../../common'; import { IndexPattern, IndexPatternField } from './types'; -import { LensFieldIcon, getColorForDataType } from './lens_field_icon'; +import { getColorForDataType, LensFieldIcon } from './lens_field_icon'; import { trackUiEvent } from '../lens_ui_telemetry'; export interface FieldItemProps { @@ -238,6 +239,7 @@ 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 IS_DARK_THEME = core.uiSettings.get('theme:darkMode'); @@ -289,7 +291,10 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { formatter = { convert: (data: unknown) => JSON.stringify(data) }; } } else { - formatter = fieldFormats.getDefaultInstance(field.type, field.esTypes); + formatter = fieldFormats.getDefaultInstance( + field.type as KBN_FIELD_TYPES, + field.esTypes as ES_FIELD_TYPES[] + ); } const euiButtonColor = @@ -370,7 +375,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { )}{' '} {fieldFormats - .getDefaultInstance('number', ['integer']) + .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) .convert(props.totalDocuments)} {' '} {i18n.translate('xpack.lens.indexPattern.ofDocumentsLabel', { diff --git a/x-pack/legacy/plugins/lens/public/legacy.ts b/x-pack/legacy/plugins/lens/public/legacy.ts new file mode 100644 index 0000000000000..0285e041de78c --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/legacy.ts @@ -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 { npSetup, npStart } from 'ui/new_platform'; +import { start as dataShimStart } from '../../../../../src/legacy/core_plugins/data/public/legacy'; + +export * from './types'; + +import { localApplicationService } from '../../../../../src/legacy/core_plugins/kibana/public/local_application_service'; +import { AppPlugin } from './app_plugin'; + +const app = new AppPlugin(); +app.setup(npSetup.core, {}); +app.start(npStart.core, { + dataShim: dataShimStart, + data: npStart.plugins.data, + __LEGACY: { + localApplicationService, + }, +}); diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx index ba1ac461161b1..9220c3ec75fad 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -9,7 +9,7 @@ import { LensMultiTable } from '../types'; import React from 'react'; import { shallow } from 'enzyme'; import { MetricConfig } from './types'; -import { FieldFormat } from 'ui/registry/field_formats'; +import { FieldFormat } from '../../../../../../src/plugins/data/public'; function sampleArgs() { const data: LensMultiTable = { diff --git a/x-pack/legacy/plugins/lens/public/redirect.ts b/x-pack/legacy/plugins/lens/public/redirect.ts new file mode 100644 index 0000000000000..25b0188214c5e --- /dev/null +++ b/x-pack/legacy/plugins/lens/public/redirect.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// 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/register_embeddable.ts b/x-pack/legacy/plugins/lens/public/register_embeddable.ts deleted file mode 100644 index f86cb91a0029e..0000000000000 --- a/x-pack/legacy/plugins/lens/public/register_embeddable.ts +++ /dev/null @@ -1,20 +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 { indexPatternDatasourceSetup } from './indexpattern_plugin'; -import { xyVisualizationSetup } from './xy_visualization_plugin'; -import { editorFrameSetup, editorFrameStart } from './editor_frame_plugin'; -import { datatableVisualizationSetup } from './datatable_visualization_plugin'; -import { metricVisualizationSetup } from './metric_visualization_plugin'; - -// bootstrap shimmed plugins to register everything necessary (expression functions and embeddables). -// the new platform will take care of this once in place. -indexPatternDatasourceSetup(); -datatableVisualizationSetup(); -xyVisualizationSetup(); -metricVisualizationSetup(); -editorFrameSetup(); -editorFrameStart(); diff --git a/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts b/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts index 562d0f0ef6135..185df12054a3c 100644 --- a/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts +++ b/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts @@ -6,10 +6,10 @@ import { i18n } from '@kbn/i18n'; import { visualizations } from '../../../../../src/legacy/core_plugins/visualizations/public'; -import { BASE_APP_URL, getEditPath } from '../common'; +import { getBasePath, getEditPath } from '../common'; visualizations.types.registerAlias({ - aliasUrl: BASE_APP_URL, + aliasUrl: getBasePath(), name: 'lens', promotion: { description: i18n.translate('xpack.lens.visTypeAlias.promotion.description', { diff --git a/x-pack/legacy/plugins/maps/public/index.js b/x-pack/legacy/plugins/maps/public/index.js index 49d8646c6a251..964753f464d95 100644 --- a/x-pack/legacy/plugins/maps/public/index.js +++ b/x-pack/legacy/plugins/maps/public/index.js @@ -10,7 +10,6 @@ import { wrapInI18nContext } from 'ui/i18n'; import { i18n } from '@kbn/i18n'; // import the uiExports that we want to "use" -import 'uiExports/fieldFormats'; import 'uiExports/inspectorViews'; import 'uiExports/search'; import 'uiExports/embeddableFactories'; diff --git a/x-pack/legacy/plugins/ml/public/app.js b/x-pack/legacy/plugins/ml/public/app.js index b88346035f306..ead1af5f64e07 100644 --- a/x-pack/legacy/plugins/ml/public/app.js +++ b/x-pack/legacy/plugins/ml/public/app.js @@ -5,8 +5,6 @@ */ -// import the uiExports that we want to "use" -import 'uiExports/fieldFormats'; import 'uiExports/savedObjectTypes'; import 'ui/autoload/all'; diff --git a/x-pack/legacy/plugins/ml/public/formatters/__tests__/abbreviate_whole_number.js b/x-pack/legacy/plugins/ml/public/formatters/__tests__/abbreviate_whole_number.js deleted file mode 100644 index 4e85bae4b1862..0000000000000 --- a/x-pack/legacy/plugins/ml/public/formatters/__tests__/abbreviate_whole_number.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 { abbreviateWholeNumber } from '../abbreviate_whole_number'; - -describe('ML - abbreviateWholeNumber formatter', () => { - - it('returns the correct format using default max digits', () => { - expect(abbreviateWholeNumber(1)).to.be(1); - expect(abbreviateWholeNumber(12)).to.be(12); - expect(abbreviateWholeNumber(123)).to.be(123); - expect(abbreviateWholeNumber(1234)).to.be('1k'); - expect(abbreviateWholeNumber(12345)).to.be('12k'); - expect(abbreviateWholeNumber(123456)).to.be('123k'); - expect(abbreviateWholeNumber(1234567)).to.be('1m'); - expect(abbreviateWholeNumber(12345678)).to.be('12m'); - expect(abbreviateWholeNumber(123456789)).to.be('123m'); - expect(abbreviateWholeNumber(1234567890)).to.be('1b'); - expect(abbreviateWholeNumber(5555555555555.55)).to.be('6t'); - }); - - it('returns the correct format using custom max digits', () => { - expect(abbreviateWholeNumber(1, 4)).to.be(1); - expect(abbreviateWholeNumber(12, 4)).to.be(12); - expect(abbreviateWholeNumber(123, 4)).to.be(123); - expect(abbreviateWholeNumber(1234, 4)).to.be(1234); - expect(abbreviateWholeNumber(12345, 4)).to.be('12k'); - expect(abbreviateWholeNumber(123456, 6)).to.be(123456); - expect(abbreviateWholeNumber(1234567, 4)).to.be('1m'); - expect(abbreviateWholeNumber(12345678, 3)).to.be('12m'); - expect(abbreviateWholeNumber(123456789, 9)).to.be(123456789); - expect(abbreviateWholeNumber(1234567890, 3)).to.be('1b'); - expect(abbreviateWholeNumber(5555555555555.55, 5)).to.be('6t'); - }); - -}); diff --git a/x-pack/legacy/plugins/ml/public/formatters/__tests__/format_value.js b/x-pack/legacy/plugins/ml/public/formatters/__tests__/format_value.js deleted file mode 100644 index ffdbdd915d3a8..0000000000000 --- a/x-pack/legacy/plugins/ml/public/formatters/__tests__/format_value.js +++ /dev/null @@ -1,93 +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-timezone'; -import { formatValue } from '../format_value'; - -describe('ML - formatValue formatter', () => { - const timeOfWeekRecord = { - job_id: 'gallery_time_of_week', - result_type: 'record', - probability: 0.012818, - record_score: 53.55134, - bucket_span: 900, - detector_index: 0, - timestamp: 1530155700000, - by_field_name: 'clientip', - by_field_value: '65.55.215.39', - function: 'time_of_week', - function_description: 'time' - }; - - const timeOfDayRecord = { - job_id: 'gallery_time_of_day', - result_type: 'record', - probability: 0.012818, - record_score: 97.94245, - bucket_span: 900, - detector_index: 0, - timestamp: 1517472900000, - by_field_name: 'clientip', - by_field_value: '157.56.93.83', - function: 'time_of_day', - function_description: 'time' - }; - - // Set timezone to US/Eastern for time_of_day and time_of_week tests. - beforeEach(() => { - moment.tz.setDefault('US/Eastern'); - }); - - afterEach(() => { - moment.tz.setDefault('Browser'); - }); - - // For time_of_day and time_of_week test values which are offsets in seconds - // from UTC start of week / day are formatted correctly using the test timezone. - it('correctly formats time_of_week value from numeric input', () => { - expect(formatValue(359739, 'time_of_week', undefined, timeOfWeekRecord)).to.be('Wed 23:55'); - }); - - it('correctly formats time_of_day value from numeric input', () => { - expect(formatValue(73781, 'time_of_day', undefined, timeOfDayRecord)).to.be('15:29'); - }); - - it('correctly formats number values from numeric input', () => { - expect(formatValue(1483228800, 'mean')).to.be(1483228800); - expect(formatValue(1234.5678, 'mean')).to.be(1234.6); - expect(formatValue(0.00012345, 'mean')).to.be(0.000123); - expect(formatValue(0, 'mean')).to.be(0); - expect(formatValue(-0.12345, 'mean')).to.be(-0.123); - expect(formatValue(-1234.5678, 'mean')).to.be(-1234.6); - expect(formatValue(-100000.1, 'mean')).to.be(-100000); - }); - - it('correctly formats time_of_week value from array input', () => { - expect(formatValue([359739], 'time_of_week', undefined, timeOfWeekRecord)).to.be('Wed 23:55'); - }); - - it('correctly formats time_of_day value from array input', () => { - expect(formatValue([73781], 'time_of_day', undefined, timeOfDayRecord)).to.be('15:29'); - }); - - it('correctly formats number values from array input', () => { - expect(formatValue([1483228800], 'mean')).to.be(1483228800); - expect(formatValue([1234.5678], 'mean')).to.be(1234.6); - expect(formatValue([0.00012345], 'mean')).to.be(0.000123); - expect(formatValue([0], 'mean')).to.be(0); - expect(formatValue([-0.12345], 'mean')).to.be(-0.123); - expect(formatValue([-1234.5678], 'mean')).to.be(-1234.6); - expect(formatValue([-100000.1], 'mean')).to.be(-100000); - }); - - it('correctly formats multi-valued array', () => { - expect(formatValue([30.3, 26.2], 'lat_long')).to.be('[30.3,26.2]'); - }); - -}); diff --git a/x-pack/legacy/plugins/ml/public/formatters/__tests__/metric_change_description.js b/x-pack/legacy/plugins/ml/public/formatters/__tests__/metric_change_description.js deleted file mode 100644 index 45864ce58bf83..0000000000000 --- a/x-pack/legacy/plugins/ml/public/formatters/__tests__/metric_change_description.js +++ /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 expect from '@kbn/expect'; -import { getMetricChangeDescription } from '../metric_change_description'; - - -describe('ML - metricChangeDescription formatter', () => { - - it('returns correct icon and message if actual > typical', () => { - expect(getMetricChangeDescription(1.01, 1)).to.eql({ iconType: 'sortUp', message: 'Unusually high' }); - expect(getMetricChangeDescription(1.123, 1)).to.eql({ iconType: 'sortUp', message: '1.1x higher' }); - expect(getMetricChangeDescription(2, 1)).to.eql({ iconType: 'sortUp', message: '2x higher' }); - expect(getMetricChangeDescription(9.5, 1)).to.eql({ iconType: 'sortUp', message: '10x higher' }); - expect(getMetricChangeDescription(1000, 1)).to.eql({ iconType: 'sortUp', message: 'More than 100x higher' }); - expect(getMetricChangeDescription(1, 0)).to.eql({ iconType: 'sortUp', message: 'Unexpected non-zero value' }); - }); - - it('returns correct icon and message if actual < typical', () => { - expect(getMetricChangeDescription(1, 1.01)).to.eql({ iconType: 'sortDown', message: 'Unusually low' }); - expect(getMetricChangeDescription(1, 1.123)).to.eql({ iconType: 'sortDown', message: '1.1x lower' }); - expect(getMetricChangeDescription(1, 2)).to.eql({ iconType: 'sortDown', message: '2x lower' }); - expect(getMetricChangeDescription(1, 9.5)).to.eql({ iconType: 'sortDown', message: '10x lower' }); - expect(getMetricChangeDescription(1, 1000)).to.eql({ iconType: 'sortDown', message: 'More than 100x lower' }); - expect(getMetricChangeDescription(0, 1)).to.eql({ iconType: 'sortDown', message: 'Unexpected zero value' }); - }); - -}); diff --git a/x-pack/legacy/plugins/ml/public/formatters/abbreviate_whole_number.test.ts b/x-pack/legacy/plugins/ml/public/formatters/abbreviate_whole_number.test.ts new file mode 100644 index 0000000000000..feabaa4064978 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/formatters/abbreviate_whole_number.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { abbreviateWholeNumber } from './abbreviate_whole_number'; + +describe('ML - abbreviateWholeNumber formatter', () => { + test('returns the correct format using default max digits', () => { + expect(abbreviateWholeNumber(1)).toBe(1); + expect(abbreviateWholeNumber(12)).toBe(12); + expect(abbreviateWholeNumber(123)).toBe(123); + expect(abbreviateWholeNumber(1234)).toBe('1k'); + expect(abbreviateWholeNumber(12345)).toBe('12k'); + expect(abbreviateWholeNumber(123456)).toBe('123k'); + expect(abbreviateWholeNumber(1234567)).toBe('1m'); + expect(abbreviateWholeNumber(12345678)).toBe('12m'); + expect(abbreviateWholeNumber(123456789)).toBe('123m'); + expect(abbreviateWholeNumber(1234567890)).toBe('1b'); + expect(abbreviateWholeNumber(5555555555555.55)).toBe('6t'); + }); + + test('returns the correct format using custom max digits', () => { + expect(abbreviateWholeNumber(1, 4)).toBe(1); + expect(abbreviateWholeNumber(12, 4)).toBe(12); + expect(abbreviateWholeNumber(123, 4)).toBe(123); + expect(abbreviateWholeNumber(1234, 4)).toBe(1234); + expect(abbreviateWholeNumber(12345, 4)).toBe('12k'); + expect(abbreviateWholeNumber(123456, 6)).toBe(123456); + expect(abbreviateWholeNumber(1234567, 4)).toBe('1m'); + expect(abbreviateWholeNumber(12345678, 3)).toBe('12m'); + expect(abbreviateWholeNumber(123456789, 9)).toBe(123456789); + expect(abbreviateWholeNumber(1234567890, 3)).toBe('1b'); + expect(abbreviateWholeNumber(5555555555555.55, 5)).toBe('6t'); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/formatters/abbreviate_whole_number.js b/x-pack/legacy/plugins/ml/public/formatters/abbreviate_whole_number.ts similarity index 89% rename from x-pack/legacy/plugins/ml/public/formatters/abbreviate_whole_number.js rename to x-pack/legacy/plugins/ml/public/formatters/abbreviate_whole_number.ts index 445681be4ff81..6d630c740a359 100644 --- a/x-pack/legacy/plugins/ml/public/formatters/abbreviate_whole_number.js +++ b/x-pack/legacy/plugins/ml/public/formatters/abbreviate_whole_number.ts @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ - /* * Formatter to abbreviate large whole numbers with metric prefixes. * Uses numeral.js to format numbers longer than the specified number of * digits with metric abbreviations e.g. 12345 as 12k, or 98000000 as 98m. -*/ + */ import numeral from '@elastic/numeral'; -export function abbreviateWholeNumber(value, maxDigits = 3) { +export function abbreviateWholeNumber(value: number, maxDigits = 3) { if (Math.abs(value) < Math.pow(10, maxDigits)) { return value; } else { diff --git a/x-pack/legacy/plugins/ml/public/formatters/format_value.test.ts b/x-pack/legacy/plugins/ml/public/formatters/format_value.test.ts new file mode 100644 index 0000000000000..5f146aef97fcc --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/formatters/format_value.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment-timezone'; +import { AnomalyRecordDoc } from '../../common/types/anomalies'; +import { formatValue } from './format_value'; + +describe('ML - formatValue formatter', () => { + const timeOfWeekRecord: AnomalyRecordDoc = { + job_id: 'gallery_time_of_week', + result_type: 'record', + probability: 0.012818, + record_score: 53.55134, + initial_record_score: 53, + bucket_span: 900, + detector_index: 0, + is_interim: false, + timestamp: 1530155700000, + by_field_name: 'clientip', + by_field_value: '65.55.215.39', + function: 'time_of_week', + function_description: 'time', + }; + + const timeOfDayRecord: AnomalyRecordDoc = { + job_id: 'gallery_time_of_day', + result_type: 'record', + probability: 0.012818, + record_score: 97.94245, + initial_record_score: 97, + bucket_span: 900, + detector_index: 0, + is_interim: false, + timestamp: 1517472900000, + by_field_name: 'clientip', + by_field_value: '157.56.93.83', + function: 'time_of_day', + function_description: 'time', + }; + + // Set timezone to US/Eastern for time_of_day and time_of_week tests. + beforeEach(() => { + moment.tz.setDefault('US/Eastern'); + }); + + afterEach(() => { + moment.tz.setDefault('Browser'); + }); + + // For time_of_day and time_of_week test values which are offsets in seconds + // from UTC start of week / day are formatted correctly using the test timezone. + test('correctly formats time_of_week value from numeric input', () => { + expect(formatValue(359739, 'time_of_week', undefined, timeOfWeekRecord)).toBe('Wed 23:55'); + }); + + test('correctly formats time_of_day value from numeric input', () => { + expect(formatValue(73781, 'time_of_day', undefined, timeOfDayRecord)).toBe('15:29'); + }); + + test('correctly formats number values from numeric input', () => { + expect(formatValue(1483228800, 'mean')).toBe(1483228800); + expect(formatValue(1234.5678, 'mean')).toBe(1234.6); + expect(formatValue(0.00012345, 'mean')).toBe(0.000123); + expect(formatValue(0, 'mean')).toBe(0); + expect(formatValue(-0.12345, 'mean')).toBe(-0.123); + expect(formatValue(-1234.5678, 'mean')).toBe(-1234.6); + expect(formatValue(-100000.1, 'mean')).toBe(-100000); + }); + + test('correctly formats time_of_week value from array input', () => { + expect(formatValue([359739], 'time_of_week', undefined, timeOfWeekRecord)).toBe('Wed 23:55'); + }); + + test('correctly formats time_of_day value from array input', () => { + expect(formatValue([73781], 'time_of_day', undefined, timeOfDayRecord)).toBe('15:29'); + }); + + test('correctly formats number values from array input', () => { + expect(formatValue([1483228800], 'mean')).toBe(1483228800); + expect(formatValue([1234.5678], 'mean')).toBe(1234.6); + expect(formatValue([0.00012345], 'mean')).toBe(0.000123); + expect(formatValue([0], 'mean')).toBe(0); + expect(formatValue([-0.12345], 'mean')).toBe(-0.123); + expect(formatValue([-1234.5678], 'mean')).toBe(-1234.6); + expect(formatValue([-100000.1], 'mean')).toBe(-100000); + }); + + test('correctly formats multi-valued array', () => { + expect(formatValue([30.3, 26.2], 'lat_long')).toBe('[30.3,26.2]'); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/formatters/format_value.js b/x-pack/legacy/plugins/ml/public/formatters/format_value.ts similarity index 75% rename from x-pack/legacy/plugins/ml/public/formatters/format_value.js rename to x-pack/legacy/plugins/ml/public/formatters/format_value.ts index 48988b5561e70..9360957c4a911 100644 --- a/x-pack/legacy/plugins/ml/public/formatters/format_value.js +++ b/x-pack/legacy/plugins/ml/public/formatters/format_value.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ - - /* * Formatter for 'typical' and 'actual' values from machine learning results. * For detectors which use the time_of_week or time_of_day @@ -14,12 +12,8 @@ */ import moment from 'moment'; - -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml'); - - -const SIGFIGS_IF_ROUNDING = 3; // Number of sigfigs to use for values < 10 +import { AnomalyRecordDoc } from '../../common/types/anomalies'; +const SIGFIGS_IF_ROUNDING = 3; // Number of sigfigs to use for values < 10 // Formats the value of an actual or typical field from a machine learning anomaly record. // mlFunction is the 'function' field from the ML record containing what the user entered e.g. 'high_count', @@ -29,7 +23,12 @@ const SIGFIGS_IF_ROUNDING = 3; // Number of sigfigs to use for values < 10 // For time_of_day or time_of_week functions the anomaly record // containing the timestamp of the anomaly should be supplied in // order to correctly format the day or week offset to the time of the anomaly. -export function formatValue(value, mlFunction, fieldFormat, record) { +export function formatValue( + value: number[] | number, + mlFunction: string, + fieldFormat?: any, + record?: AnomalyRecordDoc +) { // actual and typical values in anomaly record results will be arrays. // Unless the array is multi-valued (as it will be for multi-variate analyses such as lat_long), // simply return the formatted single value. @@ -54,7 +53,12 @@ export function formatValue(value, mlFunction, fieldFormat, record) { // For time_of_day or time_of_week functions the anomaly record // containing the timestamp of the anomaly should be supplied in // order to correctly format the day or week offset to the time of the anomaly. -function formatSingleValue(value, mlFunction, fieldFormat, record) { +function formatSingleValue( + value: number, + mlFunction: string, + fieldFormat?: any, + record?: AnomalyRecordDoc +) { if (value === undefined || value === null) { return ''; } @@ -65,14 +69,24 @@ function formatSingleValue(value, mlFunction, fieldFormat, record) { // that the anomaly occurred using record timestamp if supplied, add on the offset, and finally // revert back to configured timezone for formatting. if (mlFunction === 'time_of_week') { - const d = ((record !== undefined && record.timestamp !== undefined) ? new Date(record.timestamp) : new Date()); - const i = parseInt(value); - const utcMoment = moment.utc(d).startOf('week').add(i, 's'); + const d = + record !== undefined && record.timestamp !== undefined + ? new Date(record.timestamp) + : new Date(); + const utcMoment = moment + .utc(d) + .startOf('week') + .add(value, 's'); return moment(utcMoment.valueOf()).format('ddd HH:mm'); } else if (mlFunction === 'time_of_day') { - const d = ((record !== undefined && record.timestamp !== undefined) ? new Date(record.timestamp) : new Date()); - const i = parseInt(value); - const utcMoment = moment.utc(d).startOf('day').add(i, 's'); + const d = + record !== undefined && record.timestamp !== undefined + ? new Date(record.timestamp) + : new Date(); + const utcMoment = moment + .utc(d) + .startOf('day') + .add(value, 's'); return moment(utcMoment.valueOf()).format('HH:mm'); } else { if (fieldFormat !== undefined) { @@ -81,32 +95,32 @@ function formatSingleValue(value, mlFunction, fieldFormat, record) { // If no Kibana FieldFormat object provided, // format the value depending on its magnitude. const absValue = Math.abs(value); - if (absValue >= 10000 || absValue === Math.floor(absValue)) { + if (absValue >= 10000 || absValue === Math.floor(absValue)) { // Output 0 decimal places if whole numbers or >= 10000 if (fieldFormat !== undefined) { return fieldFormat.convert(value, 'text'); } else { return Number(value.toFixed(0)); } - } else if (absValue >= 10) { // Output to 1 decimal place between 10 and 10000 return Number(value.toFixed(1)); - } - else { + } else { // For values < 10, output to 3 significant figures let multiple; if (value > 0) { - multiple = Math.pow(10, SIGFIGS_IF_ROUNDING - Math.floor(Math.log(value) / Math.LN10) - 1); + multiple = Math.pow( + 10, + SIGFIGS_IF_ROUNDING - Math.floor(Math.log(value) / Math.LN10) - 1 + ); } else { - multiple = Math.pow(10, SIGFIGS_IF_ROUNDING - Math.floor(Math.log(-1 * value) / Math.LN10) - 1); + multiple = Math.pow( + 10, + SIGFIGS_IF_ROUNDING - Math.floor(Math.log(-1 * value) / Math.LN10) - 1 + ); } - return (Math.round(value * multiple)) / multiple; + return Math.round(value * multiple) / multiple; } } } } - -// TODO - remove the filter once all uses of the formatValue Angular filter have been removed. -module.filter('formatValue', () => formatValue); - diff --git a/x-pack/legacy/plugins/ml/public/formatters/metric_change_description.js b/x-pack/legacy/plugins/ml/public/formatters/metric_change_description.js deleted file mode 100644 index 06074e9a842c5..0000000000000 --- a/x-pack/legacy/plugins/ml/public/formatters/metric_change_description.js +++ /dev/null @@ -1,139 +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. - */ - - - -/* - * Produces a concise textual description of how the - * actual value compares to the typical value for an anomaly. - */ - -import { i18n } from '@kbn/i18n'; -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml'); - -// Returns an Object containing a text message and EuiIcon type to -// describe how the actual value compares to the typical. -export function getMetricChangeDescription(actualProp, typicalProp) { - if (actualProp === undefined || typicalProp === undefined) { - return { iconType: 'empty', message: '' }; - } - - let iconType; - let message; - - // For metric functions, actual and typical will be single value arrays. - let actual = actualProp; - let typical = typicalProp; - if (Array.isArray(actualProp)) { - if (actualProp.length === 1) { - actual = actualProp[0]; - } else { - // TODO - do we want to enhance the description depending on detector? - // e.g. 'Unusual location' if using a lat_long detector. - return { - iconType: 'alert', - message: i18n.translate('xpack.ml.formatters.metricChangeDescription.unusualValuesDescription', { - defaultMessage: 'Unusual values', - }), - }; - } - } - - if (Array.isArray(typicalProp)) { - if (typicalProp.length === 1) { - typical = typicalProp[0]; - } - } - - if (actual === typical) { - // Very unlikely, but just in case. - message = i18n.translate('xpack.ml.formatters.metricChangeDescription.actualSameAsTypicalDescription', { - defaultMessage: 'actual same as typical', - }); - } else { - // For actual / typical gives output of the form: - // 4 / 2 2x higher - // 2 / 10 5x lower - // 1000 / 1 More than 100x higher - // 999 / 1000 Unusually low - // 100 / -100 Unusually high - // 0 / 100 Unexpected zero value - // 1 / 0 Unexpected non-zero value - const isHigher = actual > typical; - iconType = isHigher ? 'sortUp' : 'sortDown'; - if (typical !== 0 && actual !== 0) { - const factor = isHigher ? actual / typical : typical / actual; - if (factor > 1.5) { - if (factor <= 100) { - message = isHigher ? i18n.translate('xpack.ml.formatters.metricChangeDescription.moreThanOneAndHalfxHigherDescription', { - defaultMessage: '{factor}x higher', - values: { factor: Math.round(factor) }, - }) : i18n.translate('xpack.ml.formatters.metricChangeDescription.moreThanOneAndHalfxLowerDescription', { - defaultMessage: '{factor}x lower', - values: { factor: Math.round(factor) }, - }); - } else { - message = isHigher ? i18n.translate('xpack.ml.formatters.metricChangeDescription.moreThan100xHigherDescription', { - defaultMessage: 'More than 100x higher', - }) : i18n.translate('xpack.ml.formatters.metricChangeDescription.moreThan100xLowerDescription', { - defaultMessage: 'More than 100x lower', - }); - } - } else if (factor >= 1.05) { - message = isHigher ? i18n.translate('xpack.ml.formatters.metricChangeDescription.moreThanOneAndFiveHundredthsxHigherDescription', { - defaultMessage: '{factor}x higher', - values: { factor: factor.toPrecision(2) }, - }) : i18n.translate('xpack.ml.formatters.metricChangeDescription.moreThanOneAndFiveHundredthsxLowerDescription', { - defaultMessage: '{factor}x lower', - values: { factor: factor.toPrecision(2) }, - }); - } else { - message = isHigher ? i18n.translate('xpack.ml.formatters.metricChangeDescription.unusuallyHighDescription', { - defaultMessage: 'Unusually high', - }) : i18n.translate('xpack.ml.formatters.metricChangeDescription.unusuallyLowDescription', { - defaultMessage: 'Unusually low', - }); - } - - } else { - if (actual === 0) { - message = i18n.translate('xpack.ml.formatters.metricChangeDescription.unexpectedZeroValueDescription', { - defaultMessage: 'Unexpected zero value', - }); - } else { - message = i18n.translate('xpack.ml.formatters.metricChangeDescription.unexpectedNonZeroValueDescription', { - defaultMessage: 'Unexpected non-zero value', - }); - } - } - } - - return { iconType, message }; -} - -// TODO - remove the filter once all uses of the metricChangeDescription Angular filter have been removed. -module.filter('metricChangeDescription', function () { - return function (actual, typical) { - - const { - iconType, - message - } = getMetricChangeDescription(actual, typical); - - switch (iconType) { - case 'sortUp': - return ` ${message}`; - case 'sortDown': - return ` ${message}`; - case 'alert': - return ` ${message}`; - } - - return message; - }; -}); - diff --git a/x-pack/legacy/plugins/ml/public/formatters/metric_change_description.test.ts b/x-pack/legacy/plugins/ml/public/formatters/metric_change_description.test.ts new file mode 100644 index 0000000000000..93533fe155e80 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/formatters/metric_change_description.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getMetricChangeDescription } from './metric_change_description'; + +describe('ML - metricChangeDescription formatter', () => { + test('returns correct icon and message if actual > typical', () => { + expect(getMetricChangeDescription(1.01, 1)).toEqual({ + iconType: 'sortUp', + message: 'Unusually high', + }); + expect(getMetricChangeDescription(1.123, 1)).toEqual({ + iconType: 'sortUp', + message: '1.1x higher', + }); + expect(getMetricChangeDescription(2, 1)).toEqual({ iconType: 'sortUp', message: '2x higher' }); + expect(getMetricChangeDescription(9.5, 1)).toEqual({ + iconType: 'sortUp', + message: '10x higher', + }); + expect(getMetricChangeDescription(1000, 1)).toEqual({ + iconType: 'sortUp', + message: 'More than 100x higher', + }); + expect(getMetricChangeDescription(1, 0)).toEqual({ + iconType: 'sortUp', + message: 'Unexpected non-zero value', + }); + }); + + test('returns correct icon and message if actual < typical', () => { + expect(getMetricChangeDescription(1, 1.01)).toEqual({ + iconType: 'sortDown', + message: 'Unusually low', + }); + expect(getMetricChangeDescription(1, 1.123)).toEqual({ + iconType: 'sortDown', + message: '1.1x lower', + }); + expect(getMetricChangeDescription(1, 2)).toEqual({ iconType: 'sortDown', message: '2x lower' }); + expect(getMetricChangeDescription(1, 9.5)).toEqual({ + iconType: 'sortDown', + message: '10x lower', + }); + expect(getMetricChangeDescription(1, 1000)).toEqual({ + iconType: 'sortDown', + message: 'More than 100x lower', + }); + expect(getMetricChangeDescription(0, 1)).toEqual({ + iconType: 'sortDown', + message: 'Unexpected zero value', + }); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/formatters/metric_change_description.ts b/x-pack/legacy/plugins/ml/public/formatters/metric_change_description.ts new file mode 100644 index 0000000000000..68f437b5a1436 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/formatters/metric_change_description.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Produces a concise textual description of how the + * actual value compares to the typical value for an anomaly. + */ + +import { i18n } from '@kbn/i18n'; + +// Returns an Object containing a text message and EuiIcon type to +// describe how the actual value compares to the typical. +export function getMetricChangeDescription( + actualProp: number[] | number, + typicalProp: number[] | number +) { + if (actualProp === undefined || typicalProp === undefined) { + return { iconType: 'empty', message: '' }; + } + + let iconType: string = 'alert'; + let message: string; + + // For metric functions, actual and typical will be single value arrays. + let actual: number = 0; + let typical: number = 0; + if (Array.isArray(actualProp)) { + if (actualProp.length === 1) { + actual = actualProp[0]; + } else { + // lat_long anomalies currently the only multi-value case. + // TODO - do we want to enhance the description depending on detector? + // e.g. 'Unusual location' if using a lat_long detector. + return { + iconType: 'alert', + message: i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.unusualValuesDescription', + { + defaultMessage: 'Unusual values', + } + ), + }; + } + } else { + actual = actualProp; + } + + if (Array.isArray(typicalProp)) { + if (typicalProp.length === 1) { + typical = typicalProp[0]; + } + } else { + typical = typicalProp; + } + + if (actual === typical) { + // Very unlikely, but just in case. + message = i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.actualSameAsTypicalDescription', + { + defaultMessage: 'actual same as typical', + } + ); + } else { + // For actual / typical gives output of the form: + // 4 / 2 2x higher + // 2 / 10 5x lower + // 1000 / 1 More than 100x higher + // 999 / 1000 Unusually low + // 100 / -100 Unusually high + // 0 / 100 Unexpected zero value + // 1 / 0 Unexpected non-zero value + const isHigher = actual > typical; + iconType = isHigher ? 'sortUp' : 'sortDown'; + if (typical !== 0 && actual !== 0) { + const factor: number = isHigher ? actual / typical : typical / actual; + if (factor > 1.5) { + if (factor <= 100) { + message = isHigher + ? i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.moreThanOneAndHalfxHigherDescription', + { + defaultMessage: '{factor}x higher', + values: { factor: Math.round(factor) }, + } + ) + : i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.moreThanOneAndHalfxLowerDescription', + { + defaultMessage: '{factor}x lower', + values: { factor: Math.round(factor) }, + } + ); + } else { + message = isHigher + ? i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.moreThan100xHigherDescription', + { + defaultMessage: 'More than 100x higher', + } + ) + : i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.moreThan100xLowerDescription', + { + defaultMessage: 'More than 100x lower', + } + ); + } + } else if (factor >= 1.05) { + message = isHigher + ? i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.moreThanOneAndFiveHundredthsxHigherDescription', + { + defaultMessage: '{factor}x higher', + values: { factor: factor.toPrecision(2) }, + } + ) + : i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.moreThanOneAndFiveHundredthsxLowerDescription', + { + defaultMessage: '{factor}x lower', + values: { factor: factor.toPrecision(2) }, + } + ); + } else { + message = isHigher + ? i18n.translate('xpack.ml.formatters.metricChangeDescription.unusuallyHighDescription', { + defaultMessage: 'Unusually high', + }) + : i18n.translate('xpack.ml.formatters.metricChangeDescription.unusuallyLowDescription', { + defaultMessage: 'Unusually low', + }); + } + } else { + if (actual === 0) { + message = i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.unexpectedZeroValueDescription', + { + defaultMessage: 'Unexpected zero value', + } + ); + } else { + message = i18n.translate( + 'xpack.ml.formatters.metricChangeDescription.unexpectedNonZeroValueDescription', + { + defaultMessage: 'Unexpected non-zero value', + } + ); + } + } + } + + return { iconType, message }; +} diff --git a/x-pack/legacy/plugins/ml/public/util/chart_utils.test.js b/x-pack/legacy/plugins/ml/public/util/chart_utils.test.js index 6d13f1bc66808..a229113826a2e 100644 --- a/x-pack/legacy/plugins/ml/public/util/chart_utils.test.js +++ b/x-pack/legacy/plugins/ml/public/util/chart_utils.test.js @@ -6,12 +6,6 @@ import seriesConfig from '../explorer/explorer_charts/__mocks__/mock_series_config_filebeat'; -jest.mock('ui/registry/field_formats', () => ({ - fieldFormats: { - getDefaultInstance: jest.fn(), - }, -})); - jest.mock('ui/timefilter', () => { const dateMath = require('@elastic/datemath'); let _time = undefined; diff --git a/x-pack/legacy/plugins/ml/public/util/time_buckets.js b/x-pack/legacy/plugins/ml/public/util/time_buckets.js index 6933ee6935e80..98cb677a4851a 100644 --- a/x-pack/legacy/plugins/ml/public/util/time_buckets.js +++ b/x-pack/legacy/plugins/ml/public/util/time_buckets.js @@ -9,10 +9,11 @@ import _ from 'lodash'; import moment from 'moment'; import dateMath from '@elastic/datemath'; import chrome from 'ui/chrome'; -import { fieldFormats } from 'ui/registry/field_formats'; +import { npStart } from 'ui/new_platform'; import { timeBucketsCalcAutoIntervalProvider } from './calc_auto_interval'; import { parseInterval } from '../../common/util/parse_interval'; +import { FIELD_FORMAT_IDS } from '../../../../../../src/plugins/data/public'; const unitsDesc = dateMath.unitsDesc; const largeMax = unitsDesc.indexOf('w'); // Multiple units of week or longer converted to days for ES intervals. @@ -316,7 +317,8 @@ TimeBuckets.prototype.getScaledDateFormat = function () { }; TimeBuckets.prototype.getScaledDateFormatter = function () { - const DateFieldFormat = fieldFormats.getType('date'); + const fieldFormats = npStart.plugins.data.fieldFormats; + const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE); return new DateFieldFormat({ pattern: this.getScaledDateFormat() }, getConfig); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts index 400f82bf81188..57a1f318a7e31 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts @@ -57,7 +57,7 @@ describe('Events Viewer', () => { .should('eq', 'Customize Columns'); }); - it.skip('closes the fields browser when the user clicks outside of it', () => { + it('closes the fields browser when the user clicks outside of it', () => { openEventsViewerFieldsBrowser(); clickOutsideFieldsBrowser(); @@ -81,7 +81,7 @@ describe('Events Viewer', () => { ); }); - it.skip('removes the message field from the timeline when the user un-checks the field', () => { + it('removes the message field from the timeline when the user un-checks the field', () => { const toggleField = 'message'; cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should('exist'); @@ -99,7 +99,7 @@ describe('Events Viewer', () => { ); }); - it.skip('filters the events by applying filter criteria from the search bar at the top of the page', () => { + it('filters the events by applying filter criteria from the search bar at the top of the page', () => { const filterInput = '4bf34c1c-eaa9-46de-8921-67a4ccc49829'; // this will never match real data cy.get(HEADER_SUBTITLE) @@ -119,7 +119,7 @@ describe('Events Viewer', () => { }); }); - it.skip('adds a field to the events viewer when the user clicks the checkbox', () => { + it('adds a field to the events viewer when the user clicks the checkbox', () => { const filterInput = 'host.geo.c'; const toggleField = 'host.geo.city_name'; @@ -158,7 +158,7 @@ describe('Events Viewer', () => { }); }); - it.skip('launches the inspect query modal when the inspect button is clicked', () => { + it('launches the inspect query modal when the inspect button is clicked', () => { // wait for data to load cy.get(HEADER_SUBTITLE) .invoke('text') @@ -171,7 +171,7 @@ describe('Events Viewer', () => { cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('exist'); }); - it.skip('resets all fields in the events viewer when `Reset Fields` is clicked', () => { + it('resets all fields in the events viewer when `Reset Fields` is clicked', () => { const filterInput = 'host.geo.c'; const toggleField = 'host.geo.city_name'; diff --git a/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx index c7e58532fc7e5..8b5f3b0f4d425 100644 --- a/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx @@ -20,6 +20,7 @@ import { SavedQueryTimeFilter } from '../../../../../../../src/legacy/core_plugi import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; export interface QueryBarComponentProps { + dataTestSubj?: string; dateRangeFrom?: string; dateRangeTo?: string; hideSavedQuery?: boolean; @@ -50,6 +51,7 @@ export const QueryBar = memo( refreshInterval, savedQuery, onSavedQuery, + dataTestSubj, }) => { const [draftQuery, setDraftQuery] = useState(filterQuery); @@ -139,6 +141,7 @@ export const QueryBar = memo( showQueryInput={true} showSaveQuery={true} timeHistory={new TimeHistory(new Storage(localStorage))} + dataTestSubj={dataTestSubj} {...searchBarProps} /> ); diff --git a/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx index 1bd30bad818d1..988bb13841fa5 100644 --- a/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx @@ -23,10 +23,7 @@ describe('Scroll to top', () => { Object.defineProperty(globalNode.window, 'scroll', { value: spyScroll }); mount( useScrollToTop()} />); - expect(spyScroll).toHaveBeenCalledWith({ - top: 0, - left: 0, - }); + expect(spyScroll).toHaveBeenCalledWith(0, 0); }); test('scrollTo have been called', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.tsx b/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.tsx index 59f9c99d6ab8e..8d4548516fc16 100644 --- a/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.tsx @@ -10,10 +10,7 @@ export const useScrollToTop = () => { useEffect(() => { // trying to use new API - https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo if (window.scroll) { - window.scroll({ - top: 0, - left: 0, - }); + window.scroll(0, 0); } else { // just a fallback for older browsers window.scrollTo(0, 0); diff --git a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx index c16c3a33872d5..33fb2b9239a6a 100644 --- a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx @@ -71,6 +71,7 @@ interface SiemSearchBarProps { id: InputsModelId; indexPattern: StaticIndexPattern; timelineId?: string; + dataTestSubj?: string; } const SearchBarContainer = styled.div` @@ -95,6 +96,7 @@ const SearchBarComponent = memo { const { timefilter } = npStart.plugins.data.query.timefilter; if (fromStr != null && toStr != null) { @@ -275,6 +277,7 @@ const SearchBarComponent = memo ); diff --git a/x-pack/legacy/plugins/siem/public/components/subtitle/index.tsx b/x-pack/legacy/plugins/siem/public/components/subtitle/index.tsx index 123e14d239182..1b7e042e90dbf 100644 --- a/x-pack/legacy/plugins/siem/public/components/subtitle/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/subtitle/index.tsx @@ -31,15 +31,26 @@ Wrapper.displayName = 'Wrapper'; interface SubtitleItemProps { children: string | React.ReactNode; + dataTestSubj?: string; } -const SubtitleItem = React.memo(({ children }) => { - if (typeof children === 'string') { - return

{children}

; - } else { - return
{children}
; +const SubtitleItem = React.memo( + ({ children, dataTestSubj = 'header-panel-subtitle' }) => { + if (typeof children === 'string') { + return ( +

+ {children} +

+ ); + } else { + return ( +
+ {children} +
+ ); + } } -}); +); SubtitleItem.displayName = 'SubtitleItem'; export interface SubtitleProps { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx index 6d76c277711d7..f24ee3155c924 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx @@ -288,6 +288,7 @@ export const QueryBarTimeline = memo( refreshInterval={refreshInterval} savedQuery={savedQuery} onSavedQuery={onSavedQuery} + dataTestSubj={'timelineQueryInput'} /> ); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.test.ts new file mode 100644 index 0000000000000..7873781fb05c4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.test.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getFilter } from './find_signals'; +import { SIGNALS_ID } from '../../../../common/constants'; + +describe('find_signals', () => { + test('it returns a full filter with an AND if sent down', () => { + expect(getFilter('alert.attributes.enabled: true')).toEqual( + `alert.attributes.alertTypeId: ${SIGNALS_ID} AND alert.attributes.enabled: true` + ); + }); + + test('it returns existing filter with no AND when not set', () => { + expect(getFilter(null)).toEqual(`alert.attributes.alertTypeId: ${SIGNALS_ID}`); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.ts index 23f4e38a95eea..63e6a069c0cfe 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.ts @@ -7,12 +7,31 @@ import { SIGNALS_ID } from '../../../../common/constants'; import { FindSignalParams } from './types'; -export const findSignals = async ({ alertsClient, perPage, page, fields }: FindSignalParams) => - alertsClient.find({ +export const getFilter = (filter: string | null | undefined) => { + if (filter == null) { + return `alert.attributes.alertTypeId: ${SIGNALS_ID}`; + } else { + return `alert.attributes.alertTypeId: ${SIGNALS_ID} AND ${filter}`; + } +}; + +export const findSignals = async ({ + alertsClient, + perPage, + page, + fields, + filter, + sortField, + sortOrder, +}: FindSignalParams) => { + return alertsClient.find({ options: { fields, page, perPage, - filter: `alert.attributes.alertTypeId: ${SIGNALS_ID}`, + filter: getFilter(filter), + sortOrder, + sortField, }, }); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts index a6cb56ada8df1..723e2aad7fe6a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts @@ -70,7 +70,9 @@ export interface FindParamsRest { per_page: number; page: number; sort_field: string; + sort_order: 'asc' | 'desc'; fields: string[]; + filter: string; } export interface Clients { @@ -95,7 +97,9 @@ export interface FindSignalsRequest extends Omit { page: number; search?: string; sort_field?: string; + filter?: string; fields?: string[]; + sort_order?: 'asc' | 'desc'; }; } @@ -104,7 +108,9 @@ export interface FindSignalParams { perPage?: number; page?: number; sortField?: string; + filter?: string; fields?: string[]; + sortOrder?: 'asc' | 'desc'; } export interface ReadSignalParams { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts index a5e6d03a3378b..d8ba455445c0c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts @@ -293,6 +293,28 @@ describe('utils', () => { ); expect(result).toEqual(true); }); + test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { + const sampleParams = sampleSignalAlertParams(10); + mockService.callCluster + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockReturnValueOnce(sampleEmptyDocSearchResults); + const result = await searchAfterAndBulkIndex( + repeatedSearchResultsWithSortId(4), + sampleParams, + mockService, + mockLogger, + sampleSignalId + ); + expect(result).toEqual(true); + }); test('if logs error when iteration is unsuccessful when bulk index results in a failure', async () => { const sampleParams = sampleSignalAlertParams(5); mockService.callCluster diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts index 2967f41ffb697..80530f9c2245f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts @@ -157,7 +157,9 @@ export const searchAfterAndBulkIndex = async ( service, logger ); - sortIds = searchAfterResult.hits.hits[0].sort; + if (searchAfterResult.hits.hits.length === 0) { + return true; + } hitsSize += searchAfterResult.hits.hits.length; logger.debug(`size adjusted: ${hitsSize}`); sortIds = searchAfterResult.hits.hits[0].sort; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_signals_route.ts index 18252c4f27fb0..120b71fab7d3a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_signals_route.ts @@ -39,6 +39,8 @@ export const createFindSignalRoute: Hapi.ServerRoute = { perPage: query.per_page, page: query.page, sortField: query.sort_field, + sortOrder: query.sort_order, + filter: query.filter, }); return transformFindAlertsOrError(signals); }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts index ecb42399932f6..352d8d57cdb83 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts @@ -1446,6 +1446,8 @@ describe('schemas', () => { page: 1, sort_field: 'some field', fields: ['field 1', 'field 2'], + filter: 'some filter', + sort_order: 'asc', }).error ).toBeFalsy(); }); @@ -1505,6 +1507,68 @@ describe('schemas', () => { test('page has a default of 1', () => { expect(findSignalsSchema.validate>({}).value.page).toEqual(1); }); + + test('filter works with a string', () => { + expect( + findSignalsSchema.validate>({ + filter: 'some value 1', + }).error + ).toBeFalsy(); + }); + + test('filter does not work with a number', () => { + expect( + findSignalsSchema.validate> & { filter: number }>({ + filter: 5, + }).error + ).toBeTruthy(); + }); + + test('sort_order requires sort_field to work', () => { + expect( + findSignalsSchema.validate>({ + sort_order: 'asc', + }).error + ).toBeTruthy(); + }); + + // TODO: Delete this if not used + test.skip('sort_field requires sort_order to work', () => { + expect( + findSignalsSchema.validate>({ + sort_field: 'some field', + }).error + ).toBeTruthy(); + }); + + test('sort_order and sort_field validate together', () => { + expect( + findSignalsSchema.validate>({ + sort_order: 'asc', + sort_field: 'some field', + }).error + ).toBeFalsy(); + }); + + test('sort_order validates with desc and sort_field', () => { + expect( + findSignalsSchema.validate>({ + sort_order: 'desc', + sort_field: 'some field', + }).error + ).toBeFalsy(); + }); + + test('sort_order does not validate with a string other than asc and desc', () => { + expect( + findSignalsSchema.validate< + Partial> & { sort_order: string } + >({ + sort_order: 'some other string', + sort_field: 'some field', + }).error + ).toBeTruthy(); + }); }); describe('querySignalSchema', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts index 596850b4a11e4..446fa7cb305b9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts @@ -28,6 +28,7 @@ const name = Joi.string(); const severity = Joi.string(); const to = Joi.string(); const type = Joi.string().valid('filter', 'query', 'saved_query'); +const queryFilter = Joi.string(); const references = Joi.array() .items(Joi.string()) .single(); @@ -38,6 +39,7 @@ const page = Joi.number() .min(1) .default(1); const sort_field = Joi.string(); +const sort_order = Joi.string().valid('asc', 'desc'); const tags = Joi.array().items(Joi.string()); const fields = Joi.array() .items(Joi.string()) @@ -113,8 +115,14 @@ export const querySignalSchema = Joi.object({ }).xor('id', 'rule_id'); export const findSignalsSchema = Joi.object({ + fields, + filter: queryFilter, per_page, page, - sort_field, - fields, + sort_field: Joi.when(Joi.ref('sort_order'), { + is: Joi.exist(), + then: sort_field.required(), + otherwise: sort_field.optional(), + }), + sort_order, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts index 69f25e84d995c..fc6aefc72e33d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -25,6 +25,7 @@ describe('utils', () => { false_positives: [], from: 'now-6m', id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', rule_id: 'rule-1', @@ -51,6 +52,7 @@ describe('utils', () => { enabled: true, false_positives: [], id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', rule_id: 'rule-1', @@ -78,6 +80,7 @@ describe('utils', () => { false_positives: [], from: 'now-6m', id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', rule_id: 'rule-1', @@ -105,6 +108,7 @@ describe('utils', () => { false_positives: [], from: 'now-6m', id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', rule_id: 'rule-1', @@ -131,6 +135,7 @@ describe('utils', () => { description: 'Detecting root and admin users', false_positives: [], id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', rule_id: 'rule-1', @@ -145,6 +150,64 @@ describe('utils', () => { type: 'query', }); }); + + test('should return enabled is equal to false', () => { + const fullSignal = getResult(); + fullSignal.enabled = false; + const signalWithEnabledFalse = transformAlertToSignal(fullSignal); + expect(signalWithEnabledFalse).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: false, + from: 'now-6m', + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + rule_id: 'rule-1', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + size: 1, + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + }); + }); + + test('should return immutable is equal to false', () => { + const fullSignal = getResult(); + fullSignal.alertTypeParams.immutable = false; + const signalWithEnabledFalse = transformAlertToSignal(fullSignal); + expect(signalWithEnabledFalse).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + from: 'now-6m', + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + rule_id: 'rule-1', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + size: 1, + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + }); + }); }); describe('getIdError', () => { @@ -208,6 +271,7 @@ describe('utils', () => { false_positives: [], from: 'now-6m', id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', rule_id: 'rule-1', @@ -243,6 +307,7 @@ describe('utils', () => { false_positives: [], from: 'now-6m', id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', rule_id: 'rule-1', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index 4d653210b2bff..fac30abd6992d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { pickBy, identity } from 'lodash/fp'; +import { pickBy } from 'lodash/fp'; import { SignalAlertType, isAlertType, OutputSignalAlertRest, isAlertTypes } from '../alerts/types'; export const getIdError = ({ @@ -27,7 +27,7 @@ export const getIdError = ({ // Transforms the data but will remove any null or undefined it encounters and not include // those on the export export const transformAlertToSignal = (signal: SignalAlertType): Partial => { - return pickBy(identity, { + return pickBy((value: unknown) => value != null, { created_by: signal.createdBy, description: signal.alertTypeParams.description, enabled: signal.enabled, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_signal_by_filter.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_signal_by_filter.sh new file mode 100755 index 0000000000000..6136f66025f3d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_signal_by_filter.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +FILTER=${1:-'alert.attributes.enabled:%20true'} + +# Example: ./find_signal_by_filter.sh "alert.attributes.enabled:%20true" +# Example: ./find_signal_by_filter.sh "alert.attributes.name:%20Detect*" +# The %20 is just an encoded space that is typical of URL's. +# Table of them for testing if needed: https://www.w3schools.com/tags/ref_urlencode.asp +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}/api/detection_engine/rules/_find?filter=$FILTER | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_signals_sort.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_signals_sort.sh new file mode 100755 index 0000000000000..3f8bab28544e3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_signals_sort.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +SORT=${1:-'enabled'} +ORDER=${2:-'asc'} + +# Example: ./find_signals_sort.sh enabled asc +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET "${KIBANA_URL}/api/detection_engine/rules/_find?sort_field=$SORT&sort_order=$ORDER" \ + | jq . diff --git a/x-pack/legacy/plugins/upgrade_assistant/common/types.ts b/x-pack/legacy/plugins/upgrade_assistant/common/types.ts index 60018173781a0..ce653e461e13b 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/common/types.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/common/types.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import { SavedObject, SavedObjectAttributes } from 'src/core/server'; export enum ReindexStep { @@ -79,15 +78,6 @@ export interface UIReindex { stop: boolean; } -export interface UpgradeAssistantTelemetryServer extends Legacy.Server { - usage: { - collectorSet: { - makeUsageCollector: any; - register: any; - }; - }; -} - export interface UpgradeAssistantTelemetrySavedObject { ui_open: { overview: number; diff --git a/x-pack/legacy/plugins/upgrade_assistant/index.ts b/x-pack/legacy/plugins/upgrade_assistant/index.ts index 7c38fbf02a564..f1762498246c7 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/index.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/index.ts @@ -3,18 +3,23 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import _ from 'lodash'; import Joi from 'joi'; import { Legacy } from 'kibana'; import { resolve } from 'path'; import mappings from './mappings.json'; -import { initServer } from './server'; +import { plugin } from './server/np_ready'; export function upgradeAssistant(kibana: any) { - return new kibana.Plugin({ + const publicSrc = resolve(__dirname, 'public'); + const npSrc = resolve(publicSrc, 'np_ready'); + + const config: Legacy.PluginSpecOptions = { id: 'upgrade_assistant', configPrefix: 'xpack.upgrade_assistant', require: ['elasticsearch'], uiExports: { + // @ts-ignore managementSections: ['plugins/upgrade_assistant'], savedObjectSchemas: { 'upgrade-assistant-reindex-operation': { @@ -24,10 +29,10 @@ export function upgradeAssistant(kibana: any) { isNamespaceAgnostic: true, }, }, - styleSheetPaths: resolve(__dirname, 'public/index.scss'), + styleSheetPaths: resolve(npSrc, 'application/index.scss'), mappings, }, - publicDir: resolve(__dirname, 'public'), + publicDir: publicSrc, config() { return Joi.object({ @@ -37,7 +42,30 @@ export function upgradeAssistant(kibana: any) { init(server: Legacy.Server) { // Add server routes and initialize the plugin here - initServer(server); + const instance = plugin({} as any); + instance.setup(server.newPlatform.setup.core, { + __LEGACY: { + // Legacy objects + events: server.events, + usage: server.usage, + savedObjects: server.savedObjects, + + // Legacy functions + log: server.log.bind(server), + + // Legacy plugins + plugins: { + elasticsearch: server.plugins.elasticsearch, + xpack_main: server.plugins.xpack_main, + cloud: { + config: { + isCloudEnabled: _.get(server.plugins, 'cloud.config.isCloudEnabled', false), + }, + }, + }, + }, + }); }, - }); + }; + return new kibana.Plugin(config); } diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/components/_index.scss deleted file mode 100644 index 8026031922301..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './tabs/checkup/index'; -@import './tabs/overview/index'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/_index.scss deleted file mode 100644 index 430d1e0aedf7b..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './deprecations/index'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/_index.scss deleted file mode 100644 index 2d52575cffbbb..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './button'; -@import './flyout/index'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/_index.scss deleted file mode 100644 index f695ae175feca..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './step_progress'; -@import './warnings_step'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/_index.scss deleted file mode 100644 index a6bbc6ba13298..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './steps'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/index.ts b/x-pack/legacy/plugins/upgrade_assistant/public/index.ts new file mode 100644 index 0000000000000..d22b5d64b6b46 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './legacy'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/index.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/index.tsx deleted file mode 100644 index 2f631d3771ecb..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore -import { management } from 'ui/management'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -// @ts-ignore -import routes from 'ui/routes'; -import { NEXT_MAJOR_VERSION } from '../common/version'; -import { RootComponent } from './app'; - -const BASE_PATH = `/management/elasticsearch/upgrade_assistant`; - -function startApp() { - management.getSection('elasticsearch').register('upgrade_assistant', { - visible: true, - display: i18n.translate('xpack.upgradeAssistant.appTitle', { - defaultMessage: '{version} Upgrade Assistant', - values: { version: `${NEXT_MAJOR_VERSION}.0` }, - }), - order: 100, - url: `#${BASE_PATH}`, - }); - - uiModules.get('kibana').directive('upgradeAssistant', (reactDirective: any) => { - return reactDirective(wrapInI18nContext(RootComponent)); - }); - - routes.when(`${BASE_PATH}/:view?`, { - template: - '', - }); -} - -startApp(); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/legacy.ts b/x-pack/legacy/plugins/upgrade_assistant/public/legacy.ts new file mode 100644 index 0000000000000..3d5144249dfef --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/legacy.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ComponentType } from 'react'; +import { i18n } from '@kbn/i18n'; + +/* LEGACY IMPORTS */ +import { npSetup } from 'ui/new_platform'; +import { wrapInI18nContext } from 'ui/i18n'; +import { management } from 'ui/management'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +import routes from 'ui/routes'; +import chrome from 'ui/chrome'; +/* LEGACY IMPORTS */ + +import { NEXT_MAJOR_VERSION } from '../common/version'; +import { plugin } from './np_ready'; + +const BASE_PATH = `/management/elasticsearch/upgrade_assistant`; + +export interface LegacyAppMountParameters { + __LEGACY: { renderToElement: (RootComponent: ComponentType) => void }; +} + +export interface LegacyApp { + mount(ctx: any, params: LegacyAppMountParameters): void; +} + +export interface LegacyManagementPlugin { + sections: { + get( + name: string + ): { + registerApp(app: LegacyApp): void; + }; + }; +} + +// Based on /rfcs/text/0006_management_section_service.md +export interface LegacyPlugins { + management: LegacyManagementPlugin; + __LEGACY: { + XSRF: string; + isCloudEnabled: boolean; + }; +} + +function startApp() { + routes.when(`${BASE_PATH}/:view?`, { + template: + '', + }); + + const legacyPluginsShim: LegacyPlugins = { + __LEGACY: { + XSRF: chrome.getXsrfToken(), + isCloudEnabled: chrome.getInjected('isCloudEnabled', false), + }, + management: { + sections: { + get(_: string) { + return { + registerApp(app) { + management.getSection('elasticsearch').register('upgrade_assistant', { + visible: true, + display: i18n.translate('xpack.upgradeAssistant.appTitle', { + defaultMessage: '{version} Upgrade Assistant', + values: { version: `${NEXT_MAJOR_VERSION}.0` }, + }), + order: 100, + url: `#${BASE_PATH}`, + }); + + app.mount( + {}, + { + __LEGACY: { + // While there is not an NP API for registering management section apps yet + renderToElement: RootComponent => { + uiModules + .get('kibana') + .directive('upgradeAssistant', (reactDirective: any) => { + return reactDirective(wrapInI18nContext(RootComponent)); + }); + }, + }, + } + ); + }, + }; + }, + }, + }, + }; + + const pluginInstance = plugin(); + + pluginInstance.setup(npSetup.core, legacyPluginsShim); +} + +startApp(); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app.tsx new file mode 100644 index 0000000000000..571967ab114c9 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app.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 { EuiPageHeader, EuiPageHeaderSection, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NEXT_MAJOR_VERSION } from '../../../common/version'; +import { UpgradeAssistantTabs } from './components/tabs'; +import { AppContextProvider, ContextValue, AppContext } from './app_context'; + +type AppDependencies = ContextValue; + +export const RootComponent = (deps: AppDependencies) => { + return ( + +
+ + + +

+ +

+
+
+
+ + {({ http }) => } + +
+
+ ); +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app_context.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app_context.tsx new file mode 100644 index 0000000000000..a48a4efa3bbdf --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app_context.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 { HttpSetup } from 'src/core/public'; +import React, { createContext, useContext } from 'react'; + +export interface ContextValue { + http: HttpSetup; + isCloudEnabled: boolean; + XSRF: string; +} + +export const AppContext = createContext({} as any); + +export const AppContextProvider = ({ + children, + value, +}: { + children: React.ReactNode; + value: ContextValue; +}) => { + return {children}; +}; + +export const useAppContext = () => { + const ctx = useContext(AppContext); + if (!ctx) { + throw new Error('useAppContext must be called from inside AppContext'); + } + return ctx; +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/_index.scss new file mode 100644 index 0000000000000..bb01107f334f6 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/_index.scss @@ -0,0 +1,2 @@ +@import 'tabs/checkup/index'; +@import 'tabs/overview/index'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/error_banner.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/error_banner.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/error_banner.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/error_banner.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/latest_minor_banner.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/latest_minor_banner.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/latest_minor_banner.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/latest_minor_banner.tsx index b76dcbd66b24a..864df292fbffe 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/latest_minor_banner.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/latest_minor_banner.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../common/version'; +import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../common/version'; export const LatestMinorBanner: React.FunctionComponent = () => ( ({ get: jest.fn(), create: jest.fn(), @@ -23,6 +21,13 @@ import { OverviewTab } from './tabs/overview'; // Used to wait for promises to resolve and renders to finish before reading updates const promisesToResolve = () => new Promise(resolve => setTimeout(resolve, 0)); +const mockHttp = { + basePath: { + prepend: () => 'test', + }, + fetch() {}, +}; + describe('UpgradeAssistantTabs', () => { test('renders loading state', async () => { // @ts-ignore @@ -31,7 +36,7 @@ describe('UpgradeAssistantTabs', () => { /* never resolve */ }) ); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); // Should pass down loading status to child component expect(wrapper.find(OverviewTab).prop('loadingState')).toEqual(LoadingState.Loading); }); @@ -44,7 +49,7 @@ describe('UpgradeAssistantTabs', () => { indices: [], }, }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); expect(axios.get).toHaveBeenCalled(); await promisesToResolve(); wrapper.update(); @@ -55,7 +60,7 @@ describe('UpgradeAssistantTabs', () => { test('network failure', async () => { // @ts-ignore axios.get.mockRejectedValue(new Error(`oh no!`)); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); await promisesToResolve(); wrapper.update(); // Should pass down error status to child component @@ -65,7 +70,7 @@ describe('UpgradeAssistantTabs', () => { it('upgrade error', async () => { // @ts-ignore axios.get.mockRejectedValue({ response: { status: 426 } }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); await promisesToResolve(); wrapper.update(); // Should display an informative message if the cluster is currently mid-upgrade diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs.tsx similarity index 93% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs.tsx index 76cc1e33ca06b..0b154fb20404d 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs.tsx @@ -16,11 +16,9 @@ import { EuiTabbedContentTab, } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import { HttpSetup } from 'src/core/public'; -import chrome from 'ui/chrome'; -import { kfetch } from 'ui/kfetch'; - -import { UpgradeAssistantStatus } from '../../server/lib/es_migration_apis'; +import { UpgradeAssistantStatus } from '../../../../server/np_ready/lib/es_migration_apis'; import { LatestMinorBanner } from './latest_minor_banner'; import { CheckupTab } from './tabs/checkup'; import { OverviewTab } from './tabs/overview'; @@ -41,13 +39,11 @@ interface TabsState { clusterUpgradeState: ClusterUpgradeState; } -export class UpgradeAssistantTabsUI extends React.Component< - ReactIntl.InjectedIntlProps, - TabsState -> { - constructor(props: ReactIntl.InjectedIntlProps) { - super(props); +type Props = ReactIntl.InjectedIntlProps & { http: HttpSetup }; +export class UpgradeAssistantTabsUI extends React.Component { + constructor(props: Props) { + super(props); this.state = { loadingState: LoadingState.Loading, clusterUpgradeState: ClusterUpgradeState.needsUpgrade, @@ -157,7 +153,9 @@ export class UpgradeAssistantTabsUI extends React.Component< private loadData = async () => { try { this.setState({ loadingState: LoadingState.Loading }); - const resp = await axios.get(chrome.addBasePath('/api/upgrade_assistant/status')); + const resp = await axios.get( + this.props.http.basePath.prepend('/api/upgrade_assistant/status') + ); this.setState({ loadingState: LoadingState.Success, checkupData: resp.data, @@ -246,8 +244,7 @@ export class UpgradeAssistantTabsUI extends React.Component< this.setState({ telemetryState: TelemetryState.Running }); - await kfetch({ - pathname: '/api/upgrade_assistant/telemetry/ui_open', + await this.props.http.fetch('/api/upgrade_assistant/telemetry/ui_open', { method: 'PUT', body: JSON.stringify(set({}, tabName, true)), }); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/__fixtures__/checkup_api_response.json b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/__fixtures__/checkup_api_response.json similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/__fixtures__/checkup_api_response.json rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/__fixtures__/checkup_api_response.json diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/_index.scss new file mode 100644 index 0000000000000..d64400a8abdcf --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/_index.scss @@ -0,0 +1 @@ +@import 'deprecations/index'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.test.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.test.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.test.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.test.tsx index 1805cb49e6ee6..9ba5441604ddc 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.test.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.test.tsx @@ -11,8 +11,6 @@ import { LoadingState } from '../../types'; import AssistanceData from '../__fixtures__/checkup_api_response.json'; import { CheckupTab } from './checkup_tab'; -jest.mock('ui/kfetch'); - const defaultProps = { checkupLabel: 'index', deprecations: AssistanceData.indices, diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.tsx similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.tsx index 5b8fd21f9c1fc..9aa40663125ae 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NEXT_MAJOR_VERSION } from '../../../../common/version'; +import { NEXT_MAJOR_VERSION } from '../../../../../../common/version'; import { LoadingErrorBanner } from '../../error_banner'; import { GroupByOption, diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/constants.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/constants.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/constants.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/constants.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/controls.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/controls.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/controls.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/controls.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_cell.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_cell.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_cell.scss rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_cell.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_deprecations.scss similarity index 88% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_index.scss rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_deprecations.scss index e370aeac0dfa2..445ef6269afb9 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_index.scss +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_deprecations.scss @@ -1,6 +1,3 @@ -@import './cell'; -@import './reindex/index'; - .upgDeprecations { // Pull the container through the padding of EuiPageContent margin-left: -$euiSizeL; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_index.scss new file mode 100644 index 0000000000000..55aff6b379db5 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_index.scss @@ -0,0 +1,3 @@ +@import 'cell'; +@import 'deprecations'; +@import 'reindex/index'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/cell.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/cell.tsx similarity index 89% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/cell.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/cell.tsx index f13b4c7511ee2..4bd2f7c4bf62c 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/cell.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/cell.tsx @@ -17,6 +17,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { ReindexButton } from './reindex'; +import { AppContext } from '../../../../app_context'; interface DeprecationCellProps { items?: Array<{ title?: string; body: string }>; @@ -77,7 +78,11 @@ export const DeprecationCell: FunctionComponent = ({ {reindexIndexName && ( - + + {({ http, XSRF }) => ( + + )} + )} diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/count_summary.tsx similarity index 93% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/count_summary.tsx index e9ab6f3fe1848..a0e55dc55c865 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/count_summary.tsx @@ -8,7 +8,7 @@ import React, { Fragment, FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; export const DeprecationCountSummary: FunctionComponent<{ deprecations: EnrichedDeprecationInfo[]; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.test.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.test.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.test.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.test.tsx index 6c541742a539b..28f5f6894b78f 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.test.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.test.tsx @@ -11,12 +11,10 @@ import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { EuiBadge, EuiPagination } from '@elastic/eui'; import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; -import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; import { GroupByOption, LevelFilterOption } from '../../../types'; import { DeprecationAccordion, filterDeps, GroupedDeprecations } from './grouped'; -jest.mock('ui/kfetch'); - describe('filterDeps', () => { test('filters on levels', () => { const fd = filterDeps(LevelFilterOption.critical); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.tsx index 86a582ee58f69..74f66b6c4fb35 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.tsx @@ -19,7 +19,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; -import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; import { GroupByOption, LevelFilterOption } from '../../../types'; import { DeprecationCountSummary } from './count_summary'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/health.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/health.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/health.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/health.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.test.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.test.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.test.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.test.tsx index 040459309f728..8c211704c7aff 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.test.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.test.tsx @@ -9,8 +9,6 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { IndexDeprecationTableProps, IndexDeprecationTableUI } from './index_table'; -jest.mock('ui/kfetch'); - describe('IndexDeprecationTable', () => { const defaultProps = { indices: [ diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.tsx similarity index 92% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.tsx index 5a1deb59c270e..eba906edc0509 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { EuiBasicTable } from '@elastic/eui'; import { injectI18n } from '@kbn/i18n/react'; import { ReindexButton } from './reindex'; +import { AppContext } from '../../../../app_context'; const PAGE_SIZES = [10, 25, 50, 100, 250, 500, 1000]; @@ -143,7 +144,13 @@ export class IndexDeprecationTableUI extends React.Component< actions: [ { render(indexDep: IndexDeprecationDetails) { - return ; + return ( + + {({ XSRF, http }) => ( + + )} + + ); }, }, ], diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.test.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.test.tsx similarity index 96% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.test.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.test.tsx index 6be89f411f580..78ded73593464 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.test.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.test.tsx @@ -7,12 +7,10 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; import { GroupByOption } from '../../../types'; import { DeprecationList } from './list'; -jest.mock('ui/kfetch'); - describe('DeprecationList', () => { describe('group by message', () => { const defaultProps = { diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.tsx similarity index 97% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.tsx index 2b0ba325c6330..15a3d94974dcd 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.tsx @@ -7,7 +7,7 @@ import React, { FunctionComponent } from 'react'; import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; -import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; import { GroupByOption } from '../../../types'; import { COLOR_MAP, LEVEL_MAP } from '../constants'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/_button.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/_button.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/_button.scss rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/_button.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/_index.scss new file mode 100644 index 0000000000000..014edc96b0565 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/_index.scss @@ -0,0 +1,2 @@ +@import 'button'; +@import 'flyout/index'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/button.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/button.tsx similarity index 95% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/button.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/button.tsx index 6addd3dae642a..2a28018a3ae81 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/button.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/button.tsx @@ -10,14 +10,16 @@ import { Subscription } from 'rxjs'; import { EuiButton, EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { kfetch } from 'ui/kfetch'; -import { ReindexStatus, UIReindexOption } from '../../../../../../common/types'; +import { HttpSetup } from 'src/core/public'; +import { ReindexStatus, UIReindexOption } from '../../../../../../../../common/types'; import { LoadingState } from '../../../../types'; import { ReindexFlyout } from './flyout'; import { ReindexPollingService, ReindexState } from './polling_service'; interface ReindexButtonProps { indexName: string; + xsrf: string; + http: HttpSetup; } interface ReindexButtonState { @@ -152,7 +154,8 @@ export class ReindexButton extends React.Component { diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx index afdd0b8caa294..91e35c0bd7dc0 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx @@ -21,7 +21,7 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ReindexWarning } from '../../../../../../../common/types'; +import { ReindexWarning } from '../../../../../../../../../common/types'; interface CheckedIds { [id: string]: boolean; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/index.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/index.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/index.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/index.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/polling_service.test.mocks.ts b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.mocks.ts similarity index 87% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/polling_service.test.mocks.ts rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.mocks.ts index f70822aef102c..dc7a758839fe5 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/polling_service.test.mocks.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.mocks.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReindexStatus, ReindexStep } from '../../../../../../common/types'; +import { ReindexStatus, ReindexStep } from '../../../../../../../../common/types'; export const mockClient = { post: jest.fn().mockResolvedValue({ diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/polling_service.test.ts b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts similarity index 75% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/polling_service.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts index 9f47f51bc8cde..cb2a0856f0f2e 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/polling_service.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts @@ -6,8 +6,9 @@ import { mockClient } from './polling_service.test.mocks'; -import { ReindexStatus, ReindexStep } from '../../../../../../common/types'; +import { ReindexStatus, ReindexStep } from '../../../../../../../../common/types'; import { ReindexPollingService } from './polling_service'; +import { httpServiceMock } from 'src/core/public/http/http_service.mock'; describe('ReindexPollingService', () => { beforeEach(() => { @@ -24,7 +25,11 @@ describe('ReindexPollingService', () => { }, }); - const service = new ReindexPollingService('myIndex'); + const service = new ReindexPollingService( + 'myIndex', + 'myXsrf', + httpServiceMock.createSetupContract() + ); service.updateStatus(); await new Promise(resolve => setTimeout(resolve, 1200)); // wait for poll interval @@ -45,7 +50,11 @@ describe('ReindexPollingService', () => { }, }); - const service = new ReindexPollingService('myIndex'); + const service = new ReindexPollingService( + 'myIndex', + 'myXsrf', + httpServiceMock.createSetupContract() + ); service.updateStatus(); await new Promise(resolve => setTimeout(resolve, 1200)); // wait for poll interval @@ -66,7 +75,11 @@ describe('ReindexPollingService', () => { }, }); - const service = new ReindexPollingService('myIndex'); + const service = new ReindexPollingService( + 'myIndex', + 'myXsrf', + httpServiceMock.createSetupContract() + ); service.updateStatus(); await new Promise(resolve => setTimeout(resolve, 1200)); // wait for poll interval @@ -76,7 +89,11 @@ describe('ReindexPollingService', () => { describe('startReindex', () => { it('posts to endpoint', async () => { - const service = new ReindexPollingService('myIndex'); + const service = new ReindexPollingService( + 'myIndex', + 'myXsrf', + httpServiceMock.createSetupContract() + ); await service.startReindex(); expect(mockClient.post).toHaveBeenCalledWith('/api/upgrade_assistant/reindex/myIndex'); @@ -85,7 +102,11 @@ describe('ReindexPollingService', () => { describe('cancelReindex', () => { it('posts to cancel endpoint', async () => { - const service = new ReindexPollingService('myIndex'); + const service = new ReindexPollingService( + 'myIndex', + 'myXsrf', + httpServiceMock.createSetupContract() + ); await service.cancelReindex(); expect(mockClient.post).toHaveBeenCalledWith('/api/upgrade_assistant/reindex/myIndex/cancel'); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/polling_service.ts b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.ts similarity index 82% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/polling_service.ts rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.ts index 3977a169f4aca..879fafe610982 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/polling_service.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.ts @@ -3,31 +3,21 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import axios from 'axios'; -import chrome from 'ui/chrome'; +import axios, { AxiosInstance } from 'axios'; import { BehaviorSubject } from 'rxjs'; + +import { HttpSetup } from 'src/core/public'; import { IndexGroup, ReindexOperation, ReindexStatus, ReindexStep, ReindexWarning, -} from '../../../../../../common/types'; +} from '../../../../../../../../common/types'; import { LoadingState } from '../../../../types'; const POLL_INTERVAL = 1000; -const XSRF = chrome.getXsrfToken(); - -export const APIClient = axios.create({ - headers: { - Accept: 'application/json', - credentials: 'same-origin', - 'Content-Type': 'application/json', - 'kbn-version': XSRF, - 'kbn-xsrf': XSRF, - }, -}); export interface ReindexState { loadingState: LoadingState; @@ -55,13 +45,24 @@ interface StatusResponse { export class ReindexPollingService { public status$: BehaviorSubject; private pollTimeout?: NodeJS.Timeout; + private APIClient: AxiosInstance; - constructor(private indexName: string) { + constructor(private indexName: string, private xsrf: string, private http: HttpSetup) { this.status$ = new BehaviorSubject({ loadingState: LoadingState.Loading, errorMessage: null, reindexTaskPercComplete: null, }); + + this.APIClient = axios.create({ + headers: { + Accept: 'application/json', + credentials: 'same-origin', + 'Content-Type': 'application/json', + 'kbn-version': this.xsrf, + 'kbn-xsrf': this.xsrf, + }, + }); } public updateStatus = async () => { @@ -69,8 +70,8 @@ export class ReindexPollingService { this.stopPolling(); try { - const { data } = await APIClient.get( - chrome.addBasePath(`/api/upgrade_assistant/reindex/${this.indexName}`) + const { data } = await this.APIClient.get( + this.http.basePath.prepend(`/api/upgrade_assistant/reindex/${this.indexName}`) ); this.updateWithResponse(data); @@ -106,8 +107,8 @@ export class ReindexPollingService { errorMessage: null, cancelLoadingState: undefined, }); - const { data } = await APIClient.post( - chrome.addBasePath(`/api/upgrade_assistant/reindex/${this.indexName}`) + const { data } = await this.APIClient.post( + this.http.basePath.prepend(`/api/upgrade_assistant/reindex/${this.indexName}`) ); this.updateWithResponse({ reindexOp: data }); @@ -124,8 +125,8 @@ export class ReindexPollingService { cancelLoadingState: LoadingState.Loading, }); - await APIClient.post( - chrome.addBasePath(`/api/upgrade_assistant/reindex/${this.indexName}/cancel`) + await this.APIClient.post( + this.http.basePath.prepend(`/api/upgrade_assistant/reindex/${this.indexName}/cancel`) ); } catch (e) { this.status$.next({ diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.test.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/filter_bar.test.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.test.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/filter_bar.test.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/filter_bar.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/filter_bar.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.test.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/group_by_bar.test.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.test.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/group_by_bar.test.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/group_by_bar.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/group_by_bar.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/index.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/index.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/index.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/index.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/_index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/_index.scss new file mode 100644 index 0000000000000..c64a8f5a5326d --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/_index.scss @@ -0,0 +1 @@ +@import 'steps'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/_steps.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/_steps.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/_steps.scss rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/_steps.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/deprecation_logging_toggle.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/deprecation_logging_toggle.tsx similarity index 87% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/deprecation_logging_toggle.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/deprecation_logging_toggle.tsx index 8d107331eb65f..97eb284c7b771 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/deprecation_logging_toggle.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/deprecation_logging_toggle.tsx @@ -10,19 +10,25 @@ import React from 'react'; import { EuiLoadingSpinner, EuiSwitch } from '@elastic/eui'; import { injectI18n } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; +import { HttpSetup } from 'src/core/public'; + import { LoadingState } from '../../types'; +interface DeprecationLoggingTabProps extends ReactIntl.InjectedIntlProps { + xsrf: string; + http: HttpSetup; +} + interface DeprecationLoggingTabState { loadingState: LoadingState; loggingEnabled?: boolean; } export class DeprecationLoggingToggleUI extends React.Component< - ReactIntl.InjectedIntlProps, + DeprecationLoggingTabProps, DeprecationLoggingTabState > { - constructor(props: ReactIntl.InjectedIntlProps) { + constructor(props: DeprecationLoggingTabProps) { super(props); this.state = { @@ -83,7 +89,7 @@ export class DeprecationLoggingToggleUI extends React.Component< try { this.setState({ loadingState: LoadingState.Loading }); const resp = await axios.get( - chrome.addBasePath('/api/upgrade_assistant/deprecation_logging') + this.props.http.basePath.prepend('/api/upgrade_assistant/deprecation_logging') ); this.setState({ loadingState: LoadingState.Success, @@ -96,18 +102,19 @@ export class DeprecationLoggingToggleUI extends React.Component< private toggleLogging = async () => { try { + const { http, xsrf } = this.props; // Optimistically toggle the UI const newEnabled = !this.state.loggingEnabled; this.setState({ loadingState: LoadingState.Loading, loggingEnabled: newEnabled }); const resp = await axios.put( - chrome.addBasePath('/api/upgrade_assistant/deprecation_logging'), + http.basePath.prepend('/api/upgrade_assistant/deprecation_logging'), { isEnabled: newEnabled, }, { headers: { - 'kbn-xsrf': chrome.getXsrfToken(), + 'kbn-xsrf': xsrf, }, } ); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/index.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/index.tsx similarity index 96% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/index.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/index.tsx index a96cb471309b2..0a05743d1623d 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/index.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/index.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NEXT_MAJOR_VERSION } from '../../../../common/version'; +import { NEXT_MAJOR_VERSION } from '../../../../../../common/version'; import { LoadingErrorBanner } from '../../error_banner'; import { LoadingState, UpgradeAssistantTabProps } from '../../types'; import { Steps } from './steps'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/steps.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/steps.tsx similarity index 96% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/steps.tsx rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/steps.tsx index 59fac976c6519..ccba51c73c136 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/overview/steps.tsx +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/steps.tsx @@ -18,11 +18,11 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; -import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../common/version'; +import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../../../common/version'; import { UpgradeAssistantTabProps } from '../../types'; import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; +import { useAppContext } from '../../../app_context'; // Leaving these here even if unused so they are picked up for i18n static analysis // Keep this until last minor release (when next major is also released). @@ -54,7 +54,7 @@ const WAIT_FOR_RELEASE_STEP = { // Swap in this step for the one above it on the last minor release. // @ts-ignore -const START_UPGRADE_STEP = { +const START_UPGRADE_STEP = (isCloudEnabled: boolean) => ({ title: i18n.translate('xpack.upgradeAssistant.overviewTab.steps.startUpgradeStep.stepTitle', { defaultMessage: 'Start your upgrade', }), @@ -62,7 +62,7 @@ const START_UPGRADE_STEP = {

- {chrome.getInjected('isCloudEnabled', false) ? ( + {isCloudEnabled ? ( ), -}; +}); export const StepsUI: FunctionComponent = ({ checkupData, @@ -103,6 +103,9 @@ export const StepsUI: FunctionComponent - + ), @@ -267,6 +270,7 @@ export const StepsUI: FunctionComponent ); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/types.ts b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/types.ts similarity index 94% rename from x-pack/legacy/plugins/upgrade_assistant/public/components/types.ts rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/types.ts index dc31308a1ea34..2d9a373f20b7e 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/components/types.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/types.ts @@ -9,7 +9,7 @@ import React from 'react'; import { EnrichedDeprecationInfo, UpgradeAssistantStatus, -} from '../../server/lib/es_migration_apis'; +} from '../../../../server/np_ready/lib/es_migration_apis'; export interface UpgradeAssistantTabProps { alertBanner?: React.ReactNode; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/index.scss b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/index.scss similarity index 66% rename from x-pack/legacy/plugins/upgrade_assistant/public/index.scss rename to x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/index.scss index 999cca93fcd7a..6000af5498cd6 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/index.scss +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/index.scss @@ -1,3 +1,3 @@ @import 'src/legacy/ui/public/styles/_styling_constants'; -@import './components/index'; +@import 'components/index'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/index.ts b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/index.ts new file mode 100644 index 0000000000000..020d6972f8280 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UpgradeAssistantUIPlugin } from './plugin'; + +export const plugin = () => { + return new UpgradeAssistantUIPlugin(); +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/plugin.ts b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/plugin.ts new file mode 100644 index 0000000000000..16a0c9632fb25 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/plugin.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Plugin, CoreSetup } from 'src/core/public'; +import { RootComponent } from './application/app'; +import { LegacyPlugins } from '../legacy'; + +export class UpgradeAssistantUIPlugin implements Plugin { + async setup( + { http }: CoreSetup, + { management, __LEGACY: { XSRF, isCloudEnabled } }: LegacyPlugins + ) { + const appRegistrar = management.sections.get('kibana'); + return appRegistrar.registerApp({ + mount(__, { __LEGACY: { renderToElement } }) { + return renderToElement(() => RootComponent({ http, XSRF, isCloudEnabled })); + }, + }); + } + async start() {} + async stop() {} +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/index.ts b/x-pack/legacy/plugins/upgrade_assistant/server/index.ts index 5fcdbe136a4f2..8b0704283509d 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/index.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/index.ts @@ -4,31 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; -import { UpgradeAssistantTelemetryServer } from '../common/types'; -import { credentialStoreFactory } from './lib/reindexing/credential_store'; -import { makeUpgradeAssistantUsageCollector } from './lib/telemetry'; -import { registerClusterCheckupRoutes } from './routes/cluster_checkup'; -import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging'; -import { registerReindexIndicesRoutes, registerReindexWorker } from './routes/reindex_indices'; -import { registerTelemetryRoutes } from './routes/telemetry'; - -export function initServer(server: Legacy.Server) { - registerClusterCheckupRoutes(server); - registerDeprecationLoggingRoutes(server); - - // The ReindexWorker uses a map of request headers that contain the authentication credentials - // for a given reindex. We cannot currently store these in an the .kibana index b/c we do not - // want to expose these credentials to any unauthenticated users. We also want to avoid any need - // to add a user for a special index just for upgrading. This in-memory cache allows us to - // process jobs without the browser staying on the page, but will require that jobs go into - // a paused state if no Kibana nodes have the required credentials. - const credentialStore = credentialStoreFactory(); - - const worker = registerReindexWorker(server, credentialStore); - registerReindexIndicesRoutes(server, worker, credentialStore); - - // Bootstrap the needed routes and the collector for the telemetry - registerTelemetryRoutes(server as UpgradeAssistantTelemetryServer); - makeUpgradeAssistantUsageCollector(server as UpgradeAssistantTelemetryServer); -} +export { plugin } from './np_ready'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts deleted file mode 100644 index 9a0fca6d4139c..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts +++ /dev/null @@ -1,125 +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 { SemVer } from 'semver'; -import { CURRENT_VERSION } from '../../common/version'; -import { - EsVersionPrecheck, - getAllNodeVersions, - verifyAllMatchKibanaVersion, -} from './es_version_precheck'; - -describe('getAllNodeVersions', () => { - it('returns a list of unique node versions', async () => { - const callCluster = jest.fn().mockResolvedValue({ - nodes: { - node1: { version: '7.0.0' }, - node2: { version: '7.0.0' }, - node3: { version: '6.0.0' }, - }, - }); - - await expect(getAllNodeVersions(callCluster)).resolves.toEqual([ - new SemVer('6.0.0'), - new SemVer('7.0.0'), - ]); - }); -}); - -describe('verifyAllMatchKibanaVersion', () => { - it('throws if any are higher version', () => { - expect(() => - verifyAllMatchKibanaVersion([new SemVer('99999.0.0')]) - ).toThrowErrorMatchingInlineSnapshot( - `"There are some nodes running a different version of Elasticsearch"` - ); - }); - - it('throws if any are lower version', () => { - expect(() => - verifyAllMatchKibanaVersion([new SemVer('0.0.0')]) - ).toThrowErrorMatchingInlineSnapshot( - `"There are some nodes running a different version of Elasticsearch"` - ); - }); - - it('does not throw if all are same major', () => { - const versions = [ - CURRENT_VERSION, - CURRENT_VERSION.inc('minor'), - CURRENT_VERSION.inc('minor').inc('minor'), - ]; - - expect(() => verifyAllMatchKibanaVersion(versions)).not.toThrow(); - }); -}); - -describe('EsVersionPrecheck', () => { - it('throws a 403 when callCluster fails with a 403', async () => { - const fakeCallWithRequest = jest.fn().mockRejectedValue({ status: 403 }); - const fakeGetCluster = jest.fn(() => ({ callWithRequest: fakeCallWithRequest })); - const fakeRequest = { - server: { plugins: { elasticsearch: { getCluster: fakeGetCluster } } }, - } as any; - - await expect(EsVersionPrecheck.method(fakeRequest, {} as any)).rejects.toHaveProperty( - 'output.statusCode', - 403 - ); - }); - - it('throws a 426 message w/ allNodesUpgraded = false when nodes are not on same version', async () => { - const fakeCallWithRequest = jest.fn().mockResolvedValue({ - nodes: { - node1: { version: CURRENT_VERSION.raw }, - node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, - }, - }); - const fakeGetCluster = jest.fn(() => ({ callWithRequest: fakeCallWithRequest })); - const fakeRequest = { - server: { plugins: { elasticsearch: { getCluster: fakeGetCluster } } }, - } as any; - - const result = EsVersionPrecheck.method(fakeRequest, {} as any); - await expect(result).rejects.toHaveProperty('output.statusCode', 426); - await expect(result).rejects.toHaveProperty( - 'output.payload.attributes.allNodesUpgraded', - false - ); - }); - - it('throws a 426 message w/ allNodesUpgraded = true when nodes are on next version', async () => { - const fakeCallWithRequest = jest.fn().mockResolvedValue({ - nodes: { - node1: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, - node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, - }, - }); - const fakeGetCluster = jest.fn(() => ({ callWithRequest: fakeCallWithRequest })); - const fakeRequest = { - server: { plugins: { elasticsearch: { getCluster: fakeGetCluster } } }, - } as any; - - const result = EsVersionPrecheck.method(fakeRequest, {} as any); - await expect(result).rejects.toHaveProperty('output.statusCode', 426); - await expect(result).rejects.toHaveProperty('output.payload.attributes.allNodesUpgraded', true); - }); - - it('returns true when nodes are on same version', async () => { - const fakeCallWithRequest = jest.fn().mockResolvedValue({ - nodes: { - node1: { version: CURRENT_VERSION.raw }, - node2: { version: CURRENT_VERSION.raw }, - }, - }); - const fakeGetCluster = jest.fn(() => ({ callWithRequest: fakeCallWithRequest })); - const fakeRequest = { - server: { plugins: { elasticsearch: { getCluster: fakeGetCluster } } }, - } as any; - - await expect(EsVersionPrecheck.method(fakeRequest, {} as any)).resolves.toBe(true); - }); -}); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_version_precheck.ts b/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_version_precheck.ts deleted file mode 100644 index d84d5f5444472..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_version_precheck.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; -import { Request, RouteOptionsPreObject } from 'hapi'; -import { uniq } from 'lodash'; -import { SemVer } from 'semver'; - -import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { CURRENT_VERSION } from '../../common/version'; - -/** - * Returns an array of all the unique Elasticsearch Node Versions in the Elasticsearch cluster. - * @param request - */ -export const getAllNodeVersions = async (callCluster: CallCluster) => { - // Get the version information for all nodes in the cluster. - const { nodes } = (await callCluster('nodes.info', { - filterPath: 'nodes.*.version', - })) as { nodes: { [nodeId: string]: { version: string } } }; - - const versionStrings = Object.values(nodes).map(({ version }) => version); - - return uniq(versionStrings) - .sort() - .map(version => new SemVer(version)); -}; - -export const verifyAllMatchKibanaVersion = (allNodeVersions: SemVer[]) => { - // Determine if all nodes in the cluster are running the same major version as Kibana. - const numDifferentVersion = allNodeVersions.filter( - esNodeVersion => esNodeVersion.major !== CURRENT_VERSION.major - ).length; - const numSameVersion = allNodeVersions.filter( - esNodeVersion => esNodeVersion.major === CURRENT_VERSION.major - ).length; - - if (numDifferentVersion) { - const error = new Boom(`There are some nodes running a different version of Elasticsearch`, { - // 426 means "Upgrade Required" and is used when semver compatibility is not met. - statusCode: 426, - }); - - error.output.payload.attributes = { allNodesUpgraded: !numSameVersion }; - throw error; - } -}; - -export const EsVersionPrecheck = { - assign: 'esVersionCheck', - async method(request: Request) { - const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('admin'); - const callCluster = callWithRequest.bind(callWithRequest, request) as CallCluster; - - let allNodeVersions: SemVer[]; - - try { - allNodeVersions = await getAllNodeVersions(callCluster); - } catch (e) { - if (e.status === 403) { - throw Boom.forbidden(e.message); - } - - throw e; - } - - // This will throw if there is an issue - verifyAllMatchKibanaVersion(allNodeVersions); - - return true; - }, -} as RouteOptionsPreObject; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/index.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/index.ts new file mode 100644 index 0000000000000..cf1b78e1e3920 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { PluginInitializerContext } from 'src/core/server'; +import { UpgradeAssistantServerPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => { + return new UpgradeAssistantServerPlugin(); +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__fixtures__/fake_deprecations.json similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__fixtures__/fake_deprecations.json diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/__mocks__/es_version_precheck.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__mocks__/es_version_precheck.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/__mocks__/es_version_precheck.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__mocks__/es_version_precheck.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__snapshots__/es_migration_apis.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__snapshots__/es_migration_apis.test.ts.snap diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.test.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.test.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.ts similarity index 94% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.ts index 2cf0a8f5020fd..199d389408442 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.ts @@ -5,8 +5,8 @@ */ import { get } from 'lodash'; -import { Legacy } from 'kibana'; import { CallClusterWithRequest } from 'src/legacy/core_plugins/elasticsearch'; +import { RequestShim } from '../types'; interface DeprecationLoggingStatus { isEnabled: boolean; @@ -14,7 +14,7 @@ interface DeprecationLoggingStatus { export async function getDeprecationLoggingStatus( callWithRequest: CallClusterWithRequest, - req: Legacy.Request + req: RequestShim ): Promise { const response = await callWithRequest(req, 'cluster.getSettings', { includeDefaults: true, @@ -27,7 +27,7 @@ export async function getDeprecationLoggingStatus( export async function setDeprecationLogging( callWithRequest: CallClusterWithRequest, - req: Legacy.Request, + req: RequestShim, isEnabled: boolean ): Promise { const response = await callWithRequest(req, 'cluster.putSettings', { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.test.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.test.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_migration_apis.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.ts similarity index 96% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/es_migration_apis.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.ts index 8ed85d19c411a..b52c4c374266f 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/es_migration_apis.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.ts @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; - -import { Request } from 'hapi'; import { CallClusterWithRequest, DeprecationAPIResponse, DeprecationInfo, } from 'src/legacy/core_plugins/elasticsearch'; +import { RequestShim } from '../types'; + export interface EnrichedDeprecationInfo extends DeprecationInfo { index?: string; node?: string; @@ -27,7 +26,7 @@ export interface UpgradeAssistantStatus { export async function getUpgradeAssistantStatus( callWithRequest: CallClusterWithRequest, - req: Request, + req: RequestShim, isCloudEnabled: boolean ): Promise { const deprecations = await callWithRequest(req, 'transport.request', { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.test.ts new file mode 100644 index 0000000000000..bbabe557df4d4 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.test.ts @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SemVer } from 'semver'; +import { IScopedClusterClient, kibanaResponseFactory } from 'src/core/server'; +import { CURRENT_VERSION } from '../../../common/version'; +import { + esVersionCheck, + getAllNodeVersions, + verifyAllMatchKibanaVersion, +} from './es_version_precheck'; + +describe('getAllNodeVersions', () => { + it('returns a list of unique node versions', async () => { + const adminClient = ({ + callAsInternalUser: jest.fn().mockResolvedValue({ + nodes: { + node1: { version: '7.0.0' }, + node2: { version: '7.0.0' }, + node3: { version: '6.0.0' }, + }, + }), + } as unknown) as IScopedClusterClient; + + await expect(getAllNodeVersions(adminClient)).resolves.toEqual([ + new SemVer('6.0.0'), + new SemVer('7.0.0'), + ]); + }); +}); + +describe('verifyAllMatchKibanaVersion', () => { + it('detects higher version nodes', () => { + const result = verifyAllMatchKibanaVersion([new SemVer('99999.0.0')]); + expect(result.allNodesMatch).toBe(false); + expect(result.allNodesUpgraded).toBe(true); + }); + + it('detects lower version nodes', () => { + const result = verifyAllMatchKibanaVersion([new SemVer('0.0.0')]); + expect(result.allNodesMatch).toBe(false); + expect(result.allNodesUpgraded).toBe(true); + }); + + it('detects if all are on same major correctly', () => { + const versions = [ + CURRENT_VERSION, + CURRENT_VERSION.inc('minor'), + CURRENT_VERSION.inc('minor').inc('minor'), + ]; + + const result = verifyAllMatchKibanaVersion(versions); + expect(result.allNodesMatch).toBe(true); + expect(result.allNodesUpgraded).toBe(false); + }); + + it('detects partial matches', () => { + const versions = [ + new SemVer('0.0.0'), + CURRENT_VERSION.inc('minor'), + CURRENT_VERSION.inc('minor').inc('minor'), + ]; + + const result = verifyAllMatchKibanaVersion(versions); + expect(result.allNodesMatch).toBe(false); + expect(result.allNodesUpgraded).toBe(false); + }); +}); + +describe('EsVersionPrecheck', () => { + it('returns a 403 when callCluster fails with a 403', async () => { + const fakeCall = jest.fn().mockRejectedValue({ status: 403 }); + + const ctx = { + core: { + elasticsearch: { + adminClient: { + callAsInternalUser: fakeCall, + }, + }, + }, + } as any; + + const result = await esVersionCheck(ctx, kibanaResponseFactory); + expect(result).toHaveProperty('status', 403); + }); + + it('returns a 426 message w/ allNodesUpgraded = false when nodes are not on same version', async () => { + const ctx = { + core: { + elasticsearch: { + adminClient: { + callAsInternalUser: jest.fn().mockResolvedValue({ + nodes: { + node1: { version: CURRENT_VERSION.raw }, + node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, + }, + }), + }, + }, + }, + } as any; + + const result = await esVersionCheck(ctx, kibanaResponseFactory); + expect(result).toHaveProperty('status', 426); + expect(result).toHaveProperty('payload.attributes.allNodesUpgraded', false); + }); + + it('returns a 426 message w/ allNodesUpgraded = true when nodes are on next version', async () => { + const ctx = { + core: { + elasticsearch: { + adminClient: { + callAsInternalUser: jest.fn().mockResolvedValue({ + nodes: { + node1: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, + node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, + }, + }), + }, + }, + }, + } as any; + + const result = await esVersionCheck(ctx, kibanaResponseFactory); + expect(result).toHaveProperty('status', 426); + expect(result).toHaveProperty('payload.attributes.allNodesUpgraded', true); + }); + + it('returns undefined when nodes are on same version', async () => { + const ctx = { + core: { + elasticsearch: { + adminClient: { + callAsInternalUser: jest.fn().mockResolvedValue({ + nodes: { + node1: { version: CURRENT_VERSION.raw }, + node2: { version: CURRENT_VERSION.raw }, + }, + }), + }, + }, + }, + } as any; + + await expect(esVersionCheck(ctx, kibanaResponseFactory)).resolves.toBe(undefined); + }); +}); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.ts new file mode 100644 index 0000000000000..2fb3effe43793 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { uniq } from 'lodash'; +import { SemVer } from 'semver'; +import { + IScopedClusterClient, + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'src/core/server'; +import { CURRENT_VERSION } from '../../../common/version'; + +/** + * Returns an array of all the unique Elasticsearch Node Versions in the Elasticsearch cluster. + */ +export const getAllNodeVersions = async (adminClient: IScopedClusterClient) => { + // Get the version information for all nodes in the cluster. + const { nodes } = (await adminClient.callAsInternalUser('nodes.info', { + filterPath: 'nodes.*.version', + })) as { nodes: { [nodeId: string]: { version: string } } }; + + const versionStrings = Object.values(nodes).map(({ version }) => version); + + return uniq(versionStrings) + .sort() + .map(version => new SemVer(version)); +}; + +export const verifyAllMatchKibanaVersion = (allNodeVersions: SemVer[]) => { + // Determine if all nodes in the cluster are running the same major version as Kibana. + const numDifferentVersion = allNodeVersions.filter( + esNodeVersion => esNodeVersion.major !== CURRENT_VERSION.major + ).length; + + const numSameVersion = allNodeVersions.filter( + esNodeVersion => esNodeVersion.major === CURRENT_VERSION.major + ).length; + + if (numDifferentVersion) { + return { + allNodesMatch: false, + // If Kibana is talking to nodes and none have the same major version as Kibana, they must a be of + // a higher major version. + allNodesUpgraded: numSameVersion === 0, + }; + } + return { + allNodesMatch: true, + allNodesUpgraded: false, + }; +}; + +/** + * This is intended as controller/handler level code so it knows about HTTP + */ +export const esVersionCheck = async ( + ctx: RequestHandlerContext, + response: KibanaResponseFactory +) => { + const { adminClient } = ctx.core.elasticsearch; + let allNodeVersions: SemVer[]; + + try { + allNodeVersions = await getAllNodeVersions(adminClient); + } catch (e) { + if (e.status === 403) { + return response.forbidden({ body: e.message }); + } + + throw e; + } + + const result = verifyAllMatchKibanaVersion(allNodeVersions); + if (!result.allNodesMatch) { + return response.customError({ + // 426 means "Upgrade Required" and is used when semver compatibility is not met. + statusCode: 426, + body: { + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + allNodesUpgraded: result.allNodesUpgraded, + }, + }, + }); + } +}; + +export const versionCheckHandlerWrapper = (handler: RequestHandler) => async ( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory +) => { + const errorResponse = await esVersionCheck(ctx, response); + if (errorResponse) { + return errorResponse; + } + return handler(ctx, request, response); +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.test.ts similarity index 95% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.test.ts index ce892df0de946..06fa755472238 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReindexSavedObject } from '../../../common/types'; +import { ReindexSavedObject } from '../../../../common/types'; import { Credential, credentialStoreFactory } from './credential_store'; describe('credentialStore', () => { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/credential_store.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.ts similarity index 96% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/credential_store.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.ts index 32f5ec9977b72..a051d16b5779f 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/credential_store.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.ts @@ -8,7 +8,7 @@ import { createHash } from 'crypto'; import { Request } from 'hapi'; import stringify from 'json-stable-stringify'; -import { ReindexSavedObject } from '../../../common/types'; +import { ReindexSavedObject } from '../../../../common/types'; export type Credential = Request['headers']; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/index.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/index.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/index_settings.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.test.ts similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/index_settings.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.test.ts index 9ec06b72f02e2..7b346cc87edf6 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/index_settings.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../common/version'; +import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../../common/version'; import { generateNewIndexName, getReindexWarnings, diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.ts similarity index 97% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.ts index f6dc471d0945d..0b95bc628fbb4 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.ts @@ -5,8 +5,8 @@ */ import { flow, omit } from 'lodash'; -import { ReindexWarning } from '../../../common/types'; -import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../common/version'; +import { ReindexWarning } from '../../../../common/types'; +import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../../common/version'; import { FlatSettings } from './types'; export interface ParsedIndexName { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.test.ts similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.test.ts index 4569fdfa33a83..3fb855958a5d0 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.test.ts @@ -13,8 +13,8 @@ import { ReindexSavedObject, ReindexStatus, ReindexStep, -} from '../../../common/types'; -import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../common/version'; +} from '../../../../common/types'; +import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../../common/version'; import { LOCK_WINDOW, ReindexActions, reindexActionsFactory } from './reindex_actions'; describe('ReindexActions', () => { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.ts similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.ts index a162186ff0059..6683f80c8e779 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.ts @@ -7,7 +7,7 @@ import moment from 'moment'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { SavedObjectsFindResponse, SavedObjectsClientContract } from 'src/core/server'; +import { SavedObjectsFindResponse, SavedObjectsClientContract } from 'kibana/server'; import { IndexGroup, REINDEX_OP_TYPE, @@ -15,7 +15,7 @@ import { ReindexSavedObject, ReindexStatus, ReindexStep, -} from '../../../common/types'; +} from '../../../../common/types'; import { generateNewIndexName } from './index_settings'; import { FlatSettings } from './types'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.test.ts similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.test.ts index 1216a8d2c4c24..9cd41c8cbe826 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.test.ts @@ -10,8 +10,8 @@ import { ReindexSavedObject, ReindexStatus, ReindexStep, -} from '../../../common/types'; -import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../common/version'; +} from '../../../../common/types'; +import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../../common/version'; import { isMlIndex, isWatcherIndex, diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.ts similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.ts index 41a4552b722de..0e6095f98b6ff 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.ts @@ -8,14 +8,14 @@ import Boom from 'boom'; import { Server } from 'hapi'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { XPackInfo } from '../../../../xpack_main/server/lib/xpack_info'; +import { XPackInfo } from '../../../../../xpack_main/server/lib/xpack_info'; import { IndexGroup, ReindexSavedObject, ReindexStatus, ReindexStep, ReindexWarning, -} from '../../../common/types'; +} from '../../../../common/types'; import { generateNewIndexName, getReindexWarnings, diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/types.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/types.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/types.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/types.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/worker.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/worker.ts similarity index 97% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/worker.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/worker.ts index 669eea623851c..628a47be9f5e7 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/reindexing/worker.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/worker.ts @@ -5,11 +5,11 @@ */ import { CallCluster, CallClusterWithRequest } from 'src/legacy/core_plugins/elasticsearch'; import { Request, Server } from 'src/legacy/server/kbn_server'; -import { SavedObjectsClientContract } from 'src/core/server'; +import { SavedObjectsClientContract } from 'kibana/server'; import moment from 'moment'; -import { XPackInfo } from '../../../../xpack_main/server/lib/xpack_info'; -import { ReindexSavedObject, ReindexStatus } from '../../../common/types'; +import { XPackInfo } from '../../../../../xpack_main/server/lib/xpack_info'; +import { ReindexSavedObject, ReindexStatus } from '../../../../common/types'; import { CredentialStore } from './credential_store'; import { reindexActionsFactory } from './reindex_actions'; import { ReindexService, reindexServiceFactory } from './reindex_service'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts index 94df444e2c1bb..4c378ba25430e 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../common/types'; +import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../../common/types'; import { upsertUIOpenOption } from './es_ui_open_apis'; /** diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.ts similarity index 75% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.ts index 7036dcc4ea1a7..b52b3b812b7f9 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.ts @@ -4,19 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import { UIOpen, UIOpenOption, UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE, - UpgradeAssistantTelemetryServer, -} from '../../../common/types'; +} from '../../../../common/types'; +import { RequestShim, ServerShim } from '../../types'; -async function incrementUIOpenOptionCounter( - server: UpgradeAssistantTelemetryServer, - uiOpenOptionCounter: UIOpenOption -) { +async function incrementUIOpenOptionCounter(server: ServerShim, uiOpenOptionCounter: UIOpenOption) { const { getSavedObjectsRepository } = server.savedObjects; const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); const internalRepository = getSavedObjectsRepository(callWithInternalUser); @@ -28,10 +24,7 @@ async function incrementUIOpenOptionCounter( ); } -export async function upsertUIOpenOption( - server: UpgradeAssistantTelemetryServer, - req: Legacy.Request -): Promise { +export async function upsertUIOpenOption(server: ServerShim, req: RequestShim): Promise { const { overview, cluster, indices } = req.payload as UIOpen; if (overview) { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts index a8a78470aabbe..26302de74743f 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../common/types'; +import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../../common/types'; import { upsertUIReindexOption } from './es_ui_reindex_apis'; /** diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.ts similarity index 86% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.ts index 3cb965523a80b..626d51b298e72 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.ts @@ -4,17 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import { UIReindex, UIReindexOption, UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE, - UpgradeAssistantTelemetryServer, -} from '../../../common/types'; +} from '../../../../common/types'; +import { RequestShim, ServerShim } from '../../types'; async function incrementUIReindexOptionCounter( - server: UpgradeAssistantTelemetryServer, + server: ServerShim, uiOpenOptionCounter: UIReindexOption ) { const { getSavedObjectsRepository } = server.savedObjects; @@ -29,8 +28,8 @@ async function incrementUIReindexOptionCounter( } export async function upsertUIReindexOption( - server: UpgradeAssistantTelemetryServer, - req: Legacy.Request + server: ServerShim, + req: RequestShim ): Promise { const { close, open, start, stop } = req.payload as UIReindex; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/index.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/index.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/index.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/index.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.test.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.test.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts similarity index 87% rename from x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts index 3a9b11a57f070..47a2cd5d51fd4 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts @@ -13,8 +13,8 @@ import { UpgradeAssistantTelemetry, UpgradeAssistantTelemetrySavedObject, UpgradeAssistantTelemetrySavedObjectAttributes, - UpgradeAssistantTelemetryServer, -} from '../../../common/types'; +} from '../../../../common/types'; +import { ServerShim } from '../../types'; import { isDeprecationLoggingEnabled } from '../es_deprecation_logging_apis'; async function getSavedObjectAttributesFromRepo( @@ -43,7 +43,7 @@ async function getDeprecationLoggingStatusValue(callCluster: any): Promise { const { getSavedObjectsRepository } = server.savedObjects; const savedObjectsRepository = getSavedObjectsRepository(callCluster); @@ -97,13 +97,12 @@ export async function fetchUpgradeAssistantMetrics( }; } -export function makeUpgradeAssistantUsageCollector(server: UpgradeAssistantTelemetryServer) { - const kbnServer = server as UpgradeAssistantTelemetryServer; - const upgradeAssistantUsageCollector = kbnServer.usage.collectorSet.makeUsageCollector({ +export function makeUpgradeAssistantUsageCollector(server: ServerShim) { + const upgradeAssistantUsageCollector = server.usage.collectorSet.makeUsageCollector({ type: UPGRADE_ASSISTANT_TYPE, isReady: () => true, fetch: async (callCluster: any) => fetchUpgradeAssistantMetrics(callCluster, server), }); - kbnServer.usage.collectorSet.register(upgradeAssistantUsageCollector); + server.usage.collectorSet.register(upgradeAssistantUsageCollector); } diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts new file mode 100644 index 0000000000000..7bc33142ca321 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Plugin, CoreSetup, CoreStart } from 'src/core/server'; +import { ServerShim, ServerShimWithRouter } from './types'; +import { credentialStoreFactory } from './lib/reindexing/credential_store'; +import { makeUpgradeAssistantUsageCollector } from './lib/telemetry'; +import { registerClusterCheckupRoutes } from './routes/cluster_checkup'; +import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging'; +import { registerReindexIndicesRoutes, registerReindexWorker } from './routes/reindex_indices'; + +import { registerTelemetryRoutes } from './routes/telemetry'; + +export class UpgradeAssistantServerPlugin implements Plugin { + setup({ http }: CoreSetup, { __LEGACY }: { __LEGACY: ServerShim }) { + const router = http.createRouter(); + const shimWithRouter: ServerShimWithRouter = { ...__LEGACY, router }; + registerClusterCheckupRoutes(shimWithRouter); + registerDeprecationLoggingRoutes(shimWithRouter); + + // The ReindexWorker uses a map of request headers that contain the authentication credentials + // for a given reindex. We cannot currently store these in an the .kibana index b/c we do not + // want to expose these credentials to any unauthenticated users. We also want to avoid any need + // to add a user for a special index just for upgrading. This in-memory cache allows us to + // process jobs without the browser staying on the page, but will require that jobs go into + // a paused state if no Kibana nodes have the required credentials. + const credentialStore = credentialStoreFactory(); + + const worker = registerReindexWorker(__LEGACY, credentialStore); + registerReindexIndicesRoutes(shimWithRouter, worker, credentialStore); + + // Bootstrap the needed routes and the collector for the telemetry + registerTelemetryRoutes(shimWithRouter); + makeUpgradeAssistantUsageCollector(__LEGACY); + } + + start(core: CoreStart, plugins: any) {} + + stop(): void {} +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/request.mock.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/request.mock.ts new file mode 100644 index 0000000000000..d09a66dbb4326 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/request.mock.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export const createRequestMock = (opts?: { + headers?: any; + params?: Record; + payload?: Record; +}) => { + return Object.assign({ headers: {} }, opts || {}); +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/routes.mock.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/routes.mock.ts new file mode 100644 index 0000000000000..3769bc389123e --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/routes.mock.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 } from 'kibana/server'; + +/** + * Creates a very crude mock of the new platform router implementation. This enables use to test + * controller/handler logic without making HTTP requests to an actual server. This does not enable + * us to test whether our paths actual match, only the response codes of controllers given certain + * inputs. This should be replaced by a more wholistic solution (like functional tests) eventually. + * + * This also bypasses any validation installed on the route. + */ +export const createMockRouter = () => { + const paths: Record>> = {}; + + const assign = (method: string) => ( + { path }: { path: string }, + handler: RequestHandler + ) => { + paths[method] = { + ...(paths[method] || {}), + ...{ [path]: handler }, + }; + }; + + return { + getHandler({ method, pathPattern }: { method: string; pathPattern: string }) { + return paths[method][pathPattern]; + }, + get: assign('get'), + post: assign('post'), + put: assign('put'), + patch: assign('patch'), + }; +}; + +export type MockRouter = ReturnType; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.test.ts similarity index 53% rename from x-pack/legacy/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.test.ts index 04a05dc2b0e9c..6afb9d2a5e935 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.test.ts @@ -3,33 +3,20 @@ * 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 } from 'src/core/server'; +import { createMockRouter, MockRouter } from './__mocks__/routes.mock'; +import { createRequestMock } from './__mocks__/request.mock'; -import Boom from 'boom'; -import { Server } from 'hapi'; - -jest.mock('../lib/es_version_precheck'); -import { EsVersionPrecheck } from '../lib/es_version_precheck'; -import { registerClusterCheckupRoutes } from './cluster_checkup'; +jest.mock('../lib/es_version_precheck', () => ({ + versionCheckHandlerWrapper: (a: any) => a, +})); // Need to require to get mock on named export to work. // eslint-disable-next-line @typescript-eslint/no-var-requires const MigrationApis = require('../lib/es_migration_apis'); MigrationApis.getUpgradeAssistantStatus = jest.fn(); -function register(plugins = {}) { - const server = new Server(); - server.plugins = { - elasticsearch: { - getCluster: () => ({ callWithRequest: jest.fn() } as any), - } as any, - ...plugins, - } as any; - server.config = () => ({ get: () => '' } as any); - - registerClusterCheckupRoutes(server); - - return server; -} +import { registerClusterCheckupRoutes } from './cluster_checkup'; /** * Since these route callbacks are so thin, these serve simply as integration tests @@ -37,50 +24,65 @@ function register(plugins = {}) { * more thoroughly in the es_migration_apis test. */ describe('cluster checkup API', () => { - const spy = jest.spyOn(MigrationApis, 'getUpgradeAssistantStatus'); - afterEach(() => jest.clearAllMocks()); - describe('with cloud enabled', () => { - it('is provided to getUpgradeAssistantStatus', async () => { - const server = register({ + let mockRouter: MockRouter; + let serverShim: any; + let ctxMock: any; + + beforeEach(() => { + mockRouter = createMockRouter(); + ctxMock = { + core: {}, + }; + serverShim = { + router: mockRouter, + plugins: { cloud: { config: { isCloudEnabled: true, }, }, - }); + elasticsearch: { + getCluster: () => ({ callWithRequest: jest.fn() } as any), + } as any, + }, + }; + registerClusterCheckupRoutes(serverShim); + }); + + describe('with cloud enabled', () => { + it('is provided to getUpgradeAssistantStatus', async () => { + const spy = jest.spyOn(MigrationApis, 'getUpgradeAssistantStatus'); MigrationApis.getUpgradeAssistantStatus.mockResolvedValue({ cluster: [], indices: [], nodes: [], }); - await server.inject({ - method: 'GET', - url: '/api/upgrade_assistant/status', - }); + await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(ctxMock, createRequestMock(), kibanaResponseFactory); expect(spy.mock.calls[0][2]).toBe(true); }); }); describe('GET /api/upgrade_assistant/reindex/{indexName}.json', () => { - const server = register(); - it('returns state', async () => { MigrationApis.getUpgradeAssistantStatus.mockResolvedValue({ cluster: [], indices: [], nodes: [], }); - const resp = await server.inject({ - method: 'GET', - url: '/api/upgrade_assistant/status', - }); + const resp = await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(ctxMock, createRequestMock(), kibanaResponseFactory); - expect(resp.statusCode).toEqual(200); - expect(resp.payload).toMatchInlineSnapshot( + expect(resp.status).toEqual(200); + expect(JSON.stringify(resp.payload)).toMatchInlineSnapshot( `"{\\"cluster\\":[],\\"indices\\":[],\\"nodes\\":[]}"` ); }); @@ -90,35 +92,23 @@ describe('cluster checkup API', () => { e.status = 403; MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(e); - const resp = await server.inject({ - method: 'GET', - url: '/api/upgrade_assistant/status', - }); + const resp = await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(ctxMock, createRequestMock(), kibanaResponseFactory); - expect(resp.statusCode).toEqual(403); + expect(resp.status).toEqual(403); }); it('returns an 500 error if it throws', async () => { MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(new Error(`scary error!`)); - const resp = await server.inject({ - method: 'GET', - url: '/api/upgrade_assistant/status', - }); - expect(resp.statusCode).toEqual(500); - }); - - it('returns a 426 if EsVersionCheck throws', async () => { - (EsVersionPrecheck.method as jest.Mock).mockRejectedValue( - new Boom(`blah`, { statusCode: 426 }) - ); - - const resp = await server.inject({ - method: 'GET', - url: '/api/upgrade_assistant/status', - }); + const resp = await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(ctxMock, createRequestMock(), kibanaResponseFactory); - expect(resp.statusCode).toEqual(426); + expect(resp.status).toEqual(500); }); }); }); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.ts new file mode 100644 index 0000000000000..3cfa567755b0f --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.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 _ from 'lodash'; +import { ServerShimWithRouter } from '../types'; +import { getUpgradeAssistantStatus } from '../lib/es_migration_apis'; +import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; + +import { createRequestShim } from './create_request_shim'; + +export function registerClusterCheckupRoutes(server: ServerShimWithRouter) { + const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); + const isCloudEnabled = _.get(server.plugins, 'cloud.config.isCloudEnabled', false); + + server.router.get( + { + path: '/api/upgrade_assistant/status', + validate: false, + }, + versionCheckHandlerWrapper(async (ctx, request, response) => { + const reqShim = createRequestShim(request); + try { + return response.ok({ + body: await getUpgradeAssistantStatus(callWithRequest, reqShim, isCloudEnabled), + }); + } catch (e) { + if (e.status === 403) { + return response.forbidden(e.message); + } + + return response.internalError({ body: e }); + } + }) + ); +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/create_request_shim.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/create_request_shim.ts new file mode 100644 index 0000000000000..b1a5c8b72d0e0 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/create_request_shim.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 { KibanaRequest } from 'kibana/server'; +import { RequestShim } from '../types'; + +export const createRequestShim = (req: KibanaRequest): RequestShim => { + return { + headers: req.headers as Record, + payload: req.body || (req as any).payload, + params: req.params, + }; +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.test.ts new file mode 100644 index 0000000000000..c488f999b538e --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 } from 'src/core/server'; +import { createMockRouter, MockRouter } from './__mocks__/routes.mock'; +import { createRequestMock } from './__mocks__/request.mock'; + +jest.mock('../lib/es_version_precheck', () => ({ + versionCheckHandlerWrapper: (a: any) => a, +})); + +import { registerDeprecationLoggingRoutes } from './deprecation_logging'; + +/** + * Since these route callbacks are so thin, these serve simply as integration tests + * to ensure they're wired up to the lib functions correctly. Business logic is tested + * more thoroughly in the es_deprecation_logging_apis test. + */ +describe('deprecation logging API', () => { + let mockRouter: MockRouter; + let serverShim: any; + let callWithRequest: any; + const ctxMock: any = {}; + + beforeEach(() => { + mockRouter = createMockRouter(); + callWithRequest = jest.fn(); + serverShim = { + router: mockRouter, + plugins: { + cloud: { + config: { + isCloudEnabled: true, + }, + }, + elasticsearch: { + getCluster: () => ({ callWithRequest } as any), + } as any, + }, + }; + registerDeprecationLoggingRoutes(serverShim); + }); + + describe('GET /api/upgrade_assistant/deprecation_logging', () => { + it('returns isEnabled', async () => { + callWithRequest.mockResolvedValue({ default: { logger: { deprecation: 'WARN' } } }); + const resp = await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/deprecation_logging', + })(ctxMock, createRequestMock(), kibanaResponseFactory); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ isEnabled: true }); + }); + + it('returns an error if it throws', async () => { + callWithRequest.mockRejectedValue(new Error(`scary error!`)); + const resp = await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/deprecation_logging', + })(ctxMock, createRequestMock(), kibanaResponseFactory); + + expect(resp.status).toEqual(500); + }); + }); + + describe('PUT /api/upgrade_assistant/deprecation_logging', () => { + it('returns isEnabled', async () => { + callWithRequest.mockResolvedValue({ default: { logger: { deprecation: 'ERROR' } } }); + const resp = await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/deprecation_logging', + })(ctxMock, createRequestMock(), kibanaResponseFactory); + + expect(resp.payload).toEqual({ isEnabled: false }); + }); + + it('returns an error if it throws', async () => { + callWithRequest.mockRejectedValue(new Error(`scary error!`)); + const resp = await serverShim.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/deprecation_logging', + })(ctxMock, { body: { isEnabled: false } }, kibanaResponseFactory); + + expect(resp.status).toEqual(500); + }); + }); +}); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.ts new file mode 100644 index 0000000000000..7e19ef3fb6047 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { + getDeprecationLoggingStatus, + setDeprecationLogging, +} from '../lib/es_deprecation_logging_apis'; +import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; +import { ServerShimWithRouter } from '../types'; +import { createRequestShim } from './create_request_shim'; + +export function registerDeprecationLoggingRoutes(server: ServerShimWithRouter) { + const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); + + server.router.get( + { + path: '/api/upgrade_assistant/deprecation_logging', + validate: false, + }, + versionCheckHandlerWrapper(async (ctx, request, response) => { + const reqShim = createRequestShim(request); + try { + const result = await getDeprecationLoggingStatus(callWithRequest, reqShim); + return response.ok({ body: result }); + } catch (e) { + return response.internalError({ body: e }); + } + }) + ); + + server.router.put( + { + path: '/api/upgrade_assistant/deprecation_logging', + validate: { + body: schema.object({ + isEnabled: schema.boolean(), + }), + }, + }, + versionCheckHandlerWrapper(async (ctx, request, response) => { + const reqShim = createRequestShim(request); + try { + const { isEnabled } = reqShim.payload as { isEnabled: boolean }; + return response.ok({ + body: await setDeprecationLogging(callWithRequest, reqShim, isEnabled), + }); + } catch (e) { + return response.internalError({ body: e }); + } + }) + ); +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/routes/reindex_indices.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.test.ts similarity index 58% rename from x-pack/legacy/plugins/upgrade_assistant/server/routes/reindex_indices.test.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.test.ts index 264c98526622c..d520324239656 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/routes/reindex_indices.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.test.ts @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Server } from 'hapi'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory } from 'src/core/server'; +import { createMockRouter, MockRouter } from './__mocks__/routes.mock'; +import { createRequestMock } from './__mocks__/request.mock'; const mockReindexService = { hasRequiredPrivileges: jest.fn(), @@ -18,14 +21,22 @@ const mockReindexService = { cancelReindexing: jest.fn(), }; -jest.mock('../lib/es_version_precheck'); +jest.mock('../lib/es_version_precheck', () => ({ + versionCheckHandlerWrapper: (a: any) => a, +})); + jest.mock('../lib/reindexing', () => { return { reindexServiceFactory: () => mockReindexService, }; }); -import { IndexGroup, ReindexSavedObject, ReindexStatus, ReindexWarning } from '../../common/types'; +import { + IndexGroup, + ReindexSavedObject, + ReindexStatus, + ReindexWarning, +} from '../../../common/types'; import { credentialStoreFactory } from '../lib/reindexing/credential_store'; import { registerReindexIndicesRoutes } from './reindex_indices'; @@ -35,28 +46,36 @@ import { registerReindexIndicesRoutes } from './reindex_indices'; * more thoroughly in the es_migration_apis test. */ describe('reindex API', () => { - const server = new Server(); - server.plugins = { - elasticsearch: { - getCluster: () => ({ callWithRequest: jest.fn() } as any), - } as any, - xpack_main: { - info: {}, - }, - } as any; - server.config = () => ({ get: () => '' } as any); - server.decorate('request', 'getSavedObjectsClient', () => jest.fn()); + let serverShim: any; + let mockRouter: MockRouter; + let ctxMock: any; const credentialStore = credentialStoreFactory(); - const worker = { includes: jest.fn(), forceRefresh: jest.fn(), } as any; - registerReindexIndicesRoutes(server, worker, credentialStore); - beforeEach(() => { + ctxMock = { + core: { + savedObjects: savedObjectsClientMock.create(), + }, + }; + mockRouter = createMockRouter(); + serverShim = { + router: mockRouter, + plugins: { + xpack_main: { + info: jest.fn(), + }, + elasticsearch: { + getCluster: () => ({ callWithRequest: jest.fn() } as any), + } as any, + }, + }; + registerReindexIndicesRoutes(serverShim, worker, credentialStore); + mockReindexService.hasRequiredPrivileges.mockResolvedValue(true); mockReindexService.detectReindexWarnings.mockReset(); mockReindexService.getIndexGroup.mockReset(); @@ -73,6 +92,8 @@ describe('reindex API', () => { credentialStore.clear(); }); + afterEach(() => jest.clearAllMocks()); + describe('GET /api/upgrade_assistant/reindex/{indexName}', () => { it('returns the attributes of the reindex operation and reindex warnings', async () => { mockReindexService.findReindexOperation.mockResolvedValueOnce({ @@ -80,18 +101,18 @@ describe('reindex API', () => { }); mockReindexService.detectReindexWarnings.mockResolvedValueOnce([ReindexWarning.allField]); - const resp = await server.inject({ - method: 'GET', - url: `/api/upgrade_assistant/reindex/wowIndex`, - }); + const resp = await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/reindex/{indexName}', + })(ctxMock, createRequestMock({ params: { indexName: 'wowIndex' } }), kibanaResponseFactory); // It called into the service correctly expect(mockReindexService.findReindexOperation).toHaveBeenCalledWith('wowIndex'); expect(mockReindexService.detectReindexWarnings).toHaveBeenCalledWith('wowIndex'); // It returned the right results - expect(resp.statusCode).toEqual(200); - const data = JSON.parse(resp.payload); + expect(resp.status).toEqual(200); + const data = resp.payload; expect(data.reindexOp).toEqual({ indexName: 'wowIndex', status: ReindexStatus.inProgress }); expect(data.warnings).toEqual([0]); }); @@ -100,13 +121,13 @@ describe('reindex API', () => { mockReindexService.findReindexOperation.mockResolvedValueOnce(null); mockReindexService.detectReindexWarnings.mockResolvedValueOnce(null); - const resp = await server.inject({ - method: 'GET', - url: `/api/upgrade_assistant/reindex/anIndex`, - }); + const resp = await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/reindex/{indexName}', + })(ctxMock, createRequestMock({ params: { indexName: 'anIndex' } }), kibanaResponseFactory); - expect(resp.statusCode).toEqual(200); - const data = JSON.parse(resp.payload); + expect(resp.status).toEqual(200); + const data = resp.payload; expect(data.reindexOp).toBeNull(); expect(data.warnings).toBeNull(); }); @@ -116,13 +137,13 @@ describe('reindex API', () => { mockReindexService.detectReindexWarnings.mockResolvedValueOnce([]); mockReindexService.getIndexGroup.mockReturnValue(IndexGroup.ml); - const resp = await server.inject({ - method: 'GET', - url: `/api/upgrade_assistant/reindex/.ml-state`, - }); + const resp = await serverShim.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/reindex/{indexName}', + })(ctxMock, createRequestMock({ params: { indexName: 'anIndex' } }), kibanaResponseFactory); - expect(resp.statusCode).toEqual(200); - const data = JSON.parse(resp.payload); + expect(resp.status).toEqual(200); + const data = resp.payload; expect(data.indexGroup).toEqual(IndexGroup.ml); }); }); @@ -133,17 +154,17 @@ describe('reindex API', () => { attributes: { indexName: 'theIndex' }, }); - const resp = await server.inject({ - method: 'POST', - url: '/api/upgrade_assistant/reindex/theIndex', - }); + const resp = await serverShim.router.getHandler({ + method: 'post', + pathPattern: '/api/upgrade_assistant/reindex/{indexName}', + })(ctxMock, createRequestMock({ params: { indexName: 'theIndex' } }), kibanaResponseFactory); // It called create correctly expect(mockReindexService.createReindexOperation).toHaveBeenCalledWith('theIndex'); // It returned the right results - expect(resp.statusCode).toEqual(200); - const data = JSON.parse(resp.payload); + expect(resp.status).toEqual(200); + const data = resp.payload; expect(data).toEqual({ indexName: 'theIndex' }); }); @@ -152,10 +173,10 @@ describe('reindex API', () => { attributes: { indexName: 'theIndex' }, }); - await server.inject({ - method: 'POST', - url: '/api/upgrade_assistant/reindex/theIndex', - }); + await serverShim.router.getHandler({ + method: 'post', + pathPattern: '/api/upgrade_assistant/reindex/{indexName}', + })(ctxMock, createRequestMock({ params: { indexName: 'theIndex' } }), kibanaResponseFactory); expect(worker.forceRefresh).toHaveBeenCalled(); }); @@ -166,13 +187,19 @@ describe('reindex API', () => { } as ReindexSavedObject; mockReindexService.createReindexOperation.mockResolvedValueOnce(reindexOp); - await server.inject({ - method: 'POST', - url: '/api/upgrade_assistant/reindex/theIndex', - headers: { - 'kbn-auth-x': 'HERE!', - }, - }); + await serverShim.router.getHandler({ + method: 'post', + pathPattern: '/api/upgrade_assistant/reindex/{indexName}', + })( + ctxMock, + createRequestMock({ + headers: { + 'kbn-auth-x': 'HERE!', + }, + params: { indexName: 'theIndex' }, + }), + kibanaResponseFactory + ); expect(credentialStore.get(reindexOp)!['kbn-auth-x']).toEqual('HERE!'); }); @@ -185,30 +212,41 @@ describe('reindex API', () => { attributes: { indexName: 'theIndex', status: ReindexStatus.inProgress }, }); - const resp = await server.inject({ - method: 'POST', - url: '/api/upgrade_assistant/reindex/theIndex', - }); - + const resp = await serverShim.router.getHandler({ + method: 'post', + pathPattern: '/api/upgrade_assistant/reindex/{indexName}', + })( + ctxMock, + createRequestMock({ + params: { indexName: 'theIndex' }, + }), + kibanaResponseFactory + ); // It called resume correctly expect(mockReindexService.resumeReindexOperation).toHaveBeenCalledWith('theIndex'); expect(mockReindexService.createReindexOperation).not.toHaveBeenCalled(); // It returned the right results - expect(resp.statusCode).toEqual(200); - const data = JSON.parse(resp.payload); + expect(resp.status).toEqual(200); + const data = resp.payload; expect(data).toEqual({ indexName: 'theIndex', status: ReindexStatus.inProgress }); }); it('returns a 403 if required privileges fails', async () => { mockReindexService.hasRequiredPrivileges.mockResolvedValueOnce(false); - const resp = await server.inject({ - method: 'POST', - url: '/api/upgrade_assistant/reindex/theIndex', - }); - - expect(resp.statusCode).toEqual(403); + const resp = await serverShim.router.getHandler({ + method: 'post', + pathPattern: '/api/upgrade_assistant/reindex/{indexName}', + })( + ctxMock, + createRequestMock({ + params: { indexName: 'theIndex' }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(403); }); }); @@ -216,13 +254,19 @@ describe('reindex API', () => { it('returns a 501', async () => { mockReindexService.cancelReindexing.mockResolvedValueOnce({}); - const resp = await server.inject({ - method: 'POST', - url: '/api/upgrade_assistant/reindex/cancelMe/cancel', - }); - - expect(resp.statusCode).toEqual(200); - expect(resp.payload).toMatchInlineSnapshot(`"{\\"acknowledged\\":true}"`); + const resp = await serverShim.router.getHandler({ + method: 'post', + pathPattern: '/api/upgrade_assistant/reindex/{indexName}/cancel', + })( + ctxMock, + createRequestMock({ + params: { indexName: 'cancelMe' }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ acknowledged: true }); expect(mockReindexService.cancelReindexing).toHaveBeenCalledWith('cancelMe'); }); }); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/routes/reindex_indices.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.ts similarity index 56% rename from x-pack/legacy/plugins/upgrade_assistant/server/routes/reindex_indices.ts rename to x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.ts index 43e4c9899d233..c22f12316bd02 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/routes/reindex_indices.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.ts @@ -4,18 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; -import { Server } from 'hapi'; - +import { schema } from '@kbn/config-schema'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { SavedObjectsClientContract } from 'src/core/server'; -import { ReindexStatus } from '../../common/types'; -import { EsVersionPrecheck } from '../lib/es_version_precheck'; +import { SavedObjectsClientContract } from 'kibana/server'; +import { ReindexStatus } from '../../../common/types'; +import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; import { reindexServiceFactory, ReindexWorker } from '../lib/reindexing'; import { CredentialStore } from '../lib/reindexing/credential_store'; import { reindexActionsFactory } from '../lib/reindexing/reindex_actions'; +import { ServerShim, ServerShimWithRouter } from '../types'; +import { createRequestShim } from './create_request_shim'; -export function registerReindexWorker(server: Server, credentialStore: CredentialStore) { +export function registerReindexWorker(server: ServerShim, credentialStore: CredentialStore) { const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster( 'admin' ); @@ -29,11 +29,8 @@ export function registerReindexWorker(server: Server, credentialStore: Credentia // Cannot pass server.log directly because it's value changes during startup (?). // Use this function to proxy through. - const log: Server['log'] = ( - tags: string | string[], - data?: string | object | (() => any), - timestamp?: number - ) => server.log(tags, data, timestamp); + const log = (tags: string | string[], data?: string | object | (() => any), timestamp?: number) => + server.log(tags, data, timestamp); const worker = new ReindexWorker( savedObjectsClient, @@ -54,7 +51,7 @@ export function registerReindexWorker(server: Server, credentialStore: Credentia } export function registerReindexIndicesRoutes( - server: Server, + server: ServerShimWithRouter, worker: ReindexWorker, credentialStore: CredentialStore ) { @@ -63,16 +60,20 @@ export function registerReindexIndicesRoutes( const BASE_PATH = '/api/upgrade_assistant/reindex'; // Start reindex for an index - server.route({ - path: `${BASE_PATH}/{indexName}`, - method: 'POST', - options: { - pre: [EsVersionPrecheck], + server.router.post( + { + path: `${BASE_PATH}/{indexName}`, + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, }, - async handler(request) { - const client = request.getSavedObjectsClient(); - const { indexName } = request.params; - const callCluster = callWithRequest.bind(null, request) as CallCluster; + versionCheckHandlerWrapper(async (ctx, request, response) => { + const reqShim = createRequestShim(request); + const { indexName } = reqShim.params; + const { client } = ctx.core.savedObjects; + const callCluster = callWithRequest.bind(null, reqShim) as CallCluster; const reindexActions = reindexActionsFactory(client, callCluster); const reindexService = reindexServiceFactory( callCluster, @@ -83,7 +84,9 @@ export function registerReindexIndicesRoutes( try { if (!(await reindexService.hasRequiredPrivileges(indexName))) { - throw Boom.forbidden(`You do not have adequate privileges to reindex this index.`); + return response.forbidden({ + body: `You do not have adequate privileges to reindex this index.`, + }); } const existingOp = await reindexService.findReindexOperation(indexName); @@ -95,33 +98,33 @@ export function registerReindexIndicesRoutes( : await reindexService.createReindexOperation(indexName); // Add users credentials for the worker to use - credentialStore.set(reindexOp, request.headers); + credentialStore.set(reindexOp, reqShim.headers); // Kick the worker on this node to immediately pickup the new reindex operation. worker.forceRefresh(); - return reindexOp.attributes; + return response.ok({ body: reindexOp.attributes }); } catch (e) { - if (!e.isBoom) { - return Boom.boomify(e, { statusCode: 500 }); - } - - return e; + return response.internalError({ body: e }); } - }, - }); + }) + ); // Get status - server.route({ - path: `${BASE_PATH}/{indexName}`, - method: 'GET', - options: { - pre: [EsVersionPrecheck], + server.router.get( + { + path: `${BASE_PATH}/{indexName}`, + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, }, - async handler(request) { - const client = request.getSavedObjectsClient(); - const { indexName } = request.params; - const callCluster = callWithRequest.bind(null, request) as CallCluster; + versionCheckHandlerWrapper(async (ctx, request, response) => { + const reqShim = createRequestShim(request); + const { client } = ctx.core.savedObjects; + const { indexName } = reqShim.params; + const callCluster = callWithRequest.bind(null, reqShim) as CallCluster; const reindexActions = reindexActionsFactory(client, callCluster); const reindexService = reindexServiceFactory( callCluster, @@ -139,33 +142,43 @@ export function registerReindexIndicesRoutes( : []; const indexGroup = reindexService.getIndexGroup(indexName); - return { - reindexOp: reindexOp ? reindexOp.attributes : null, - warnings, - indexGroup, - hasRequiredPrivileges, - }; + return response.ok({ + body: { + reindexOp: reindexOp ? reindexOp.attributes : null, + warnings, + indexGroup, + hasRequiredPrivileges, + }, + }); } catch (e) { if (!e.isBoom) { - return Boom.boomify(e, { statusCode: 500 }); + return response.internalError({ body: e }); } - - return e; + return response.customError({ + body: { + message: e.message, + }, + statusCode: e.statusCode, + }); } - }, - }); + }) + ); // Cancel reindex - server.route({ - path: `${BASE_PATH}/{indexName}/cancel`, - method: 'POST', - options: { - pre: [EsVersionPrecheck], + server.router.post( + { + path: `${BASE_PATH}/{indexName}/cancel`, + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, }, - async handler(request) { - const client = request.getSavedObjectsClient(); - const { indexName } = request.params; - const callCluster = callWithRequest.bind(null, request) as CallCluster; + versionCheckHandlerWrapper(async (ctx, request, response) => { + const reqShim = createRequestShim(request); + const { indexName } = reqShim.params; + const { client } = ctx.core.savedObjects; + const callCluster = callWithRequest.bind(null, reqShim) as CallCluster; const reindexActions = reindexActionsFactory(client, callCluster); const reindexService = reindexServiceFactory( callCluster, @@ -177,14 +190,18 @@ export function registerReindexIndicesRoutes( try { await reindexService.cancelReindexing(indexName); - return { acknowledged: true }; + return response.ok({ body: { acknowledged: true } }); } catch (e) { if (!e.isBoom) { - return Boom.boomify(e, { statusCode: 500 }); + return response.internalError({ body: e }); } - - return e; + return response.customError({ + body: { + message: e.message, + }, + statusCode: e.statusCode, + }); } - }, - }); + }) + ); } diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.test.ts new file mode 100644 index 0000000000000..582c75e3701b6 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.test.ts @@ -0,0 +1,190 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 } from 'src/core/server'; +import { createMockRouter, MockRouter } from './__mocks__/routes.mock'; +import { createRequestMock } from './__mocks__/request.mock'; + +jest.mock('../lib/telemetry/es_ui_open_apis', () => ({ + upsertUIOpenOption: jest.fn(), +})); + +jest.mock('../lib/telemetry/es_ui_reindex_apis', () => ({ + upsertUIReindexOption: jest.fn(), +})); + +import { upsertUIOpenOption } from '../lib/telemetry/es_ui_open_apis'; +import { upsertUIReindexOption } from '../lib/telemetry/es_ui_reindex_apis'; +import { registerTelemetryRoutes } from './telemetry'; + +/** + * Since these route callbacks are so thin, these serve simply as integration tests + * to ensure they're wired up to the lib functions correctly. Business logic is tested + * more thoroughly in the lib/telemetry tests. + */ +describe('Upgrade Assistant Telemetry API', () => { + let serverShim: any; + let mockRouter: MockRouter; + let ctxMock: any; + beforeEach(() => { + ctxMock = {}; + mockRouter = createMockRouter(); + serverShim = { + router: mockRouter, + plugins: { + xpack_main: { + info: jest.fn(), + }, + elasticsearch: { + getCluster: () => ({ callWithRequest: jest.fn() } as any), + } as any, + }, + }; + registerTelemetryRoutes(serverShim); + }); + afterEach(() => jest.clearAllMocks()); + + describe('PUT /api/upgrade_assistant/telemetry/ui_open', () => { + it('returns correct payload with single option', async () => { + const returnPayload = { + overview: true, + cluster: false, + indices: false, + }; + + (upsertUIOpenOption as jest.Mock).mockResolvedValue(returnPayload); + + const resp = await serverShim.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/telemetry/ui_open', + })(ctxMock, createRequestMock(), kibanaResponseFactory); + + expect(resp.payload).toEqual(returnPayload); + }); + + it('returns correct payload with multiple option', async () => { + const returnPayload = { + overview: true, + cluster: true, + indices: true, + }; + + (upsertUIOpenOption as jest.Mock).mockResolvedValue(returnPayload); + + const resp = await serverShim.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/telemetry/ui_open', + })( + ctxMock, + createRequestMock({ + payload: { + overview: true, + cluster: true, + indices: true, + }, + }), + kibanaResponseFactory + ); + + expect(resp.payload).toEqual(returnPayload); + }); + + it('returns an error if it throws', async () => { + (upsertUIOpenOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + + const resp = await serverShim.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/telemetry/ui_open', + })( + ctxMock, + createRequestMock({ + payload: { + overview: false, + }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(500); + }); + }); + + describe('PUT /api/upgrade_assistant/telemetry/ui_reindex', () => { + it('returns correct payload with single option', async () => { + const returnPayload = { + close: false, + open: false, + start: true, + stop: false, + }; + + (upsertUIReindexOption as jest.Mock).mockRejectedValue(returnPayload); + + const resp = await serverShim.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', + })( + ctxMock, + createRequestMock({ + payload: { + overview: false, + }, + }), + kibanaResponseFactory + ); + + expect(resp.payload).toEqual(returnPayload); + }); + + it('returns correct payload with multiple option', async () => { + const returnPayload = { + close: true, + open: true, + start: true, + stop: true, + }; + + (upsertUIReindexOption as jest.Mock).mockRejectedValue(returnPayload); + + const resp = await serverShim.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', + })( + ctxMock, + createRequestMock({ + payload: { + close: true, + open: true, + start: true, + stop: true, + }, + }), + kibanaResponseFactory + ); + + expect(resp.payload).toEqual(returnPayload); + }); + + it('returns an error if it throws', async () => { + (upsertUIReindexOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + + const resp = await serverShim.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', + })( + ctxMock, + createRequestMock({ + payload: { + start: false, + }, + }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(500); + }); + }); +}); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.ts new file mode 100644 index 0000000000000..f08c49809033d --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { upsertUIOpenOption } from '../lib/telemetry/es_ui_open_apis'; +import { upsertUIReindexOption } from '../lib/telemetry/es_ui_reindex_apis'; +import { ServerShimWithRouter } from '../types'; +import { createRequestShim } from './create_request_shim'; + +export function registerTelemetryRoutes(server: ServerShimWithRouter) { + server.router.put( + { + path: '/api/upgrade_assistant/telemetry/ui_open', + validate: { + body: schema.object({ + overview: schema.boolean({ defaultValue: false }), + cluster: schema.boolean({ defaultValue: false }), + indices: schema.boolean({ defaultValue: false }), + }), + }, + }, + async (ctx, request, response) => { + const reqShim = createRequestShim(request); + try { + return response.ok({ body: await upsertUIOpenOption(server, reqShim) }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); + + server.router.put( + { + path: '/api/upgrade_assistant/telemetry/ui_reindex', + validate: { + body: schema.object({ + close: schema.boolean({ defaultValue: false }), + open: schema.boolean({ defaultValue: false }), + start: schema.boolean({ defaultValue: false }), + stop: schema.boolean({ defaultValue: false }), + }), + }, + }, + async (ctx, request, response) => { + const reqShim = createRequestShim(request); + try { + return response.ok({ body: await upsertUIReindexOption(server, reqShim) }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/types.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/types.ts new file mode 100644 index 0000000000000..dc4ecbc806320 --- /dev/null +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/types.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Legacy } from 'kibana'; +import { IRouter } from 'src/core/server'; +import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; +import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; + +export interface ServerShim { + usage: { + collectorSet: { + makeUsageCollector: any; + register: any; + }; + }; + plugins: { + elasticsearch: ElasticsearchPlugin; + xpack_main: XPackMainPlugin; + cloud: { + config: { + isCloudEnabled: boolean; + }; + }; + }; + log: any; + events: any; + savedObjects: Legacy.SavedObjectsService; +} + +export interface ServerShimWithRouter extends ServerShim { + router: IRouter; +} + +export interface RequestShim { + headers: Record; + payload: any; + params: any; +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/legacy/plugins/upgrade_assistant/server/routes/cluster_checkup.ts deleted file mode 100644 index 21c7bc4e5e65d..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/routes/cluster_checkup.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 Boom from 'boom'; -import { Legacy } from 'kibana'; -import _ from 'lodash'; - -import { getUpgradeAssistantStatus } from '../lib/es_migration_apis'; -import { EsVersionPrecheck } from '../lib/es_version_precheck'; - -export function registerClusterCheckupRoutes(server: Legacy.Server) { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); - const isCloudEnabled = _.get(server.plugins, 'cloud.config.isCloudEnabled', false); - - server.route({ - path: '/api/upgrade_assistant/status', - method: 'GET', - options: { - pre: [EsVersionPrecheck], - }, - async handler(request) { - try { - return await getUpgradeAssistantStatus(callWithRequest, request, isCloudEnabled); - } catch (e) { - if (e.status === 403) { - return Boom.forbidden(e.message); - } - - return Boom.boomify(e, { - statusCode: 500, - }); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts deleted file mode 100644 index e7918835d461d..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts +++ /dev/null @@ -1,78 +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 { Server } from 'hapi'; - -jest.mock('../lib/es_version_precheck'); -import { registerDeprecationLoggingRoutes } from './deprecation_logging'; - -/** - * Since these route callbacks are so thin, these serve simply as integration tests - * to ensure they're wired up to the lib functions correctly. Business logic is tested - * more thoroughly in the es_deprecation_logging_apis test. - */ -describe('deprecation logging API', () => { - const callWithRequest = jest.fn(); - const server = new Server(); - server.plugins = { - elasticsearch: { - getCluster: () => ({ callWithRequest } as any), - } as any, - } as any; - - registerDeprecationLoggingRoutes(server); - - describe('GET /api/upgrade_assistant/deprecation_logging', () => { - it('returns isEnabled', async () => { - callWithRequest.mockResolvedValue({ default: { logger: { deprecation: 'WARN' } } }); - const resp = await server.inject({ - method: 'GET', - url: '/api/upgrade_assistant/deprecation_logging', - }); - - expect(resp.statusCode).toEqual(200); - expect(JSON.parse(resp.payload)).toEqual({ isEnabled: true }); - }); - - it('returns an error if it throws', async () => { - callWithRequest.mockRejectedValue(new Error(`scary error!`)); - const resp = await server.inject({ - method: 'GET', - url: '/api/upgrade_assistant/deprecation_logging', - }); - - expect(resp.statusCode).toEqual(500); - }); - }); - - describe('PUT /api/upgrade_assistant/deprecation_logging', () => { - it('returns isEnabled', async () => { - callWithRequest.mockResolvedValue({ default: { logger: { deprecation: 'ERROR' } } }); - const resp = await server.inject({ - method: 'GET', - url: '/api/upgrade_assistant/deprecation_logging', - payload: { - isEnabled: false, - }, - }); - - expect(JSON.parse(resp.payload)).toEqual({ isEnabled: false }); - }); - - it('returns an error if it throws', async () => { - callWithRequest.mockRejectedValue(new Error(`scary error!`)); - const resp = await server.inject({ - method: 'PUT', - url: '/api/upgrade_assistant/deprecation_logging', - payload: { - isEnabled: false, - }, - }); - - expect(resp.statusCode).toEqual(500); - }); - }); -}); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/routes/deprecation_logging.ts b/x-pack/legacy/plugins/upgrade_assistant/server/routes/deprecation_logging.ts deleted file mode 100644 index d16a87916ad7d..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/routes/deprecation_logging.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; -import Joi from 'joi'; -import { Legacy } from 'kibana'; - -import { - getDeprecationLoggingStatus, - setDeprecationLogging, -} from '../lib/es_deprecation_logging_apis'; -import { EsVersionPrecheck } from '../lib/es_version_precheck'; - -export function registerDeprecationLoggingRoutes(server: Legacy.Server) { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); - - server.route({ - path: '/api/upgrade_assistant/deprecation_logging', - method: 'GET', - options: { - pre: [EsVersionPrecheck], - }, - async handler(request) { - try { - return await getDeprecationLoggingStatus(callWithRequest, request); - } catch (e) { - return Boom.boomify(e, { statusCode: 500 }); - } - }, - }); - - server.route({ - path: '/api/upgrade_assistant/deprecation_logging', - method: 'PUT', - options: { - pre: [EsVersionPrecheck], - validate: { - payload: Joi.object({ - isEnabled: Joi.boolean(), - }), - }, - }, - async handler(request) { - try { - const { isEnabled } = request.payload as { isEnabled: boolean }; - return await setDeprecationLogging(callWithRequest, request, isEnabled); - } catch (e) { - return Boom.boomify(e, { statusCode: 500 }); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/routes/telemetry.test.js b/x-pack/legacy/plugins/upgrade_assistant/server/routes/telemetry.test.js deleted file mode 100644 index a3706231f2297..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/routes/telemetry.test.js +++ /dev/null @@ -1,146 +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. - */ - -jest.mock('../lib/telemetry/es_ui_open_apis', () => ({ - upsertUIOpenOption: jest.fn(), -})); - -jest.mock('../lib/telemetry/es_ui_reindex_apis', () => ({ - upsertUIReindexOption: jest.fn(), -})); - -import { Server } from 'hapi'; -import { upsertUIOpenOption } from '../lib/telemetry/es_ui_open_apis'; -import { upsertUIReindexOption } from '../lib/telemetry/es_ui_reindex_apis'; -import { registerTelemetryRoutes } from './telemetry'; - -/** - * Since these route callbacks are so thin, these serve simply as integration tests - * to ensure they're wired up to the lib functions correctly. Business logic is tested - * more thoroughly in the lib/telemetry tests. - */ -describe('Upgrade Assistant Telemetry API', () => { - const server = new Server(); - - registerTelemetryRoutes(server); - - describe('PUT /api/upgrade_assistant/telemetry/ui_open', () => { - it('returns correct payload with single option', async () => { - const returnPayload = { - overview: true, - cluster: false, - indices: false, - }; - - upsertUIOpenOption.mockResolvedValue(returnPayload); - - const resp = await server.inject({ - method: 'PUT', - url: '/api/upgrade_assistant/telemetry/ui_open', - payload: { - overview: true, - }, - }); - - expect(JSON.parse(resp.payload)).toEqual(returnPayload); - }); - - it('returns correct payload with multiple option', async () => { - const returnPayload = { - overview: true, - cluster: true, - indices: true, - }; - - upsertUIOpenOption.mockResolvedValue(returnPayload); - - const resp = await server.inject({ - method: 'PUT', - url: '/api/upgrade_assistant/telemetry/ui_open', - payload: { - overview: true, - cluster: true, - indices: true, - }, - }); - - expect(JSON.parse(resp.payload)).toEqual(returnPayload); - }); - - it('returns an error if it throws', async () => { - upsertUIOpenOption.mockRejectedValue(new Error(`scary error!`)); - const resp = await server.inject({ - method: 'PUT', - url: '/api/upgrade_assistant/telemetry/ui_open', - payload: { - overview: false, - }, - }); - - expect(resp.statusCode).toEqual(500); - }); - }); - - describe('PUT /api/upgrade_assistant/telemetry/ui_reindex', () => { - it('returns correct payload with single option', async () => { - const returnPayload = { - close: false, - open: false, - start: true, - stop: false, - }; - - upsertUIReindexOption.mockResolvedValue(returnPayload); - - const resp = await server.inject({ - method: 'PUT', - url: '/api/upgrade_assistant/telemetry/ui_reindex', - payload: { - start: true, - }, - }); - - expect(JSON.parse(resp.payload)).toEqual(returnPayload); - }); - - it('returns correct payload with multiple option', async () => { - const returnPayload = { - close: true, - open: true, - start: true, - stop: true, - }; - - upsertUIReindexOption.mockResolvedValue(returnPayload); - - const resp = await server.inject({ - method: 'PUT', - url: '/api/upgrade_assistant/telemetry/ui_reindex', - payload: { - close: true, - open: true, - start: true, - stop: true, - }, - }); - - expect(JSON.parse(resp.payload)).toEqual(returnPayload); - }); - - it('returns an error if it throws', async () => { - upsertUIReindexOption.mockRejectedValue(new Error(`scary error!`)); - const resp = await server.inject({ - method: 'PUT', - url: '/api/upgrade_assistant/telemetry/ui_reindex', - payload: { - start: false, - }, - }); - - expect(resp.statusCode).toEqual(500); - }); - }); -}); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/routes/telemetry.ts b/x-pack/legacy/plugins/upgrade_assistant/server/routes/telemetry.ts deleted file mode 100644 index 6def6d1e72ea3..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/routes/telemetry.ts +++ /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 Boom from 'boom'; -import Joi from 'joi'; -import { UpgradeAssistantTelemetryServer } from '../../common/types'; -import { upsertUIOpenOption } from '../lib/telemetry/es_ui_open_apis'; -import { upsertUIReindexOption } from '../lib/telemetry/es_ui_reindex_apis'; - -export function registerTelemetryRoutes(server: UpgradeAssistantTelemetryServer) { - server.route({ - path: '/api/upgrade_assistant/telemetry/ui_open', - method: 'PUT', - options: { - validate: { - payload: Joi.object({ - overview: Joi.boolean().default(false), - cluster: Joi.boolean().default(false), - indices: Joi.boolean().default(false), - }), - }, - }, - async handler(request) { - try { - return await upsertUIOpenOption(server, request); - } catch (e) { - return Boom.boomify(e, { statusCode: 500 }); - } - }, - }); - - server.route({ - path: '/api/upgrade_assistant/telemetry/ui_reindex', - method: 'PUT', - options: { - validate: { - payload: Joi.object({ - close: Joi.boolean().default(false), - open: Joi.boolean().default(false), - start: Joi.boolean().default(false), - stop: Joi.boolean().default(false), - }), - }, - }, - async handler(request) { - try { - return await upsertUIReindexOption(server, request); - } catch (e) { - return Boom.boomify(e, { statusCode: 500 }); - } - }, - }); -} diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index 21120ab37b06a..6511a5dc3f31b 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -31,7 +31,7 @@ import { ConfigType } from './config'; import { toggleUICapabilities } from './lib/toggle_ui_capabilities'; import { initSpacesRequestInterceptors } from './lib/request_interceptors'; import { initExternalSpacesApi } from './routes/api/external'; -import { HomePluginSetup } from '../../../../src/plugins/home/server'; +import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; /** * Describes a set of APIs that is available in the legacy platform only and required by this plugin * to function properly. @@ -62,7 +62,7 @@ export interface PluginsSetup { features: FeaturesPluginSetup; licensing: LicensingPluginSetup; security?: SecurityPluginSetup; - home?: HomePluginSetup; + home?: HomeServerPluginSetup; } export interface SpacesPluginSetup { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts index b9b40be3a04b3..648944a9256b2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts @@ -12,7 +12,7 @@ const ES_TEST_INDEX_NAME = 'functional-test-actions-index'; // eslint-disable-next-line import/no-default-export export default function indexTest({ getService }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts index 44970a9260c42..0c05ad3e3e68a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts @@ -18,7 +18,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - const es = getService('es'); + const es = getService('legacyEs'); const retry = getService('retry'); const esTestIndexTool = new ESTestIndexTool(es, retry); 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 0a300c4ce65da..c43e159bbe8ca 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 @@ -20,7 +20,7 @@ import { // eslint-disable-next-line import/no-default-export export default function alertTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const retry = getService('retry'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const esTestIndexTool = new ESTestIndexTool(es, retry); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index bd0226d024d1f..d94556d6cedda 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createAlertTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const supertestWithoutAuth = getService('supertestWithoutAuth'); describe('create', () => { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts index 6d5147e9f87b8..aab683df09740 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createDeleteTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const supertestWithoutAuth = getService('supertestWithoutAuth'); describe('delete', () => { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts index 8a9b7e3fc35c4..d2076e0f92b3c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createDisableAlertTests({ getService }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts index 543805fb83b18..528db61dba21c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createEnableAlertTests({ getService }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts index 74a1255dacfe5..7e971d033b5c4 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts @@ -12,7 +12,7 @@ const ES_TEST_INDEX_NAME = 'functional-test-actions-index'; // eslint-disable-next-line import/no-default-export export default function indexTest({ getService }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts index ba8a5aa9160a5..e97b6d480c470 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts @@ -17,7 +17,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const retry = getService('retry'); const esTestIndexTool = new ESTestIndexTool(es, retry); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts index badec079d6828..28634c46b6350 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts @@ -19,7 +19,7 @@ import { // eslint-disable-next-line import/no-default-export export default function alertTests({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); - const es = getService('es'); + const es = getService('legacyEs'); const retry = getService('retry'); const esTestIndexTool = new ESTestIndexTool(es, retry); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 17ec83b5b4f18..80459690af732 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createAlertTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); describe('create', () => { const objectRemover = new ObjectRemover(supertest); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts index 3ef501cfaa588..3aea982f948ea 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createDeleteTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); describe('delete', () => { const objectRemover = new ObjectRemover(supertest); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts index 664e74835d415..750f94201216a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createDisableAlertTests({ getService }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const supertestWithoutAuth = getService('supertestWithoutAuth'); describe('disable', () => { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts index 2a8de1f6e31c3..00cd40c0e80cd 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createEnableAlertTests({ getService }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const supertestWithoutAuth = getService('supertestWithoutAuth'); describe('enable', () => { diff --git a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js index 27435d49c252f..e4637d3807d4d 100644 --- a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js +++ b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js @@ -10,7 +10,7 @@ import { ES_INDEX_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const randomness = getService('randomness'); describe('assign_tags_to_beats', () => { diff --git a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js index 6cd4fdf22bb41..09cfb33e4fad2 100644 --- a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js +++ b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js @@ -10,7 +10,7 @@ import { ES_INDEX_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); describe('create_enrollment_token', () => { it('should create one token by default', async () => { diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js index 4b4767e1d9849..59c42db7c1f81 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -12,7 +12,7 @@ import { ES_INDEX_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); const randomness = getService('randomness'); - const es = getService('es'); + const es = getService('legacyEs'); describe('enroll_beat', () => { let validEnrollmentToken; diff --git a/x-pack/test/api_integration/apis/beats/get_beat.js b/x-pack/test/api_integration/apis/beats/get_beat.js index 07cc056e3af99..03667a53920c9 100644 --- a/x-pack/test/api_integration/apis/beats/get_beat.js +++ b/x-pack/test/api_integration/apis/beats/get_beat.js @@ -10,7 +10,7 @@ import { ES_INDEX_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); describe('get_beat_configuration', () => { const archive = 'beats/list'; diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index da47fdbf77fc7..8ca7390ad5b1f 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -7,7 +7,7 @@ import { ES_INDEX_NAME } from './constants'; export default function ({ getService, loadTestFile }) { - const es = getService('es'); + const es = getService('legacyEs'); describe('beats', () => { const cleanup = () => diff --git a/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js b/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js index 1548aff1182b3..dde8916dd24d5 100644 --- a/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js +++ b/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js @@ -10,7 +10,7 @@ import { ES_INDEX_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const randomness = getService('randomness'); describe('remove_tags_from_beats', () => { diff --git a/x-pack/test/api_integration/apis/beats/set_config.js b/x-pack/test/api_integration/apis/beats/set_config.js index 5e15145cf47c8..21a09333dc31a 100644 --- a/x-pack/test/api_integration/apis/beats/set_config.js +++ b/x-pack/test/api_integration/apis/beats/set_config.js @@ -9,7 +9,7 @@ import { ES_INDEX_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); describe('set_config', () => { diff --git a/x-pack/test/api_integration/apis/beats/set_tag.js b/x-pack/test/api_integration/apis/beats/set_tag.js index ee9a601b25096..630c3772b1661 100644 --- a/x-pack/test/api_integration/apis/beats/set_tag.js +++ b/x-pack/test/api_integration/apis/beats/set_tag.js @@ -9,7 +9,7 @@ import { ES_INDEX_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); describe('set_tag', () => { it('should create a tag', async () => { diff --git a/x-pack/test/api_integration/apis/beats/update_beat.js b/x-pack/test/api_integration/apis/beats/update_beat.js index 228d651a590e6..82582b553886c 100644 --- a/x-pack/test/api_integration/apis/beats/update_beat.js +++ b/x-pack/test/api_integration/apis/beats/update_beat.js @@ -11,7 +11,7 @@ import moment from 'moment'; export default function ({ getService }) { const supertest = getService('supertest'); const randomness = getService('randomness'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); describe('update_beat', () => { diff --git a/x-pack/test/api_integration/apis/es/has_privileges.js b/x-pack/test/api_integration/apis/es/has_privileges.js index 5cbbefd8f2f37..37d6d73499552 100644 --- a/x-pack/test/api_integration/apis/es/has_privileges.js +++ b/x-pack/test/api_integration/apis/es/has_privileges.js @@ -11,7 +11,7 @@ export default function ({ getService }) { describe('has_privileges', () => { before(async () => { - const es = getService('es'); + const es = getService('legacyEs'); await es.shield.postPrivileges({ body: { @@ -104,7 +104,7 @@ export default function ({ getService }) { }); // Create privilege - const es = getService('es'); + const es = getService('legacyEs'); await es.shield.postPrivileges({ body: { [application]: { diff --git a/x-pack/test/api_integration/apis/es/post_privileges.js b/x-pack/test/api_integration/apis/es/post_privileges.js index 4b1695487f832..1c8f723b7a278 100644 --- a/x-pack/test/api_integration/apis/es/post_privileges.js +++ b/x-pack/test/api_integration/apis/es/post_privileges.js @@ -9,7 +9,7 @@ export default function ({ getService }) { describe('post_privileges', () => { it('should allow privileges to be updated', async () => { - const es = getService('es'); + const es = getService('legacyEs'); const application = 'foo'; const response = await es.shield.postPrivileges({ body: { diff --git a/x-pack/test/api_integration/apis/lens/telemetry.ts b/x-pack/test/api_integration/apis/lens/telemetry.ts index 6428ef9f478d4..5e6830c8f4689 100644 --- a/x-pack/test/api_integration/apis/lens/telemetry.ts +++ b/x-pack/test/api_integration/apis/lens/telemetry.ts @@ -22,7 +22,7 @@ const COMMON_HEADERS = { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const es: Client = getService('es'); + const es: Client = getService('legacyEs'); const callCluster: CallCluster = (((path: 'search', searchParams: SearchParams) => { return es[path].call(es, searchParams); }) as unknown) as CallCluster; diff --git a/x-pack/test/api_integration/apis/logstash/cluster/load.js b/x-pack/test/api_integration/apis/logstash/cluster/load.js index a20c524f0a8f8..36b992559e15b 100644 --- a/x-pack/test/api_integration/apis/logstash/cluster/load.js +++ b/x-pack/test/api_integration/apis/logstash/cluster/load.js @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); describe('load', () => { it('should return the ES cluster info', async () => { diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js index 8ed10ccf31dce..cd128d92498cf 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js @@ -14,7 +14,7 @@ import { registerHelpers as registerFollowerIndicesnHelpers } from './follower_i export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { addCluster, deleteAllClusters } = registerRemoteClustersHelpers(supertest); const { diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/indices.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/indices.js index d0d08101e5444..020d6fc5741b9 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/indices.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/indices.js @@ -13,7 +13,7 @@ import { getPolicyPayload } from './fixtures'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { getIndex, diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/nodes.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/nodes.js index 5ce1d7d956d67..bc8b2af401423 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/nodes.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/nodes.js @@ -13,7 +13,7 @@ import { initElasticsearchHelpers } from './lib'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { getNodesStats } = initElasticsearchHelpers(es); const { loadNodes, getNodeDetails } = registerHelpers({ supertest }); diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js index c13ae4a15c97e..598db2ddc8a65 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js @@ -15,7 +15,7 @@ import { DEFAULT_POLICY_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { createIndex, diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/templates.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/templates.js index 7dcf3c995e3ce..5c6e3d0e89c81 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/templates.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/templates.js @@ -12,7 +12,7 @@ import { registerHelpers as registerPoliciesHelpers } from './policies.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { createIndexTemplate, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(es); diff --git a/x-pack/test/api_integration/apis/management/index_management/indices.js b/x-pack/test/api_integration/apis/management/index_management/indices.js index 09215eff20c69..9302b0fe0e16b 100644 --- a/x-pack/test/api_integration/apis/management/index_management/indices.js +++ b/x-pack/test/api_integration/apis/management/index_management/indices.js @@ -11,7 +11,7 @@ import { registerHelpers } from './indices.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { createIndex, diff --git a/x-pack/test/api_integration/apis/management/index_management/mapping.js b/x-pack/test/api_integration/apis/management/index_management/mapping.js index 91f995a7d8045..0a2713e9407f1 100644 --- a/x-pack/test/api_integration/apis/management/index_management/mapping.js +++ b/x-pack/test/api_integration/apis/management/index_management/mapping.js @@ -11,7 +11,7 @@ import { registerHelpers } from './mapping.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { createIndex, diff --git a/x-pack/test/api_integration/apis/management/index_management/settings.js b/x-pack/test/api_integration/apis/management/index_management/settings.js index dc41f530085b1..d9a9e19fe2490 100644 --- a/x-pack/test/api_integration/apis/management/index_management/settings.js +++ b/x-pack/test/api_integration/apis/management/index_management/settings.js @@ -11,7 +11,7 @@ import { registerHelpers } from './settings.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { createIndex, diff --git a/x-pack/test/api_integration/apis/management/index_management/stats.js b/x-pack/test/api_integration/apis/management/index_management/stats.js index 58dac9f5b911a..743b1f596e9d7 100644 --- a/x-pack/test/api_integration/apis/management/index_management/stats.js +++ b/x-pack/test/api_integration/apis/management/index_management/stats.js @@ -11,7 +11,7 @@ import { registerHelpers } from './stats.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { createIndex, diff --git a/x-pack/test/api_integration/apis/management/index_management/templates.js b/x-pack/test/api_integration/apis/management/index_management/templates.js index a6a7493218499..00a97e55c013c 100644 --- a/x-pack/test/api_integration/apis/management/index_management/templates.js +++ b/x-pack/test/api_integration/apis/management/index_management/templates.js @@ -11,7 +11,7 @@ import { registerHelpers } from './templates.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { cleanUp: cleanUpEsResources, diff --git a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js index 25bc503682bc7..78a04b729ba66 100644 --- a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js +++ b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js @@ -13,7 +13,7 @@ import { getRandomString } from './lib'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { createIndexWithMappings, diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup.js b/x-pack/test/api_integration/apis/management/rollup/rollup.js index 5ac4e06adb23a..6e99af2ba8e32 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup.js @@ -11,7 +11,7 @@ import { registerHelpers } from './rollup.test_helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { createIndexWithMappings, diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js index 2a38cd563f312..073473f202fa3 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js @@ -12,7 +12,7 @@ import { getRandomString } from './lib'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); + const es = getService('legacyEs'); const { createIndexWithMappings, diff --git a/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js b/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js index 6d3dbe68c77ac..2443448ccd062 100644 --- a/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js +++ b/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js @@ -13,7 +13,7 @@ import * as beatsMetrics from '../../../../../legacy/plugins/monitoring/server/l import * as apmMetrics from '../../../../../legacy/plugins/monitoring/server/lib/metrics/apm/metrics'; export default function ({ getService }) { - const es = getService('es'); + const es = getService('legacyEs'); const metricSets = [ { diff --git a/x-pack/test/api_integration/apis/security/roles.js b/x-pack/test/api_integration/apis/security/roles.js index d1d4c3c7b7af8..7108656783f52 100644 --- a/x-pack/test/api_integration/apis/security/roles.js +++ b/x-pack/test/api_integration/apis/security/roles.js @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { - const es = getService('es'); + const es = getService('legacyEs'); const supertest = getService('supertest'); const config = getService('config'); const basic = config.get('esTestCluster.license') === 'basic'; diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts b/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts index de03fff7edcf7..582864795015c 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts @@ -10,7 +10,7 @@ import { TelemetrySavedObjectAttributes } from '../../../../../src/legacy/core_p import { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { - const client: Client = getService('es'); + const client: Client = getService('legacyEs'); const supertest = getService('supertest'); describe('/api/telemetry/v2/optIn API Telemetry User has seen OptIn Notice', () => { diff --git a/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts b/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts index 9dca3f40c6303..c305bb99c28f7 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts +++ b/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts @@ -106,7 +106,7 @@ export default function({ getService }: FtrProviderContext) { before(async () => { const index = 'heartbeat-8.0.0'; - const es = getService('es'); + const es = getService('legacyEs'); dateRangeStart = new Date().toISOString(); checks = await makeChecks(es, index, testMonitorId, 1, numIps, {}, d => { if (d.summary) { diff --git a/x-pack/test/api_integration/apis/uptime/index.js b/x-pack/test/api_integration/apis/uptime/index.js index 8f4e4ab9a7ea1..6eb77fb584133 100644 --- a/x-pack/test/api_integration/apis/uptime/index.js +++ b/x-pack/test/api_integration/apis/uptime/index.js @@ -5,7 +5,7 @@ */ export default function ({ getService, loadTestFile }) { - const es = getService('es'); + const es = getService('legacyEs'); describe('uptime', () => { before(() => diff --git a/x-pack/test/api_integration/services/index.ts b/x-pack/test/api_integration/services/index.ts index 7e3b747c81993..4be89172e24f0 100644 --- a/x-pack/test/api_integration/services/index.ts +++ b/x-pack/test/api_integration/services/index.ts @@ -30,7 +30,7 @@ export const services = { esSupertest: kibanaApiIntegrationServices.esSupertest, supertest: kibanaApiIntegrationServices.supertest, - es: LegacyEsProvider, + legacyEs: LegacyEsProvider, esSupertestWithoutAuth: EsSupertestWithoutAuthProvider, infraOpsGraphQLClient: InfraOpsGraphQLClientProvider, infraOpsGraphQLClientFactory: InfraOpsGraphQLClientFactoryProvider, diff --git a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js index dca5534fb68b3..95bae4b24a535 100644 --- a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js +++ b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js @@ -11,7 +11,7 @@ import mockRolledUpData, { mockIndices } from './hybrid_index_helper'; export default function ({ getService, getPageObjects }) { - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'settings']); diff --git a/x-pack/test/functional/apps/rollup_job/rollup_jobs.js b/x-pack/test/functional/apps/rollup_job/rollup_jobs.js index eeff20105aed2..0888f0972592e 100644 --- a/x-pack/test/functional/apps/rollup_job/rollup_jobs.js +++ b/x-pack/test/functional/apps/rollup_job/rollup_jobs.js @@ -11,7 +11,7 @@ import { mockIndices } from './hybrid_index_helper'; export default function ({ getService, getPageObjects }) { - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); const PageObjects = getPageObjects(['rollup', 'common']); diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index 270722a97d6b6..2fc027a81ea8c 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { JOB_STATE, DATAFEED_STATE } from '../../../../legacy/plugins/ml/common/constants/states'; export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const log = getService('log'); const retry = getService('retry'); const esSupertest = getService('esSupertest'); diff --git a/x-pack/test/functional/services/transform_ui/api.ts b/x-pack/test/functional/services/transform_ui/api.ts index 9050b5944dee3..a6756e5940d72 100644 --- a/x-pack/test/functional/services/transform_ui/api.ts +++ b/x-pack/test/functional/services/transform_ui/api.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export function TransformAPIProvider({ getService }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const log = getService('log'); const retry = getService('retry'); diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts index 0a3b7f595c66d..450f7b1a427dc 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts @@ -362,7 +362,7 @@ export default function({ getService }: FtrProviderContext) { // Let's delete tokens from `.security-tokens` index directly to simulate the case when // Elasticsearch automatically removes access/refresh token document from the index after // some period of time. - const esResponse = await getService('es').deleteByQuery({ + const esResponse = await getService('legacyEs').deleteByQuery({ index: '.security-tokens', q: 'doc_type:token', refresh: true, diff --git a/x-pack/test/kerberos_api_integration/services.ts b/x-pack/test/kerberos_api_integration/services.ts index 42505994e0602..e2e7a5b8844a7 100644 --- a/x-pack/test/kerberos_api_integration/services.ts +++ b/x-pack/test/kerberos_api_integration/services.ts @@ -7,7 +7,7 @@ import { services as apiIntegrationServices } from '../api_integration/services'; export const services = { - es: apiIntegrationServices.es, + legacyEs: apiIntegrationServices.legacyEs, esSupertest: apiIntegrationServices.esSupertest, supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, }; diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js index 7460b1f9e238c..80ef6bd6df4ff 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js +++ b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js @@ -482,7 +482,7 @@ export default function ({ getService }) { // Let's delete tokens from `.security-tokens` index directly to simulate the case when // Elasticsearch automatically removes access/refresh token document from the index // after some period of time. - const esResponse = await getService('es').deleteByQuery({ + const esResponse = await getService('legacyEs').deleteByQuery({ index: '.security-tokens', q: 'doc_type:token', refresh: true, diff --git a/x-pack/test/oidc_api_integration/services.ts b/x-pack/test/oidc_api_integration/services.ts index e4ff6048a8cce..dda9f1d1a8886 100644 --- a/x-pack/test/oidc_api_integration/services.ts +++ b/x-pack/test/oidc_api_integration/services.ts @@ -7,6 +7,6 @@ import { services as apiIntegrationServices } from '../api_integration/services'; export const services = { - es: apiIntegrationServices.es, + legacyEs: apiIntegrationServices.legacyEs, supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, }; diff --git a/x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/encrypted_saved_objects_api.ts b/x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/encrypted_saved_objects_api.ts index 98d653d71b5ec..ab9f7d2cdd339 100644 --- a/x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/encrypted_saved_objects_api.ts +++ b/x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/encrypted_saved_objects_api.ts @@ -9,7 +9,7 @@ import { SavedObject } from 'src/core/server'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ getService }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const randomness = getService('randomness'); const supertest = getService('supertest'); diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js index a8ff03dc71d24..9b4297e995cbd 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js @@ -12,7 +12,7 @@ import supertestAsPromised from 'supertest-as-promised'; const { task: { properties: taskManagerIndexMapping } } = require('../../../../legacy/plugins/task_manager/mappings.json'); export default function ({ getService }) { - const es = getService('es'); + const es = getService('legacyEs'); const log = getService('log'); const retry = getService('retry'); const config = getService('config'); diff --git a/x-pack/test/saml_api_integration/apis/security/saml_login.ts b/x-pack/test/saml_api_integration/apis/security/saml_login.ts index 276b42423860c..d512894fbb1f2 100644 --- a/x-pack/test/saml_api_integration/apis/security/saml_login.ts +++ b/x-pack/test/saml_api_integration/apis/security/saml_login.ts @@ -622,7 +622,7 @@ export default function({ getService }: FtrProviderContext) { // Let's delete tokens from `.security` index directly to simulate the case when // Elasticsearch automatically removes access/refresh token document from the index // after some period of time. - const esResponse = await getService('es').deleteByQuery({ + const esResponse = await getService('legacyEs').deleteByQuery({ index: '.security-tokens', q: 'doc_type:token', refresh: true, diff --git a/x-pack/test/saml_api_integration/config.ts b/x-pack/test/saml_api_integration/config.ts index 9d3029db013d3..6ea29b0d9e56e 100644 --- a/x-pack/test/saml_api_integration/config.ts +++ b/x-pack/test/saml_api_integration/config.ts @@ -21,7 +21,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { servers: xPackAPITestsConfig.get('servers'), services: { randomness: kibanaAPITestsConfig.get('services.randomness'), - es: kibanaAPITestsConfig.get('services.es'), + legacyEs: kibanaAPITestsConfig.get('services.legacyEs'), supertestWithoutAuth: xPackAPITestsConfig.get('services.supertestWithoutAuth'), }, junit: { diff --git a/x-pack/test/saml_api_integration/services.ts b/x-pack/test/saml_api_integration/services.ts index 307108fa097f4..d207112f6e81f 100644 --- a/x-pack/test/saml_api_integration/services.ts +++ b/x-pack/test/saml_api_integration/services.ts @@ -8,6 +8,6 @@ import { services as apiIntegrationServices } from '../api_integration/services' export const services = { randomness: apiIntegrationServices.randomness, - es: apiIntegrationServices.es, + legacyEs: apiIntegrationServices.legacyEs, supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, }; diff --git a/x-pack/test/saved_object_api_integration/common/services/index.ts b/x-pack/test/saved_object_api_integration/common/services/index.ts index 1ea312714fc1e..aba605fee8947 100644 --- a/x-pack/test/saved_object_api_integration/common/services/index.ts +++ b/x-pack/test/saved_object_api_integration/common/services/index.ts @@ -12,7 +12,7 @@ import { services as kibanaApiIntegrationServices } from '../../../../../test/ap import { services as kibanaFunctionalServices } from '../../../../../test/functional/services'; export const services = { - es: LegacyEsProvider, + legacyEs: LegacyEsProvider, esSupertestWithoutAuth: apiIntegrationServices.esSupertestWithoutAuth, supertest: kibanaApiIntegrationServices.supertest, supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts index 7e00d0a9d7f75..7768665f3b941 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts @@ -12,7 +12,7 @@ import { bulkCreateTestSuiteFactory } from '../../common/suites/bulk_create'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { bulkCreateTest, diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts index 4cc94bf21f235..e4adaa580c1db 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts @@ -11,7 +11,7 @@ import { createTestSuiteFactory } from '../../common/suites/create'; export default function({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); const { diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/import.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/import.ts index f9048d1493cc6..58859c292ce35 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/import.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/import.ts @@ -12,7 +12,7 @@ import { importTestSuiteFactory } from '../../common/suites/import'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { importTest, diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/index.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/index.ts index 1b1e9762a54e5..bb42c5422ece5 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/index.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/index.ts @@ -8,7 +8,7 @@ import { createUsersAndRoles } from '../../common/lib/create_users_and_roles'; import { FtrProviderContext } from '../../common/ftr_provider_context'; export default function({ getService, loadTestFile }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const supertest = getService('supertest'); describe('saved objects security and spaces enabled', function() { diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve_import_errors.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve_import_errors.ts index fa070ac76ce57..6c91fe6310170 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve_import_errors.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve_import_errors.ts @@ -12,7 +12,7 @@ import { resolveImportErrorsTestSuiteFactory } from '../../common/suites/resolve export default function({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { resolveImportErrorsTest, diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts index 2a5757fb0cc57..943a22c4399c7 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts @@ -11,7 +11,7 @@ import { bulkCreateTestSuiteFactory } from '../../common/suites/bulk_create'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { bulkCreateTest, diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/create.ts b/x-pack/test/saved_object_api_integration/security_only/apis/create.ts index bb2bf7e0e0d7a..60a9fa0a86aa6 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/create.ts @@ -10,7 +10,7 @@ import { createTestSuiteFactory } from '../../common/suites/create'; export default function({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); const { diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/import.ts b/x-pack/test/saved_object_api_integration/security_only/apis/import.ts index 8ad7e070edf54..770410dcfed81 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/import.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/import.ts @@ -11,7 +11,7 @@ import { importTestSuiteFactory } from '../../common/suites/import'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { importTest, diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/index.ts b/x-pack/test/saved_object_api_integration/security_only/apis/index.ts index 6e930338a1184..bb637a9bc4c90 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/index.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/index.ts @@ -8,7 +8,7 @@ import { createUsersAndRoles } from '../../common/lib/create_users_and_roles'; import { FtrProviderContext } from '../../common/ftr_provider_context'; export default function({ getService, loadTestFile }: FtrProviderContext) { - const es = getService('es'); + const es = getService('legacyEs'); const supertest = getService('supertest'); describe('saved objects security only enabled', function() { diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/resolve_import_errors.ts b/x-pack/test/saved_object_api_integration/security_only/apis/resolve_import_errors.ts index e1d72f8641e93..59d50c16c259a 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/resolve_import_errors.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/resolve_import_errors.ts @@ -11,7 +11,7 @@ import { resolveImportErrorsTestSuiteFactory } from '../../common/suites/resolve export default function({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { resolveImportErrorsTest, diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts index 6b67c63ed349a..d89e3b4f8605b 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts @@ -25,7 +25,7 @@ const expectNamespaceSpecifiedBadRequest = (resp: { [key: string]: any }) => { export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { bulkCreateTest, diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts index d6bd0b497759f..cf34f7505cdb2 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts @@ -23,7 +23,7 @@ const expectNamespaceSpecifiedBadRequest = (resp: { [key: string]: any }) => { export default function({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); - const es = getService('es'); + const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); const { diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/import.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/import.ts index c8b2e68291dc3..c78a0e1cc2cce 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/import.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/import.ts @@ -11,7 +11,7 @@ import { importTestSuiteFactory } from '../../common/suites/import'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { importTest, diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve_import_errors.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve_import_errors.ts index 93d60ebbf4d6d..22a7ab81e5530 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve_import_errors.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve_import_errors.ts @@ -11,7 +11,7 @@ import { resolveImportErrorsTestSuiteFactory } from '../../common/suites/resolve export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { resolveImportErrorsTest, diff --git a/x-pack/test/spaces_api_integration/common/config.ts b/x-pack/test/spaces_api_integration/common/config.ts index 23514c635d356..dffc6c524cc6e 100644 --- a/x-pack/test/spaces_api_integration/common/config.ts +++ b/x-pack/test/spaces_api_integration/common/config.ts @@ -34,7 +34,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) testFiles: [require.resolve(`../${name}/apis/`)], servers: config.xpack.api.get('servers'), services: { - es: LegacyEsProvider, + legacyEs: LegacyEsProvider, esSupertestWithoutAuth: config.xpack.api.get('services.esSupertestWithoutAuth'), supertest: config.kibana.api.get('services.supertest'), supertestWithoutAuth: config.xpack.api.get('services.supertestWithoutAuth'), diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space.ts index c6c41f7cd3a97..1c4ca2fe20662 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space.ts @@ -13,7 +13,7 @@ import { copyToSpaceTestSuiteFactory } from '../../common/suites/copy_to_space'; export default function copyToSpaceSpacesAndSecuritySuite({ getService }: TestInvoker) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { copyToSpaceTest, diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts index 25d7317d7dd90..df4a2f51b4872 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts @@ -13,7 +13,7 @@ import { deleteTestSuiteFactory } from '../../common/suites/delete'; export default function deleteSpaceTestSuite({ getService }: TestInvoker) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { deleteTest, diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts index 300949f41f036..e918ab0b53841 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts @@ -9,7 +9,7 @@ import { TestInvoker } from '../../common/lib/types'; // eslint-disable-next-line import/no-default-export export default function({ loadTestFile, getService }: TestInvoker) { - const es = getService('es'); + const es = getService('legacyEs'); const supertest = getService('supertest'); describe('spaces api with security', function() { diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/copy_to_space.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/copy_to_space.ts index b1427a9b77be0..4781c4bea9b30 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/copy_to_space.ts @@ -11,7 +11,7 @@ import { copyToSpaceTestSuiteFactory } from '../../common/suites/copy_to_space'; export default function copyToSpacesOnlySuite({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { copyToSpaceTest, diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts index 350b682429778..64cbb7a02776c 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts @@ -12,7 +12,7 @@ import { deleteTestSuiteFactory } from '../../common/suites/delete'; export default function deleteSpaceTestSuite({ getService }: TestInvoker) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); const { deleteTest, diff --git a/x-pack/test/token_api_integration/auth/header.js b/x-pack/test/token_api_integration/auth/header.js index 2317f62e63809..4b27fd5db3166 100644 --- a/x-pack/test/token_api_integration/auth/header.js +++ b/x-pack/test/token_api_integration/auth/header.js @@ -6,7 +6,7 @@ export default function ({ getService }) { const supertest = getService('supertestWithoutAuth'); - const es = getService('es'); + const es = getService('legacyEs'); async function createToken() { const { access_token: accessToken } = await es.shield.getAccessToken({ diff --git a/x-pack/test/token_api_integration/auth/session.js b/x-pack/test/token_api_integration/auth/session.js index 6045021894823..8a9f1d7a3f229 100644 --- a/x-pack/test/token_api_integration/auth/session.js +++ b/x-pack/test/token_api_integration/auth/session.js @@ -132,7 +132,7 @@ export default function ({ getService }) { // Let's delete tokens from `.security` index directly to simulate the case when // Elasticsearch automatically removes access/refresh token document from the index // after some period of time. - const esResponse = await getService('es').deleteByQuery({ + const esResponse = await getService('legacyEs').deleteByQuery({ index: '.security-tokens', q: 'doc_type:token', refresh: true, diff --git a/x-pack/test/token_api_integration/config.js b/x-pack/test/token_api_integration/config.js index 44086245a90c0..8cf9bc329a374 100644 --- a/x-pack/test/token_api_integration/config.js +++ b/x-pack/test/token_api_integration/config.js @@ -11,7 +11,7 @@ export default async function ({ readConfigFile }) { testFiles: [require.resolve('./auth')], servers: xPackAPITestsConfig.get('servers'), services: { - es: xPackAPITestsConfig.get('services.es'), + legacyEs: xPackAPITestsConfig.get('services.legacyEs'), supertestWithoutAuth: xPackAPITestsConfig.get('services.supertestWithoutAuth'), }, junit: { diff --git a/x-pack/test/upgrade_assistant_integration/config.js b/x-pack/test/upgrade_assistant_integration/config.js index 4cd4ca78d3a58..3a43d43543b61 100644 --- a/x-pack/test/upgrade_assistant_integration/config.js +++ b/x-pack/test/upgrade_assistant_integration/config.js @@ -20,9 +20,9 @@ export default async function ({ readConfigFile }) { testFiles: [require.resolve('./upgrade_assistant')], servers: xPackFunctionalTestsConfig.get('servers'), services: { + ...kibanaCommonConfig.get('services'), supertest: kibanaAPITestsConfig.get('services.supertest'), - es: LegacyEsProvider, - esArchiver: kibanaCommonConfig.get('services.esArchiver'), + legacyEs: LegacyEsProvider, }, esArchiver: xPackFunctionalTestsConfig.get('esArchiver'), junit: { diff --git a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js index 233336c722611..137de18fc98d9 100644 --- a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js +++ b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js @@ -12,7 +12,7 @@ import { ReindexStatus, REINDEX_OP_TYPE } from '../../../legacy/plugins/upgrade_ export default function ({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('es'); + const es = getService('legacyEs'); // Utility function that keeps polling API until reindex operation has completed or failed. const waitForReindexToComplete = async (indexName) => {