diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c91d1a702b7eca3..168db2c4efb84c4 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -349,6 +349,21 @@
/x-pack/test/case_api_integration @elastic/security-threat-hunting
/x-pack/plugins/lists @elastic/security-detections-response
+## Security Solution sub teams - security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/public/management/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/public/common/lib/endpoint*/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/public/common/components/endpoint/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/common/endpoint/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/server/endpoint/routes/actions/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/server/endpoint/routes/metadata/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/server/endpoint/lib/policy/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/server/lib/license/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/server/fleet_integration/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/scripts/endpoint/event_filters/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/ @elastic/security-onboarding-and-lifecycle-mgt
+/x-pack/test/security_solution_endpoint/apps/endpoint/ @elastic/security-onboarding-and-lifecycle-mgt
+
# Security Intelligence And Analytics
/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules @elastic/security-intelligence-analytics
diff --git a/.i18nrc.json b/.i18nrc.json
index 390e5e917d08e7c..732644b43e1f7ce 100644
--- a/.i18nrc.json
+++ b/.i18nrc.json
@@ -16,6 +16,7 @@
"esUi": "src/plugins/es_ui_shared",
"devTools": "src/plugins/dev_tools",
"expressions": "src/plugins/expressions",
+ "expressionError": "src/plugins/expression_error",
"expressionRevealImage": "src/plugins/expression_reveal_image",
"inputControl": "src/plugins/input_control_vis",
"inspector": "src/plugins/inspector",
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index 5f49360c926bfa8..c1535e8a2146f0d 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -72,6 +72,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a
|This plugin contains reusable code in the form of self-contained modules (or libraries). Each of these modules exports a set of functionality relevant to the domain of the module.
+|{kib-repo}blob/{branch}/src/plugins/expression_error/README.md[expressionError]
+|Expression Error plugin adds an error renderer to the expression plugin. The renderer will display the error image.
+
+
|{kib-repo}blob/{branch}/src/plugins/expression_reveal_image/README.md[expressionRevealImage]
|Expression Reveal Image plugin adds a revealImage function to the expression plugin and an associated renderer. The renderer will display the given percentage of a given image.
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md
index de6f4563b678ac4..7c850a89dff1320 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md
@@ -19,6 +19,7 @@ export interface QuerySuggestionGetFnArgs
| [boolFilter](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.boolfilter.md) | any
| |
| [indexPatterns](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.indexpatterns.md) | IIndexPattern[]
| |
| [language](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.language.md) | string
| |
+| [method](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.method.md) | ValueSuggestionsMethod
| |
| [query](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.query.md) | string
| |
| [selectionEnd](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.selectionend.md) | number
| |
| [selectionStart](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.selectionstart.md) | number
| |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.method.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.method.md
new file mode 100644
index 000000000000000..2bc9a4fba61c37a
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.method.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QuerySuggestionGetFnArgs](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md) > [method](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.method.md)
+
+## QuerySuggestionGetFnArgs.method property
+
+Signature:
+
+```typescript
+method?: ValueSuggestionsMethod;
+```
diff --git a/docs/getting-started/images/add-sample-data.png b/docs/getting-started/images/add-sample-data.png
index 9dee27dcde71b4e..07a536b19d7d098 100644
Binary files a/docs/getting-started/images/add-sample-data.png and b/docs/getting-started/images/add-sample-data.png differ
diff --git a/docs/getting-started/images/tutorial-discover-3.png b/docs/getting-started/images/tutorial-discover-3.png
index b024ad6dc39fe82..79cf94058bb7612 100644
Binary files a/docs/getting-started/images/tutorial-discover-3.png and b/docs/getting-started/images/tutorial-discover-3.png differ
diff --git a/docs/getting-started/images/tutorial-discover-4.png b/docs/getting-started/images/tutorial-discover-4.png
index 945a6155c02cd4b..584221e8cfd04ac 100644
Binary files a/docs/getting-started/images/tutorial-discover-4.png and b/docs/getting-started/images/tutorial-discover-4.png differ
diff --git a/docs/getting-started/images/tutorial-final-dashboard.gif b/docs/getting-started/images/tutorial-final-dashboard.gif
index 53b7bc04c5f656e..6c82c4e53ca1070 100644
Binary files a/docs/getting-started/images/tutorial-final-dashboard.gif and b/docs/getting-started/images/tutorial-final-dashboard.gif differ
diff --git a/docs/getting-started/images/tutorial-sample-dashboard.png b/docs/getting-started/images/tutorial-sample-dashboard.png
index 4c95c04c5e43ed0..5e06009d0824e50 100644
Binary files a/docs/getting-started/images/tutorial-sample-dashboard.png and b/docs/getting-started/images/tutorial-sample-dashboard.png differ
diff --git a/docs/getting-started/images/tutorial-sample-filter.png b/docs/getting-started/images/tutorial-sample-filter.png
index 56ebacadbef45a1..8823da311ebb549 100644
Binary files a/docs/getting-started/images/tutorial-sample-filter.png and b/docs/getting-started/images/tutorial-sample-filter.png differ
diff --git a/docs/getting-started/images/tutorial-sample-filter2.png b/docs/getting-started/images/tutorial-sample-filter2.png
index 21402feacdecdeb..4215b63d89fa1f2 100644
Binary files a/docs/getting-started/images/tutorial-sample-filter2.png and b/docs/getting-started/images/tutorial-sample-filter2.png differ
diff --git a/docs/getting-started/images/tutorial-sample-query.png b/docs/getting-started/images/tutorial-sample-query.png
deleted file mode 100644
index 4f1ca24924b284e..000000000000000
Binary files a/docs/getting-started/images/tutorial-sample-query.png and /dev/null differ
diff --git a/docs/getting-started/images/tutorial-sample-query2.png b/docs/getting-started/images/tutorial-sample-query2.png
deleted file mode 100644
index 0e91e1069a2012d..000000000000000
Binary files a/docs/getting-started/images/tutorial-sample-query2.png and /dev/null differ
diff --git a/docs/getting-started/images/tutorial-treemap.png b/docs/getting-started/images/tutorial-treemap.png
deleted file mode 100644
index 32e14fd2308e319..000000000000000
Binary files a/docs/getting-started/images/tutorial-treemap.png and /dev/null differ
diff --git a/docs/getting-started/images/tutorial-visualization-dropdown.png b/docs/getting-started/images/tutorial-visualization-dropdown.png
index 29d1b99700964f4..a069af95ed14a62 100644
Binary files a/docs/getting-started/images/tutorial-visualization-dropdown.png and b/docs/getting-started/images/tutorial-visualization-dropdown.png differ
diff --git a/docs/getting-started/images/tutorial-visualization-treemap.png b/docs/getting-started/images/tutorial-visualization-treemap.png
new file mode 100644
index 000000000000000..c6e8db133cb4440
Binary files /dev/null and b/docs/getting-started/images/tutorial-visualization-treemap.png differ
diff --git a/docs/getting-started/quick-start-guide.asciidoc b/docs/getting-started/quick-start-guide.asciidoc
index d9835b312f3ee76..ed249008ac8de72 100644
--- a/docs/getting-started/quick-start-guide.asciidoc
+++ b/docs/getting-started/quick-start-guide.asciidoc
@@ -7,7 +7,7 @@ When you've finished, you'll know how to:
* <>
-* <>
+* <>
[float]
=== Required privileges
@@ -24,125 +24,125 @@ include::{docs-root}/shared/cloud/ess-getting-started.asciidoc[]
[[gs-get-data-into-kibana]]
== Add the sample data
-Sample data sets come with sample visualizations, dashboards, and more to help you explore {kib} without adding your own data.
+Sample data sets come with sample visualizations, dashboards, and more to help you explore {kib} before you ingest or add your own data.
-. From the home page, click *Try our sample data*.
+. On the home page, click *Try our sample data*.
. On the *Sample eCommerce orders* card, click *Add data*.
+
[role="screenshot"]
-image::getting-started/images/add-sample-data.png[Add data UI]
+image::images/add-sample-data.png[Add data UI for the sample data sets]
[float]
[[explore-the-data]]
== Explore the data
-*Discover* displays an interactive histogram that shows the distribution of of data, or documents, over time, and a table that lists the fields for each document that matches the index. By default, all fields are shown for each matching document.
+*Discover* displays the data in an interactive histogram that shows the distribution of data, or documents, over time, and a table that lists the fields for each document that matches the index pattern. To view a subset of the documents, you can apply filters to the data, and customize the table to display only the fields you want to explore.
. Open the main menu, then click *Discover*.
. Change the <> to *Last 7 days*.
+
[role="screenshot"]
-image::images/tutorial-discover-2.png[]
+image::images/tutorial-discover-2.png[Time filter menu with Last 7 days filter configured]
-. To focus in on the documents you want to view, use the <>. In the *KQL* search field, enter:
+. To view the sales orders for women's clothing that are $60 or more, use the <> search field:
+
[source,text]
-products.taxless_price >= 60 AND category : Women's Clothing
-+
-The query returns the women's clothing orders for $60 and more.
+products.taxless_price >= 60 and category : Women's Clothing
+
[role="screenshot"]
-image::images/tutorial-discover-4.png[]
+image::images/tutorial-discover-4.png[Discover tables that displays only the orders for women's clothing that are $60 or more]
-. Hover over the list of *Available fields*, then click *+* next to the fields you want to view in the table.
-+
-For example, when you add the *category* field, the table displays the product categories for the orders.
+. To view only the product categories that contain sales orders, hover over the *category* field, then click *+*.
+
[role="screenshot"]
-image::images/tutorial-discover-3.png[]
-+
-For more information, refer to <>.
+image::images/tutorial-discover-3.png[Discover table that displays only the product categories that contain orders]
[float]
[[view-and-analyze-the-data]]
== View and analyze the data
-A dashboard is a collection of panels that you can use to view and analyze the data. Panels contain visualizations, interactive controls, Markdown, and more.
+A dashboard is a collection of panels that you can use to view and analyze the data. Panels contain visualizations, interactive controls, text, and more.
. Open the main menu, then click *Dashboard*.
. Click *[eCommerce] Revenue Dashboard*.
+
[role="screenshot"]
-image::getting-started/images/tutorial-sample-dashboard.png[]
+image::getting-started/images/tutorial-sample-dashboard.png[The [eCommerce] Revenue Dashboard that comes with the Sample eCommerce order data set]
[float]
-[[filter-and-query-the-data]]
-=== Filter the data
+[[create-a-visualization]]
+=== Create a visualization panel
+
+Create a treemap panel that shows the top sales regions and manufacturers, then add the panel to the dashboard.
-To focus in on the data you want to view on the dashboard, use filters.
+. From the toolbar, click *Edit*, then click *Create visualzation*.
-. From the *[eCommerce] Controls* panel, make a selection from the *Manufacturer* and *Category* dropdowns, then click *Apply changes*.
+. Open the *Chart type* menu, then select *Treemap*.
+
-For example, the following dashboard shows the data for women's clothing from Gnomehouse.
+[role="screenshot"]
+image::getting-started/images/tutorial-visualization-dropdown.png[Chart type menu with Treemap selected]
+
+. From the *Available fields* list, drag and drop the following fields onto the workspace:
+
+* *geoip.city_name*
+
+* *manufacturer.keyword*
+
[role="screenshot"]
-image::getting-started/images/tutorial-sample-filter.png[]
+image::getting-started/images/tutorial-visualization-treemap.png[Treemap that displays Top values of geoip.city_name and Top values or manufacturer.keyword fields]
-. To manually add a filter, click *Add filter*, then specify the options.
+. Click *Save and return*.
+
-For example, to view the orders for Wednesday, select *day_of_week* from the *Field* dropdown, select *is* from the *Operator* dropdown, then select *Wednesday* from the *Value* dropdown.
+The treemap appears as the last visualization panel on the dashboard.
+
[role="screenshot"]
-image::getting-started/images/tutorial-sample-filter2.png[]
+image::getting-started/images/tutorial-final-dashboard.gif[Final dashboard with new treemap visualization]
+
+[float]
+[[interact-with-the-data]]
+=== Interact with the data
+
+You can interact with the dashboard data using controls that allow you to apply dashboard-level filters. Interact with the *[eCommerce] Controls* panel to view the women's clothing data from the Gnomehouse manufacturer.
-. When you are done, remove the filters.
+. From the *Manufacturer* dropdown, select *Gnomehouse*.
+
+. From the *Category* dropdown, select *Women's Clothing*.
+
+. Click *Apply changes*.
+
-For more information, refer to <>.
+[role="screenshot"]
+image::getting-started/images/tutorial-sample-filter.png[The [eCommerce] Revenue Dashboard that shows only the women's clothing data from the Gnomehouse manufacturer]
[float]
-[[create-a-visualization]]
-=== Create a visualization panel
-
-Create a treemap panel that shows the top regions and manufacturers, then add the panel to the dashboard.
+[[filter-and-query-the-data]]
+=== Filter the data
-. From the toolbar, click *Edit*, then click *Create new*.
+To view a subset of the data, you can apply filters to the dashboard panels. Apply a filter to view the women's clothing data generated on Wednesday from the Gnomehouse manufacturer.
-. On the *New Visualization* window, click *Lens*.
+. Click *Add filter*.
-. From the *Available fields* list, drag and drop the following fields to the visualization builder:
+. From the *Field* dropdown, select *day_of_week*.
-* *geoip.city_name*
+. From the *Operator* dropdown, select *is*.
-* *manufacturer.keyword*
-+
-. From the visualization dropdown, select *Treemap*.
-+
-[role="screenshot"]
-image::getting-started/images/tutorial-visualization-dropdown.png[Visualization dropdown with Treemap selected]
+. From the *Value* dropdown, select *Wednesday*.
. Click *Save*.
-
-. On the *Save Lens visualization*, enter a title and make sure *Add to Dashboard after saving* is selected, then click *Save and return*.
-+
-The treemap appears as the last visualization panel on the dashboard.
+
[role="screenshot"]
-image::getting-started/images/tutorial-final-dashboard.gif[Final dashboard with new treemap visualization]
-+
-For more information, refer to <>.
+image::getting-started/images/tutorial-sample-filter2.png[The [eCommerce] Revenue Dashboard that shows only the women's clothing data generated on Wednesday from the Gnomehouse manufacturer]
[float]
[[quick-start-whats-next]]
== What's next?
-If you are you ready to add your own data, refer to <>.
+*Add your own data.* Ready to add your own data? Go to {fleet-guide}/fleet-quick-start.html[Quick start: Get logs and metrics into the Elastic Stack] to learn how to ingest your data, or go to <> and learn about all the other ways you can add data.
-If you want to ingest your data, refer to {fleet-guide}/fleet-quick-start.html[Quick start: Get logs and metrics into the Elastic Stack].
+*Explore your own data in Discover.* Ready to learn more about exploring your data in *Discover*? Go to <>.
-If you want to secure access to your data, refer to our guide on <>
+*Create a dashboard with your own data.* Ready to learn more about analyzing your data in *Dashboard*? Go to <>.
-If you want to try out {ml-features} with the sample data sets, refer to
-{ml-docs}/ml-getting-started.html[Getting started with {ml}].
\ No newline at end of file
+*Try out the {ml-features}.* Ready to analyze the sample data sets and generate models for its patterns of behavior? Go to {ml-docs}/ml-getting-started.html[Getting started with {ml}].
\ No newline at end of file
diff --git a/docs/management/images/management-create-rollup-bar-chart.png b/docs/management/images/management-create-rollup-bar-chart.png
index 324cfcb9ee5fb6c..68ba4344c0ecf6c 100644
Binary files a/docs/management/images/management-create-rollup-bar-chart.png and b/docs/management/images/management-create-rollup-bar-chart.png differ
diff --git a/docs/management/images/management-rollup-index-pattern.png b/docs/management/images/management-rollup-index-pattern.png
index 57ac00be7977c3d..de7976e63f0503c 100644
Binary files a/docs/management/images/management-rollup-index-pattern.png and b/docs/management/images/management-rollup-index-pattern.png differ
diff --git a/docs/management/images/management_create_rollup_job.png b/docs/management/images/management_create_rollup_job.png
index c3139c9f8df1a69..f1dd1580c3a860b 100644
Binary files a/docs/management/images/management_create_rollup_job.png and b/docs/management/images/management_create_rollup_job.png differ
diff --git a/docs/management/images/management_rollup_job_dashboard.png b/docs/management/images/management_rollup_job_dashboard.png
index d3c394183cad843..9573ab7a863e8e3 100644
Binary files a/docs/management/images/management_rollup_job_dashboard.png and b/docs/management/images/management_rollup_job_dashboard.png differ
diff --git a/docs/management/images/management_rollup_job_details.png b/docs/management/images/management_rollup_job_details.png
index e6e93a6dae130c9..5372ba4ad7d137d 100644
Binary files a/docs/management/images/management_rollup_job_details.png and b/docs/management/images/management_rollup_job_details.png differ
diff --git a/docs/management/images/management_rollup_job_vis.png b/docs/management/images/management_rollup_job_vis.png
deleted file mode 100644
index e1f46e4db5c0a6a..000000000000000
Binary files a/docs/management/images/management_rollup_job_vis.png and /dev/null differ
diff --git a/docs/management/images/management_rollup_list.png b/docs/management/images/management_rollup_list.png
index 60e9a3507100382..505930bcb9a38ac 100644
Binary files a/docs/management/images/management_rollup_list.png and b/docs/management/images/management_rollup_list.png differ
diff --git a/docs/management/rollups/create_and_manage_rollups.asciidoc b/docs/management/rollups/create_and_manage_rollups.asciidoc
index bde2ca472b258c1..51821a935d3f57a 100644
--- a/docs/management/rollups/create_and_manage_rollups.asciidoc
+++ b/docs/management/rollups/create_and_manage_rollups.asciidoc
@@ -64,13 +64,16 @@ You can read more at {ref}/rollup-job-config.html[rollup job configuration].
=== Try it: Create and visualize rolled up data
This example creates a rollup job to capture log data from sample web logs.
-To follow along, add the sample web logs data set.
+Before you start, <>.
In this example, you want data that is older than 7 days in the target index pattern `kibana_sample_data_logs`
-to roll up once a day into the index `rollup_logstash`. You’ll bucket the
+to roll up into the `rollup_logstash` index. You’ll bucket the
rolled up data on an hourly basis, using 60m for the time bucket configuration.
This allows for more granular queries, such as 2h and 12h.
+For this example, the job will perform the rollup every minute. However, you'd
+typically roll up less frequently in production.
+
[float]
==== Create the rollup job
@@ -80,7 +83,7 @@ As you walk through the *Create rollup job* UI, enter the data:
|*Field* |*Value*
|Name
-|logs_job
+|`logs_job`
|Index pattern
|`kibana_sample_data_logs`
@@ -89,12 +92,13 @@ As you walk through the *Create rollup job* UI, enter the data:
|`rollup_logstash`
|Frequency
-|Every day at midnight
+|Every minute
|Page size
|1000
-|Delay (latency buffer)|7d
+|Latency buffer
+|7d
|Date field
|@timestamp
@@ -118,6 +122,8 @@ As you walk through the *Create rollup job* UI, enter the data:
|bytes (average)
|===
+On the **Review and save** page, click **Start job now** and **Save**.
+
The terms, histogram, and metrics fields reflect
the key information to retain in the rolled up data: where visitors are from (geo.src),
what operating system they are using (machine.os.keyword),
@@ -133,7 +139,6 @@ rollup index, or you can remove or archive it using < Index Patterns*.
. Click *Create index pattern*, and select *Rollup index pattern* from the dropdown.
@@ -149,7 +154,11 @@ is `rollup_logstash,kibana_sample_data_logs`. In this index pattern, `rollup_log
matches the rolled up index pattern and `kibana_sample_data_logs` matches the index
pattern for raw data.
-. Open the main menu, click *Dashboard*, then create and add a vertical bar chart.
+. Open the main menu, click *Dashboard*, then *Create dashboard*.
+
+. Set the <> to *Last 90 days*.
+
+. On the dashboard, click *Create visualization*.
. Choose `rollup_logstash,kibana_sample_data_logs`
as your source to see both the raw and rolled up data.
@@ -157,13 +166,15 @@ as your source to see both the raw and rolled up data.
[role="screenshot"]
image::images/management-create-rollup-bar-chart.png[][Create visualization of rolled up data]
-. Look at the data in your visualization.
-+
-[role="screenshot"]
-image::images/management_rollup_job_vis.png[][Visualization of rolled up data]
+. Select *Bar vertical stacked* in the chart type dropdown.
-. Optionally, create a dashboard that contains visualizations of the rolled up
-data, raw data, or both.
+. Add the `@timestamp` field to the *Horizontal axis*.
+
+. Add the `bytes` field to the *Vertical axis*, defaulting to an `Average of
+bytes`.
++
+{kib} creates a vertical bar chart of your data. Select a section of the chart
+to zoom in.
+
[role="screenshot"]
image::images/management_rollup_job_dashboard.png[][Dashboard with rolled up data]
diff --git a/docs/management/snapshot-restore/images/create-policy-example.png b/docs/management/snapshot-restore/images/create-policy-example.png
old mode 100755
new mode 100644
index e871c925f5fd525..4ab5e438b306b4b
Binary files a/docs/management/snapshot-restore/images/create-policy-example.png and b/docs/management/snapshot-restore/images/create-policy-example.png differ
diff --git a/docs/management/snapshot-restore/images/create-policy.png b/docs/management/snapshot-restore/images/create-policy.png
old mode 100755
new mode 100644
index d9a0dce0f419024..3ba33e2522bd5b2
Binary files a/docs/management/snapshot-restore/images/create-policy.png and b/docs/management/snapshot-restore/images/create-policy.png differ
diff --git a/docs/management/snapshot-restore/images/create_snapshot.png b/docs/management/snapshot-restore/images/create_snapshot.png
deleted file mode 100644
index 14c1229a23ce1ab..000000000000000
Binary files a/docs/management/snapshot-restore/images/create_snapshot.png and /dev/null differ
diff --git a/docs/management/snapshot-restore/images/register_repo.png b/docs/management/snapshot-restore/images/register_repo.png
old mode 100755
new mode 100644
index 9e7ee9db4ce9149..c742028ce108cfe
Binary files a/docs/management/snapshot-restore/images/register_repo.png and b/docs/management/snapshot-restore/images/register_repo.png differ
diff --git a/docs/management/snapshot-restore/images/repository_list.png b/docs/management/snapshot-restore/images/repository_list.png
old mode 100755
new mode 100644
index a4678e87bfb2cce..c4eb4fc1a3d1a84
Binary files a/docs/management/snapshot-restore/images/repository_list.png and b/docs/management/snapshot-restore/images/repository_list.png differ
diff --git a/docs/management/snapshot-restore/images/restore-status.png b/docs/management/snapshot-restore/images/restore-status.png
deleted file mode 100755
index fa48e32d2fef3f3..000000000000000
Binary files a/docs/management/snapshot-restore/images/restore-status.png and /dev/null differ
diff --git a/docs/management/snapshot-restore/images/snapshot-restore.png b/docs/management/snapshot-restore/images/snapshot-restore.png
old mode 100755
new mode 100644
index 41a292f97c85360..8ca5dc95e589269
Binary files a/docs/management/snapshot-restore/images/snapshot-restore.png and b/docs/management/snapshot-restore/images/snapshot-restore.png differ
diff --git a/docs/management/snapshot-restore/images/snapshot-retention.png b/docs/management/snapshot-restore/images/snapshot-retention.png
old mode 100755
new mode 100644
index 7b390357a21b6b3..44dfecc1a332187
Binary files a/docs/management/snapshot-restore/images/snapshot-retention.png and b/docs/management/snapshot-restore/images/snapshot-retention.png differ
diff --git a/docs/management/snapshot-restore/images/snapshot_details.png b/docs/management/snapshot-restore/images/snapshot_details.png
old mode 100755
new mode 100644
index 2bd226eecd84e34..e6c463d7acb7f01
Binary files a/docs/management/snapshot-restore/images/snapshot_details.png and b/docs/management/snapshot-restore/images/snapshot_details.png differ
diff --git a/docs/management/snapshot-restore/images/snapshot_list.png b/docs/management/snapshot-restore/images/snapshot_list.png
old mode 100755
new mode 100644
index dcbb43ec2ab8423..f844bfddac4bead
Binary files a/docs/management/snapshot-restore/images/snapshot_list.png and b/docs/management/snapshot-restore/images/snapshot_list.png differ
diff --git a/docs/management/snapshot-restore/images/snapshot_permissions.png b/docs/management/snapshot-restore/images/snapshot_permissions.png
deleted file mode 100644
index 463d4d6e389c615..000000000000000
Binary files a/docs/management/snapshot-restore/images/snapshot_permissions.png and /dev/null differ
diff --git a/docs/management/snapshot-restore/index.asciidoc b/docs/management/snapshot-restore/index.asciidoc
index 62633441ef1616f..b041bd0873a058e 100644
--- a/docs/management/snapshot-restore/index.asciidoc
+++ b/docs/management/snapshot-restore/index.asciidoc
@@ -2,8 +2,8 @@
[[snapshot-repositories]]
== Snapshot and Restore
-*Snapshot and Restore* enables you to backup your {es}
-indices and clusters using data and state snapshots.
+*Snapshot and Restore* lets you back up a running {es}
+cluster using data and state snapshots.
Snapshots are important because they provide a copy of your data in case
something goes wrong. If you need to roll back to an older version of your data,
you can restore a snapshot from the repository.
@@ -34,17 +34,12 @@ The minimum required permissions to access *Snapshot and Restore* include:
To add privileges, open the main menu, then click *Stack Management > Roles*.
-[role="screenshot"]
-image:management/snapshot-restore/images/snapshot_permissions.png["Edit Role"]
-
[float]
[[kib-snapshot-register-repository]]
=== Register a repository
A repository is where your snapshots live. You must register a snapshot
repository before you can perform snapshot and restore operations.
-If you don't have a repository, Kibana walks you through the process of
-registering one.
{kib} supports three repository types
out of the box: shared file system, read-only URL, and source-only.
For more information on these repositories and their settings,
@@ -52,11 +47,9 @@ see {ref}/snapshots-register-repository.html[Repositories].
To use other repositories, such as S3, see
{ref}/snapshots-register-repository.html#snapshots-repository-plugins[Repository plugins].
-
-Once you create a repository, it is listed in the *Repositories*
-view.
-Click a repository name to view its type, number of snapshots, and settings,
-and to verify status.
+The *Repositories* view displays a list of registered repositories. Click a
+repository name to view information about the repository, verify its status, or
+clean it up.
[role="screenshot"]
image:management/snapshot-restore/images/repository_list.png["Repository list"]
@@ -73,15 +66,8 @@ into each snapshot for further investigation.
[role="screenshot"]
image:management/snapshot-restore/images/snapshot_details.png["Snapshot details"]
-If you don’t have any snapshots, you can create them from the {kib} <>. The
-{ref}/snapshots-take-snapshot.html[snapshot API]
-takes the current state and data in your index or cluster, and then saves it to a
-shared repository.
-
-The snapshot process is "smart." Your first snapshot is a complete copy of
-the data in your index or cluster.
-All subsequent snapshots save the changes between the existing snapshots and
-the new data.
+If you don’t have any snapshots, you can create them using the
+{ref}/create-snapshot-api.html[create snapshot API].
[float]
[[kib-restore-snapshot]]
@@ -93,14 +79,14 @@ restore a snapshot made from one cluster to another cluster. You might
use the restore operation to:
* Recover data lost due to a failure
-* Migrate a current Elasticsearch cluster to a new version
+* Migrate an {es} cluster to a new version
* Move data from one cluster to another cluster
To get started, go to the *Snapshots* view, find the
snapshot, and click the restore icon in the *Actions* column.
The Restore wizard presents
options for the restore operation, including which
-indices to restore and whether to modify the index settings.
+data streams and indices to restore and whether to change index settings.
You can restore an existing index only if it’s closed and has the same
number of shards as the index in the snapshot.
@@ -119,7 +105,7 @@ Use a {ref}/snapshot-lifecycle-management-api.html[snapshot lifecycle policy]
to automate the creation and deletion
of cluster snapshots. Taking automatic snapshots:
-* Ensures your {es} indices and clusters are backed up on a regular basis
+* Ensures your {es} data is backed up on a regular basis
* Ensures a recent and relevant snapshot is available if a situation
arises where a cluster needs to be recovered
* Allows you to manage your snapshots in {kib}, instead of using a
@@ -138,8 +124,8 @@ You can drill down into each policy to examine its settings and last successful
You can perform the following actions on a snapshot policy:
-* *Run* a policy immediately without waiting for the scheduled time.
-This action is useful before an upgrade or before performing maintenance on indices.
+* *Run* a policy immediately without waiting for the scheduled time. This action
+is useful before an upgrade or before performing maintenance.
* *Edit* a policy and immediately apply changes to the schedule.
* *Delete* a policy to prevent any future snapshots from being taken.
This action does not cancel any currently ongoing snapshots or remove any previously taken snapshots.
@@ -160,7 +146,7 @@ and then click *Delete snapshots*.
[role="xpack"]
[[snapshot-restore-tutorial]]
-=== Tutorial: Snapshot and Restore
+=== Tutorial: Snapshot and Restore
Ready to try *Snapshot and Restore*? In this tutorial, you'll learn to:
@@ -174,15 +160,12 @@ Ready to try *Snapshot and Restore*? In this tutorial, you'll learn to:
This example shows you how to register a shared file system repository
and store snapshots.
-Before you begin, you must register the location of the repository in the
-{ref}/snapshots-register-repository.html#snapshots-filesystem-repository[path.repo] setting on
-your master and data nodes. You can do this in one of two ways:
-* Edit your `elasticsearch.yml` to include the `path.repo` setting.
-
-* Pass the `path.repo` setting when you start Elasticsearch.
-+
-`bin/elasticsearch -E path.repo=/tmp/es-backups`
+Before you begin, you must first mount the file system to the same location on
+all master and data nodes. Then add the file system’s path or parent directory
+to the
+{ref}/snapshots-register-repository.html#snapshots-filesystem-repository[`path.repo`]
+setting in `elasticsearch.yml` for each master and data node.
[float]
[[register-repo-example]]
@@ -216,13 +199,10 @@ Use the {ref}/snapshots-take-snapshot.html[snapshot API] to create a snapshot.
. Create the snapshot:
+
[source,js]
-PUT /_snapshot/my_backup/2019-04-25_snapshot?wait_for_completion=true
+PUT /_snapshot/my_backup/2099-04-25_snapshot?wait_for_completion=true
+
-In this example, the snapshot name is `2019-04-25_snapshot`. You can also
+In this example, the snapshot name is `2099-04-25_snapshot`. You can also
use {ref}/date-math-index-names.html[date math expression] for the snapshot name.
-+
-[role="screenshot"]
-image:management/snapshot-restore/images/create_snapshot.png["Create snapshot"]
. Return to *Snapshot and Restore*.
+
@@ -251,16 +231,17 @@ image:management/snapshot-restore/images/create-policy-example.png["Create polic
|Snapshot name
|``
-|Schedule
-|Every day at 1:30 a.m.
-
|Repository
|`my_backup`
+|Schedule
+|Every day at 1:30 a.m.
+
|*Snapshot settings* |
-|Indices
-|Select the indices to back up. By default, all indices, including system indices, are backed up.
+|Data streams and indices
+|Select the data streams and indices to back up. By default, all data streams
+and indices, including system indices, are backed up.
|All other settings
|Use the defaults.
@@ -280,20 +261,22 @@ Your new policy is listed in the *Policies* view, and you see a summary of its d
[[restore-snapshot-example]]
==== Restore a snapshot
-Finally, you'll restore indices from an existing snapshot.
+Finally, you'll restore data streams and indices from an existing snapshot.
-. In the *Snapshots* view, find the snapshot you want to restore, for example `2019-04-25_snapshot`.
+. In the *Snapshots* view, find the snapshot you want to restore, for example `2099-04-25_snapshot`.
. Click the restore icon in the *Actions* column.
. As you walk through the wizard, enter the following values:
+
|===
|*Logistics* |
-|Indices
-|Toggle to choose specific indices to restore, or leave in place to restore all indices.
+|Data streams and indices
+|Toggle to choose specific data streams and indices to restore. Use the default
+to restore all data streams and indices in the snapshot.
-|Rename indices
-|Toggle to give your restored indices new names, or leave in place to restore under original index names.
+|Rename data streams and indices
+|Toggle to give your restored data streams and indices new names. Use the
+default to restore the original data stream and index names.
|All other fields
|Use the defaults.
@@ -313,4 +296,4 @@ or leave in place to keep existing settings.
+
The operation loads for a few seconds,
and then you’re navigated to *Restore Status*,
-where you can monitor the status of your restored indices.
+where you can monitor the status of your restored data streams and indices.
diff --git a/docs/maps/images/gs_dashboard_with_map.png b/docs/maps/images/gs_dashboard_with_map.png
index 49b71c16c12b2fe..a4bf95948edf00d 100644
Binary files a/docs/maps/images/gs_dashboard_with_map.png and b/docs/maps/images/gs_dashboard_with_map.png differ
diff --git a/docs/maps/images/gs_dashboard_with_terms_filter.png b/docs/maps/images/gs_dashboard_with_terms_filter.png
index 21b5c044cb35d9a..bf84b2ee371af39 100644
Binary files a/docs/maps/images/gs_dashboard_with_terms_filter.png and b/docs/maps/images/gs_dashboard_with_terms_filter.png differ
diff --git a/docs/maps/images/layer_search.png b/docs/maps/images/layer_search.png
index 8e0e8ff62895316..d3828ed5f4551b9 100644
Binary files a/docs/maps/images/layer_search.png and b/docs/maps/images/layer_search.png differ
diff --git a/docs/maps/images/quantitative_data_driven_styling.png b/docs/maps/images/quantitative_data_driven_styling.png
index a7852ed2020167c..03dc22f433eeec8 100644
Binary files a/docs/maps/images/quantitative_data_driven_styling.png and b/docs/maps/images/quantitative_data_driven_styling.png differ
diff --git a/docs/maps/images/sample_data_ecommerce.png b/docs/maps/images/sample_data_ecommerce.png
index 5b261bb5350225d..7fba3da608d1548 100644
Binary files a/docs/maps/images/sample_data_ecommerce.png and b/docs/maps/images/sample_data_ecommerce.png differ
diff --git a/docs/maps/images/sample_data_web_logs.png b/docs/maps/images/sample_data_web_logs.png
index f4f4de88f199213..e4902c3e896101d 100644
Binary files a/docs/maps/images/sample_data_web_logs.png and b/docs/maps/images/sample_data_web_logs.png differ
diff --git a/docs/maps/images/vector_style_class.png b/docs/maps/images/vector_style_class.png
index 8c685dfcf0ab609..69549b9f5f2d821 100644
Binary files a/docs/maps/images/vector_style_class.png and b/docs/maps/images/vector_style_class.png differ
diff --git a/docs/maps/images/vector_style_dynamic.png b/docs/maps/images/vector_style_dynamic.png
index aeaef412b5220d5..3032e74180afa02 100644
Binary files a/docs/maps/images/vector_style_dynamic.png and b/docs/maps/images/vector_style_dynamic.png differ
diff --git a/docs/maps/images/vector_style_static.png b/docs/maps/images/vector_style_static.png
index 47d9c3b21fcb6fc..34908aa02fac73a 100644
Binary files a/docs/maps/images/vector_style_static.png and b/docs/maps/images/vector_style_static.png differ
diff --git a/docs/user/production-considerations/task-manager-health-monitoring.asciidoc b/docs/user/production-considerations/task-manager-health-monitoring.asciidoc
index 8f2c8d106c77cfa..3321a9d0c02a155 100644
--- a/docs/user/production-considerations/task-manager-health-monitoring.asciidoc
+++ b/docs/user/production-considerations/task-manager-health-monitoring.asciidoc
@@ -57,8 +57,12 @@ xpack.task_manager.monitored_task_execution_thresholds:
The health API is best consumed by via the `/api/task_manager/_health` endpoint.
-Additionally, the metrics are logged in the {kib} `DEBUG` logger at a regular cadence.
-To enable Task Manager DEBUG logging in your {kib} instance, add the following to your `kibana.yml`:
+Additionally, there are two ways to consume these metrics:
+
+*Debug logging*
+
+The metrics are logged in the {kib} `DEBUG` logger at a regular cadence.
+To enable Task Manager debug logging in your {kib} instance, add the following to your `kibana.yml`:
[source,yml]
----
@@ -69,7 +73,22 @@ logging:
level: debug
----
-These stats are logged based the number of milliseconds set in your <> setting, which means it could add substantial noise to your logs. Only enable this level of logging temporarily.
+These stats are logged based on the number of milliseconds set in your <> setting, which could add substantial noise to your logs. Only enable this level of logging temporarily.
+
+*Automatic logging*
+
+By default, the health API runs at a regular cadence, and each time it runs, it attempts to self evaluate its performance. If this self evaluation yields a potential problem,
+a message will log to the {kib} server log. In addition, the health API will look at how long tasks have waited to start (from when they were scheduled to start). If this number exceeds a configurable threshold (<>), the same message as above will log to the {kib} server log.
+
+This message looks like:
+
+[source,log]
+----
+Detected potential performance issue with Task Manager. Set 'xpack.task_manager.monitored_stats_health_verbose_log.enabled: true' in your Kibana.yml to enable debug logging`
+----
+
+
+If this message appears, set <> to `true` in your `kibana.yml`. This will start logging the health metrics at either a `warn` or `error` log level, depending on the detected severity of the potential problem.
[float]
[[making-sense-of-task-manager-health-stats]]
diff --git a/jest.config.integration.js b/jest.config.integration.js
index 8ff142714eebf97..e2b2afaa715ee2a 100644
--- a/jest.config.integration.js
+++ b/jest.config.integration.js
@@ -6,24 +6,8 @@
* Side Public License, v 1.
*/
-const preset = require('@kbn/test/jest-preset');
-
module.exports = {
- preset: '@kbn/test',
+ preset: '@kbn/test/jest_integration',
rootDir: '.',
roots: ['/src', '/packages'],
- testMatch: ['**/integration_tests**/*.test.{js,mjs,ts,tsx}'],
- testPathIgnorePatterns: preset.testPathIgnorePatterns.filter(
- (pattern) => !pattern.includes('integration_tests')
- ),
- setupFilesAfterEnv: [
- '/node_modules/@kbn/test/target_node/jest/setup/after_env.integration.js',
- ],
- reporters: [
- 'default',
- ['@kbn/test/target_node/jest/junit_reporter', { reportName: 'Jest Integration Tests' }],
- ],
- coverageReporters: !!process.env.CI
- ? [['json', { file: 'jest-integration.json' }]]
- : ['html', 'text'],
};
diff --git a/package.json b/package.json
index 5cf72e2110982fd..2ee46c031fbf002 100644
--- a/package.json
+++ b/package.json
@@ -83,10 +83,8 @@
"**/minimist": "^1.2.5",
"**/node-jose/node-forge": "^0.10.0",
"**/pdfkit/crypto-js": "4.0.0",
- "**/prismjs": "1.24.0",
"**/react-syntax-highlighter": "^15.3.1",
"**/react-syntax-highlighter/**/highlight.js": "^10.4.1",
- "**/refractor": "^3.3.1",
"**/request": "^2.88.2",
"**/trim": "1.0.1",
"**/typescript": "4.1.3",
@@ -103,7 +101,7 @@
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.13",
"@elastic/ems-client": "7.14.0",
- "@elastic/eui": "34.5.2",
+ "@elastic/eui": "35.0.0",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "^9.0.1-kibana3",
"@elastic/maki": "6.3.0",
@@ -155,6 +153,7 @@
"@kbn/securitysolution-utils": "link:bazel-bin/packages/kbn-securitysolution-utils",
"@kbn/server-http-tools": "link:bazel-bin/packages/kbn-server-http-tools",
"@kbn/server-route-repository": "link:bazel-bin/packages/kbn-server-route-repository",
+ "@kbn/typed-react-router-config": "link:bazel-bin/packages/kbn-typed-react-router-config",
"@kbn/std": "link:bazel-bin/packages/kbn-std",
"@kbn/tinymath": "link:bazel-bin/packages/kbn-tinymath",
"@kbn/ui-framework": "link:bazel-bin/packages/kbn-ui-framework",
@@ -179,6 +178,7 @@
"@turf/distance": "6.0.1",
"@turf/helpers": "6.0.1",
"@turf/length": "^6.0.2",
+ "@types/react-router-config": "^5.0.2",
"@types/redux-logger": "^3.0.8",
"JSONStream": "1.3.5",
"abort-controller": "^3.0.0",
@@ -358,6 +358,7 @@
"react-resize-detector": "^4.2.0",
"react-reverse-portal": "^1.0.4",
"react-router": "^5.2.0",
+ "react-router-config": "^5.1.1",
"react-router-dom": "^5.2.0",
"react-router-redux": "^4.0.8",
"react-shortcuts": "^2.0.0",
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index de7a27fd512769c..938afdc205a440e 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -56,6 +56,7 @@ filegroup(
"//packages/kbn-test:build",
"//packages/kbn-test-subj-selector:build",
"//packages/kbn-tinymath:build",
+ "//packages/kbn-typed-react-router-config:build",
"//packages/kbn-ui-framework:build",
"//packages/kbn-ui-shared-deps:build",
"//packages/kbn-utility-types:build",
diff --git a/packages/kbn-io-ts-utils/src/deep_exact_rt/index.test.ts b/packages/kbn-io-ts-utils/src/deep_exact_rt/index.test.ts
new file mode 100644
index 000000000000000..fe09fb442799c5c
--- /dev/null
+++ b/packages/kbn-io-ts-utils/src/deep_exact_rt/index.test.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import * as t from 'io-ts';
+import { deepExactRt } from '.';
+import { mergeRt } from '../merge_rt';
+
+describe('deepExactRt', () => {
+ it('recursively wraps partial/interface types in t.exact', () => {
+ const a = t.type({
+ path: t.type({
+ serviceName: t.string,
+ }),
+ query: t.type({
+ foo: t.string,
+ }),
+ });
+
+ const b = t.type({
+ path: t.type({
+ transactionType: t.string,
+ }),
+ });
+
+ const merged = mergeRt(a, b);
+
+ expect(
+ deepExactRt(a).decode({
+ path: {
+ serviceName: '',
+ transactionType: '',
+ },
+ query: {
+ foo: '',
+ bar: '',
+ },
+ // @ts-ignore
+ }).right
+ ).toEqual({ path: { serviceName: '' }, query: { foo: '' } });
+
+ expect(
+ deepExactRt(b).decode({
+ path: {
+ serviceName: '',
+ transactionType: '',
+ },
+ query: {
+ foo: '',
+ bar: '',
+ },
+ // @ts-ignore
+ }).right
+ ).toEqual({ path: { transactionType: '' } });
+
+ expect(
+ deepExactRt(merged).decode({
+ path: {
+ serviceName: '',
+ transactionType: '',
+ },
+ query: {
+ foo: '',
+ bar: '',
+ },
+ // @ts-ignore
+ }).right
+ ).toEqual({ path: { serviceName: '', transactionType: '' }, query: { foo: '' } });
+ });
+});
diff --git a/packages/kbn-io-ts-utils/src/deep_exact_rt/index.ts b/packages/kbn-io-ts-utils/src/deep_exact_rt/index.ts
new file mode 100644
index 000000000000000..8ebb9bbdd52f919
--- /dev/null
+++ b/packages/kbn-io-ts-utils/src/deep_exact_rt/index.ts
@@ -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
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import * as t from 'io-ts';
+import { mapValues } from 'lodash';
+import { mergeRt } from '../merge_rt';
+import { isParsableType, ParseableType } from '../parseable_types';
+
+export function deepExactRt | ParseableType>(type: T): T;
+
+export function deepExactRt(type: t.Type | ParseableType) {
+ if (!isParsableType(type)) {
+ return type;
+ }
+
+ switch (type._tag) {
+ case 'ArrayType':
+ return t.array(deepExactRt(type.type));
+
+ case 'DictionaryType':
+ return t.dictionary(type.domain, deepExactRt(type.codomain));
+
+ case 'InterfaceType':
+ return t.exact(t.interface(mapValues(type.props, deepExactRt)));
+
+ case 'PartialType':
+ return t.exact(t.partial(mapValues(type.props, deepExactRt)));
+
+ case 'IntersectionType':
+ return t.intersection(type.types.map(deepExactRt) as any);
+
+ case 'UnionType':
+ return t.union(type.types.map(deepExactRt) as any);
+
+ case 'MergeType':
+ return mergeRt(deepExactRt(type.types[0]), deepExactRt(type.types[1]));
+
+ default:
+ return type;
+ }
+}
diff --git a/packages/kbn-io-ts-utils/src/parseable_types/index.ts b/packages/kbn-io-ts-utils/src/parseable_types/index.ts
new file mode 100644
index 000000000000000..089717ad8891bfe
--- /dev/null
+++ b/packages/kbn-io-ts-utils/src/parseable_types/index.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import * as t from 'io-ts';
+import { MergeType } from '../merge_rt';
+
+export type ParseableType =
+ | t.StringType
+ | t.NumberType
+ | t.BooleanType
+ | t.ArrayType
+ | t.RecordC
+ | t.DictionaryType
+ | t.InterfaceType
+ | t.PartialType
+ | t.UnionType
+ | t.IntersectionType
+ | MergeType;
+
+const parseableTags = [
+ 'StringType',
+ 'NumberType',
+ 'BooleanType',
+ 'ArrayType',
+ 'DictionaryType',
+ 'InterfaceType',
+ 'PartialType',
+ 'UnionType',
+ 'IntersectionType',
+ 'MergeType',
+];
+
+export const isParsableType = (type: t.Type | ParseableType): type is ParseableType => {
+ return '_tag' in type && parseableTags.includes(type._tag);
+};
diff --git a/packages/kbn-io-ts-utils/src/to_json_schema/index.ts b/packages/kbn-io-ts-utils/src/to_json_schema/index.ts
index fc196a7c3123eb1..702c0150d07f741 100644
--- a/packages/kbn-io-ts-utils/src/to_json_schema/index.ts
+++ b/packages/kbn-io-ts-utils/src/to_json_schema/index.ts
@@ -7,35 +7,7 @@
*/
import * as t from 'io-ts';
import { mapValues } from 'lodash';
-
-type JSONSchemableValueType =
- | t.StringType
- | t.NumberType
- | t.BooleanType
- | t.ArrayType
- | t.RecordC
- | t.DictionaryType
- | t.InterfaceType
- | t.PartialType
- | t.UnionType
- | t.IntersectionType;
-
-const tags = [
- 'StringType',
- 'NumberType',
- 'BooleanType',
- 'ArrayType',
- 'DictionaryType',
- 'InterfaceType',
- 'PartialType',
- 'UnionType',
- 'IntersectionType',
-];
-
-const isSchemableValueType = (type: t.Mixed): type is JSONSchemableValueType => {
- // @ts-ignore
- return tags.includes(type._tag);
-};
+import { isParsableType } from '../parseable_types';
interface JSONSchemaObject {
type: 'object';
@@ -74,7 +46,7 @@ type JSONSchema =
| JSONSchemaAnyOf;
export const toJsonSchema = (type: t.Mixed): JSONSchema => {
- if (isSchemableValueType(type)) {
+ if (isParsableType(type)) {
switch (type._tag) {
case 'ArrayType':
return { type: 'array', items: toJsonSchema(type.type) };
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 4524cbe8912cf6e..d38c3aa34614782 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -112,4 +112,5 @@ pageLoadAssetSize:
visTypePie: 35583
expressionRevealImage: 25675
cases: 144442
+ expressionError: 22127
userSetup: 18532
diff --git a/packages/kbn-rule-data-utils/package.json b/packages/kbn-rule-data-utils/package.json
index 6f0b8439ec89158..42223e51ec2d6dd 100644
--- a/packages/kbn-rule-data-utils/package.json
+++ b/packages/kbn-rule-data-utils/package.json
@@ -4,10 +4,5 @@
"types": "./target/index.d.ts",
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0",
- "private": true,
- "scripts": {
- "build": "../../node_modules/.bin/tsc",
- "kbn:bootstrap": "yarn build",
- "kbn:watch": "yarn build --watch"
- }
+ "private": true
}
diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel
index 28c42a4c4768468..432778f888e7a7e 100644
--- a/packages/kbn-test/BUILD.bazel
+++ b/packages/kbn-test/BUILD.bazel
@@ -28,6 +28,7 @@ filegroup(
NPM_MODULE_EXTRA_FILES = [
"jest/package.json",
"jest-preset.js",
+ "jest_integration/jest-preset.js",
"jest.config.js",
"README.md",
"package.json",
diff --git a/packages/kbn-test/jest_integration/jest-preset.js b/packages/kbn-test/jest_integration/jest-preset.js
new file mode 100644
index 000000000000000..7504dec9e7a20f5
--- /dev/null
+++ b/packages/kbn-test/jest_integration/jest-preset.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+const preset = require('../jest-preset');
+
+module.exports = {
+ ...preset,
+ testMatch: ['**/integration_tests**/*.test.{js,mjs,ts,tsx}'],
+ testPathIgnorePatterns: preset.testPathIgnorePatterns.filter(
+ (pattern) => !pattern.includes('integration_tests')
+ ),
+ setupFilesAfterEnv: [
+ '/node_modules/@kbn/test/target_node/jest/setup/after_env.integration.js',
+ '/node_modules/@kbn/test/target_node/jest/setup/mocks.js',
+ ],
+ reporters: [
+ 'default',
+ ['@kbn/test/target_node/jest/junit_reporter', { reportName: 'Jest Integration Tests' }],
+ ],
+ coverageReporters: !!process.env.CI
+ ? [['json', { file: 'jest-integration.json' }]]
+ : ['html', 'text'],
+};
diff --git a/packages/kbn-typed-react-router-config/BUILD.bazel b/packages/kbn-typed-react-router-config/BUILD.bazel
new file mode 100644
index 000000000000000..90f1acf43d3e7d4
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/BUILD.bazel
@@ -0,0 +1,113 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
+
+PKG_BASE_NAME = "kbn-typed-react-router-config"
+PKG_REQUIRE_NAME = "@kbn/typed-react-router-config"
+
+SOURCE_FILES = glob(
+ [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ ],
+ exclude = [
+ "**/*.test.*",
+ ]
+)
+
+SRCS = SOURCE_FILES
+
+filegroup(
+ name = "srcs",
+ srcs = SRCS,
+)
+
+NPM_MODULE_EXTRA_FILES = [
+ "package.json",
+]
+
+SRC_DEPS = [
+ "@npm//tslib",
+ "@npm//utility-types",
+ "@npm//io-ts",
+ "@npm//query-string",
+ "@npm//react-router-config",
+ "@npm//react-router-dom",
+ "//packages/kbn-io-ts-utils",
+]
+
+TYPES_DEPS = [
+ "@npm//@types/jest",
+ "@npm//@types/node",
+ "@npm//@types/react-router-config",
+ "@npm//@types/react-router-dom",
+]
+
+DEPS = SRC_DEPS + TYPES_DEPS
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//:tsconfig.base.json",
+ ],
+)
+
+ts_config(
+ name = "tsconfig_browser",
+ src = "tsconfig.browser.json",
+ deps = [
+ "//:tsconfig.base.json",
+ "//:tsconfig.browser.json",
+ ],
+)
+
+ts_project(
+ name = "tsc",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = True,
+ declaration_dir = "target_types",
+ declaration_map = True,
+ incremental = True,
+ out_dir = "target_node",
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig",
+)
+
+ts_project(
+ name = "tsc_browser",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = False,
+ incremental = True,
+ out_dir = "target_web",
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig_browser",
+)
+
+js_library(
+ name = PKG_BASE_NAME,
+ srcs = NPM_MODULE_EXTRA_FILES,
+ deps = DEPS + [":tsc", ":tsc_browser"],
+ package_name = PKG_REQUIRE_NAME,
+ visibility = ["//visibility:public"],
+)
+
+pkg_npm(
+ name = "npm_module",
+ deps = [
+ ":%s" % PKG_BASE_NAME,
+ ]
+)
+
+filegroup(
+ name = "build",
+ srcs = [
+ ":npm_module",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/packages/kbn-typed-react-router-config/jest.config.js b/packages/kbn-typed-react-router-config/jest.config.js
new file mode 100644
index 000000000000000..3a6b09c5677dba5
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/jest.config.js
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+module.exports = {
+ preset: '@kbn/test',
+ rootDir: '../..',
+ roots: ['/packages/kbn-typed-react-router-config'],
+};
diff --git a/packages/kbn-typed-react-router-config/package.json b/packages/kbn-typed-react-router-config/package.json
new file mode 100644
index 000000000000000..50c2e4b5d7e8908
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@kbn/typed-react-router-config",
+ "main": "target_node/index.js",
+ "types": "target_types/index.d.ts",
+ "browser": "target_web/index.js",
+ "version": "1.0.0",
+ "license": "SSPL-1.0 OR Elastic License 2.0",
+ "private": true
+}
diff --git a/packages/kbn-typed-react-router-config/src/create_router.test.tsx b/packages/kbn-typed-react-router-config/src/create_router.test.tsx
new file mode 100644
index 000000000000000..49f6961fa3a8547
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/create_router.test.tsx
@@ -0,0 +1,239 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React from 'react';
+import * as t from 'io-ts';
+import { toNumberRt } from '@kbn/io-ts-utils/target/to_number_rt';
+import { createRouter } from './create_router';
+import { createMemoryHistory } from 'history';
+import { route } from './route';
+
+describe('createRouter', () => {
+ const routes = route([
+ {
+ path: '/',
+ element: <>>,
+ children: [
+ {
+ path: '/',
+ element: <>>,
+ params: t.type({
+ query: t.type({
+ rangeFrom: t.string,
+ rangeTo: t.string,
+ }),
+ }),
+ children: [
+ {
+ path: '/services',
+ element: <>>,
+ params: t.type({
+ query: t.type({
+ transactionType: t.string,
+ }),
+ }),
+ },
+ {
+ path: '/services/:serviceName',
+ element: <>>,
+ params: t.type({
+ path: t.type({
+ serviceName: t.string,
+ }),
+ query: t.type({
+ transactionType: t.string,
+ environment: t.string,
+ }),
+ }),
+ },
+ {
+ path: '/traces',
+ element: <>>,
+ params: t.type({
+ query: t.type({
+ aggregationType: t.string,
+ }),
+ }),
+ },
+ {
+ path: '/service-map',
+ element: <>>,
+ params: t.type({
+ query: t.type({
+ maxNumNodes: t.string.pipe(toNumberRt as any),
+ }),
+ }),
+ },
+ ],
+ },
+ ],
+ },
+ ] as const);
+
+ let history = createMemoryHistory();
+ const router = createRouter(routes);
+
+ beforeEach(() => {
+ history = createMemoryHistory();
+ });
+
+ describe('getParams', () => {
+ it('returns parameters for routes matching the path only', () => {
+ history.push('/services?rangeFrom=now-15m&rangeTo=now&transactionType=request');
+ const topLevelParams = router.getParams('/', history.location);
+
+ expect(topLevelParams).toEqual({
+ path: {},
+ query: {
+ rangeFrom: 'now-15m',
+ rangeTo: 'now',
+ },
+ });
+
+ history.push('/services?rangeFrom=now-15m&rangeTo=now&transactionType=request');
+
+ const inventoryParams = router.getParams('/services', history.location);
+
+ expect(inventoryParams).toEqual({
+ path: {},
+ query: {
+ rangeFrom: 'now-15m',
+ rangeTo: 'now',
+ transactionType: 'request',
+ },
+ });
+
+ history.push('/traces?rangeFrom=now-15m&rangeTo=now&aggregationType=avg');
+
+ const topTracesParams = router.getParams('/traces', history.location);
+
+ expect(topTracesParams).toEqual({
+ path: {},
+ query: {
+ rangeFrom: 'now-15m',
+ rangeTo: 'now',
+ aggregationType: 'avg',
+ },
+ });
+
+ history.push(
+ '/services/opbeans-java?rangeFrom=now-15m&rangeTo=now&environment=production&transactionType=request'
+ );
+
+ const serviceOverviewParams = router.getParams('/services/:serviceName', history.location);
+
+ expect(serviceOverviewParams).toEqual({
+ path: {
+ serviceName: 'opbeans-java',
+ },
+ query: {
+ rangeFrom: 'now-15m',
+ rangeTo: 'now',
+ environment: 'production',
+ transactionType: 'request',
+ },
+ });
+ });
+
+ it('decodes the path and query parameters based on the route type', () => {
+ history.push('/service-map?rangeFrom=now-15m&rangeTo=now&maxNumNodes=3');
+ const topServiceMapParams = router.getParams('/service-map', history.location);
+
+ expect(topServiceMapParams).toEqual({
+ path: {},
+ query: {
+ rangeFrom: 'now-15m',
+ rangeTo: 'now',
+ maxNumNodes: 3,
+ },
+ });
+ });
+
+ it('throws an error if the given path does not match any routes', () => {
+ expect(() => {
+ router.getParams('/service-map', history.location);
+ }).toThrowError('No matching route found for /service-map');
+ });
+ });
+
+ describe('matchRoutes', () => {
+ it('returns only the routes matching the path', () => {
+ history.push('/service-map?rangeFrom=now-15m&rangeTo=now&maxNumNodes=3');
+
+ expect(router.matchRoutes('/', history.location).length).toEqual(2);
+ expect(router.matchRoutes('/service-map', history.location).length).toEqual(3);
+ });
+
+ it('throws an error if the given path does not match any routes', () => {
+ history.push('/service-map?rangeFrom=now-15m&rangeTo=now&maxNumNodes=3');
+
+ expect(() => {
+ router.matchRoutes('/traces', history.location);
+ }).toThrowError('No matching route found for /traces');
+ });
+ });
+
+ describe('link', () => {
+ it('returns a link for the given route', () => {
+ const serviceOverviewLink = router.link('/services/:serviceName', {
+ path: { serviceName: 'opbeans-java' },
+ query: {
+ rangeFrom: 'now-15m',
+ rangeTo: 'now',
+ environment: 'production',
+ transactionType: 'request',
+ },
+ });
+
+ expect(serviceOverviewLink).toEqual(
+ '/services/opbeans-java?environment=production&rangeFrom=now-15m&rangeTo=now&transactionType=request'
+ );
+
+ const servicesLink = router.link('/services', {
+ query: {
+ rangeFrom: 'now-15m',
+ rangeTo: 'now',
+ transactionType: 'request',
+ },
+ });
+
+ expect(servicesLink).toEqual(
+ '/services?rangeFrom=now-15m&rangeTo=now&transactionType=request'
+ );
+
+ const serviceMapLink = router.link('/service-map', {
+ query: {
+ maxNumNodes: '3',
+ rangeFrom: 'now-15m',
+ rangeTo: 'now',
+ },
+ });
+
+ expect(serviceMapLink).toEqual('/service-map?maxNumNodes=3&rangeFrom=now-15m&rangeTo=now');
+ });
+
+ it('validates the parameters needed for the route', () => {
+ expect(() => {
+ router.link('/traces', {
+ query: {
+ rangeFrom: {},
+ },
+ } as any);
+ }).toThrowError();
+
+ expect(() => {
+ router.link('/service-map', {
+ query: {
+ maxNumNodes: 3,
+ rangeFrom: 'now-15m',
+ rangeTo: 'now',
+ },
+ } as any);
+ }).toThrowError();
+ });
+ });
+});
diff --git a/packages/kbn-typed-react-router-config/src/create_router.ts b/packages/kbn-typed-react-router-config/src/create_router.ts
new file mode 100644
index 000000000000000..51b6e2a2f56928d
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/create_router.ts
@@ -0,0 +1,158 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import { isLeft } from 'fp-ts/lib/Either';
+import { Location } from 'history';
+import { PathReporter } from 'io-ts/lib/PathReporter';
+import {
+ matchRoutes as matchRoutesConfig,
+ RouteConfig as ReactRouterConfig,
+} from 'react-router-config';
+import qs from 'query-string';
+import { findLastIndex, merge, compact } from 'lodash';
+import { deepExactRt } from '@kbn/io-ts-utils/target/deep_exact_rt';
+import { mergeRt } from '@kbn/io-ts-utils/target/merge_rt';
+import { Route, Router } from './types';
+
+export function createRouter(routes: TRoutes): Router {
+ const routesByReactRouterConfig = new Map();
+ const reactRouterConfigsByRoute = new Map();
+
+ const reactRouterConfigs = routes.map((route) => toReactRouterConfigRoute(route));
+
+ function toReactRouterConfigRoute(route: Route, prefix: string = ''): ReactRouterConfig {
+ const path = `${prefix}${route.path}`.replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
+ const reactRouterConfig: ReactRouterConfig = {
+ component: () => route.element,
+ routes:
+ (route.children as Route[] | undefined)?.map((child) =>
+ toReactRouterConfigRoute(child, path)
+ ) ?? [],
+ exact: !route.children?.length,
+ path,
+ };
+
+ routesByReactRouterConfig.set(reactRouterConfig, route);
+ reactRouterConfigsByRoute.set(route, reactRouterConfig);
+
+ return reactRouterConfig;
+ }
+
+ const matchRoutes = (...args: any[]) => {
+ let path: string = args[0];
+ let location: Location = args[1];
+
+ if (args.length === 1) {
+ location = args[0] as Location;
+ path = location.pathname;
+ }
+
+ const greedy = path.endsWith('/*') || args.length === 1;
+
+ if (!path) {
+ path = '/';
+ }
+
+ const matches = matchRoutesConfig(reactRouterConfigs, location.pathname);
+
+ const matchIndex = greedy
+ ? matches.length - 1
+ : findLastIndex(matches, (match) => match.route.path === path);
+
+ if (matchIndex === -1) {
+ throw new Error(`No matching route found for ${path}`);
+ }
+
+ return matches.slice(0, matchIndex + 1).map((matchedRoute) => {
+ const route = routesByReactRouterConfig.get(matchedRoute.route);
+
+ if (route?.params) {
+ const decoded = deepExactRt(route.params).decode({
+ path: matchedRoute.match.params,
+ query: qs.parse(location.search),
+ });
+
+ if (isLeft(decoded)) {
+ throw new Error(PathReporter.report(decoded).join('\n'));
+ }
+
+ return {
+ match: {
+ ...matchedRoute.match,
+ params: decoded.right,
+ },
+ route,
+ };
+ }
+
+ return {
+ match: {
+ ...matchedRoute.match,
+ params: {
+ path: {},
+ query: {},
+ },
+ },
+ route,
+ };
+ });
+ };
+
+ const link = (path: string, ...args: any[]) => {
+ const params: { path?: Record; query?: Record } | undefined = args[0];
+
+ const paramsWithDefaults = merge({ path: {}, query: {} }, params);
+
+ path = path
+ .split('/')
+ .map((part) => {
+ return part.startsWith(':') ? paramsWithDefaults.path[part.split(':')[1]] : part;
+ })
+ .join('/');
+
+ const matches = matchRoutesConfig(reactRouterConfigs, path);
+
+ if (!matches.length) {
+ throw new Error(`No matching route found for ${path}`);
+ }
+
+ const validationType = mergeRt(
+ ...(compact(
+ matches.map((match) => {
+ return routesByReactRouterConfig.get(match.route)?.params;
+ })
+ ) as [any, any])
+ );
+
+ const validation = validationType.decode(paramsWithDefaults);
+
+ if (isLeft(validation)) {
+ throw new Error(PathReporter.report(validation).join('\n'));
+ }
+
+ return qs.stringifyUrl({
+ url: path,
+ query: paramsWithDefaults.query,
+ });
+ };
+
+ return {
+ link: (path, ...args) => {
+ return link(path, ...args);
+ },
+ getParams: (path, location) => {
+ const matches = matchRoutes(path, location);
+ return merge({ path: {}, query: {} }, ...matches.map((match) => match.match.params));
+ },
+ matchRoutes: (...args: any[]) => {
+ return matchRoutes(...args) as any;
+ },
+ getRoutePath: (route) => {
+ return reactRouterConfigsByRoute.get(route)!.path as string;
+ },
+ };
+}
diff --git a/packages/kbn-typed-react-router-config/src/index.ts b/packages/kbn-typed-react-router-config/src/index.ts
new file mode 100644
index 000000000000000..b58c70998901c3f
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+export * from './create_router';
+export * from './types';
+export * from './outlet';
+export * from './route';
+export * from './route_renderer';
+export * from './router_provider';
+export * from './unconst';
+export * from './use_current_route';
+export * from './use_match_routes';
+export * from './use_params';
+export * from './use_router';
+export * from './use_route_path';
diff --git a/packages/kbn-typed-react-router-config/src/outlet.tsx b/packages/kbn-typed-react-router-config/src/outlet.tsx
new file mode 100644
index 000000000000000..696085489abee13
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/outlet.tsx
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import { useCurrentRoute } from './use_current_route';
+
+export function Outlet() {
+ const { element } = useCurrentRoute();
+ return element;
+}
diff --git a/packages/kbn-typed-react-router-config/src/route.ts b/packages/kbn-typed-react-router-config/src/route.ts
new file mode 100644
index 000000000000000..b9b228d1009e2f6
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/route.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import { Route } from './types';
+import { Unconst, unconst } from './unconst';
+
+export function route(
+ r: TRoute
+): Unconst {
+ return unconst(r);
+}
diff --git a/packages/kbn-typed-react-router-config/src/route_renderer.tsx b/packages/kbn-typed-react-router-config/src/route_renderer.tsx
new file mode 100644
index 000000000000000..e7a39aa7d5d165e
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/route_renderer.tsx
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React from 'react';
+import { CurrentRouteContextProvider } from './use_current_route';
+import { RouteMatch } from './types';
+import { useMatchRoutes } from './use_match_routes';
+
+export function RouteRenderer() {
+ const matches: RouteMatch[] = useMatchRoutes();
+
+ return matches
+ .concat()
+ .reverse()
+ .reduce((prev, match) => {
+ const { element } = match.route;
+ return (
+
+ {element}
+
+ );
+ }, <>>);
+}
diff --git a/packages/kbn-typed-react-router-config/src/router_provider.tsx b/packages/kbn-typed-react-router-config/src/router_provider.tsx
new file mode 100644
index 000000000000000..d2512ba8fe4265b
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/router_provider.tsx
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import { History } from 'history';
+import React from 'react';
+import { Router as ReactRouter } from 'react-router-dom';
+import { Route, Router } from './types';
+import { RouterContextProvider } from './use_router';
+
+export function RouterProvider({
+ children,
+ router,
+ history,
+}: {
+ router: Router;
+ history: History;
+ children: React.ReactElement;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/kbn-typed-react-router-config/src/types/index.ts b/packages/kbn-typed-react-router-config/src/types/index.ts
new file mode 100644
index 000000000000000..dfd989396649188
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/types/index.ts
@@ -0,0 +1,421 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Location } from 'history';
+import * as t from 'io-ts';
+import { ReactElement } from 'react';
+import { RequiredKeys, ValuesType } from 'utility-types';
+// import { unconst } from '../unconst';
+import { NormalizePath } from './utils';
+
+export type PathsOf = keyof MapRoutes & string;
+
+export interface RouteMatch {
+ route: TRoute;
+ match: {
+ isExact: boolean;
+ path: string;
+ url: string;
+ params: TRoute extends {
+ params: t.Type;
+ }
+ ? t.OutputOf
+ : {};
+ };
+}
+
+type ToRouteMatch = TRoutes extends []
+ ? []
+ : TRoutes extends [Route]
+ ? [RouteMatch]
+ : TRoutes extends [Route, ...infer TTail]
+ ? TTail extends Route[]
+ ? [RouteMatch, ...ToRouteMatch]
+ : []
+ : [];
+
+type UnwrapRouteMap = TRoute extends {
+ parents: Route[];
+}
+ ? ToRouteMatch<[...TRoute['parents'], Omit]>
+ : ToRouteMatch<[Omit]>;
+
+export type Match = MapRoutes extends {
+ [key in TPath]: Route;
+}
+ ? UnwrapRouteMap[TPath]>
+ : [];
+
+interface PlainRoute {
+ path: string;
+ element: ReactElement;
+ children?: PlainRoute[];
+ params?: t.Type;
+}
+
+interface ReadonlyPlainRoute {
+ readonly path: string;
+ readonly element: ReactElement;
+ readonly children?: readonly ReadonlyPlainRoute[];
+ readonly params?: t.Type;
+}
+
+export type Route = PlainRoute | ReadonlyPlainRoute;
+
+interface DefaultOutput {
+ path: {};
+ query: {};
+}
+
+type OutputOfRouteMatch = TRouteMatch extends {
+ route: { params: t.Type };
+}
+ ? t.OutputOf
+ : DefaultOutput;
+
+type OutputOfMatches = TRouteMatches extends [RouteMatch]
+ ? OutputOfRouteMatch
+ : TRouteMatches extends [RouteMatch, ...infer TNextRouteMatches]
+ ? OutputOfRouteMatch &
+ (TNextRouteMatches extends RouteMatch[] ? OutputOfMatches : DefaultOutput)
+ : TRouteMatches extends RouteMatch[]
+ ? OutputOfRouteMatch>
+ : DefaultOutput;
+
+export type OutputOf> = OutputOfMatches<
+ Match
+> &
+ DefaultOutput;
+
+type TypeOfRouteMatch = TRouteMatch extends {
+ route: { params: t.Type };
+}
+ ? t.TypeOf
+ : {};
+
+type TypeOfMatches = TRouteMatches extends [RouteMatch]
+ ? TypeOfRouteMatch
+ : TRouteMatches extends [RouteMatch, ...infer TNextRouteMatches]
+ ? TypeOfRouteMatch &
+ (TNextRouteMatches extends RouteMatch[] ? TypeOfMatches : {})
+ : {};
+
+export type TypeOf> = TypeOfMatches<
+ Match
+>;
+
+export type TypeAsArgs = keyof TObject extends never
+ ? []
+ : RequiredKeys extends never
+ ? [TObject] | []
+ : [TObject];
+
+export interface Router {
+ matchRoutes>(
+ path: TPath,
+ location: Location
+ ): Match;
+ matchRoutes(location: Location): Match>;
+ getParams>(
+ path: TPath,
+ location: Location
+ ): OutputOf;
+ link>(
+ path: TPath,
+ ...args: TypeAsArgs>
+ ): string;
+ getRoutePath(route: Route): string;
+}
+
+type AppendPath<
+ TPrefix extends string,
+ TPath extends string
+> = NormalizePath<`${TPrefix}${NormalizePath<`/${TPath}`>}`>;
+
+type MaybeUnion, U extends Record> = Omit &
+ {
+ [key in keyof U]: key extends keyof T ? T[key] | U[key] : U[key];
+ };
+
+type MapRoute<
+ TRoute extends Route,
+ TPrefix extends string,
+ TParents extends Route[] = []
+> = TRoute extends Route
+ ? MaybeUnion<
+ {
+ [key in AppendPath]: TRoute & { parents: TParents };
+ },
+ TRoute extends { children: Route[] }
+ ? MaybeUnion<
+ MapRoutes<
+ TRoute['children'],
+ AppendPath,
+ [...TParents, TRoute]
+ >,
+ {
+ [key in AppendPath>]: ValuesType<
+ MapRoutes<
+ TRoute['children'],
+ AppendPath,
+ [...TParents, TRoute]
+ >
+ >;
+ }
+ >
+ : {}
+ >
+ : {};
+
+type MapRoutes<
+ TRoutes,
+ TPrefix extends string = '',
+ TParents extends Route[] = []
+> = TRoutes extends [Route]
+ ? MapRoute
+ : TRoutes extends [Route, Route]
+ ? MapRoute & MapRoute
+ : TRoutes extends [Route, Route, Route]
+ ? MapRoute &
+ MapRoute &
+ MapRoute
+ : TRoutes extends [Route, Route, Route, Route]
+ ? MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute
+ : TRoutes extends [Route, Route, Route, Route, Route]
+ ? MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute
+ : TRoutes extends [Route, Route, Route, Route, Route, Route]
+ ? MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute
+ : TRoutes extends [Route, Route, Route, Route, Route, Route, Route]
+ ? MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute
+ : TRoutes extends [Route, Route, Route, Route, Route, Route, Route, Route]
+ ? MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute
+ : TRoutes extends [Route, Route, Route, Route, Route, Route, Route, Route, Route]
+ ? MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute
+ : TRoutes extends [Route, Route, Route, Route, Route, Route, Route, Route, Route, Route]
+ ? MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute &
+ MapRoute
+ : {};
+
+// const element = null as any;
+
+// const routes = unconst([
+// {
+// path: '/',
+// element,
+// children: [
+// {
+// path: '/settings',
+// element,
+// children: [
+// {
+// path: '/agent-configuration',
+// element,
+// },
+// {
+// path: '/agent-configuration/create',
+// element,
+// params: t.partial({
+// query: t.partial({
+// pageStep: t.string,
+// }),
+// }),
+// },
+// {
+// path: '/agent-configuration/edit',
+// element,
+// params: t.partial({
+// query: t.partial({
+// pageStep: t.string,
+// }),
+// }),
+// },
+// {
+// path: '/apm-indices',
+// element,
+// },
+// {
+// path: '/customize-ui',
+// element,
+// },
+// {
+// path: '/schema',
+// element,
+// },
+// {
+// path: '/anomaly-detection',
+// element,
+// },
+// {
+// path: '/',
+// element,
+// },
+// ],
+// },
+// {
+// path: '/services/:serviceName',
+// element,
+// params: t.intersection([
+// t.type({
+// path: t.type({
+// serviceName: t.string,
+// }),
+// }),
+// t.partial({
+// query: t.partial({
+// environment: t.string,
+// rangeFrom: t.string,
+// rangeTo: t.string,
+// comparisonEnabled: t.string,
+// comparisonType: t.string,
+// latencyAggregationType: t.string,
+// transactionType: t.string,
+// kuery: t.string,
+// }),
+// }),
+// ]),
+// children: [
+// {
+// path: '/overview',
+// element,
+// },
+// {
+// path: '/transactions',
+// element,
+// },
+// {
+// path: '/errors',
+// element,
+// children: [
+// {
+// path: '/:groupId',
+// element,
+// params: t.type({
+// path: t.type({
+// groupId: t.string,
+// }),
+// }),
+// },
+// {
+// path: '/',
+// element,
+// params: t.partial({
+// query: t.partial({
+// sortDirection: t.string,
+// sortField: t.string,
+// pageSize: t.string,
+// page: t.string,
+// }),
+// }),
+// },
+// ],
+// },
+// {
+// path: '/foo',
+// element,
+// },
+// {
+// path: '/bar',
+// element,
+// },
+// {
+// path: '/baz',
+// element,
+// },
+// {
+// path: '/',
+// element,
+// },
+// ],
+// },
+// {
+// path: '/',
+// element,
+// params: t.partial({
+// query: t.partial({
+// rangeFrom: t.string,
+// rangeTo: t.string,
+// }),
+// }),
+// children: [
+// {
+// path: '/services',
+// element,
+// },
+// {
+// path: '/traces',
+// element,
+// },
+// {
+// path: '/service-map',
+// element,
+// },
+// {
+// path: '/',
+// element,
+// },
+// ],
+// },
+// ],
+// },
+// ] as const);
+
+// type Routes = typeof routes;
+
+// type Mapped = keyof MapRoutes;
+
+// type Bar = ValuesType>['route']['path'];
+// type Foo = OutputOf;
+
+// const { path }: Foo = {} as any;
+
+// function _useApmParams>(p: TPath): OutputOf {
+// return {} as any;
+// }
+
+// const params = _useApmParams('/*');
diff --git a/packages/kbn-typed-react-router-config/src/types/utils.ts b/packages/kbn-typed-react-router-config/src/types/utils.ts
new file mode 100644
index 000000000000000..38b23c5118d0f6c
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/types/utils.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import * as t from 'io-ts';
+
+export type MaybeOutputOf = T extends t.Type ? [t.OutputOf] : [];
+export type NormalizePath = T extends `//${infer TRest}`
+ ? NormalizePath<`/${TRest}`>
+ : T extends '/'
+ ? T
+ : T extends `${infer TRest}/`
+ ? TRest
+ : T;
+export type DeeplyMutableRoutes = T extends React.ReactElement
+ ? T
+ : T extends t.Type
+ ? T
+ : T extends readonly [infer U]
+ ? [DeeplyMutableRoutes]
+ : T extends readonly [infer U, ...infer V]
+ ? [DeeplyMutableRoutes, ...DeeplyMutableRoutes]
+ : T extends Record
+ ? {
+ -readonly [key in keyof T]: DeeplyMutableRoutes;
+ }
+ : T;
diff --git a/packages/kbn-typed-react-router-config/src/unconst.ts b/packages/kbn-typed-react-router-config/src/unconst.ts
new file mode 100644
index 000000000000000..d10c8290e20e9aa
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/unconst.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
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import * as t from 'io-ts';
+import { DeepReadonly } from 'utility-types';
+
+export type MaybeConst = TObject extends [object]
+ ? [DeepReadonly | TObject]
+ : TObject extends [object, ...infer TTail]
+ ? [DeepReadonly | TObject, ...(TTail extends object[] ? MaybeConst : [])]
+ : TObject extends object[]
+ ? DeepReadonly
+ : TObject extends object
+ ? [DeepReadonly | TObject]
+ : [];
+
+export type Unconst = T extends React.ReactElement
+ ? React.ReactElement
+ : T extends t.Type
+ ? T
+ : T extends readonly [any]
+ ? [Unconst]
+ : T extends readonly [any, any]
+ ? [Unconst, Unconst]
+ : T extends readonly [any, any, any]
+ ? [Unconst, Unconst, Unconst]
+ : T extends readonly [any, any, any, any]
+ ? [Unconst, Unconst, Unconst, Unconst]
+ : T extends readonly [any, any, any, any, any]
+ ? [Unconst, Unconst, Unconst, Unconst, Unconst]
+ : T extends readonly [any, any, any, any, any, any]
+ ? [Unconst, Unconst, Unconst, Unconst, Unconst, Unconst]
+ : T extends readonly [any, any, any, any, any, any, any]
+ ? [
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst
+ ]
+ : T extends readonly [any, any, any, any, any, any, any, any]
+ ? [
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst
+ ]
+ : T extends readonly [any, any, any, any, any, any, any, any, any]
+ ? [
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst
+ ]
+ : T extends readonly [any, any, any, any, any, any, any, any, any, any]
+ ? [
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst,
+ Unconst
+ ]
+ : T extends readonly [infer U, ...infer V]
+ ? [Unconst, ...Unconst]
+ : T extends Record
+ ? { -readonly [key in keyof T]: Unconst }
+ : T;
+
+export function unconst(value: T): Unconst {
+ return value as Unconst;
+}
diff --git a/packages/kbn-typed-react-router-config/src/use_current_route.tsx b/packages/kbn-typed-react-router-config/src/use_current_route.tsx
new file mode 100644
index 000000000000000..9227b119107b373
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/use_current_route.tsx
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React, { createContext, useContext } from 'react';
+import { RouteMatch } from './types';
+
+const CurrentRouteContext = createContext<
+ { match: RouteMatch; element: React.ReactElement } | undefined
+>(undefined);
+
+export const CurrentRouteContextProvider = ({
+ match,
+ element,
+ children,
+}: {
+ match: RouteMatch;
+ element: React.ReactElement;
+ children: React.ReactElement;
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const useCurrentRoute = () => {
+ const currentRoute = useContext(CurrentRouteContext);
+ if (!currentRoute) {
+ throw new Error('No match was found in context');
+ }
+ return currentRoute;
+};
diff --git a/packages/kbn-typed-react-router-config/src/use_match_routes.ts b/packages/kbn-typed-react-router-config/src/use_match_routes.ts
new file mode 100644
index 000000000000000..b818ff06e9ae619
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/use_match_routes.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
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { useLocation } from 'react-router-dom';
+import { RouteMatch } from './types';
+import { useRouter } from './use_router';
+
+export function useMatchRoutes(path?: string): RouteMatch[] {
+ const router = useRouter();
+ const location = useLocation();
+
+ return typeof path === 'undefined'
+ ? router.matchRoutes(location)
+ : router.matchRoutes(path as never, location);
+}
diff --git a/packages/kbn-typed-react-router-config/src/use_params.ts b/packages/kbn-typed-react-router-config/src/use_params.ts
new file mode 100644
index 000000000000000..3f730e5d156f6f4
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/use_params.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { useLocation } from 'react-router-dom';
+import { useRouter } from './use_router';
+
+export function useParams(path: string) {
+ const router = useRouter();
+ const location = useLocation();
+
+ return router.getParams(path as never, location);
+}
diff --git a/packages/kbn-typed-react-router-config/src/use_route_path.tsx b/packages/kbn-typed-react-router-config/src/use_route_path.tsx
new file mode 100644
index 000000000000000..c77fc7d04b620c9
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/use_route_path.tsx
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { last } from 'lodash';
+import { useMatchRoutes } from './use_match_routes';
+import { useRouter } from './use_router';
+
+export function useRoutePath() {
+ const lastRouteMatch = last(useMatchRoutes());
+ const router = useRouter();
+ if (!lastRouteMatch) {
+ throw new Error('No route was matched');
+ }
+
+ return router.getRoutePath(lastRouteMatch.route);
+}
diff --git a/packages/kbn-typed-react-router-config/src/use_router.tsx b/packages/kbn-typed-react-router-config/src/use_router.tsx
new file mode 100644
index 000000000000000..b54530ed0fbdb40
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/src/use_router.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { createContext, useContext } from 'react';
+import { Route, Router } from './types';
+
+const RouterContext = createContext | undefined>(undefined);
+
+export const RouterContextProvider = ({
+ router,
+ children,
+}: {
+ router: Router;
+ children: React.ReactElement;
+}) => {children};
+
+export function useRouter(): Router {
+ const router = useContext(RouterContext);
+
+ if (!router) {
+ throw new Error('Router not found in context');
+ }
+
+ return router;
+}
diff --git a/packages/kbn-typed-react-router-config/tsconfig.browser.json b/packages/kbn-typed-react-router-config/tsconfig.browser.json
new file mode 100644
index 000000000000000..1de1603fec28665
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/tsconfig.browser.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../tsconfig.browser.json",
+ "compilerOptions": {
+ "incremental": true,
+ "outDir": "./target_web",
+ "stripInternal": true,
+ "declaration": false,
+ "isolatedModules": true,
+ "sourceMap": true,
+ "sourceRoot": "../../../../../packages/kbn-typed-react-router-config/src",
+ "types": [
+ "node",
+ "jest"
+ ]
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/packages/kbn-typed-react-router-config/tsconfig.json b/packages/kbn-typed-react-router-config/tsconfig.json
new file mode 100644
index 000000000000000..fb7262aa6866266
--- /dev/null
+++ b/packages/kbn-typed-react-router-config/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "incremental": true,
+ "declarationDir": "./target_types",
+ "outDir": "./target_node",
+ "stripInternal": true,
+ "declaration": true,
+ "declarationMap": true,
+ "isolatedModules": true,
+ "sourceMap": true,
+ "sourceRoot": "../../../../../packages/kbn-typed-react-router-config/src",
+ "types": [
+ "node",
+ "jest"
+ ]
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap
index 801fa452e833230..16504cf97366ec9 100644
--- a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap
+++ b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap
@@ -29,7 +29,6 @@ exports[`#start() returns \`Context\` component 1`] = `
"euiCodeEditor.stopEditing": "When you're done, press Escape to stop editing.",
"euiCodeEditor.stopInteracting": "When you're done, press Escape to stop interacting with the code.",
"euiCollapsedItemActions.allActions": "All actions",
- "euiCollapsibleNav.closeButtonLabel": "close",
"euiColorPicker.alphaLabel": "Alpha channel (opacity) value",
"euiColorPicker.closeLabel": "Press the down key to open a popover containing color options",
"euiColorPicker.colorErrorMessage": "Invalid color value",
@@ -45,6 +44,7 @@ exports[`#start() returns \`Context\` component 1`] = `
"euiColorStopThumb.stopErrorMessage": "Value is out of range",
"euiColorStopThumb.stopLabel": "Stop value",
"euiColorStops.screenReaderAnnouncement": [Function],
+ "euiColumnActions.hideColumn": "Hide column",
"euiColumnActions.moveLeft": "Move left",
"euiColumnActions.moveRight": "Move right",
"euiColumnActions.sort": [Function],
@@ -75,6 +75,9 @@ exports[`#start() returns \`Context\` component 1`] = `
"euiComboBoxOptionsList.noMatchingOptions": [Function],
"euiComboBoxPill.removeSelection": [Function],
"euiCommonlyUsedTimeRanges.legend": "Commonly used",
+ "euiControlBar.customScreenReaderAnnouncement": [Function],
+ "euiControlBar.screenReaderAnnouncement": "There is a new region landmark with page level controls at the end of the document.",
+ "euiControlBar.screenReaderHeading": "Page level controls",
"euiDataGrid.ariaLabel": [Function],
"euiDataGrid.ariaLabelGridPagination": [Function],
"euiDataGrid.ariaLabelledBy": [Function],
@@ -100,11 +103,11 @@ exports[`#start() returns \`Context\` component 1`] = `
"euiFieldPassword.showPassword": "Show password as plain text. Note: this will visually expose your password on the screen.",
"euiFilePicker.clearSelectedFiles": "Clear selected files",
"euiFilePicker.filesSelected": "files selected",
+ "euiFilePicker.removeSelected": "Remove",
"euiFilterButton.filterBadge": [Function],
"euiFlyout.closeAriaLabel": "Close this dialog",
"euiForm.addressFormErrors": "Please address the highlighted errors.",
"euiFormControlLayoutClearButton.label": "Clear input",
- "euiHeaderAlert.dismiss": "Dismiss",
"euiHeaderLinks.appNavigation": "App menu",
"euiHeaderLinks.openNavigationMenu": "Open menu",
"euiHue.label": "Select the HSV color mode \\"hue\\" value",
@@ -134,11 +137,16 @@ exports[`#start() returns \`Context\` component 1`] = `
"euiNotificationEventReadButton.markAsReadAria": [Function],
"euiNotificationEventReadButton.markAsUnread": "Mark as unread",
"euiNotificationEventReadButton.markAsUnreadAria": [Function],
+ "euiNotificationEventReadIcon.read": "Read",
+ "euiNotificationEventReadIcon.readAria": [Function],
+ "euiNotificationEventReadIcon.unread": "Unread",
+ "euiNotificationEventReadIcon.unreadAria": [Function],
"euiPagination.disabledNextPage": "Next page",
"euiPagination.disabledPreviousPage": "Previous page",
"euiPagination.firstRangeAriaLabel": [Function],
"euiPagination.lastRangeAriaLabel": [Function],
"euiPagination.nextPage": [Function],
+ "euiPagination.pageOfTotalCompressed": [Function],
"euiPagination.previousPage": [Function],
"euiPaginationButton.longPageString": [Function],
"euiPaginationButton.shortPageString": [Function],
@@ -211,19 +219,16 @@ exports[`#start() returns \`Context\` component 1`] = `
"euiSuperUpdateButton.refreshButtonLabel": "Refresh",
"euiSuperUpdateButton.updateButtonLabel": "Update",
"euiSuperUpdateButton.updatingButtonLabel": "Updating",
- "euiTableHeaderCell.clickForAscending": "Click to sort in ascending order",
- "euiTableHeaderCell.clickForDescending": "Click to sort in descending order",
- "euiTableHeaderCell.clickForUnsort": "Click to unsort",
- "euiTableHeaderCell.titleTextWithSort": [Function],
+ "euiTableHeaderCell.titleTextWithDesc": [Function],
"euiTablePagination.rowsPerPage": "Rows per page",
"euiTablePagination.rowsPerPageOption": [Function],
"euiTableSortMobile.sorting": "Sorting",
"euiToast.dismissToast": "Dismiss toast",
"euiToast.newNotification": "A new notification appears",
"euiToast.notification": "Notification",
- "euiTour.closeTour": "Close tour",
- "euiTour.endTour": "End tour",
- "euiTour.skipTour": "Skip tour",
+ "euiTourStep.closeTour": "Close tour",
+ "euiTourStep.endTour": "End tour",
+ "euiTourStep.skipTour": "Skip tour",
"euiTourStepIndicator.ariaLabel": [Function],
"euiTourStepIndicator.isActive": "active",
"euiTourStepIndicator.isComplete": "complete",
diff --git a/src/core/public/i18n/i18n_eui_mapping.tsx b/src/core/public/i18n/i18n_eui_mapping.tsx
index 1cccc4d94a78dda..5e7e6ae7c0e4e53 100644
--- a/src/core/public/i18n/i18n_eui_mapping.tsx
+++ b/src/core/public/i18n/i18n_eui_mapping.tsx
@@ -9,13 +9,14 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiTokensObject } from '@elastic/eui';
interface EuiValues {
[key: string]: any;
}
export const getEuiContextMapping = () => {
- const euiContextMapping = {
+ const euiContextMapping: EuiTokensObject = {
'euiAccordion.isLoading': i18n.translate('core.euiAccordion.isLoading', {
defaultMessage: 'Loading',
}),
@@ -143,12 +144,6 @@ export const getEuiContextMapping = () => {
'ARIA label and tooltip content describing a button that expands an actions menu',
}
),
- 'euiCollapsibleNav.closeButtonLabel': i18n.translate(
- 'core.euiCollapsibleNav.closeButtonLabel',
- {
- defaultMessage: 'close',
- }
- ),
'euiColorPicker.screenReaderAnnouncement': i18n.translate(
'core.euiColorPicker.screenReaderAnnouncement',
{
@@ -235,6 +230,9 @@ export const getEuiContextMapping = () => {
'euiColumnActions.moveRight': i18n.translate('core.euiColumnActions.moveRight', {
defaultMessage: 'Move right',
}),
+ 'euiColumnActions.hideColumn': i18n.translate('core.euiColumnActions.hideColumn', {
+ defaultMessage: 'Hide column',
+ }),
'euiColumnSelector.hideAll': i18n.translate('core.euiColumnSelector.hideAll', {
defaultMessage: 'Hide all',
}),
@@ -368,6 +366,22 @@ export const getEuiContextMapping = () => {
'euiCommonlyUsedTimeRanges.legend': i18n.translate('core.euiCommonlyUsedTimeRanges.legend', {
defaultMessage: 'Commonly used',
}),
+ 'euiControlBar.screenReaderHeading': i18n.translate('core.euiControlBar.screenReaderHeading', {
+ defaultMessage: 'Page level controls',
+ }),
+ 'euiControlBar.customScreenReaderAnnouncement': ({ landmarkHeading }: EuiValues) =>
+ i18n.translate('core.euiControlBar.customScreenReaderAnnouncement', {
+ defaultMessage:
+ 'There is a new region landmark called {landmarkHeading} with page level controls at the end of the document.',
+ values: { landmarkHeading },
+ }),
+ 'euiControlBar.screenReaderAnnouncement': i18n.translate(
+ 'core.euiControlBar.screenReaderAnnouncement',
+ {
+ defaultMessage:
+ 'There is a new region landmark with page level controls at the end of the document.',
+ }
+ ),
'euiDataGrid.screenReaderNotice': i18n.translate('core.euiDataGrid.screenReaderNotice', {
defaultMessage: 'Cell contains interactive content.',
}),
@@ -500,6 +514,9 @@ export const getEuiContextMapping = () => {
'euiFilePicker.filesSelected': i18n.translate('core.euiFilePicker.filesSelected', {
defaultMessage: 'files selected',
}),
+ 'euiFilePicker.removeSelected': i18n.translate('core.euiFilePicker.removeSelected', {
+ defaultMessage: 'Remove',
+ }),
'euiFilterButton.filterBadge': ({ count, hasActiveFilters }: EuiValues) =>
i18n.translate('core.euiFilterButton.filterBadge', {
defaultMessage: '${count} ${filterCountLabel} filters',
@@ -518,10 +535,6 @@ export const getEuiContextMapping = () => {
description: 'ARIA label on a button that removes any entry in a form field',
}
),
- 'euiHeaderAlert.dismiss': i18n.translate('core.euiHeaderAlert.dismiss', {
- defaultMessage: 'Dismiss',
- description: 'ARIA label on a button that dismisses/removes a notification',
- }),
'euiHeaderLinks.appNavigation': i18n.translate('core.euiHeaderLinks.appNavigation', {
defaultMessage: 'App menu',
description: 'ARIA label on a `nav` element',
@@ -669,6 +682,25 @@ export const getEuiContextMapping = () => {
defaultMessage: 'Mark as unread',
}
),
+ 'euiNotificationEventReadIcon.readAria': ({ eventName }: EuiValues) =>
+ i18n.translate('core.euiNotificationEventReadIcon.readAria', {
+ defaultMessage: '{eventName} is read',
+ values: { eventName },
+ }),
+ 'euiNotificationEventReadIcon.unreadAria': ({ eventName }: EuiValues) =>
+ i18n.translate('core.euiNotificationEventReadIcon.unreadAria', {
+ defaultMessage: '{eventName} is unread',
+ values: { eventName },
+ }),
+ 'euiNotificationEventReadIcon.read': i18n.translate('core.euiNotificationEventReadIcon.read', {
+ defaultMessage: 'Read',
+ }),
+ 'euiNotificationEventReadIcon.unread': i18n.translate(
+ 'core.euiNotificationEventReadIcon.unread',
+ {
+ defaultMessage: 'Unread',
+ }
+ ),
'euiNotificationEventMessages.accordionHideText': i18n.translate(
'core.euiNotificationEventMessages.accordionHideText',
{
@@ -680,6 +712,11 @@ export const getEuiContextMapping = () => {
defaultMessage: 'Next page, {page}',
values: { page },
}),
+ 'euiPagination.pageOfTotalCompressed': ({ page, total }: EuiValues) =>
+ i18n.translate('core.euiPagination.pageOfTotalCompressed', {
+ defaultMessage: '{page} of {total}',
+ values: { page, total },
+ }),
'euiPagination.previousPage': ({ page }: EuiValues) =>
i18n.translate('core.euiPagination.previousPage', {
defaultMessage: 'Previous page, {page}',
@@ -1043,29 +1080,10 @@ export const getEuiContextMapping = () => {
description: 'Displayed in a button that updates based on date picked',
}
),
- 'euiTableHeaderCell.clickForAscending': i18n.translate(
- 'core.euiTableHeaderCell.clickForAscending',
- {
- defaultMessage: 'Click to sort in ascending order',
- description: 'Displayed in a button that toggles a table sorting',
- }
- ),
- 'euiTableHeaderCell.clickForDescending': i18n.translate(
- 'core.euiTableHeaderCell.clickForDescending',
- {
- defaultMessage: 'Click to sort in descending order',
- description: 'Displayed in a button that toggles a table sorting',
- }
- ),
- 'euiTableHeaderCell.clickForUnsort': i18n.translate('core.euiTableHeaderCell.clickForUnsort', {
- defaultMessage: 'Click to unsort',
- description: 'Displayed in a button that toggles a table sorting',
- }),
- 'euiTableHeaderCell.titleTextWithSort': ({ innerText, ariaSortValue }: EuiValues) =>
- i18n.translate('core.euiTableHeaderCell.titleTextWithSort', {
- defaultMessage: '{innerText}; Sorted in {ariaSortValue} order',
- values: { innerText, ariaSortValue },
- description: 'Text describing the table sort order',
+ 'euiTableHeaderCell.titleTextWithDesc': ({ innerText, description }: EuiValues) =>
+ i18n.translate('core.euiTableHeaderCell.titleTextWithDesc', {
+ defaultMessage: '{innerText}; {description}',
+ values: { innerText, description },
}),
'euiTablePagination.rowsPerPage': i18n.translate('core.euiTablePagination.rowsPerPage', {
defaultMessage: 'Rows per page',
@@ -1091,15 +1109,6 @@ export const getEuiContextMapping = () => {
defaultMessage: 'Notification',
description: 'ARIA label on an element containing a notification',
}),
- 'euiTour.endTour': i18n.translate('core.euiTour.endTour', {
- defaultMessage: 'End tour',
- }),
- 'euiTour.skipTour': i18n.translate('core.euiTour.skipTour', {
- defaultMessage: 'Skip tour',
- }),
- 'euiTour.closeTour': i18n.translate('core.euiTour.closeTour', {
- defaultMessage: 'Close tour',
- }),
'euiTourStepIndicator.isActive': i18n.translate('core.euiTourStepIndicator.isActive', {
defaultMessage: 'active',
description: 'Text for an active tour step',
@@ -1112,6 +1121,15 @@ export const getEuiContextMapping = () => {
defaultMessage: 'incomplete',
description: 'Text for an incomplete tour step',
}),
+ 'euiTourStep.endTour': i18n.translate('core.euiTourStep.endTour', {
+ defaultMessage: 'End tour',
+ }),
+ 'euiTourStep.skipTour': i18n.translate('core.euiTourStep.skipTour', {
+ defaultMessage: 'Skip tour',
+ }),
+ 'euiTourStep.closeTour': i18n.translate('core.euiTourStep.closeTour', {
+ defaultMessage: 'Close tour',
+ }),
'euiTourStepIndicator.ariaLabel': ({ status, number }: EuiValues) =>
i18n.translate('core.euiTourStepIndicator.ariaLabel', {
defaultMessage: 'Step {number} {status}',
diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts
index b3b7bf5e8eed7d6..893061c8e1b8967 100644
--- a/src/dev/license_checker/config.ts
+++ b/src/dev/license_checker/config.ts
@@ -74,6 +74,7 @@ export const LICENSE_OVERRIDES = {
'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts
'@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint
'@elastic/ems-client@7.14.0': ['Elastic License 2.0'],
+ '@elastic/eui@35.0.0': ['SSPL-1.0 OR Elastic License 2.0'],
// TODO can be removed if the https://github.com/jindw/xmldom/issues/239 is released
'xmldom@0.1.27': ['MIT'],
diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js
index 57ae640da3c8451..a71013cb06a88ee 100644
--- a/src/dev/precommit_hook/casing_check_config.js
+++ b/src/dev/precommit_hook/casing_check_config.js
@@ -40,6 +40,7 @@ export const IGNORE_FILE_GLOBS = [
'vars/*',
'.ci/pipeline-library/**/*',
'packages/kbn-test/jest-preset.js',
+ 'packages/kbn-test/jest_integration/jest-preset.js',
'test/package/Vagrantfile',
'**/test/**/fixtures/**/*',
diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts
index 6fc0841551fad81..15497258d457477 100644
--- a/src/dev/storybook/aliases.ts
+++ b/src/dev/storybook/aliases.ts
@@ -17,6 +17,7 @@ export const storybookAliases = {
dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook',
data_enhanced: 'x-pack/plugins/data_enhanced/.storybook',
embeddable: 'src/plugins/embeddable/.storybook',
+ expression_error: 'src/plugins/expression_error/.storybook',
expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook',
infra: 'x-pack/plugins/infra/.storybook',
security_solution: 'x-pack/plugins/security_solution/.storybook',
diff --git a/src/plugins/dashboard/public/application/top_nav/save_modal.tsx b/src/plugins/dashboard/public/application/top_nav/save_modal.tsx
index 79ac3917fb9680e..b0ed1ad0de9b624 100644
--- a/src/plugins/dashboard/public/application/top_nav/save_modal.tsx
+++ b/src/plugins/dashboard/public/application/top_nav/save_modal.tsx
@@ -8,6 +8,7 @@
import React, { Fragment } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
import { EuiFormRow, EuiTextArea, EuiSwitch } from '@elastic/eui';
import type { SavedObjectsTaggingApi } from '../../services/saved_objects_tagging_oss';
@@ -148,7 +149,9 @@ export class DashboardSaveModal extends React.Component