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 { title={this.props.title} showCopyOnSave={this.props.showCopyOnSave} initialCopyOnSave={this.props.showCopyOnSave} - objectType="dashboard" + objectType={i18n.translate('dashboard.topNav.saveModal.objectType', { + defaultMessage: 'dashboard', + })} options={this.renderDashboardSaveOptions()} showDescription={false} /> diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index c6bfbfc75c290ce..645de0f9fed1712 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -12,6 +12,8 @@ export const KIBANA_USER_QUERY_LANGUAGE_KEY = 'kibana.userQueryLanguage'; /** @public **/ export const INDEX_PATTERN_SAVED_OBJECT_TYPE = 'index-pattern'; +export type ValueSuggestionsMethod = 'terms_enum' | 'terms_agg'; + export const UI_SETTINGS = { META_FIELDS: 'metaFields', DOC_HIGHLIGHT: 'doc_table:highlight', diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 7c0473077d18240..90f5ff331b9718a 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -909,6 +909,16 @@ describe('SearchSource', () => { expect(callOptions.strategy).toBe(ES_SEARCH_STRATEGY); }); + test('should remove searchSessionId when forcing ES_SEARCH_STRATEGY', async () => { + searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); + const options = { sessionId: 'test' }; + await searchSource.fetch$(options).toPromise(); + + const [, callOptions] = mockSearchMethod.mock.calls[0]; + expect(callOptions.strategy).toBe(ES_SEARCH_STRATEGY); + expect(callOptions.sessionId).toBeUndefined(); + }); + test('should not override strategy if set ', async () => { searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const options = { strategy: 'banana' }; diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 19e80c7a487dcb8..e60e6fa00b27054 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -287,6 +287,8 @@ export class SearchSource { // This still uses bfetch for batching. if (!options?.strategy && syncSearchByDefault) { options.strategy = ES_SEARCH_STRATEGY; + // `ES_SEARCH_STRATEGY` doesn't support search sessions, hence remove sessionId + options.sessionId = undefined; } const s$ = defer(() => this.requestIsStarting(options)).pipe( diff --git a/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts index f8fc9d165fc6b91..cee7d37a1a12d9c 100644 --- a/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts +++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts @@ -35,7 +35,7 @@ export const setupGetValueSuggestions: KqlQuerySuggestionProvider = ( .getStartServices() .then(([_, __, dataStart]) => dataStart.autocomplete); return async ( - { indexPatterns, boolFilter, useTimeRange, signal }, + { indexPatterns, boolFilter, useTimeRange, signal, method }, { start, end, prefix, suffix, fieldName, nestedPath } ): Promise => { const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; @@ -59,6 +59,7 @@ export const setupGetValueSuggestions: KqlQuerySuggestionProvider = ( boolFilter, useTimeRange, signal, + method, }).then((valueSuggestions) => { const quotedValues = valueSuggestions.map((value) => typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}` diff --git a/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts index 449b056a60c7548..ebd90c7e92d75e0 100644 --- a/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { ValueSuggestionsMethod } from '../../../common'; import { IFieldType, IIndexPattern } from '../../../common/index_patterns'; export enum QuerySuggestionTypes { @@ -30,6 +31,7 @@ export interface QuerySuggestionGetFnArgs { signal?: AbortSignal; useTimeRange?: boolean; boolFilter?: any; + method?: ValueSuggestionsMethod; } /** @public **/ diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index 3dda97566da5adf..05dc38b8e5ac5b2 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -9,7 +9,13 @@ import dateMath from '@elastic/datemath'; import { memoize } from 'lodash'; import { CoreSetup } from 'src/core/public'; -import { IIndexPattern, IFieldType, UI_SETTINGS, buildQueryFromFilters } from '../../../common'; +import { + IIndexPattern, + IFieldType, + UI_SETTINGS, + buildQueryFromFilters, + ValueSuggestionsMethod, +} from '../../../common'; import { TimefilterSetup } from '../../query'; import { AutocompleteUsageCollector } from '../collectors'; @@ -22,6 +28,7 @@ interface ValueSuggestionsGetFnArgs { useTimeRange?: boolean; boolFilter?: any[]; signal?: AbortSignal; + method?: ValueSuggestionsMethod; } const getAutocompleteTimefilter = ( @@ -54,12 +61,25 @@ export const setupValueSuggestionProvider = ( } const requestSuggestions = memoize( - (index: string, field: IFieldType, query: string, filters: any = [], signal?: AbortSignal) => { + ( + index: string, + field: IFieldType, + query: string, + filters: any = [], + signal?: AbortSignal, + method?: ValueSuggestionsMethod + ) => { usageCollector?.trackRequest(); return core.http .fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', - body: JSON.stringify({ query, field: field.name, fieldMeta: field?.toSpec?.(), filters }), + body: JSON.stringify({ + query, + field: field.name, + fieldMeta: field?.toSpec?.(), + filters, + method, + }), signal, }) .then((r) => { @@ -77,6 +97,7 @@ export const setupValueSuggestionProvider = ( useTimeRange, boolFilter, signal, + method, }: ValueSuggestionsGetFnArgs): Promise => { const shouldSuggestValues = core!.uiSettings.get( UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES @@ -98,7 +119,7 @@ export const setupValueSuggestionProvider = ( const filters = [...(boolFilter ? boolFilter : []), ...filterQuery]; try { usageCollector?.trackCall(); - return await requestSuggestions(title, field, query, filters, signal); + return await requestSuggestions(title, field, query, filters, signal, method); } catch (e) { if (!signal?.aborted) { usageCollector?.trackError(); diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 6c0ddd000f30aa6..4508ad5948927f6 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2178,6 +2178,10 @@ export interface QuerySuggestionGetFnArgs { indexPatterns: IIndexPattern[]; // (undocumented) language: string; + // Warning: (ae-forgotten-export) The symbol "ValueSuggestionsMethod" needs to be exported by the entry point index.d.ts + // + // (undocumented) + method?: ValueSuggestionsMethod; // (undocumented) query: string; // (undocumented) diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index bd622d0151c93e2..42f2c4d4e6341dc 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -33,6 +33,9 @@ export function registerValueSuggestionsRoute(router: IRouter, config$: Observab query: schema.string(), filters: schema.maybe(schema.any()), fieldMeta: schema.maybe(schema.any()), + method: schema.maybe( + schema.oneOf([schema.literal('terms_agg'), schema.literal('terms_enum')]) + ), }, { unknowns: 'allow' } ), @@ -40,13 +43,13 @@ export function registerValueSuggestionsRoute(router: IRouter, config$: Observab }, async (context, request, response) => { const config = await config$.pipe(first()).toPromise(); - const { field: fieldName, query, filters, fieldMeta } = request.body; + const { field: fieldName, query, filters, fieldMeta, method } = request.body; const { index } = request.params; const abortSignal = getRequestAbortedSignal(request.events.aborted$); try { const fn = - config.autocomplete.valueSuggestions.method === 'terms_enum' + (method ?? config.autocomplete.valueSuggestions.method) === 'terms_enum' ? termsEnumSuggestions : termsAggSuggestions; const body = await fn( diff --git a/src/plugins/discover/public/application/apps/main/components/top_nav/on_save_search.tsx b/src/plugins/discover/public/application/apps/main/components/top_nav/on_save_search.tsx index f4b969e977254be..c3d1df096c32f22 100644 --- a/src/plugins/discover/public/application/apps/main/components/top_nav/on_save_search.tsx +++ b/src/plugins/discover/public/application/apps/main/components/top_nav/on_save_search.tsx @@ -135,7 +135,9 @@ export async function onSaveSearch({ onClose={() => {}} title={savedSearch.title} showCopyOnSave={!!savedSearch.id} - objectType="search" + objectType={i18n.translate('discover.localMenu.saveSaveSearchObjectType', { + defaultMessage: 'search', + })} description={i18n.translate('discover.localMenu.saveSaveSearchDescription', { defaultMessage: 'Save your Discover search so you can use it in visualizations and dashboards', diff --git a/src/plugins/expression_error/.storybook/main.js b/src/plugins/expression_error/.storybook/main.js new file mode 100644 index 000000000000000..742239e638b8ac0 --- /dev/null +++ b/src/plugins/expression_error/.storybook/main.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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. + */ + +// eslint-disable-next-line import/no-commonjs +module.exports = require('@kbn/storybook').defaultConfig; diff --git a/src/plugins/expression_error/README.md b/src/plugins/expression_error/README.md new file mode 100755 index 000000000000000..5e22d8fc652c790 --- /dev/null +++ b/src/plugins/expression_error/README.md @@ -0,0 +1,9 @@ +# expressionRevealImage + +Expression Error plugin adds an `error` renderer to the expression plugin. The renderer will display the error image. + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/expression_error/common/constants.ts b/src/plugins/expression_error/common/constants.ts new file mode 100644 index 000000000000000..3a522d200090d5d --- /dev/null +++ b/src/plugins/expression_error/common/constants.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 const PLUGIN_ID = 'expressionError'; +export const PLUGIN_NAME = 'expressionError'; + +export const JSON = 'JSON'; diff --git a/src/plugins/expression_error/common/index.ts b/src/plugins/expression_error/common/index.ts new file mode 100755 index 000000000000000..d8989abcc3d6f1b --- /dev/null +++ b/src/plugins/expression_error/common/index.ts @@ -0,0 +1,9 @@ +/* + * 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 './constants'; diff --git a/src/plugins/expression_error/common/types/expression_renderers.ts b/src/plugins/expression_error/common/types/expression_renderers.ts new file mode 100644 index 000000000000000..25a9d5edac4adb4 --- /dev/null +++ b/src/plugins/expression_error/common/types/expression_renderers.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 type OriginString = 'bottom' | 'left' | 'top' | 'right'; + +export interface ErrorRendererConfig { + error: Error; +} + +export interface NodeDimensions { + width: number; + height: number; +} diff --git a/src/plugins/expression_error/common/types/index.ts b/src/plugins/expression_error/common/types/index.ts new file mode 100644 index 000000000000000..22961a0dc2fe042 --- /dev/null +++ b/src/plugins/expression_error/common/types/index.ts @@ -0,0 +1,9 @@ +/* + * 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 './expression_renderers'; diff --git a/src/plugins/expression_error/jest.config.js b/src/plugins/expression_error/jest.config.js new file mode 100644 index 000000000000000..64f3e9292ff0733 --- /dev/null +++ b/src/plugins/expression_error/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: ['/src/plugins/expression_error'], +}; diff --git a/src/plugins/expression_error/kibana.json b/src/plugins/expression_error/kibana.json new file mode 100755 index 000000000000000..9d8dd566d5b3a1a --- /dev/null +++ b/src/plugins/expression_error/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "expressionError", + "version": "1.0.0", + "kibanaVersion": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["expressions", "presentationUtil"], + "optionalPlugins": [], + "requiredBundles": [] +} diff --git a/x-pack/plugins/canvas/public/components/debug/__stories__/__snapshots__/debug.stories.storyshot b/src/plugins/expression_error/public/components/debug/__stories__/__snapshots__/debug.stories.storyshot similarity index 100% rename from x-pack/plugins/canvas/public/components/debug/__stories__/__snapshots__/debug.stories.storyshot rename to src/plugins/expression_error/public/components/debug/__stories__/__snapshots__/debug.stories.storyshot diff --git a/x-pack/plugins/canvas/public/components/debug/__stories__/debug.stories.tsx b/src/plugins/expression_error/public/components/debug/__stories__/debug.stories.tsx similarity index 72% rename from x-pack/plugins/canvas/public/components/debug/__stories__/debug.stories.tsx rename to src/plugins/expression_error/public/components/debug/__stories__/debug.stories.tsx index f29ab4b7bfda405..7dce5e8f1862b38 100644 --- a/x-pack/plugins/canvas/public/components/debug/__stories__/debug.stories.tsx +++ b/src/plugins/expression_error/public/components/debug/__stories__/debug.stories.tsx @@ -1,8 +1,9 @@ /* * 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; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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'; diff --git a/x-pack/plugins/canvas/public/components/debug/__stories__/helpers.tsx b/src/plugins/expression_error/public/components/debug/__stories__/helpers.tsx similarity index 99% rename from x-pack/plugins/canvas/public/components/debug/__stories__/helpers.tsx rename to src/plugins/expression_error/public/components/debug/__stories__/helpers.tsx index 38ff5977254f9d1..666731e199b4d81 100644 --- a/x-pack/plugins/canvas/public/components/debug/__stories__/helpers.tsx +++ b/src/plugins/expression_error/public/components/debug/__stories__/helpers.tsx @@ -1,8 +1,9 @@ /* * 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; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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 const largePayload = { diff --git a/x-pack/plugins/canvas/public/components/debug/debug.scss b/src/plugins/expression_error/public/components/debug/debug.scss similarity index 100% rename from x-pack/plugins/canvas/public/components/debug/debug.scss rename to src/plugins/expression_error/public/components/debug/debug.scss diff --git a/x-pack/plugins/canvas/public/components/debug/debug.tsx b/src/plugins/expression_error/public/components/debug/debug.tsx similarity index 66% rename from x-pack/plugins/canvas/public/components/debug/debug.tsx rename to src/plugins/expression_error/public/components/debug/debug.tsx index 8dc09a32415a5b9..925616d5550b11e 100644 --- a/x-pack/plugins/canvas/public/components/debug/debug.tsx +++ b/src/plugins/expression_error/public/components/debug/debug.tsx @@ -1,13 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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 PropTypes from 'prop-types'; import { EuiCode } from '@elastic/eui'; +import './debug.scss'; const LimitRows = (key: string, value: any) => { if (key === 'rows') { @@ -16,14 +17,10 @@ const LimitRows = (key: string, value: any) => { return value; }; -export const Debug = ({ payload }: any) => ( +export const Debug = ({ payload }: { payload: unknown }) => (
       {JSON.stringify(payload, LimitRows, 2)}
     
); - -Debug.propTypes = { - payload: PropTypes.object, -}; diff --git a/src/plugins/expression_error/public/components/debug/index.tsx b/src/plugins/expression_error/public/components/debug/index.tsx new file mode 100644 index 000000000000000..1984eecfe4e39ff --- /dev/null +++ b/src/plugins/expression_error/public/components/debug/index.tsx @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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. + */ + +// eslint-disable-next-line import/no-default-export +export { Debug as default } from './debug'; diff --git a/src/plugins/expression_error/public/components/debug_component.tsx b/src/plugins/expression_error/public/components/debug_component.tsx new file mode 100644 index 000000000000000..6cb927380d66990 --- /dev/null +++ b/src/plugins/expression_error/public/components/debug_component.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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, { useState, useEffect, useCallback } from 'react'; +import { useResizeObserver } from '@elastic/eui'; +import { IInterpreterRenderHandlers } from '../../../expressions'; +import { withSuspense } from '../../../presentation_util/public'; +import { NodeDimensions } from '../../common/types'; +import { LazyDebugComponent } from '.'; + +const Debug = withSuspense(LazyDebugComponent); + +interface DebugComponentProps { + onLoaded: IInterpreterRenderHandlers['done']; + parentNode: HTMLElement; + payload: any; +} + +function DebugComponent({ onLoaded, parentNode, payload }: DebugComponentProps) { + const parentNodeDimensions = useResizeObserver(parentNode); + const [dimensions, setDimensions] = useState({ + width: parentNode.offsetWidth, + height: parentNode.offsetHeight, + }); + + const updateDebugView = useCallback(() => { + setDimensions({ + width: parentNode.offsetWidth, + height: parentNode.offsetHeight, + }); + onLoaded(); + }, [parentNode, onLoaded]); + + useEffect(() => { + updateDebugView(); + }, [parentNodeDimensions, updateDebugView]); + + return ( +
+ +
+ ); +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { DebugComponent as default }; diff --git a/x-pack/plugins/canvas/public/components/error/error.tsx b/src/plugins/expression_error/public/components/error/error.tsx similarity index 76% rename from x-pack/plugins/canvas/public/components/error/error.tsx rename to src/plugins/expression_error/public/components/error/error.tsx index cb2c2cd5d58c180..99318357d860245 100644 --- a/x-pack/plugins/canvas/public/components/error/error.tsx +++ b/src/plugins/expression_error/public/components/error/error.tsx @@ -1,28 +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; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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, { FC } from 'react'; -import PropTypes from 'prop-types'; import { EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; - import { ShowDebugging } from './show_debugging'; const strings = { getDescription: () => - i18n.translate('xpack.canvas.errorComponent.description', { + i18n.translate('expressionError.errorComponent.description', { defaultMessage: 'Expression failed with the message:', }), getTitle: () => - i18n.translate('xpack.canvas.errorComponent.title', { + i18n.translate('expressionError.errorComponent.title', { defaultMessage: 'Whoops! Expression failed', }), }; + export interface Props { payload: { error: Error; @@ -46,7 +46,3 @@ export const Error: FC = ({ payload }) => { ); }; - -Error.propTypes = { - payload: PropTypes.object.isRequired, -}; diff --git a/src/plugins/expression_error/public/components/error/index.ts b/src/plugins/expression_error/public/components/error/index.ts new file mode 100644 index 000000000000000..4edaa4260d1ce70 --- /dev/null +++ b/src/plugins/expression_error/public/components/error/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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. + */ + +// eslint-disable-next-line import/no-default-export +export { Error as default } from './error'; diff --git a/x-pack/plugins/canvas/public/components/error/show_debugging.tsx b/src/plugins/expression_error/public/components/error/show_debugging.tsx similarity index 77% rename from x-pack/plugins/canvas/public/components/error/show_debugging.tsx rename to src/plugins/expression_error/public/components/error/show_debugging.tsx index 844bd9fdbff6e9f..5ce3f79a6139b64 100644 --- a/x-pack/plugins/canvas/public/components/error/show_debugging.tsx +++ b/src/plugins/expression_error/public/components/error/show_debugging.tsx @@ -1,14 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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, { FC, useState } from 'react'; -import PropTypes from 'prop-types'; import { EuiButtonEmpty } from '@elastic/eui'; -import { Debug } from '../debug'; +import Debug from '../debug'; import { Props } from './error'; export const ShowDebugging: FC = ({ payload }) => { @@ -30,7 +30,3 @@ export const ShowDebugging: FC = ({ payload }) => { ); }; - -ShowDebugging.propTypes = { - payload: PropTypes.object.isRequired, -}; diff --git a/src/plugins/expression_error/public/components/error_component.tsx b/src/plugins/expression_error/public/components/error_component.tsx new file mode 100644 index 000000000000000..58161d8a068a226 --- /dev/null +++ b/src/plugins/expression_error/public/components/error_component.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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, { useState, useEffect, useCallback } from 'react'; +import { EuiIcon, useResizeObserver, EuiPopover } from '@elastic/eui'; +import { IInterpreterRenderHandlers } from '../../../expressions'; +import { withSuspense } from '../../../presentation_util/public'; +import { ErrorRendererConfig } from '../../common/types'; +import { LazyErrorComponent } from '.'; + +const Error = withSuspense(LazyErrorComponent); + +interface ErrorComponentProps extends ErrorRendererConfig { + onLoaded: IInterpreterRenderHandlers['done']; + parentNode: HTMLElement; +} + +function ErrorComponent({ onLoaded, parentNode, error }: ErrorComponentProps) { + const getButtonSize = (node: HTMLElement) => Math.min(node.clientHeight, node.clientWidth); + const parentNodeDimensions = useResizeObserver(parentNode); + + const [buttonSize, setButtonSize] = useState(getButtonSize(parentNode)); + const [isPopoverOpen, setPopoverOpen] = useState(false); + + const handlePopoverClick = () => setPopoverOpen(!isPopoverOpen); + const closePopover = () => setPopoverOpen(false); + + const updateErrorView = useCallback(() => { + setButtonSize(getButtonSize(parentNode)); + onLoaded(); + }, [parentNode, onLoaded]); + + useEffect(() => { + updateErrorView(); + }, [parentNodeDimensions, updateErrorView]); + + return ( +
+ + } + isOpen={isPopoverOpen} + > + + +
+ ); +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { ErrorComponent as default }; diff --git a/src/plugins/expression_error/public/components/index.ts b/src/plugins/expression_error/public/components/index.ts new file mode 100644 index 000000000000000..23eb02fa063a7c6 --- /dev/null +++ b/src/plugins/expression_error/public/components/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { lazy } from 'react'; + +export const LazyErrorComponent = lazy(() => import('./error')); +export const LazyDebugComponent = lazy(() => import('./debug')); + +export const LazyErrorRenderComponent = lazy(() => import('./error_component')); +export const LazyDebugRenderComponent = lazy(() => import('./debug_component')); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/__snapshots__/error.stories.storyshot b/src/plugins/expression_error/public/expression_renderers/__stories__/__snapshots__/error.stories.storyshot similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/__snapshots__/error.stories.storyshot rename to src/plugins/expression_error/public/expression_renderers/__stories__/__snapshots__/error.stories.storyshot diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/error.stories.tsx b/src/plugins/expression_error/public/expression_renderers/__stories__/error_renderer.stories.tsx similarity index 51% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/error.stories.tsx rename to src/plugins/expression_error/public/expression_renderers/__stories__/error_renderer.stories.tsx index 9598fa00e4e3585..9081a8512c11adf 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/error.stories.tsx +++ b/src/plugins/expression_error/public/expression_renderers/__stories__/error_renderer.stories.tsx @@ -1,19 +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; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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 { storiesOf } from '@storybook/react'; -import { error } from '../'; -import { Render } from '../../__stories__/render'; +import { errorRenderer } from '../error_renderer'; +import { Render } from '../../../../presentation_util/public/__stories__'; storiesOf('renderers/error', module).add('default', () => { const thrownError = new Error('There was an error'); const config = { error: thrownError, }; - return ; + return ; }); diff --git a/src/plugins/expression_error/public/expression_renderers/debug_renderer.tsx b/src/plugins/expression_error/public/expression_renderers/debug_renderer.tsx new file mode 100644 index 000000000000000..e3cf86b67148f1f --- /dev/null +++ b/src/plugins/expression_error/public/expression_renderers/debug_renderer.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { render, unmountComponentAtNode } from 'react-dom'; +import React from 'react'; +import { ExpressionRenderDefinition } from 'src/plugins/expressions/common'; +import { i18n } from '@kbn/i18n'; +import { withSuspense } from '../../../../../src/plugins/presentation_util/public'; +import { LazyDebugRenderComponent } from '../components'; +import { JSON } from '../../common'; + +const Debug = withSuspense(LazyDebugRenderComponent); + +const strings = { + getDisplayName: () => + i18n.translate('expressionError.renderer.debug.displayName', { + defaultMessage: 'Debug', + }), + getHelpDescription: () => + i18n.translate('expressionError.renderer.debug.helpDescription', { + defaultMessage: 'Render debug output as formatted {JSON}', + values: { + JSON, + }, + }), +}; + +export const debugRenderer = (): ExpressionRenderDefinition => ({ + name: 'debug', + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, + render(domNode, config, handlers) { + handlers.onDestroy(() => unmountComponentAtNode(domNode)); + render(, domNode); + }, +}); diff --git a/src/plugins/expression_error/public/expression_renderers/error_renderer.tsx b/src/plugins/expression_error/public/expression_renderers/error_renderer.tsx new file mode 100644 index 000000000000000..8ce4d5fdbbbca92 --- /dev/null +++ b/src/plugins/expression_error/public/expression_renderers/error_renderer.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { render, unmountComponentAtNode } from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { withSuspense } from '../../../presentation_util/public'; +import { ErrorRendererConfig } from '../../common/types'; +import { LazyErrorRenderComponent } from '../components'; + +const errorStrings = { + getDisplayName: () => + i18n.translate('expressionError.renderer.error.displayName', { + defaultMessage: 'Error information', + }), + getHelpDescription: () => + i18n.translate('expressionError.renderer.error.helpDescription', { + defaultMessage: 'Render error data in a way that is helpful to users', + }), +}; + +const ErrorComponent = withSuspense(LazyErrorRenderComponent); + +export const errorRenderer = (): ExpressionRenderDefinition => ({ + name: 'error', + displayName: errorStrings.getDisplayName(), + help: errorStrings.getHelpDescription(), + reuseDomNode: true, + render: async ( + domNode: HTMLElement, + config: ErrorRendererConfig, + handlers: IInterpreterRenderHandlers + ) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + + + , + domNode + ); + }, +}); diff --git a/src/plugins/expression_error/public/expression_renderers/index.ts b/src/plugins/expression_error/public/expression_renderers/index.ts new file mode 100644 index 000000000000000..237ee5644cdc050 --- /dev/null +++ b/src/plugins/expression_error/public/expression_renderers/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { errorRenderer } from './error_renderer'; +import { debugRenderer } from './debug_renderer'; + +export const renderers = [errorRenderer, debugRenderer]; + +export { errorRenderer, debugRenderer }; diff --git a/src/plugins/expression_error/public/index.ts b/src/plugins/expression_error/public/index.ts new file mode 100755 index 000000000000000..04c29a96b853a96 --- /dev/null +++ b/src/plugins/expression_error/public/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { ExpressionErrorPlugin } from './plugin'; + +export type { ExpressionErrorPluginSetup, ExpressionErrorPluginStart } from './plugin'; + +export function plugin() { + return new ExpressionErrorPlugin(); +} + +export * from './expression_renderers'; +export { LazyDebugComponent, LazyErrorComponent } from './components'; diff --git a/src/plugins/expression_error/public/plugin.ts b/src/plugins/expression_error/public/plugin.ts new file mode 100755 index 000000000000000..3727cab5436c9fc --- /dev/null +++ b/src/plugins/expression_error/public/plugin.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public'; +import { errorRenderer, debugRenderer } from './expression_renderers'; + +interface SetupDeps { + expressions: ExpressionsSetup; +} + +interface StartDeps { + expression: ExpressionsStart; +} + +export type ExpressionErrorPluginSetup = void; +export type ExpressionErrorPluginStart = void; + +export class ExpressionErrorPlugin + implements Plugin { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionErrorPluginSetup { + expressions.registerRenderer(errorRenderer); + expressions.registerRenderer(debugRenderer); + } + + public start(core: CoreStart): ExpressionErrorPluginStart {} + + public stop() {} +} diff --git a/src/plugins/expression_error/tsconfig.json b/src/plugins/expression_error/tsconfig.json new file mode 100644 index 000000000000000..aa4562ec7357654 --- /dev/null +++ b/src/plugins/expression_error/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "isolatedModules": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../presentation_util/tsconfig.json" }, + { "path": "../expressions/tsconfig.json" }, + ] +} diff --git a/src/plugins/kibana_react/public/page_template/page_template.test.tsx b/src/plugins/kibana_react/public/page_template/page_template.test.tsx index 43f96a6c2b98cf4..2fdedce23b09b03 100644 --- a/src/plugins/kibana_react/public/page_template/page_template.test.tsx +++ b/src/plugins/kibana_react/public/page_template/page_template.test.tsx @@ -125,4 +125,20 @@ describe('KibanaPageTemplate', () => { ); expect(component).toMatchSnapshot(); }); + + test('render sidebar classes', () => { + const component = shallow( + + ); + expect(component.prop('pageSideBarProps').className).toEqual( + 'kbnPageTemplate__pageSideBar customClass' + ); + }); }); diff --git a/src/plugins/kibana_react/public/page_template/page_template.tsx b/src/plugins/kibana_react/public/page_template/page_template.tsx index dabec978b67a490..1e63f2e068508b9 100644 --- a/src/plugins/kibana_react/public/page_template/page_template.tsx +++ b/src/plugins/kibana_react/public/page_template/page_template.tsx @@ -48,6 +48,7 @@ export const KibanaPageTemplate: FunctionComponent = ({ isEmptyState, restrictWidth = true, pageSideBar, + pageSideBarProps, solutionNav, ...rest }) => { @@ -117,8 +118,8 @@ export const KibanaPageTemplate: FunctionComponent = ({ pageSideBar={pageSideBar} pageSideBarProps={{ paddingSize: solutionNav ? 'none' : 'l', - ...rest.pageSideBarProps, - className: classNames(sideBarClasses, rest.pageSideBarProps?.className), + ...pageSideBarProps, + className: classNames(sideBarClasses, pageSideBarProps?.className), }} {...rest} > diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index a63e577e02c2692..a314c89ed351364 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -452,7 +452,9 @@ export const getTopNavConfig = ( onSave={onSave} options={tagOptions} getAppNameFromId={stateTransfer.getAppNameFromId} - objectType={'visualization'} + objectType={i18n.translate('visualize.topNavMenu.saveVisualizationObjectType', { + defaultMessage: 'visualization', + })} onClose={() => {}} originatingApp={originatingApp} returnToOriginSwitchLabel={ @@ -478,7 +480,9 @@ export const getTopNavConfig = ( canSaveByReference={Boolean(visualizeCapabilities.save)} onSave={onSave} tagOptions={tagOptions} - objectType={'visualization'} + objectType={i18n.translate('visualize.topNavMenu.saveVisualizationObjectType', { + defaultMessage: 'visualization', + })} onClose={() => {}} /> ); diff --git a/test/functional/apps/context/_context_navigation.js b/test/functional/apps/context/_context_navigation.js index 2efc145b12561b3..7f72d44c50ea000 100644 --- a/test/functional/apps/context/_context_navigation.js +++ b/test/functional/apps/context/_context_navigation.js @@ -21,8 +21,7 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['common', 'context', 'discover', 'timePicker']); const kibanaServer = getService('kibanaServer'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104364 - describe.skip('discover - context - back navigation', function contextSize() { + describe('discover - context - back navigation', function contextSize() { before(async function () { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await kibanaServer.uiSettings.update({ 'doc_table:legacy': true }); diff --git a/test/functional/apps/context/_discover_navigation.js b/test/functional/apps/context/_discover_navigation.js index 6a2298ba48cb45b..a09be8b35ba8f67 100644 --- a/test/functional/apps/context/_discover_navigation.js +++ b/test/functional/apps/context/_discover_navigation.js @@ -32,8 +32,7 @@ export default function ({ getService, getPageObjects }) { const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104413 - describe.skip('context link in discover', () => { + describe('context link in discover', () => { before(async () => { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await kibanaServer.uiSettings.update({ diff --git a/test/functional/apps/dashboard/saved_search_embeddable.ts b/test/functional/apps/dashboard/saved_search_embeddable.ts index 33d015a4c601997..5bcec338aad1ebc 100644 --- a/test/functional/apps/dashboard/saved_search_embeddable.ts +++ b/test/functional/apps/dashboard/saved_search_embeddable.ts @@ -17,8 +17,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'timePicker', 'discover']); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104365 - describe.skip('dashboard saved search embeddable', () => { + describe('dashboard saved search embeddable', () => { before(async () => { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data'); diff --git a/test/functional/apps/dashboard/view_edit.ts b/test/functional/apps/dashboard/view_edit.ts index 1ca70112c3d1ed9..b29b07f9df4e4b6 100644 --- a/test/functional/apps/dashboard/view_edit.ts +++ b/test/functional/apps/dashboard/view_edit.ts @@ -19,8 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardName = 'dashboard with filter'; const filterBar = getService('filterBar'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104467 - describe.skip('dashboard view edit mode', function viewEditModeTests() { + describe('dashboard view edit mode', function viewEditModeTests() { before(async () => { await esArchiver.load('test/functional/fixtures/es_archiver/dashboard/current/kibana'); await kibanaServer.uiSettings.replace({ diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index 245b895d75b3a58..bb75b4441f8802b 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -38,8 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); }); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104409 - describe.skip('query', function () { + describe('query', function () { const queryName1 = 'Query # 1'; it('should show correct time range string by timepicker', async function () { diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index ec9f9cf65e0fa80..338d17ba31ff49f 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -33,7 +33,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); - describe('field data', function () { it('search php should show the correct hit count', async function () { const expectedHitCount = '445'; diff --git a/test/functional/apps/discover/_saved_queries.ts b/test/functional/apps/discover/_saved_queries.ts index 29073c5fe4ebb40..20f2cab907d9bfa 100644 --- a/test/functional/apps/discover/_saved_queries.ts +++ b/test/functional/apps/discover/_saved_queries.ts @@ -40,8 +40,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); }); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104366 - describe.skip('saved query management component functionality', function () { + describe('saved query management component functionality', function () { before(async function () { // set up a query with filters and a time filter log.debug('set up a query with filters to save'); diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts index ac8aa50085f33a3..3a18a55fe138ba0 100644 --- a/test/functional/apps/discover/index.ts +++ b/test/functional/apps/discover/index.ts @@ -12,8 +12,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover app', function () { + describe('discover app', function () { this.tags('ciGroup6'); before(function () { diff --git a/x-pack/plugins/apm/common/agent_configuration/constants.ts b/x-pack/plugins/apm/common/agent_configuration/constants.ts new file mode 100644 index 000000000000000..6ca70606cece00c --- /dev/null +++ b/x-pack/plugins/apm/common/agent_configuration/constants.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import * as t from 'io-ts'; + +export enum AgentConfigurationPageStep { + ChooseService = 'choose-service-step', + ChooseSettings = 'choose-settings-step', + Review = 'review-step', +} + +export const agentConfigurationPageStepRt = t.union([ + t.literal(AgentConfigurationPageStep.ChooseService), + t.literal(AgentConfigurationPageStep.ChooseSettings), + t.literal(AgentConfigurationPageStep.Review), +]); diff --git a/x-pack/plugins/apm/server/utils/queries.test.ts b/x-pack/plugins/apm/common/utils/environment_query.test.ts similarity index 80% rename from x-pack/plugins/apm/server/utils/queries.test.ts rename to x-pack/plugins/apm/common/utils/environment_query.test.ts index 9fa97940db8f3c8..a4ffec0d64d3ea3 100644 --- a/x-pack/plugins/apm/server/utils/queries.test.ts +++ b/x-pack/plugins/apm/common/utils/environment_query.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { SERVICE_ENVIRONMENT } from '../../common/elasticsearch_fieldnames'; -import { ENVIRONMENT_NOT_DEFINED } from '../../common/environment_filter_values'; -import { environmentQuery } from './queries'; +import { SERVICE_ENVIRONMENT } from '../elasticsearch_fieldnames'; +import { ENVIRONMENT_NOT_DEFINED } from '../environment_filter_values'; +import { environmentQuery } from './environment_query'; describe('environmentQuery', () => { describe('when environment is undefined', () => { diff --git a/x-pack/plugins/apm/server/utils/queries.ts b/x-pack/plugins/apm/common/utils/environment_query.ts similarity index 68% rename from x-pack/plugins/apm/server/utils/queries.ts rename to x-pack/plugins/apm/common/utils/environment_query.ts index a82b49a84dc6e3c..acc75c13a0e355d 100644 --- a/x-pack/plugins/apm/server/utils/queries.ts +++ b/x-pack/plugins/apm/common/utils/environment_query.ts @@ -5,15 +5,12 @@ * 2.0. */ -import { ESFilter } from '../../../../../src/core/types/elasticsearch'; -import { SERVICE_ENVIRONMENT } from '../../common/elasticsearch_fieldnames'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; +import { SERVICE_ENVIRONMENT } from '../elasticsearch_fieldnames'; import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, -} from '../../common/environment_filter_values'; -export { kqlQuery, rangeQuery } from '../../../observability/server'; - -type QueryDslQueryContainer = ESFilter; +} from '../environment_filter_values'; export function environmentQuery( environment?: string diff --git a/x-pack/plugins/apm/dev_docs/routing_and_linking.md b/x-pack/plugins/apm/dev_docs/routing_and_linking.md index d27513d44935f6d..7c5a00f43fe4b2b 100644 --- a/x-pack/plugins/apm/dev_docs/routing_and_linking.md +++ b/x-pack/plugins/apm/dev_docs/routing_and_linking.md @@ -12,18 +12,47 @@ The path and query string parameters are defined in the calls to `createRoute` w ### Client-side -The client-side routing uses [React Router](https://reactrouter.com/), The [`ApmRoute` component from the Elastic RUM Agent](https://www.elastic.co/guide/en/apm/agent/rum-js/current/react-integration.html), and the `history` object provided by the Kibana Platform. +The client-side routing uses `@kbn/typed-react-router-config`, which is a wrapper around [React Router](https://reactrouter.com/) and [React Router Config](https://www.npmjs.com/package/react-router-config). Its goal is to provide a layer of high-fidelity types that allows us to parse and format URLs for routes while making sure the needed parameters are provided and/or available (typed and validated at runtime). The `history` object used by React Router is injected by the Kibana Platform. -Routes are defined in [public/components/app/Main/route_config/index.tsx](../public/components/app/Main/route_config/index.tsx). These contain route definitions as well as the breadcrumb text. +Routes (and their parameters) are defined in [public/components/routing/apm_config.tsx](../public/components/routing/apm_config.tsx). #### Parameter handling -Path parameters (like `serviceName` in '/services/:serviceName/transactions') are handled by the `match.params` props passed into -routes by React Router. The types of these parameters are defined in the route definitions. - -If the parameters are not available as props you can use React Router's `useParams`, but their type definitions should be delcared inline and it's a good idea to make the properties optional if you don't know where a component will be used, since those parameters might not be available at that route. - -Query string parameters can be used in any component with `useUrlParams`. All of the available parameters are defined by this hook and its context. +Path (like `serviceName` in '/services/:serviceName/transactions') and query parameters are defined in the route definitions. + +For each parameter, an io-ts runtime type needs to be present: + +```tsx +{ + route: '/services/:serviceName', + element: , + params: t.intersection([ + t.type({ + path: t.type({ + serviceName: t.string, + }) + }), + t.partial({ + query: t.partial({ + transactionType: t.string + }) + }) + ]) +} +``` + +To be able to use the parameters, you can use `useApmParams`, which will automatically infer the parameter types from the route path: + +```ts +const { + path: { serviceName }, // string + query: { transactionType } // string | undefined +} = useApmParams('/services/:serviceName'); +``` + +`useApmParams` will strip query parameters for which there is no validation. The route path should match exactly, but you can also use wildcards: `useApmParams('/*)`. In that case, the return type will be a union type of all possible matching routes. + +Previously we used `useUrlParams` for path and query parameters, which we are trying to get away from. When possible, any usage of `useUrlParams` should be replaced by `useApmParams` or other custom hooks that use `useApmParams` internally. ## Linking @@ -31,7 +60,16 @@ Raw URLs should almost never be used in the APM UI. Instead, we have mechanisms ### In-app linking -Links that stay inside APM should use the [`getAPMHref` function and `APMLink` component](../public/components/shared/Links/apm/APMLink.tsx). Other components inside that directory contain other functions and components that provide the same functionality for linking to more specific sections inside the APM plugin. +For links that stay inside APM, the preferred way of linking is to call the `useApmRouter` hook, and call `router.link` with the route path and required path and query parameters: + +```ts +const apmRouter = useApmRouter(); +const serviceOverviewLink = apmRouter.link('/services/:serviceName', { path: { serviceName: 'opbeans-java' }, query: { transactionType: 'request' }}); +``` + + If you're not in React context, you can also import `apmRouter` directly and call its `link` function - but you have to prepend the basePath manually in that case. + +We also have the [`getAPMHref` function and `APMLink` component](../public/components/shared/Links/apm/APMLink.tsx), but we should consider them deprecated, in favor of `router.link`. Other components inside that directory contain other functions and components that provide the same functionality for linking to more specific sections inside the APM plugin. ### Cross-app linking diff --git a/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx b/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx index eef3271d5932d6f..70da0be7ebae5c0 100644 --- a/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx @@ -14,7 +14,7 @@ import { import { getInitialAlertValues } from '../get_initial_alert_values'; import { ApmPluginStartDeps } from '../../../plugin'; import { useServiceName } from '../../../hooks/use_service_name'; -import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context'; + interface Props { addFlyoutVisible: boolean; setAddFlyoutVisibility: React.Dispatch>; @@ -44,11 +44,5 @@ export function AlertingFlyout(props: Props) { /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertType, onCloseAddFlyout, services.triggersActionsUi] ); - return ( - <> - {addFlyoutVisible && ( - {addAlertFlyout} - )} - - ); + return <>{addFlyoutVisible && addAlertFlyout}; } diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx index 811353067ab60e6..4ed3b9eeb19ffc5 100644 --- a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx @@ -18,7 +18,7 @@ import { ChartPreview } from '../chart_preview'; import { EnvironmentField, IsAboveField, ServiceField } from '../fields'; import { getAbsoluteTimeRange } from '../helper'; import { ServiceAlertTrigger } from '../service_alert_trigger'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useServiceName } from '../../../hooks/use_service_name'; export interface AlertParams { windowSize: number; @@ -37,12 +37,12 @@ interface Props { export function ErrorCountAlertTrigger(props: Props) { const { setAlertParams, setAlertProperty, alertParams } = props; - const { serviceName: serviceNameFromContext } = useApmServiceContext(); + const serviceNameFromUrl = useServiceName(); const { urlParams } = useUrlParams(); const { start, end, environment: environmentFromUrl } = urlParams; const { environmentOptions } = useEnvironmentsFetcher({ - serviceName: serviceNameFromContext, + serviceName: serviceNameFromUrl, start, end, }); @@ -56,7 +56,7 @@ export function ErrorCountAlertTrigger(props: Props) { windowSize: 1, windowUnit: 'm', environment: environmentFromUrl || ENVIRONMENT_ALL.value, - serviceName: serviceNameFromContext, + serviceName: serviceNameFromUrl, } ); diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx index 8f2713685127e94..fa5f394d1747e3c 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx @@ -12,10 +12,13 @@ import React from 'react'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { getDurationFormatter } from '../../../../common/utils/formatters'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { getTransactionType } from '../../../context/apm_service/apm_service_context'; +import { useServiceAgentNameFetcher } from '../../../context/apm_service/use_service_agent_name_fetcher'; +import { useServiceTransactionTypesFetcher } from '../../../context/apm_service/use_service_transaction_types_fetcher'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; import { useFetcher } from '../../../hooks/use_fetcher'; +import { useServiceName } from '../../../hooks/use_service_name'; import { getMaxY, getResponseTimeTickFormatter, @@ -74,11 +77,18 @@ export function TransactionDurationAlertTrigger(props: Props) { const { start, end, environment: environmentFromUrl } = urlParams; - const { + const serviceNameFromUrl = useServiceName(); + + const transactionTypes = useServiceTransactionTypesFetcher( + serviceNameFromUrl + ); + const { agentName } = useServiceAgentNameFetcher(serviceNameFromUrl); + + const transactionTypeFromUrl = getTransactionType({ + transactionType: urlParams.transactionType, transactionTypes, - transactionType: transactionTypeFromContext, - serviceName: serviceNameFromContext, - } = useApmServiceContext(); + agentName, + }); const params = defaults( { @@ -90,8 +100,8 @@ export function TransactionDurationAlertTrigger(props: Props) { threshold: 1500, windowSize: 5, windowUnit: 'm', - transactionType: transactionTypeFromContext, - serviceName: serviceNameFromContext, + transactionType: transactionTypeFromUrl, + serviceName: serviceNameFromUrl, } ); diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx index 11307b1cd5ae38e..bdf20bc14ad90ac 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx @@ -23,7 +23,10 @@ import { ServiceField, TransactionTypeField, } from '../fields'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useServiceName } from '../../../hooks/use_service_name'; +import { useServiceTransactionTypesFetcher } from '../../../context/apm_service/use_service_transaction_types_fetcher'; +import { useServiceAgentNameFetcher } from '../../../context/apm_service/use_service_agent_name_fetcher'; +import { getTransactionType } from '../../../context/apm_service/apm_service_context'; interface AlertParams { windowSize: number; @@ -47,11 +50,19 @@ interface Props { export function TransactionDurationAnomalyAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { - serviceName: serviceNameFromContext, - transactionType: transactionTypeFromContext, + + const serviceNameFromUrl = useServiceName(); + + const transactionTypes = useServiceTransactionTypesFetcher( + serviceNameFromUrl + ); + const { agentName } = useServiceAgentNameFetcher(serviceNameFromUrl); + + const transactionTypeFromUrl = getTransactionType({ + transactionType: urlParams.transactionType, transactionTypes, - } = useApmServiceContext(); + agentName, + }); const { start, end, environment: environmentFromUrl } = urlParams; @@ -62,10 +73,10 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { { windowSize: 15, windowUnit: 'm', - transactionType: transactionTypeFromContext, + transactionType: transactionTypeFromUrl, environment: environmentFromUrl || ENVIRONMENT_ALL.value, anomalySeverityType: ANOMALY_SEVERITY.CRITICAL, - serviceName: serviceNameFromContext, + serviceName: serviceNameFromUrl, } ); diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx index 4eb0b0e79757120..cb519996f949648 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx @@ -10,7 +10,6 @@ import { defaults } from 'lodash'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { asPercent } from '../../../../common/utils/formatters'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; import { useFetcher } from '../../../hooks/use_fetcher'; @@ -23,6 +22,10 @@ import { } from '../fields'; import { getAbsoluteTimeRange } from '../helper'; import { ServiceAlertTrigger } from '../service_alert_trigger'; +import { useServiceName } from '../../../hooks/use_service_name'; +import { useServiceTransactionTypesFetcher } from '../../../context/apm_service/use_service_transaction_types_fetcher'; +import { useServiceAgentNameFetcher } from '../../../context/apm_service/use_service_agent_name_fetcher'; +import { getTransactionType } from '../../../context/apm_service/apm_service_context'; interface AlertParams { windowSize: number; @@ -42,28 +45,36 @@ interface Props { export function TransactionErrorRateAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { - transactionType: transactionTypeFromContext, + + const serviceNameFromUrl = useServiceName(); + + const transactionTypes = useServiceTransactionTypesFetcher( + serviceNameFromUrl + ); + const { agentName } = useServiceAgentNameFetcher(serviceNameFromUrl); + + const transactionTypeFromUrl = getTransactionType({ + transactionType: urlParams.transactionType, transactionTypes, - serviceName: serviceNameFromContext, - } = useApmServiceContext(); + agentName, + }); const { start, end, environment: environmentFromUrl } = urlParams; - const params = defaults, AlertParams>( + const params = defaults( + { ...alertParams }, { threshold: 30, windowSize: 5, windowUnit: 'm', - transactionType: transactionTypeFromContext, + transactionType: transactionTypeFromUrl, environment: environmentFromUrl || ENVIRONMENT_ALL.value, - serviceName: serviceNameFromContext, - }, - alertParams + serviceName: serviceNameFromUrl, + } ); const { environmentOptions } = useEnvironmentsFetcher({ - serviceName: serviceNameFromContext, + serviceName: params.serviceName, start, end, }); diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/List/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/List/index.tsx index 93ae8b270b5de19..51a025df88d8ed3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/List/index.tsx @@ -16,16 +16,12 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useState } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useApmRouter } from '../../../../../hooks/use_apm_router'; import { APIReturnType } from '../../../../../services/rest/createCallApmApi'; import { getOptionLabel } from '../../../../../../common/agent_configuration/all_option'; import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; import { useTheme } from '../../../../../hooks/use_theme'; -import { - createAgentConfigurationHref, - editAgentConfigurationHref, -} from '../../../../shared/Links/apm/agentConfigurationLinks'; import { LoadingStatePrompt } from '../../../../shared/LoadingStatePrompt'; import { ITableColumn, ManagedTable } from '../../../../shared/managed_table'; import { TimestampTooltip } from '../../../../shared/TimestampTooltip'; @@ -46,13 +42,17 @@ export function AgentConfigurationList({ }: Props) { const { core } = useApmPluginContext(); const canSave = core.application.capabilities.apm.save; - const { basePath } = core.http; - const { search } = useLocation(); const theme = useTheme(); const [configToBeDeleted, setConfigToBeDeleted] = useState( null ); + const apmRouter = useApmRouter(); + + const createAgentConfigurationHref = apmRouter.link( + '/settings/agent-configuration/create' + ); + const emptyStatePrompt = ( {i18n.translate( @@ -159,7 +159,12 @@ export function AgentConfigurationList({ flush="left" size="s" color="primary" - href={editAgentConfigurationHref(config.service, search, basePath)} + href={apmRouter.link('/settings/agent-configuration/edit', { + query: { + name: config.service.name, + environment: config.service.environment, + }, + })} > {getOptionLabel(config.service.name)} @@ -195,11 +200,12 @@ export function AgentConfigurationList({ ), }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/index.tsx index 32c93e43175dfbf..f7cfc56bf4eac3f 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/index.tsx @@ -17,10 +17,9 @@ import { import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React from 'react'; -import { useLocation } from 'react-router-dom'; +import { useApmRouter } from '../../../../hooks/use_apm_router'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useFetcher } from '../../../../hooks/use_fetcher'; -import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks'; import { AgentConfigurationList } from './List'; const INITIAL_DATA = { configurations: [] }; @@ -72,10 +71,10 @@ export function AgentConfigurations() { } function CreateConfigurationButton() { + const href = useApmRouter().link('/settings/agent-configuration/create'); + const { core } = useApmPluginContext(); - const { basePath } = core.http; - const { search } = useLocation(); - const href = createAgentConfigurationHref(search, basePath); + const canSave = core.application.capabilities.apm.save; return ( diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx index d0c2b5c59803978..36ebb239fd7dd0e 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx @@ -7,22 +7,23 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import React from 'react'; -import { Redirect, RouteComponentProps } from 'react-router-dom'; +import { Redirect } from 'react-router-dom'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { getRedirectToTransactionDetailPageUrl } from './get_redirect_to_transaction_detail_page_url'; import { getRedirectToTracePageUrl } from './get_redirect_to_trace_page_url'; +import { useApmParams } from '../../../hooks/use_apm_params'; const CentralizedContainer = euiStyled.div` height: 100%; display: flex; `; -export function TraceLink({ match }: RouteComponentProps<{ traceId: string }>) { - const { traceId } = match.params; - const { urlParams } = useUrlParams(); - const { rangeFrom, rangeTo } = urlParams; +export function TraceLink() { + const { + path: { traceId }, + query: { rangeFrom, rangeTo }, + } = useApmParams('/link-to/trace/:traceId'); const { data = { transaction: null }, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx index c78e9fc0a107a8c..0661b5ddc871d7a 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx @@ -8,7 +8,7 @@ import { act, render, waitFor } from '@testing-library/react'; import { shallow } from 'enzyme'; import React, { ReactNode } from 'react'; -import { MemoryRouter, RouteComponentProps } from 'react-router-dom'; +import { MemoryRouter } from 'react-router-dom'; import { TraceLink } from './'; import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { @@ -17,6 +17,7 @@ import { } from '../../../context/apm_plugin/mock_apm_plugin_context'; import * as hooks from '../../../hooks/use_fetcher'; import * as urlParamsHooks from '../../../context/url_params_context/use_url_params'; +import * as useApmParamsHooks from '../../../hooks/use_apm_params'; function Wrapper({ children }: { children?: ReactNode }) { return ( @@ -46,12 +47,19 @@ describe('TraceLink', () => { }); it('renders a transition page', async () => { - const props = ({ - match: { params: { traceId: 'x' } }, - } as unknown) as RouteComponentProps<{ traceId: string }>; + jest.spyOn(useApmParamsHooks as any, 'useApmParams').mockReturnValue({ + path: { + traceId: 'x', + }, + query: { + rangeFrom: 'now-24h', + rangeTo: 'now', + }, + }); + let result; act(() => { - const component = render(, renderOptions); + const component = render(, renderOptions); result = component.getByText('Fetching trace...'); }); @@ -76,10 +84,17 @@ describe('TraceLink', () => { refetch: jest.fn(), }); - const props = ({ - match: { params: { traceId: '123' } }, - } as unknown) as RouteComponentProps<{ traceId: string }>; - const component = shallow(); + jest.spyOn(useApmParamsHooks as any, 'useApmParams').mockReturnValue({ + path: { + traceId: '123', + }, + query: { + rangeFrom: 'now-24h', + rangeTo: 'now', + }, + }); + + const component = shallow(); expect(component.prop('to')).toEqual( '/traces?kuery=trace.id%2520%253A%2520%2522123%2522&rangeFrom=now-24h&rangeTo=now' @@ -93,10 +108,7 @@ describe('TraceLink', () => { rangeId: 0, refreshTimeRange: jest.fn(), uxUiFilters: {}, - urlParams: { - rangeFrom: 'now-24h', - rangeTo: 'now', - }, + urlParams: {}, }); }); @@ -116,10 +128,17 @@ describe('TraceLink', () => { refetch: jest.fn(), }); - const props = ({ - match: { params: { traceId: '123' } }, - } as unknown) as RouteComponentProps<{ traceId: string }>; - const component = shallow(); + jest.spyOn(useApmParamsHooks as any, 'useApmParams').mockReturnValue({ + path: { + traceId: '123', + }, + query: { + rangeFrom: 'now-24h', + rangeTo: 'now', + }, + }); + + const component = shallow(); expect(component.prop('to')).toEqual( '/services/foo/transactions/view?traceId=123&transactionId=456&transactionName=bar&transactionType=GET&rangeFrom=now-24h&rangeTo=now' diff --git a/x-pack/plugins/apm/public/components/app/breadcrumb/index.tsx b/x-pack/plugins/apm/public/components/app/breadcrumb/index.tsx new file mode 100644 index 000000000000000..5cc1293d39f7d09 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/breadcrumb/index.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; + +export const Breadcrumb = ({ + title, + href, + children, +}: { + title: string; + href: string; + children: React.ReactElement; +}) => { + const { core } = useApmPluginContext(); + useBreadcrumb({ title, href: core.http.basePath.prepend('/app/apm' + href) }); + + return children; +}; diff --git a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx index f0d7f8d60eb6caa..7202231fa66b283 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx @@ -18,8 +18,8 @@ import { import { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; -import { useParams } from 'react-router-dom'; import { useUiTracker } from '../../../../../observability/public'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; @@ -51,7 +51,7 @@ export function ErrorCorrelations({ onClose }: Props) { setSelectedSignificantTerm, ] = useState(null); - const { serviceName } = useParams<{ serviceName?: string }>(); + const { serviceName } = useApmServiceContext(); const { urlParams } = useUrlParams(); const { environment, diff --git a/x-pack/plugins/apm/public/components/app/correlations/index.tsx b/x-pack/plugins/apm/public/components/app/correlations/index.tsx index 9ad5088bb0bcf43..31232e818cd9f3a 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/index.tsx @@ -23,7 +23,7 @@ import { EuiBetaBadge, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useHistory, useParams } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { MlLatencyCorrelations } from './ml_latency_correlations'; import { ErrorCorrelations } from './error_correlations'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; @@ -49,6 +49,7 @@ import { SERVICE_NAME, TRANSACTION_NAME, } from '../../../../common/elasticsearch_fieldnames'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; const errorRateTab = { key: 'errorRate', @@ -70,7 +71,7 @@ export function Correlations() { const license = useLicenseContext(); const hasActivePlatinumLicense = isActivePlatinumLicense(license); const { urlParams } = useUrlParams(); - const { serviceName } = useParams<{ serviceName: string }>(); + const { serviceName } = useApmServiceContext(); const history = useHistory(); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx index e65bad8088c170a..400a9f227959ff1 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx @@ -14,7 +14,6 @@ import { Settings, } from '@elastic/charts'; import React, { useState } from 'react'; -import { useParams } from 'react-router-dom'; import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { getDurationFormatter } from '../../../../common/utils/formatters'; @@ -31,6 +30,7 @@ import { CustomFields, PercentileOption } from './custom_fields'; import { useFieldNames } from './use_field_names'; import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { useUiTracker } from '../../../../../observability/public'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; type OverallLatencyApiResponse = NonNullable< APIReturnType<'GET /api/apm/correlations/latency/overall_distribution'> @@ -50,7 +50,7 @@ export function LatencyCorrelations({ onClose }: Props) { setSelectedSignificantTerm, ] = useState(null); - const { serviceName } = useParams<{ serviceName?: string }>(); + const { serviceName } = useApmServiceContext(); const { urlParams } = useUrlParams(); const { environment, diff --git a/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations.tsx index 03fab3e788639a5..319f8f70414ddcb 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations.tsx @@ -6,7 +6,7 @@ */ import React, { useEffect, useMemo, useState } from 'react'; -import { useHistory, useParams } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { EuiIcon, EuiBasicTableColumn, @@ -36,6 +36,7 @@ import { useCorrelations } from './use_correlations'; import { push } from '../../shared/Links/url_helpers'; import { useUiTracker } from '../../../../../observability/public'; import { asPreciseDecimal } from '../../../../common/utils/formatters'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { LatencyCorrelationsHelpPopover } from './ml_latency_correlations_help_popover'; const DEFAULT_PERCENTILE_THRESHOLD = 95; @@ -60,17 +61,10 @@ export function MlLatencyCorrelations({ onClose }: Props) { core: { notifications }, } = useApmPluginContext(); - const { serviceName } = useParams<{ serviceName: string }>(); - const { - urlParams: { - environment, - kuery, - transactionName, - transactionType, - start, - end, - }, - } = useUrlParams(); + const { serviceName, transactionType } = useApmServiceContext(); + const { urlParams } = useUrlParams(); + + const { environment, kuery, transactionName, start, end } = urlParams; const { error, diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx index 225186cab12c824..3b7dea1e6406061 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx @@ -18,7 +18,11 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../hooks/use_apm_router'; import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; import { useFetcher } from '../../../hooks/use_fetcher'; import { DetailView } from './detail_view'; @@ -88,17 +92,27 @@ function ErrorGroupHeader({ ); } -interface ErrorGroupDetailsProps { - groupId: string; - serviceName: string; -} - -export function ErrorGroupDetails({ - serviceName, - groupId, -}: ErrorGroupDetailsProps) { +export function ErrorGroupDetails() { const { urlParams } = useUrlParams(); const { environment, kuery, start, end } = urlParams; + const { serviceName } = useApmServiceContext(); + + const apmRouter = useApmRouter(); + + const { + path: { groupId }, + } = useApmParams('/services/:serviceName/errors/:groupId'); + + useBreadcrumb({ + title: groupId, + href: apmRouter.link('/services/:serviceName/errors/:groupId', { + path: { + serviceName, + groupId, + }, + }), + }); + const { data: errorGroupData } = useFetcher( (callApmApi) => { if (start && end) { diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx index 8d8d0cb9c107cbe..910f6aba3303d8e 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx @@ -14,20 +14,25 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmParams } from '../../../hooks/use_apm_params'; import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; import { useFetcher } from '../../../hooks/use_fetcher'; import { ErrorDistribution } from '../error_group_details/Distribution'; import { ErrorGroupList } from './List'; -interface ErrorGroupOverviewProps { - serviceName: string; -} +export function ErrorGroupOverview() { + const { serviceName } = useApmServiceContext(); + + const { + query: { environment, kuery, sortField, sortDirection }, + } = useApmParams('/services/:serviceName/errors'); -export function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { const { - urlParams: { environment, kuery, start, end, sortField, sortDirection }, + urlParams: { start, end }, } = useUrlParams(); + const { errorDistributionData } = useErrorGroupDistributionFetcher({ serviceName, groupId: undefined, diff --git a/x-pack/plugins/apm/public/components/app/service_map/empty_banner.test.tsx b/x-pack/plugins/apm/public/components/app/service_map/empty_banner.test.tsx index 092ee2dd967aeda..5d30c777cd5958f 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/empty_banner.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/empty_banner.test.tsx @@ -8,6 +8,7 @@ import { act, waitFor } from '@testing-library/react'; import cytoscape from 'cytoscape'; import React, { ReactNode } from 'react'; +import { MemoryRouter } from 'react-router-dom'; import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { renderWithTheme } from '../../../utils/testHelpers'; import { CytoscapeContext } from './Cytoscape'; @@ -17,11 +18,13 @@ const cy = cytoscape({}); function wrapper({ children }: { children: ReactNode }) { return ( - - - {children} - - + + + + {children} + + + ); } diff --git a/x-pack/plugins/apm/public/components/app/service_map/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/index.tsx index 22adb10512d7aea..b10b527de25dd01 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/index.tsx @@ -11,7 +11,7 @@ import { EuiLoadingSpinner, EuiPanel, } from '@elastic/eui'; -import React, { PropsWithChildren, ReactNode } from 'react'; +import React, { ReactNode } from 'react'; import { isActivePlatinumLicense } from '../../../../common/license_check'; import { invalidLicenseMessage, @@ -31,10 +31,7 @@ import { Popover } from './Popover'; import { TimeoutPrompt } from './timeout_prompt'; import { useRefDimensions } from './useRefDimensions'; import { SearchBar } from '../../shared/search_bar'; - -interface ServiceMapProps { - serviceName?: string; -} +import { useServiceName } from '../../../hooks/use_service_name'; function PromptContainer({ children }: { children: ReactNode }) { return ( @@ -66,13 +63,13 @@ function LoadingSpinner() { ); } -export function ServiceMap({ - serviceName, -}: PropsWithChildren) { +export function ServiceMap() { const theme = useTheme(); const license = useLicenseContext(); const { urlParams } = useUrlParams(); + const serviceName = useServiceName(); + const { data = { elements: [] }, status, error } = useFetcher( (callApmApi) => { // When we don't have a license or a valid license, don't make the request. diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx index 8711366fdd18599..bff224cdc791c43 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx @@ -16,10 +16,7 @@ describe('ServiceNodeMetrics', () => { expect(() => shallow( - + ) ).not.toThrowError(); diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx index 07afcbc9c4682f2..1b5754ef74e8b0f 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx @@ -19,10 +19,16 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; -import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; +import { + getServiceNodeName, + SERVICE_NODE_NAME_MISSING, +} from '../../../../common/service_nodes'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../hooks/use_apm_router'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric_charts_fetcher'; import { truncate, unit } from '../../../utils/style'; @@ -39,19 +45,33 @@ const Truncate = euiStyled.span` ${truncate(unit * 12)} `; -interface ServiceNodeMetricsProps { - serviceName: string; - serviceNodeName: string; -} - -export function ServiceNodeMetrics({ - serviceName, - serviceNodeName, -}: ServiceNodeMetricsProps) { +export function ServiceNodeMetrics() { const { urlParams: { kuery, start, end }, } = useUrlParams(); - const { agentName } = useApmServiceContext(); + const { agentName, serviceName } = useApmServiceContext(); + + const apmRouter = useApmRouter(); + + const { + path: { serviceNodeName }, + query, + } = useApmParams('/services/:serviceName/nodes/:serviceNodeName/metrics'); + + useBreadcrumb({ + title: getServiceNodeName(serviceNodeName), + href: apmRouter.link( + '/services/:serviceName/nodes/:serviceNodeName/metrics', + { + path: { + serviceName, + serviceNodeName, + }, + query, + } + ), + }); + const { data } = useServiceMetricChartsFetcher({ serviceNodeName }); const { data: { host, containerId } = INITIAL_DATA, status } = useFetcher( diff --git a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx index 58541e2c5501b58..6431a8ad6d01c68 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx @@ -17,6 +17,7 @@ import { asInteger, asPercent, } from '../../../../common/utils/formatters'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../hooks/use_fetcher'; import { truncate, unit } from '../../../utils/style'; @@ -31,15 +32,13 @@ const ServiceNodeName = euiStyled.div` ${truncate(8 * unit)} `; -interface ServiceNodeOverviewProps { - serviceName: string; -} - -function ServiceNodeOverview({ serviceName }: ServiceNodeOverviewProps) { +function ServiceNodeOverview() { const { urlParams: { kuery, start, end }, } = useUrlParams(); + const { serviceName } = useApmServiceContext(); + const { data } = useFetcher( (callApmApi) => { if (!start || !end) { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 374b2d59ea3472f..803e9c73e992514 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -27,12 +27,8 @@ import { ServiceOverviewTransactionsTable } from './service_overview_transaction */ export const chartHeight = 288; -interface ServiceOverviewProps { - serviceName: string; -} - -export function ServiceOverview({ serviceName }: ServiceOverviewProps) { - const { agentName } = useApmServiceContext(); +export function ServiceOverview() { + const { agentName, serviceName } = useApmServiceContext(); // The default EuiFlexGroup breaks at 768, but we want to break at 992, so we // observe the window width and set the flex directions of rows accordingly @@ -61,7 +57,7 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) { - + diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index 4d6c0be9ff818f3..19318553727cb72 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -8,13 +8,13 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { CoreStart } from 'src/core/public'; +import { isEqual } from 'lodash'; import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, } from '../../../context/apm_plugin/mock_apm_plugin_context'; -import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; import * as useDynamicIndexPatternHooks from '../../../hooks/use_dynamic_index_pattern'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import * as useAnnotationsHooks from '../../../context/annotations/use_annotations_context'; @@ -28,11 +28,27 @@ import { getCallApmApiSpy, getCreateCallApmApiSpy, } from '../../../services/rest/callApmApiSpy'; +import { fromQuery } from '../../shared/Links/url_helpers'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import { uiSettingsServiceMock } from '../../../../../../../src/core/public/mocks'; const KibanaReactContext = createKibanaReactContext({ usageCollection: { reportUiCounter: () => {} }, } as Partial); +const mockParams = { + rangeFrom: 'now-15m', + rangeTo: 'now', + latencyAggregationType: LatencyAggregationType.avg, +}; + +const location = { + pathname: '/services/test%20service%20name/overview', + search: fromQuery(mockParams), +}; + +const uiSettings = uiSettingsServiceMock.create().setup({} as any); + function Wrapper({ children }: { children?: ReactNode }) { const value = ({ ...mockApmPluginContextValue, @@ -46,16 +62,14 @@ function Wrapper({ children }: { children?: ReactNode }) { } as unknown) as ApmPluginContextValue; return ( - - + + - + {children} @@ -69,6 +83,7 @@ describe('ServiceOverview', () => { jest .spyOn(useApmServiceContextHooks, 'useApmServiceContext') .mockReturnValue({ + serviceName: 'test service name', agentName: 'java', transactionType: 'request', transactionTypes: ['request'], @@ -96,6 +111,35 @@ describe('ServiceOverview', () => { }, 'GET /api/apm/services/{serviceName}/dependencies': [], 'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics': [], + 'GET /api/apm/services/{serviceName}/transactions/charts/latency': { + currentPeriod: { + overallAvgDuration: null, + latencyTimeseries: [], + }, + previousPeriod: { + overallAvgDuration: null, + latencyTimeseries: [], + }, + }, + 'GET /api/apm/services/{serviceName}/throughput': { + currentPeriod: [], + previousPeriod: [], + }, + 'GET /api/apm/services/{serviceName}/transactions/charts/error_rate': { + currentPeriod: { + transactionErrorRate: [], + noHits: true, + average: null, + }, + previousPeriod: { + transactionErrorRate: [], + noHits: true, + average: null, + }, + }, + 'GET /api/apm/services/{serviceName}/annotation/search': { + annotations: [], + }, }; /* eslint-enable @typescript-eslint/naming-convention */ @@ -118,16 +162,16 @@ describe('ServiceOverview', () => { status: FETCH_STATUS.SUCCESS, }); - const { findAllByText } = renderWithTheme( - , - { - wrapper: Wrapper, - } - ); + const { findAllByText } = renderWithTheme(, { + wrapper: Wrapper, + }); - await waitFor(() => - expect(callApmApiSpy).toHaveBeenCalledTimes(Object.keys(calls).length) - ); + await waitFor(() => { + const endpoints = callApmApiSpy.mock.calls.map( + (call) => call[0].endpoint + ); + return isEqual(endpoints.sort(), Object.keys(calls).sort()); + }); expect((await findAllByText('Latency')).length).toBeGreaterThan(0); }); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index c8da31da1b5d86a..1d6c538570f8fce 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -8,7 +8,6 @@ import { EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useParams } from 'react-router-dom'; import { asTransactionRate } from '../../../../common/utils/formatters'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; @@ -31,7 +30,7 @@ export function ServiceOverviewThroughputChart({ height?: number; }) { const theme = useTheme(); - const { serviceName } = useParams<{ serviceName?: string }>(); + const { urlParams: { environment, @@ -42,7 +41,8 @@ export function ServiceOverviewThroughputChart({ comparisonType, }, } = useUrlParams(); - const { transactionType } = useApmServiceContext(); + + const { transactionType, serviceName } = useApmServiceContext(); const comparisonChartTheme = getComparisonChartTheme(theme); const { comparisonStart, comparisonEnd } = getTimeRangeComparison({ start, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index a25bb807bdc460a..952bb339cf39689 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -25,10 +25,6 @@ import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time import { ServiceOverviewTableContainer } from '../service_overview_table_container'; import { getColumns } from './get_columns'; -interface Props { - serviceName: string; -} - type ApiResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics'>; const INITIAL_STATE = { transactionGroups: [] as ApiResponse['transactionGroups'], @@ -45,7 +41,7 @@ const DEFAULT_SORT = { field: 'impact' as const, }; -export function ServiceOverviewTransactionsTable({ serviceName }: Props) { +export function ServiceOverviewTransactionsTable() { const [tableOptions, setTableOptions] = useState<{ pageIndex: number; sort: { @@ -60,7 +56,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { const { pageIndex, sort } = tableOptions; const { direction, field } = sort; - const { transactionType } = useApmServiceContext(); + const { transactionType, serviceName } = useApmServiceContext(); const { urlParams: { start, diff --git a/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx b/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx index c6e1f575298c680..82195bc5b4d1776 100644 --- a/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx @@ -10,24 +10,24 @@ import { getValueTypeConfig, ProfilingValueType, } from '../../../../common/profiling'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmParams } from '../../../hooks/use_apm_params'; import { useFetcher } from '../../../hooks/use_fetcher'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { ServiceProfilingFlamegraph } from './service_profiling_flamegraph'; import { ServiceProfilingTimeline } from './service_profiling_timeline'; -interface ServiceProfilingProps { - serviceName: string; - environment?: string; -} - type ApiResponse = APIReturnType<'GET /api/apm/services/{serviceName}/profiling/timeline'>; const DEFAULT_DATA: ApiResponse = { profilingTimeline: [] }; -export function ServiceProfiling({ - serviceName, - environment, -}: ServiceProfilingProps) { +export function ServiceProfiling() { + const { serviceName } = useApmServiceContext(); + + const { + query: { environment }, + } = useApmParams('/services/:serviceName/profiling'); + const { urlParams: { kuery, start, end }, } = useUrlParams(); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx index f59b3ddab7c0556..a2db4cc87a81bab 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/Distribution/index.tsx @@ -25,7 +25,6 @@ import { isEmpty, keyBy } from 'lodash'; import React from 'react'; import { ValuesType } from 'utility-types'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import type { IUrlParams } from '../../../../context/url_params_context/types'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; @@ -94,7 +93,6 @@ export const formatYLong = (t: number) => { interface Props { distribution?: TransactionDistributionAPIResponse; - urlParams: IUrlParams; fetchStatus: FETCH_STATUS; bucketIndex: number; onBucketClick: ( @@ -104,7 +102,6 @@ interface Props { export function TransactionDistribution({ distribution, - urlParams: { transactionType }, fetchStatus, bucketIndex, onBucketClick, diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx index 40f50e768e76e04..6f0639de93e43f2 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx @@ -9,8 +9,11 @@ import { EuiHorizontalRule, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { flatten, isEmpty } from 'lodash'; import React from 'react'; import { useHistory } from 'react-router-dom'; +import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../hooks/use_apm_router'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useTransactionDistributionFetcher } from '../../../hooks/use_transaction_distribution_fetcher'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; @@ -28,17 +31,33 @@ interface Sample { export function TransactionDetails() { const { urlParams } = useUrlParams(); const history = useHistory(); - const { - distributionData, - distributionStatus, - } = useTransactionDistributionFetcher(); const { waterfall, exceedsMax, status: waterfallStatus, } = useWaterfallFetcher(); - const { transactionName } = urlParams; + + const { path, query } = useApmParams( + '/services/:serviceName/transactions/view' + ); + + const apmRouter = useApmRouter(); + + const { transactionName } = query; + + const { + distributionData, + distributionStatus, + } = useTransactionDistributionFetcher({ transactionName }); + + useBreadcrumb({ + title: transactionName, + href: apmRouter.link('/services/:serviceName/transactions/view', { + path, + query, + }), + }); const selectedSample = flatten( distributionData.buckets.map((bucket) => bucket.samples) @@ -90,7 +109,6 @@ export function TransactionDetails() { { if (!isEmpty(bucket.samples)) { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx index 817e747551d95c3..c352afbe03ff2c3 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx @@ -7,7 +7,6 @@ import React from 'react'; import { keyBy } from 'lodash'; -import { useParams } from 'react-router-dom'; import { IUrlParams } from '../../../../../context/url_params_context/types'; import { IWaterfall, @@ -15,6 +14,7 @@ import { } from './Waterfall/waterfall_helpers/waterfall_helpers'; import { Waterfall } from './Waterfall'; import { WaterfallLegends } from './WaterfallLegends'; +import { useApmServiceContext } from '../../../../../context/apm_service/use_apm_service_context'; interface Props { urlParams: IUrlParams; @@ -27,7 +27,7 @@ export function WaterfallContainer({ waterfall, exceedsMax, }: Props) { - const { serviceName } = useParams<{ serviceName: string }>(); + const { serviceName } = useApmServiceContext(); if (!waterfall) { return null; diff --git a/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx index c6394f09b0d3cf1..25cbf2d319587b5 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx @@ -7,23 +7,22 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import React from 'react'; -import { Redirect, RouteComponentProps } from 'react-router-dom'; +import { Redirect } from 'react-router-dom'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { getRedirectToTransactionDetailPageUrl } from '../TraceLink/get_redirect_to_transaction_detail_page_url'; +import { useApmParams } from '../../../hooks/use_apm_params'; const CentralizedContainer = euiStyled.div` height: 100%; display: flex; `; -export function TransactionLink({ - match, -}: RouteComponentProps<{ transactionId: string }>) { - const { transactionId } = match.params; - const { urlParams } = useUrlParams(); - const { rangeFrom, rangeTo } = urlParams; +export function TransactionLink() { + const { + path: { transactionId }, + query: { rangeFrom, rangeTo }, + } = useApmParams('/link-to/transaction/:transactionId'); const { data = { transaction: null }, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 2435e5fc5a1f6e0..819292095403ab2 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -49,14 +49,10 @@ function getRedirectLocation({ } } -interface TransactionOverviewProps { - serviceName: string; -} - -export function TransactionOverview({ serviceName }: TransactionOverviewProps) { +export function TransactionOverview() { const location = useLocation(); const { urlParams } = useUrlParams(); - const { transactionType } = useApmServiceContext(); + const { transactionType, serviceName } = useApmServiceContext(); // redirect to first transaction type useRedirect(getRedirectLocation({ location, transactionType, urlParams })); diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx index 9c4c2aa11a85821..afed0be7cc20959 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx @@ -9,7 +9,6 @@ import { queryByLabelText } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { CoreStart } from 'kibana/public'; import React from 'react'; -import { Router } from 'react-router-dom'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context'; @@ -63,14 +62,12 @@ function setup({ return renderWithTheme( - - - - - - - - + + + + + + ); diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts index 062fd5470e60cba..59207a6a499a210 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { useParams } from 'react-router-dom'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { useFetcher } from '../../../hooks/use_fetcher'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; type TransactionsAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups'>; @@ -22,7 +22,8 @@ export function useTransactionListFetcher() { const { urlParams: { environment, kuery, transactionType, start, end }, } = useUrlParams(); - const { serviceName } = useParams<{ serviceName?: string }>(); + const { serviceName } = useApmServiceContext(); + const { data = DEFAULT_RESPONSE, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { diff --git a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx index e00b7893b548ee7..099519416baf27e 100644 --- a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx +++ b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx @@ -5,534 +5,68 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; +import { createRouter, Outlet, route } from '@kbn/typed-react-router-config'; +import * as t from 'io-ts'; import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; -import { getServiceNodeName } from '../../../common/service_nodes'; -import { APMRouteDefinition } from '../../application/routes'; -import { toQuery } from '../shared/Links/url_helpers'; -import { ErrorGroupDetails } from '../app/error_group_details'; -import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; -import { ServiceNodeMetrics } from '../app/service_node_metrics'; -import { SettingsTemplate } from './templates/settings_template'; -import { AgentConfigurations } from '../app/Settings/agent_configurations'; -import { AnomalyDetection } from '../app/Settings/anomaly_detection'; -import { ApmIndices } from '../app/Settings/ApmIndices'; -import { CustomizeUI } from '../app/Settings/customize_ui'; -import { Schema } from '../app/Settings/schema'; +import { Breadcrumb } from '../app/breadcrumb'; import { TraceLink } from '../app/TraceLink'; import { TransactionLink } from '../app/transaction_link'; -import { TransactionDetails } from '../app/transaction_details'; -import { enableServiceOverview } from '../../../common/ui_settings_keys'; -import { redirectTo } from './redirect_to'; -import { ApmMainTemplate } from './templates/apm_main_template'; -import { ApmServiceTemplate } from './templates/apm_service_template'; -import { ServiceProfiling } from '../app/service_profiling'; -import { ErrorGroupOverview } from '../app/error_group_overview'; -import { ServiceMap } from '../app/service_map'; -import { ServiceNodeOverview } from '../app/service_node_overview'; -import { ServiceMetrics } from '../app/service_metrics'; -import { ServiceOverview } from '../app/service_overview'; -import { TransactionOverview } from '../app/transaction_overview'; -import { ServiceInventory } from '../app/service_inventory'; -import { TraceOverview } from '../app/trace_overview'; -import { useFetcher } from '../../hooks/use_fetcher'; -import { AgentConfigurationCreateEdit } from '../app/Settings/agent_configurations/AgentConfigurationCreateEdit'; - -// These component function definitions are used below with the `component` -// property of the route definitions. -// -// If you provide an inline function to the component prop, you would create a -// new component every render. This results in the existing component unmounting -// and the new component mounting instead of just updating the existing component. - -const ServiceInventoryTitle = i18n.translate( - 'xpack.apm.views.serviceInventory.title', - { defaultMessage: 'Services' } -); - -function ServiceInventoryView() { - return ( - - - - ); -} - -const TraceOverviewTitle = i18n.translate( - 'xpack.apm.views.traceOverview.title', - { - defaultMessage: 'Traces', - } -); - -function TraceOverviewView() { - return ( - - - - ); -} - -const ServiceMapTitle = i18n.translate('xpack.apm.views.serviceMap.title', { - defaultMessage: 'Service Map', -}); - -function ServiceMapView() { - return ( - - - - ); -} - -function ServiceDetailsErrorsRouteView( - props: RouteComponentProps<{ serviceName: string }> -) { - const { serviceName } = props.match.params; - return ( - - - - ); -} - -function ErrorGroupDetailsRouteView( - props: RouteComponentProps<{ serviceName: string; groupId: string }> -) { - const { serviceName, groupId } = props.match.params; - return ( - - - - ); -} - -function ServiceDetailsMetricsRouteView( - props: RouteComponentProps<{ serviceName: string }> -) { - const { serviceName } = props.match.params; - return ( - - - - ); -} - -function ServiceDetailsNodesRouteView( - props: RouteComponentProps<{ serviceName: string }> -) { - const { serviceName } = props.match.params; - return ( - - - - ); -} - -function ServiceDetailsOverviewRouteView( - props: RouteComponentProps<{ serviceName: string }> -) { - const { serviceName } = props.match.params; - return ( - - - - ); -} - -function ServiceDetailsServiceMapRouteView( - props: RouteComponentProps<{ serviceName: string }> -) { - const { serviceName } = props.match.params; - return ( - - - - ); -} - -function ServiceDetailsTransactionsRouteView( - props: RouteComponentProps<{ serviceName: string }> -) { - const { serviceName } = props.match.params; - return ( - - - - ); -} - -function ServiceDetailsProfilingRouteView( - props: RouteComponentProps<{ serviceName: string }> -) { - const { serviceName } = props.match.params; - return ( - - - - ); -} - -function ServiceNodeMetricsRouteView( - props: RouteComponentProps<{ - serviceName: string; - serviceNodeName: string; - }> -) { - const { serviceName, serviceNodeName } = props.match.params; - return ( - - - - ); -} - -function TransactionDetailsRouteView( - props: RouteComponentProps<{ serviceName: string }> -) { - const { serviceName } = props.match.params; - return ( - - - - ); -} - -function SettingsAgentConfigurationRouteView() { - return ( - - - - ); -} - -function SettingsAnomalyDetectionRouteView() { - return ( - - - - ); -} - -function SettingsApmIndicesRouteView() { - return ( - - - - ); -} - -function SettingsCustomizeUI() { - return ( - - - - ); -} - -function SettingsSchema() { - return ( - - - - ); -} - -export function EditAgentConfigurationRouteView(props: RouteComponentProps) { - const { search } = props.history.location; - - // typescript complains because `pageStop` does not exist in `APMQueryParams` - // Going forward we should move away from globally declared query params and this is a first step - // @ts-expect-error - const { name, environment, pageStep } = toQuery(search); - - const res = useFetcher( - (callApmApi) => { - return callApmApi({ - endpoint: 'GET /api/apm/settings/agent-configuration/view', - params: { query: { name, environment } }, - }); - }, - [name, environment] - ); - - return ( - - - - ); -} - -export function CreateAgentConfigurationRouteView(props: RouteComponentProps) { - const { search } = props.history.location; - - // Ignoring here because we specifically DO NOT want to add the query params to the global route handler - // @ts-expect-error - const { pageStep } = toQuery(search); - - return ( - - - - ); -} - -const SettingsApmIndicesTitle = i18n.translate( - 'xpack.apm.views.settings.indices.title', - { defaultMessage: 'Indices' } -); - -const SettingsAgentConfigurationTitle = i18n.translate( - 'xpack.apm.views.settings.agentConfiguration.title', - { defaultMessage: 'Agent Configuration' } -); -const CreateAgentConfigurationTitle = i18n.translate( - 'xpack.apm.views.settings.createAgentConfiguration.title', - { defaultMessage: 'Create Agent Configuration' } -); -const EditAgentConfigurationTitle = i18n.translate( - 'xpack.apm.views.settings.editAgentConfiguration.title', - { defaultMessage: 'Edit Agent Configuration' } -); -const SettingsCustomizeUITitle = i18n.translate( - 'xpack.apm.views.settings.customizeUI.title', - { defaultMessage: 'Customize app' } -); -const SettingsSchemaTitle = i18n.translate( - 'xpack.apm.views.settings.schema.title', - { defaultMessage: 'Schema' } -); -const SettingsAnomalyDetectionTitle = i18n.translate( - 'xpack.apm.views.settings.anomalyDetection.title', - { defaultMessage: 'Anomaly detection' } -); -const SettingsTitle = i18n.translate('xpack.apm.views.listSettings.title', { - defaultMessage: 'Settings', -}); +import { home } from './home'; +import { serviceDetail } from './service_detail'; +import { settings } from './settings'; /** * The array of route definitions to be used when the application * creates the routes. */ -export const apmRouteConfig: APMRouteDefinition[] = [ - /* - * Home routes - */ +const apmRoutes = route([ { - exact: true, path: '/', - render: redirectTo('/services'), - breadcrumb: 'APM', - }, - { - exact: true, - path: '/services', // !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts - component: ServiceInventoryView, - breadcrumb: ServiceInventoryTitle, - }, - { - exact: true, - path: '/traces', // !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts - component: TraceOverviewView, - breadcrumb: TraceOverviewTitle, - }, - { - exact: true, - path: '/service-map', // !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts - component: ServiceMapView, - breadcrumb: ServiceMapTitle, - }, - - /* - * Settings routes - */ - { - exact: true, - path: '/settings', - render: redirectTo('/settings/agent-configuration'), - breadcrumb: SettingsTitle, + element: ( + + + + ), + children: [settings, serviceDetail, home], }, { - exact: true, - path: '/settings/agent-configuration', - component: SettingsAgentConfigurationRouteView, - breadcrumb: SettingsAgentConfigurationTitle, - }, - { - exact: true, - path: '/settings/agent-configuration/create', - component: CreateAgentConfigurationRouteView, - breadcrumb: CreateAgentConfigurationTitle, - }, - { - exact: true, - path: '/settings/agent-configuration/edit', - breadcrumb: EditAgentConfigurationTitle, - component: EditAgentConfigurationRouteView, - }, - { - exact: true, - path: '/settings/apm-indices', - component: SettingsApmIndicesRouteView, - breadcrumb: SettingsApmIndicesTitle, - }, - { - exact: true, - path: '/settings/customize-ui', - component: SettingsCustomizeUI, - breadcrumb: SettingsCustomizeUITitle, - }, - { - exact: true, - path: '/settings/schema', - component: SettingsSchema, - breadcrumb: SettingsSchemaTitle, - }, - { - exact: true, - path: '/settings/anomaly-detection', - component: SettingsAnomalyDetectionRouteView, - breadcrumb: SettingsAnomalyDetectionTitle, - }, - - /* - * Services routes (with APM Service context) - */ - { - exact: true, - path: '/services/:serviceName', - breadcrumb: ({ match }) => match.params.serviceName, - component: RedirectToDefaultServiceRouteView, - }, - { - exact: true, - path: '/services/:serviceName/overview', - breadcrumb: i18n.translate('xpack.apm.views.overview.title', { - defaultMessage: 'Overview', - }), - component: ServiceDetailsOverviewRouteView, - }, - { - exact: true, - path: '/services/:serviceName/transactions', - component: ServiceDetailsTransactionsRouteView, - breadcrumb: i18n.translate('xpack.apm.views.transactions.title', { - defaultMessage: 'Transactions', - }), - }, - { - exact: true, - path: '/services/:serviceName/errors/:groupId', - component: ErrorGroupDetailsRouteView, - breadcrumb: ({ match }) => match.params.groupId, - }, - { - exact: true, - path: '/services/:serviceName/errors', - component: ServiceDetailsErrorsRouteView, - breadcrumb: i18n.translate('xpack.apm.views.errors.title', { - defaultMessage: 'Errors', - }), - }, - { - exact: true, - path: '/services/:serviceName/metrics', - component: ServiceDetailsMetricsRouteView, - breadcrumb: i18n.translate('xpack.apm.views.metrics.title', { - defaultMessage: 'Metrics', - }), - }, - // service nodes, only enabled for java agents for now - { - exact: true, - path: '/services/:serviceName/nodes', - component: ServiceDetailsNodesRouteView, - breadcrumb: i18n.translate('xpack.apm.views.nodes.title', { - defaultMessage: 'JVMs', - }), - }, - // node metrics - { - exact: true, - path: '/services/:serviceName/nodes/:serviceNodeName/metrics', - component: ServiceNodeMetricsRouteView, - breadcrumb: ({ match }) => getServiceNodeName(match.params.serviceNodeName), - }, - { - exact: true, - path: '/services/:serviceName/transactions/view', - component: TransactionDetailsRouteView, - breadcrumb: ({ location }) => { - const query = toQuery(location.search); - return query.transactionName as string; - }, - }, - { - exact: true, - path: '/services/:serviceName/profiling', - component: ServiceDetailsProfilingRouteView, - breadcrumb: i18n.translate('xpack.apm.views.serviceProfiling.title', { - defaultMessage: 'Profiling', - }), - }, - { - exact: true, - path: '/services/:serviceName/service-map', - component: ServiceDetailsServiceMapRouteView, - breadcrumb: i18n.translate('xpack.apm.views.serviceMap.title', { - defaultMessage: 'Service Map', - }), + path: '/link-to/transaction/:transactionId', + element: , + params: t.intersection([ + t.type({ + path: t.type({ + transactionId: t.string, + }), + }), + t.partial({ + query: t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + }), + }), + ]), }, - /* - * Utilility routes - */ { - exact: true, path: '/link-to/trace/:traceId', - component: TraceLink, - breadcrumb: null, - }, - { - exact: true, - path: '/link-to/transaction/:transactionId', - component: TransactionLink, - breadcrumb: null, - }, -]; - -function RedirectToDefaultServiceRouteView( - props: RouteComponentProps<{ serviceName: string }> -) { - const { uiSettings } = useApmPluginContext().core; - const { serviceName } = props.match.params; - if (uiSettings.get(enableServiceOverview)) { - return redirectTo(`/services/${serviceName}/overview`)(props); - } - return redirectTo(`/services/${serviceName}/transactions`)(props); -} + element: , + params: t.intersection([ + t.type({ + path: t.type({ + traceId: t.string, + }), + }), + t.partial({ + query: t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + }), + }), + ]), + }, +] as const); + +export type ApmRoutes = typeof apmRoutes; + +export const apmRouter = createRouter(apmRoutes); + +export type ApmRouter = typeof apmRouter; diff --git a/x-pack/plugins/apm/public/components/routing/app_root.tsx b/x-pack/plugins/apm/public/components/routing/app_root.tsx index a924c1f31cbefba..e82897083ae029c 100644 --- a/x-pack/plugins/apm/public/components/routing/app_root.tsx +++ b/x-pack/plugins/apm/public/components/routing/app_root.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import { ApmRoute } from '@elastic/apm-rum-react'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config'; import React from 'react'; -import { Route, RouteComponentProps, Router, Switch } from 'react-router-dom'; +import { Route } from 'react-router-dom'; import { DefaultTheme, ThemeProvider } from 'styled-components'; import { APP_WRAPPER_CLASS } from '../../../../../../src/core/public'; import { @@ -25,13 +25,13 @@ import { ApmPluginContextValue, } from '../../context/apm_plugin/apm_plugin_context'; import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; +import { BreadcrumbsContextProvider } from '../../context/breadcrumbs/context'; import { LicenseProvider } from '../../context/license/license_context'; import { UrlParamsProvider } from '../../context/url_params_context/url_params_context'; -import { useApmBreadcrumbs } from '../../hooks/use_apm_breadcrumbs'; import { ApmPluginStartDeps } from '../../plugin'; import { ApmHeaderActionMenu } from '../shared/apm_header_action_menu'; -import { apmRouteConfig } from './apm_route_config'; -import { TelemetryWrapper } from './telemetry_wrapper'; +import { apmRouter } from './apm_route_config'; +import { TrackPageview } from './track_pageview'; export function ApmAppRoot({ apmPluginContextValue, @@ -54,33 +54,24 @@ export function ApmAppRoot({ - - - - - - + + + + + + + + - - - {apmRouteConfig.map((route, i) => { - const { component, render, ...rest } = route; - return ( - { - return TelemetryWrapper({ route, props }); - }} - /> - ); - })} - - - - - - + + + + + + + + + @@ -89,7 +80,6 @@ export function ApmAppRoot({ } function MountApmHeaderActionMenu() { - useApmBreadcrumbs(apmRouteConfig); const { setHeaderActionMenu } = useApmPluginContext().appMountParameters; return ( diff --git a/x-pack/plugins/apm/public/components/routing/home/index.tsx b/x-pack/plugins/apm/public/components/routing/home/index.tsx new file mode 100644 index 000000000000000..454dcdedace901c --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/home/index.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; +import { Outlet } from '@kbn/typed-react-router-config'; +import * as t from 'io-ts'; +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { Breadcrumb } from '../../app/breadcrumb'; +import { ServiceInventory } from '../../app/service_inventory'; +import { ServiceMap } from '../../app/service_map'; +import { TraceOverview } from '../../app/trace_overview'; +import { ApmMainTemplate } from '../templates/apm_main_template'; + +function page({ + path, + element, + title, +}: { + path: TPath; + element: React.ReactElement; + title: string; +}): { path: TPath; element: React.ReactElement } { + return { + path, + element: ( + + {element} + + ), + }; +} + +export const ServiceInventoryTitle = i18n.translate( + 'xpack.apm.views.serviceInventory.title', + { + defaultMessage: 'Services', + } +); + +export const home = { + path: '/', + element: , + params: t.partial({ + query: t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + }), + }), + children: [ + page({ + path: '/services', + title: ServiceInventoryTitle, + element: , + }), + page({ + path: '/traces', + title: i18n.translate('xpack.apm.views.traceOverview.title', { + defaultMessage: 'Traces', + }), + element: , + }), + page({ + path: '/service-map', + title: i18n.translate('xpack.apm.views.serviceMap.title', { + defaultMessage: 'Service Map', + }), + element: , + }), + { + path: '/', + element: , + }, + ], +} as const; diff --git a/x-pack/plugins/apm/public/components/routing/route_config.test.tsx b/x-pack/plugins/apm/public/components/routing/route_config.test.tsx deleted file mode 100644 index b1d5c1a83b43bbf..000000000000000 --- a/x-pack/plugins/apm/public/components/routing/route_config.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { apmRouteConfig } from './apm_route_config'; - -describe('routes', () => { - describe('/', () => { - const route = apmRouteConfig.find((r) => r.path === '/'); - - describe('with no hash path', () => { - it('redirects to /services', () => { - const location = { hash: '', pathname: '/', search: '' }; - expect( - (route!.render!({ location } as any) as any).props.to.pathname - ).toEqual('/services'); - }); - }); - - describe('with a hash path', () => { - it('redirects to the hash path', () => { - const location = { - hash: - '#/services/opbeans-python/transactions/view?rangeFrom=now-24h&rangeTo=now&refreshInterval=10000&refreshPaused=false&traceId=d919c89dc7ca48d84b9dde1fef01d1f8&transactionId=1b542853d787ba7b&transactionName=GET%20opbeans.views.product_customers&transactionType=request&flyoutDetailTab=&waterfallItemId=1b542853d787ba7b', - pathname: '', - search: '', - }; - - expect((route!.render!({ location } as any) as any).props.to).toEqual({ - hash: '', - pathname: '/services/opbeans-python/transactions/view', - search: - '?rangeFrom=now-24h&rangeTo=now&refreshInterval=10000&refreshPaused=false&traceId=d919c89dc7ca48d84b9dde1fef01d1f8&transactionId=1b542853d787ba7b&transactionName=GET%20opbeans.views.product_customers&transactionType=request&flyoutDetailTab=&waterfallItemId=1b542853d787ba7b', - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/routing/service_detail/apm_service_wrapper.tsx b/x-pack/plugins/apm/public/components/routing/service_detail/apm_service_wrapper.tsx new file mode 100644 index 000000000000000..aa69aa4fa796511 --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/service_detail/apm_service_wrapper.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { Outlet } from '@kbn/typed-react-router-config'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../hooks/use_apm_router'; +import { ServiceInventoryTitle } from '../home'; +import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; + +export function ApmServiceWrapper() { + const { + path: { serviceName }, + query, + } = useApmParams('/services/:serviceName'); + + const router = useApmRouter(); + + useBreadcrumb([ + { + title: ServiceInventoryTitle, + href: router.link('/services', { query }), + }, + { + title: serviceName, + href: router.link('/services/:serviceName', { + query, + path: { serviceName }, + }), + }, + ]); + + return ; +} diff --git a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx new file mode 100644 index 000000000000000..19db296c428c8bf --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx @@ -0,0 +1,226 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import * as t from 'io-ts'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { Outlet } from '@kbn/typed-react-router-config'; +import { ServiceOverview } from '../../app/service_overview'; +import { ApmServiceTemplate } from '../templates/apm_service_template'; +import { RedirectToDefaultServiceRouteView } from './redirect_to_default_service_route_view'; +import { TransactionOverview } from '../../app/transaction_overview'; +import { ApmServiceWrapper } from './apm_service_wrapper'; +import { ErrorGroupOverview } from '../../app/error_group_overview'; +import { ErrorGroupDetails } from '../../app/error_group_details'; +import { ServiceMetrics } from '../../app/service_metrics'; +import { ServiceNodeOverview } from '../../app/service_node_overview'; +import { ServiceNodeMetrics } from '../../app/service_node_metrics'; +import { ServiceMap } from '../../app/service_map'; +import { TransactionDetails } from '../../app/transaction_details'; +import { ServiceProfiling } from '../../app/service_profiling'; + +function page({ + path, + title, + tab, + element, + searchBarOptions, +}: { + path: TPath; + title: string; + tab: React.ComponentProps['selectedTab']; + element: React.ReactElement; + searchBarOptions?: { + showTransactionTypeSelector?: boolean; + showTimeComparison?: boolean; + hidden?: boolean; + }; +}): { + element: React.ReactElement; + path: TPath; +} { + return { + path, + element: ( + + {element} + + ), + } as any; +} + +export const serviceDetail = { + 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: [ + page({ + path: '/overview', + element: , + tab: 'overview', + title: i18n.translate('xpack.apm.views.overview.title', { + defaultMessage: 'Overview', + }), + searchBarOptions: { + showTransactionTypeSelector: true, + showTimeComparison: true, + }, + }), + { + ...page({ + path: '/transactions', + tab: 'transactions', + title: i18n.translate('xpack.apm.views.transactions.title', { + defaultMessage: 'Transactions', + }), + element: , + searchBarOptions: { + showTransactionTypeSelector: true, + }, + }), + children: [ + { + path: '/view', + element: , + params: t.type({ + query: t.intersection([ + t.type({ + transactionName: t.string, + }), + t.partial({ + traceId: t.string, + transactionId: t.string, + }), + ]), + }), + }, + { + path: '/', + element: , + }, + ], + }, + { + ...page({ + path: '/errors', + tab: 'errors', + title: i18n.translate('xpack.apm.views.errors.title', { + defaultMessage: 'Errors', + }), + element: , + }), + params: t.partial({ + query: t.partial({ + sortDirection: t.string, + sortField: t.string, + pageSize: t.string, + page: t.string, + }), + }), + children: [ + { + path: '/:groupId', + element: , + params: t.type({ + path: t.type({ + groupId: t.string, + }), + }), + }, + { + path: '/', + element: , + }, + ], + }, + page({ + path: '/metrics', + tab: 'metrics', + title: i18n.translate('xpack.apm.views.metrics.title', { + defaultMessage: 'Metrics', + }), + element: , + }), + { + ...page({ + path: '/nodes', + tab: 'nodes', + title: i18n.translate('xpack.apm.views.nodes.title', { + defaultMessage: 'JVMs', + }), + element: , + }), + children: [ + { + path: '/:serviceNodeName/metrics', + element: , + params: t.type({ + path: t.type({ + serviceNodeName: t.string, + }), + }), + }, + { + path: '/', + element: , + params: t.partial({ + query: t.partial({ + sortDirection: t.string, + sortField: t.string, + pageSize: t.string, + page: t.string, + }), + }), + }, + ], + }, + page({ + path: '/service-map', + tab: 'service-map', + title: i18n.translate('xpack.apm.views.serviceMap.title', { + defaultMessage: 'Service Map', + }), + element: , + searchBarOptions: { + hidden: true, + }, + }), + page({ + path: '/profiling', + tab: 'profiling', + title: i18n.translate('xpack.apm.views.serviceProfiling.title', { + defaultMessage: 'Profiling', + }), + element: , + }), + { + path: '/', + element: , + }, + ], +} as const; diff --git a/x-pack/plugins/apm/public/components/routing/service_detail/redirect_to_default_service_route_view.tsx b/x-pack/plugins/apm/public/components/routing/service_detail/redirect_to_default_service_route_view.tsx new file mode 100644 index 000000000000000..37ec76f2b299ead --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/service_detail/redirect_to_default_service_route_view.tsx @@ -0,0 +1,35 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import qs from 'query-string'; +import { enableServiceOverview } from '../../../../common/ui_settings_keys'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useApmParams } from '../../../hooks/use_apm_params'; + +export function RedirectToDefaultServiceRouteView() { + const { + core: { uiSettings }, + } = useApmPluginContext(); + const { + path: { serviceName }, + query, + } = useApmParams('/services/:serviceName/*'); + + const search = qs.stringify(query); + + if (uiSettings.get(enableServiceOverview)) { + return ( + + ); + } + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/routing/settings/create_agent_configuration_route_view.tsx b/x-pack/plugins/apm/public/components/routing/settings/create_agent_configuration_route_view.tsx new file mode 100644 index 000000000000000..b01882031bea59c --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/settings/create_agent_configuration_route_view.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { AgentConfigurationPageStep } from '../../../../common/agent_configuration/constants'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { AgentConfigurationCreateEdit } from '../../app/Settings/agent_configurations/AgentConfigurationCreateEdit'; + +export function CreateAgentConfigurationRouteView() { + const { + query: { pageStep = AgentConfigurationPageStep.ChooseService }, + } = useApmParams('/settings/agent-configuration/create'); + + return ; +} diff --git a/x-pack/plugins/apm/public/components/routing/settings/edit_agent_configuration_route_view.tsx b/x-pack/plugins/apm/public/components/routing/settings/edit_agent_configuration_route_view.tsx new file mode 100644 index 000000000000000..70f1ce4d1d9db2a --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/settings/edit_agent_configuration_route_view.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { AgentConfigurationPageStep } from '../../../../common/agent_configuration/constants'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { AgentConfigurationCreateEdit } from '../../app/Settings/agent_configurations/AgentConfigurationCreateEdit'; + +export function EditAgentConfigurationRouteView() { + const { + query: { + name, + environment, + pageStep = AgentConfigurationPageStep.ChooseSettings, + }, + } = useApmParams('/settings/agent-configuration/edit'); + + const res = useFetcher( + (callApmApi) => { + return callApmApi({ + endpoint: 'GET /api/apm/settings/agent-configuration/view', + params: { query: { name, environment } }, + }); + }, + [name, environment] + ); + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/routing/settings/index.tsx b/x-pack/plugins/apm/public/components/routing/settings/index.tsx new file mode 100644 index 000000000000000..e844f05050d1799 --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/settings/index.tsx @@ -0,0 +1,140 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import * as t from 'io-ts'; +import { Outlet } from '@kbn/typed-react-router-config'; +import { i18n } from '@kbn/i18n'; +import { Redirect } from 'react-router-dom'; +import { agentConfigurationPageStepRt } from '../../../../common/agent_configuration/constants'; +import { Breadcrumb } from '../../app/breadcrumb'; +import { SettingsTemplate } from '../templates/settings_template'; +import { AgentConfigurations } from '../../app/Settings/agent_configurations'; +import { CreateAgentConfigurationRouteView } from './create_agent_configuration_route_view'; +import { EditAgentConfigurationRouteView } from './edit_agent_configuration_route_view'; +import { ApmIndices } from '../../app/Settings/ApmIndices'; +import { CustomizeUI } from '../../app/Settings/customize_ui'; +import { Schema } from '../../app/Settings/schema'; +import { AnomalyDetection } from '../../app/Settings/anomaly_detection'; + +function page({ + path, + title, + tab, + element, +}: { + path: TPath; + title: string; + tab: React.ComponentProps['selectedTab']; + element: React.ReactElement; +}): { + element: React.ReactElement; + path: TPath; +} { + return { + path, + element: ( + + {element} + + ), + } as any; +} + +export const settings = { + path: '/settings', + element: ( + + + + ), + children: [ + page({ + path: '/agent-configuration', + tab: 'agent-configurations', + title: i18n.translate( + 'xpack.apm.views.settings.agentConfiguration.title', + { defaultMessage: 'Agent Configuration' } + ), + element: , + }), + { + ...page({ + path: '/agent-configuration/create', + title: i18n.translate( + 'xpack.apm.views.settings.createAgentConfiguration.title', + { defaultMessage: 'Create Agent Configuration' } + ), + tab: 'agent-configurations', + element: , + }), + params: t.partial({ + query: t.partial({ + pageStep: agentConfigurationPageStepRt, + }), + }), + }, + { + ...page({ + path: '/agent-configuration/edit', + title: i18n.translate( + 'xpack.apm.views.settings.editAgentConfiguration.title', + { defaultMessage: 'Edit Agent Configuration' } + ), + tab: 'agent-configurations', + element: , + }), + params: t.partial({ + query: t.partial({ + name: t.string, + environment: t.string, + pageStep: agentConfigurationPageStepRt, + }), + }), + }, + page({ + path: '/apm-indices', + title: i18n.translate('xpack.apm.views.settings.indices.title', { + defaultMessage: 'Indices', + }), + tab: 'apm-indices', + element: , + }), + page({ + path: '/customize-ui', + title: i18n.translate('xpack.apm.views.settings.customizeUI.title', { + defaultMessage: 'Customize app', + }), + tab: 'customize-ui', + element: , + }), + page({ + path: '/schema', + title: i18n.translate('xpack.apm.views.settings.schema.title', { + defaultMessage: 'Schema', + }), + element: , + tab: 'schema', + }), + page({ + path: '/anomaly-detection', + title: i18n.translate('xpack.apm.views.settings.anomalyDetection.title', { + defaultMessage: 'Anomaly detection', + }), + element: , + tab: 'anomaly-detection', + }), + { + path: '/', + element: , + }, + ], +} as const; diff --git a/x-pack/plugins/apm/public/components/routing/telemetry_wrapper.tsx b/x-pack/plugins/apm/public/components/routing/telemetry_wrapper.tsx deleted file mode 100644 index fc3fc6a338d1898..000000000000000 --- a/x-pack/plugins/apm/public/components/routing/telemetry_wrapper.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; -import { useTrackPageview } from '../../../../observability/public'; -import { APMRouteDefinition } from '../../application/routes'; -import { redirectTo } from './redirect_to'; - -export function TelemetryWrapper({ - route, - props, -}: { - route: APMRouteDefinition; - props: RouteComponentProps; -}) { - const { component, render, path } = route; - const pathAsString = path as string; - - useTrackPageview({ app: 'apm', path: pathAsString }); - useTrackPageview({ app: 'apm', path: pathAsString, delay: 15000 }); - - if (component) { - return React.createElement(component, props); - } - if (render) { - return <>{render(props)}; - } - return <>{redirectTo('/')}; -} diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx index b77b07a23455aa4..2e10c853f542951 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx @@ -16,6 +16,7 @@ import { EuiToolTip, EuiButtonEmpty, } from '@elastic/eui'; +import { omit } from 'lodash'; import { ApmMainTemplate } from './apm_main_template'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context'; @@ -28,13 +29,6 @@ import { import { ServiceIcons } from '../../shared/service_icons'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { useErrorOverviewHref } from '../../shared/Links/apm/ErrorOverviewLink'; -import { useMetricOverviewHref } from '../../shared/Links/apm/MetricOverviewLink'; -import { useServiceMapHref } from '../../shared/Links/apm/ServiceMapLink'; -import { useServiceNodeOverviewHref } from '../../shared/Links/apm/ServiceNodeOverviewLink'; -import { useServiceOverviewHref } from '../../shared/Links/apm/service_overview_link'; -import { useServiceProfilingHref } from '../../shared/Links/apm/service_profiling_link'; -import { useTransactionsOverviewHref } from '../../shared/Links/apm/transaction_overview_link'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ENVIRONMENT_NOT_DEFINED } from '../../../../common/environment_filter_values'; import { @@ -47,22 +41,25 @@ import { createExploratoryViewUrl, SeriesUrl, } from '../../../../../observability/public'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; +import { useApmRouter } from '../../../hooks/use_apm_router'; type Tab = NonNullable[0] & { key: + | 'overview' + | 'transactions' | 'errors' | 'metrics' | 'nodes' - | 'overview' | 'service-map' - | 'profiling' - | 'transactions'; + | 'profiling'; hidden?: boolean; }; interface Props { + title: string; children: React.ReactNode; - serviceName: string; selectedTab: Tab['key']; searchBarOptions?: React.ComponentProps; } @@ -76,12 +73,27 @@ export function ApmServiceTemplate(props: Props) { } function TemplateWithContext({ + title, children, - serviceName, selectedTab, searchBarOptions, }: Props) { - const tabs = useTabs({ serviceName, selectedTab }); + const { + path: { serviceName }, + query, + } = useApmParams('/services/:serviceName/*'); + + const router = useApmRouter(); + + const tabs = useTabs({ selectedTab }); + + useBreadcrumb({ + title, + href: router.link(`/services/:serviceName/${selectedTab}` as const, { + path: { serviceName }, + query, + }), + }); return ( diff --git a/x-pack/plugins/apm/public/components/routing/track_pageview.tsx b/x-pack/plugins/apm/public/components/routing/track_pageview.tsx new file mode 100644 index 000000000000000..20e02a505bc4368 --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/track_pageview.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { useRoutePath } from '@kbn/typed-react-router-config'; +import { useTrackPageview } from '../../../../observability/public'; + +export function TrackPageview({ children }: { children: React.ReactElement }) { + const routePath = useRoutePath(); + + useTrackPageview({ app: 'apm', path: routePath }); + useTrackPageview({ app: 'apm', path: routePath, delay: 15000 }); + + return children; +} diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx index d5a685c4ea70bf8..28e674bc4150b87 100644 --- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx @@ -9,7 +9,7 @@ import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { History } from 'history'; import React from 'react'; -import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, @@ -17,6 +17,7 @@ import { import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; +import { useApmParams } from '../../../hooks/use_apm_params'; function updateEnvironmentUrl( history: History, @@ -63,12 +64,12 @@ function getOptions(environments: string[]) { export function EnvironmentFilter() { const history = useHistory(); const location = useLocation(); - const { serviceName } = useParams<{ serviceName?: string }>(); + const { path } = useApmParams('/*'); const { urlParams } = useUrlParams(); const { environment, start, end } = urlParams; const { environments, status = 'loading' } = useEnvironmentsFetcher({ - serviceName, + serviceName: 'serviceName' in path ? path.serviceName : undefined, start, end, }); diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/agentConfigurationLinks.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/agentConfigurationLinks.tsx index d009f1f82b0610b..58c510eff13a448 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/agentConfigurationLinks.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/agentConfigurationLinks.tsx @@ -26,14 +26,3 @@ export function editAgentConfigurationHref( }, }); } - -export function createAgentConfigurationHref( - search: string, - basePath: IBasePath -) { - return getAPMHref({ - basePath, - path: '/settings/agent-configuration/create', - search, - }); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index a80c859459557fc..233d0821a531979 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -5,17 +5,15 @@ * 2.0. */ -import { useParams } from 'react-router-dom'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; export function useTransactionBreakdown() { - const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams: { environment, kuery, start, end, transactionName }, } = useUrlParams(); - const { transactionType } = useApmServiceContext(); + const { transactionType, serviceName } = useApmServiceContext(); const { data = { timeseries: undefined }, error, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx index a64355e47f75771..e0f4ddb24c35045 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx @@ -9,7 +9,6 @@ import { EuiFlexItem, EuiIconTip, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React from 'react'; -import { useParams } from 'react-router-dom'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; @@ -33,9 +32,8 @@ const ShiftedEuiText = euiStyled(EuiText)` `; export function MLHeader({ hasValidMlLicense, mlJobId }: Props) { - const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); - const { transactionType } = useApmServiceContext(); + const { transactionType, serviceName } = useApmServiceContext(); if (!hasValidMlLicense || !mlJobId) { return null; diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index 96cb7c49a67104b..18c765c50fbf790 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -8,7 +8,6 @@ import { EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useParams } from 'react-router-dom'; import { RULE_ID } from '../../../../../../rule_registry/common/technical_rule_data_field_names'; import { AlertType } from '../../../../../common/alert_types'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; @@ -52,7 +51,6 @@ export function TransactionErrorRateChart({ showAnnotations = true, }: Props) { const theme = useTheme(); - const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams: { environment, @@ -64,7 +62,7 @@ export function TransactionErrorRateChart({ comparisonType, }, } = useUrlParams(); - const { transactionType, alerts } = useApmServiceContext(); + const { serviceName, transactionType, alerts } = useApmServiceContext(); const comparisonChartThem = getComparisonChartTheme(theme); const { comparisonStart, comparisonEnd } = getTimeRangeComparison({ start, diff --git a/x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts index 150a4d9efc2cb7a..50db1db8f38fea2 100644 --- a/x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts +++ b/x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts @@ -14,6 +14,7 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { UIProcessorEvent } from '../../../../common/processor_event'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { IUrlParams } from '../../../context/url_params_context/types'; export function getBoolFilter({ @@ -35,6 +36,14 @@ export function getBoolFilter({ }); } + boolFilter.push(...environmentQuery(urlParams.environment)); + + if (urlParams.transactionType) { + boolFilter.push({ + term: { [TRANSACTION_TYPE]: urlParams.transactionType }, + }); + } + switch (processorEvent) { case 'transaction': boolFilter.push({ diff --git a/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx b/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx index 1b503e9b0528688..72c5bac1f9f1743 100644 --- a/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { startsWith, uniqueId } from 'lodash'; import React, { useState } from 'react'; -import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; import { esKuery, IIndexPattern, @@ -16,6 +16,7 @@ import { } from '../../../../../../../src/plugins/data/public'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmParams } from '../../../hooks/use_apm_params'; import { useDynamicIndexPatternFetcher } from '../../../hooks/use_dynamic_index_pattern'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { getBoolFilter } from './get_bool_filter'; @@ -34,10 +35,11 @@ function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { } export function KueryBar(props: { prepend?: React.ReactNode | string }) { - const { groupId, serviceName } = useParams<{ - groupId?: string; - serviceName?: string; - }>(); + const { path } = useApmParams('/*'); + + const serviceName = 'serviceName' in path ? path.serviceName : undefined; + const groupId = 'groupId' in path ? path.groupId : undefined; + const history = useHistory(); const [state, setState] = useState({ suggestions: [], @@ -101,6 +103,7 @@ export function KueryBar(props: { prepend?: React.ReactNode | string }) { selectionStart, selectionEnd: selectionStart, useTimeRange: true, + method: 'terms_agg', })) || [] ) .filter((suggestion) => !startsWith(suggestion.text, 'span.')) diff --git a/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx index ea2feb3d2a4adc8..78ddb1e4560c521 100644 --- a/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx +++ b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx @@ -6,8 +6,8 @@ */ import React, { createContext } from 'react'; -import { useParams } from 'react-router-dom'; import { Annotation } from '../../../common/annotations'; +import { useApmParams } from '../../hooks/use_apm_params'; import { useFetcher } from '../../hooks/use_fetcher'; import { useUrlParams } from '../url_params_context/use_url_params'; @@ -22,7 +22,10 @@ export function AnnotationsContextProvider({ }: { children: React.ReactNode; }) { - const { serviceName } = useParams<{ serviceName?: string }>(); + const { path } = useApmParams('/*'); + + const serviceName = 'serviceName' in path ? path.serviceName : undefined; + const { urlParams: { environment, start, end }, } = useUrlParams(); diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index a356f2962b3c6d8..5666c64376c203e 100644 --- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -5,14 +5,18 @@ * 2.0. */ -import React, { ReactNode } from 'react'; +import React, { ReactNode, useMemo } from 'react'; import { Observable, of } from 'rxjs'; +import { RouterProvider } from '@kbn/typed-react-router-config'; +import { useHistory } from 'react-router-dom'; +import { createMemoryHistory, History } from 'history'; import { UrlService } from '../../../../../../src/plugins/share/common/url_service'; import { createObservabilityRuleTypeRegistryMock } from '../../../../observability/public'; import { ApmPluginContext, ApmPluginContextValue } from './apm_plugin_context'; import { ConfigSchema } from '../..'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; import { createCallApmApi } from '../../services/rest/createCallApmApi'; +import { apmRouter } from '../../components/routing/apm_route_config'; import { MlLocatorDefinition } from '../../../../ml/public'; const uiSettings: Record = { @@ -124,21 +128,32 @@ export const mockApmPluginContextValue = { export function MockApmPluginContextWrapper({ children, value = {} as ApmPluginContextValue, + history, }: { children?: React.ReactNode; value?: ApmPluginContextValue; + history?: History; }) { if (value.core) { createCallApmApi(value.core); } + + const contextHistory = useHistory(); + + const usedHistory = useMemo(() => { + return history || contextHistory || createMemoryHistory(); + }, [history, contextHistory]); + return ( - - {children} - + + + {children} + + ); } diff --git a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx index 1379bd8603999ce..8751145081f4ae5 100644 --- a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx @@ -13,7 +13,7 @@ describe('getTransactionType', () => { expect( getTransactionType({ transactionTypes: ['worker', 'request'], - urlParams: { transactionType: 'custom' }, + transactionType: 'custom', agentName: 'nodejs', }) ).toBe('custom'); @@ -25,7 +25,6 @@ describe('getTransactionType', () => { expect( getTransactionType({ transactionTypes: [], - urlParams: {}, }) ).toBeUndefined(); }); @@ -37,7 +36,6 @@ describe('getTransactionType', () => { expect( getTransactionType({ transactionTypes: ['worker', 'request'], - urlParams: {}, agentName: 'nodejs', }) ).toEqual('request'); @@ -49,7 +47,6 @@ describe('getTransactionType', () => { expect( getTransactionType({ transactionTypes: ['worker', 'custom'], - urlParams: {}, agentName: 'nodejs', }) ).toEqual('worker'); @@ -62,7 +59,6 @@ describe('getTransactionType', () => { expect( getTransactionType({ transactionTypes: ['http-request', 'page-load'], - urlParams: {}, agentName: 'js-base', }) ).toEqual('page-load'); diff --git a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx index cb826763425c2bb..1f454855b0f259c 100644 --- a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -13,40 +13,39 @@ import { TRANSACTION_REQUEST, } from '../../../common/transaction_types'; import { useServiceTransactionTypesFetcher } from './use_service_transaction_types_fetcher'; -import { useUrlParams } from '../url_params_context/use_url_params'; import { useServiceAgentNameFetcher } from './use_service_agent_name_fetcher'; -import { IUrlParams } from '../url_params_context/types'; import { APIReturnType } from '../../services/rest/createCallApmApi'; import { useServiceAlertsFetcher } from './use_service_alerts_fetcher'; -import { useServiceName } from '../../hooks/use_service_name'; +import { useApmParams } from '../../hooks/use_apm_params'; export type APMServiceAlert = ValuesType< APIReturnType<'GET /api/apm/services/{serviceName}/alerts'>['alerts'] >; export const APMServiceContext = createContext<{ + serviceName: string; agentName?: string; transactionType?: string; transactionTypes: string[]; alerts: APMServiceAlert[]; - serviceName?: string; -}>({ transactionTypes: [], alerts: [] }); +}>({ serviceName: '', transactionTypes: [], alerts: [] }); export function ApmServiceContextProvider({ children, }: { children: ReactNode; }) { - const { urlParams } = useUrlParams(); - - const serviceName = useServiceName(); + const { + path: { serviceName }, + query, + } = useApmParams('/services/:serviceName'); const { agentName } = useServiceAgentNameFetcher(serviceName); const transactionTypes = useServiceTransactionTypesFetcher(serviceName); const transactionType = getTransactionType({ - urlParams, + transactionType: query.transactionType, transactionTypes, agentName, }); @@ -56,11 +55,11 @@ export function ApmServiceContextProvider({ return ( @@ -68,16 +67,16 @@ export function ApmServiceContextProvider({ } export function getTransactionType({ - urlParams, + transactionType, transactionTypes, agentName, }: { - urlParams: IUrlParams; + transactionType?: string; transactionTypes: string[]; agentName?: string; }) { - if (urlParams.transactionType) { - return urlParams.transactionType; + if (transactionType) { + return transactionType; } if (!agentName || transactionTypes.length === 0) { diff --git a/x-pack/plugins/apm/public/context/breadcrumbs/context.tsx b/x-pack/plugins/apm/public/context/breadcrumbs/context.tsx new file mode 100644 index 000000000000000..906d2b19abf9f81 --- /dev/null +++ b/x-pack/plugins/apm/public/context/breadcrumbs/context.tsx @@ -0,0 +1,92 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + Route, + RouteMatch, + useMatchRoutes, +} from '@kbn/typed-react-router-config'; +import { ChromeBreadcrumb } from 'kibana/public'; +import { compact, isEqual } from 'lodash'; +import React, { createContext, useMemo, useState } from 'react'; +import { useBreadcrumbs } from '../../../../observability/public'; + +export interface Breadcrumb { + title: string; + href: string; +} + +interface BreadcrumbApi { + set(route: Route, breadcrumb: Breadcrumb[]): void; + unset(route: Route): void; + getBreadcrumbs(matches: RouteMatch[]): Breadcrumb[]; +} + +export const BreadcrumbsContext = createContext( + undefined +); + +export function BreadcrumbsContextProvider({ + children, +}: { + children: React.ReactElement; +}) { + const [, forceUpdate] = useState({}); + + const breadcrumbs = useMemo(() => { + return new Map(); + }, []); + + const matches: RouteMatch[] = useMatchRoutes(); + + const api = useMemo( + () => ({ + set(route, breadcrumb) { + if (!isEqual(breadcrumbs.get(route), breadcrumb)) { + breadcrumbs.set(route, breadcrumb); + forceUpdate({}); + } + }, + unset(route) { + if (breadcrumbs.has(route)) { + breadcrumbs.delete(route); + forceUpdate({}); + } + }, + getBreadcrumbs(currentMatches: RouteMatch[]) { + return compact( + currentMatches.flatMap((match) => { + const breadcrumb = breadcrumbs.get(match.route); + + return breadcrumb; + }) + ); + }, + }), + [breadcrumbs] + ); + + const formattedBreadcrumbs: ChromeBreadcrumb[] = api + .getBreadcrumbs(matches) + .map((breadcrumb, index, array) => { + return { + text: breadcrumb.title, + ...(index === array.length - 1 + ? {} + : { + href: breadcrumb.href, + }), + }; + }); + + useBreadcrumbs(formattedBreadcrumbs); + + return ( + + {children} + + ); +} diff --git a/x-pack/plugins/apm/public/context/breadcrumbs/use_breadcrumb.ts b/x-pack/plugins/apm/public/context/breadcrumbs/use_breadcrumb.ts new file mode 100644 index 000000000000000..dfc33c0f10ffc8a --- /dev/null +++ b/x-pack/plugins/apm/public/context/breadcrumbs/use_breadcrumb.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCurrentRoute } from '@kbn/typed-react-router-config'; +import { useContext, useEffect, useRef } from 'react'; +import { castArray } from 'lodash'; +import { Breadcrumb, BreadcrumbsContext } from './context'; + +export function useBreadcrumb(breadcrumb: Breadcrumb | Breadcrumb[]) { + const api = useContext(BreadcrumbsContext); + + if (!api) { + throw new Error('Missing Breadcrumb API in context'); + } + + const { match } = useCurrentRoute(); + + const matchedRoute = useRef(match?.route); + + if (matchedRoute.current && matchedRoute.current !== match?.route) { + api.unset(matchedRoute.current); + } + + matchedRoute.current = match?.route; + + if (matchedRoute.current) { + api.set(matchedRoute.current, castArray(breadcrumb)); + } + + useEffect(() => { + return () => { + if (matchedRoute.current) { + api.unset(matchedRoute.current); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); +} diff --git a/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.test.tsx b/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.test.tsx deleted file mode 100644 index 1cdb84c32475018..000000000000000 --- a/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.test.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { renderHook } from '@testing-library/react-hooks'; -import produce from 'immer'; -import React, { ReactNode } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { apmRouteConfig } from '../components/routing/apm_route_config'; -import { ApmPluginContextValue } from '../context/apm_plugin/apm_plugin_context'; -import { - mockApmPluginContextValue, - MockApmPluginContextWrapper, -} from '../context/apm_plugin/mock_apm_plugin_context'; -import { useApmBreadcrumbs } from './use_apm_breadcrumbs'; -import { useBreadcrumbs } from '../../../observability/public'; - -jest.mock('../../../observability/public'); - -function createWrapper(path: string) { - return ({ children }: { children?: ReactNode }) => { - const value = (produce(mockApmPluginContextValue, (draft) => { - draft.core.application.navigateToUrl = (url: string) => Promise.resolve(); - }) as unknown) as ApmPluginContextValue; - - return ( - - - {children} - - - ); - }; -} - -function mountBreadcrumb(path: string) { - renderHook(() => useApmBreadcrumbs(apmRouteConfig), { - wrapper: createWrapper(path), - }); -} - -describe('useApmBreadcrumbs', () => { - test('/services/:serviceName/errors/:groupId', () => { - mountBreadcrumb( - '/services/opbeans-node/errors/myGroupId?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0' - ); - - expect(useBreadcrumbs).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ - text: 'APM', - href: - '/basepath/app/apm/?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', - }), - expect.objectContaining({ - text: 'Services', - href: - '/basepath/app/apm/services?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', - }), - expect.objectContaining({ - text: 'opbeans-node', - href: - '/basepath/app/apm/services/opbeans-node?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', - }), - expect.objectContaining({ - text: 'Errors', - href: - '/basepath/app/apm/services/opbeans-node/errors?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', - }), - expect.objectContaining({ text: 'myGroupId', href: undefined }), - ]) - ); - }); - - test('/services/:serviceName/errors', () => { - mountBreadcrumb('/services/opbeans-node/errors?kuery=myKuery'); - - expect(useBreadcrumbs).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ - text: 'APM', - href: '/basepath/app/apm/?kuery=myKuery', - }), - expect.objectContaining({ - text: 'Services', - href: '/basepath/app/apm/services?kuery=myKuery', - }), - expect.objectContaining({ - text: 'opbeans-node', - href: '/basepath/app/apm/services/opbeans-node?kuery=myKuery', - }), - expect.objectContaining({ text: 'Errors', href: undefined }), - ]) - ); - }); - - test('/services/:serviceName/transactions', () => { - mountBreadcrumb('/services/opbeans-node/transactions?kuery=myKuery'); - - expect(useBreadcrumbs).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ - text: 'APM', - href: '/basepath/app/apm/?kuery=myKuery', - }), - expect.objectContaining({ - text: 'Services', - href: '/basepath/app/apm/services?kuery=myKuery', - }), - expect.objectContaining({ - text: 'opbeans-node', - href: '/basepath/app/apm/services/opbeans-node?kuery=myKuery', - }), - expect.objectContaining({ text: 'Transactions', href: undefined }), - ]) - ); - }); - - test('/services/:serviceName/transactions/view?transactionName=my-transaction-name', () => { - mountBreadcrumb( - '/services/opbeans-node/transactions/view?kuery=myKuery&transactionName=my-transaction-name' - ); - - expect(useBreadcrumbs).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ - text: 'APM', - href: '/basepath/app/apm/?kuery=myKuery', - }), - expect.objectContaining({ - text: 'Services', - href: '/basepath/app/apm/services?kuery=myKuery', - }), - expect.objectContaining({ - text: 'opbeans-node', - href: '/basepath/app/apm/services/opbeans-node?kuery=myKuery', - }), - expect.objectContaining({ - text: 'Transactions', - href: - '/basepath/app/apm/services/opbeans-node/transactions?kuery=myKuery', - }), - expect.objectContaining({ - text: 'my-transaction-name', - href: undefined, - }), - ]) - ); - }); -}); diff --git a/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.ts b/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.ts deleted file mode 100644 index d64bcadf7957758..000000000000000 --- a/x-pack/plugins/apm/public/hooks/use_apm_breadcrumbs.ts +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { History, Location } from 'history'; -import { ChromeBreadcrumb } from 'kibana/public'; -import { MouseEvent } from 'react'; -import { - match as Match, - matchPath, - RouteComponentProps, - useHistory, - useLocation, -} from 'react-router-dom'; -import { useBreadcrumbs } from '../../../observability/public'; -import { APMRouteDefinition, BreadcrumbTitle } from '../application/routes'; -import { getAPMHref } from '../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; - -interface BreadcrumbWithoutLink extends ChromeBreadcrumb { - match: Match>; -} - -interface BreadcrumbFunctionArgs extends RouteComponentProps { - breadcrumbTitle: BreadcrumbTitle; -} - -/** - * Call the breadcrumb function if there is one, otherwise return it as a string - */ -function getBreadcrumbText({ - breadcrumbTitle, - history, - location, - match, -}: BreadcrumbFunctionArgs) { - return typeof breadcrumbTitle === 'function' - ? breadcrumbTitle({ history, location, match }) - : breadcrumbTitle; -} - -/** - * Get a breadcrumb from the current path and route definitions. - */ -function getBreadcrumb({ - currentPath, - history, - location, - routes, -}: { - currentPath: string; - history: History; - location: Location; - routes: APMRouteDefinition[]; -}) { - return routes.reduce( - (found, { breadcrumb, ...routeDefinition }) => { - if (found) { - return found; - } - - if (!breadcrumb) { - return null; - } - - const match = matchPath>( - currentPath, - routeDefinition - ); - - if (match) { - return { - match, - text: getBreadcrumbText({ - breadcrumbTitle: breadcrumb, - history, - location, - match, - }), - }; - } - - return null; - }, - null - ); -} - -/** - * Once we have the breadcrumbs, we need to iterate through the list again to - * add the href and onClick, since we need to know which one is the final - * breadcrumb - */ -function addLinksToBreadcrumbs({ - breadcrumbs, - navigateToUrl, - wrappedGetAPMHref, -}: { - breadcrumbs: BreadcrumbWithoutLink[]; - navigateToUrl: (url: string) => Promise; - wrappedGetAPMHref: (path: string) => string; -}) { - return breadcrumbs.map((breadcrumb, index) => { - const isLastBreadcrumbItem = index === breadcrumbs.length - 1; - - // Make the link not clickable if it's the last item - const href = isLastBreadcrumbItem - ? undefined - : wrappedGetAPMHref(breadcrumb.match.url); - const onClick = !href - ? undefined - : (event: MouseEvent) => { - event.preventDefault(); - navigateToUrl(href); - }; - - return { - ...breadcrumb, - match: undefined, - href, - onClick, - }; - }); -} - -/** - * Convert a list of route definitions to a list of breadcrumbs - */ -function routeDefinitionsToBreadcrumbs({ - history, - location, - routes, -}: { - history: History; - location: Location; - routes: APMRouteDefinition[]; -}) { - const breadcrumbs: BreadcrumbWithoutLink[] = []; - const { pathname } = location; - - pathname - .split('?')[0] - .replace(/\/$/, '') - .split('/') - .reduce((acc, next) => { - // `/1/2/3` results in match checks for `/1`, `/1/2`, `/1/2/3`. - const currentPath = !next ? '/' : `${acc}/${next}`; - const breadcrumb = getBreadcrumb({ - currentPath, - history, - location, - routes, - }); - - if (breadcrumb) { - breadcrumbs.push(breadcrumb); - } - - return currentPath === '/' ? '' : currentPath; - }, ''); - - return breadcrumbs; -} - -/** - * Determine the breadcrumbs from the routes, set them, and update the page - * title when the route changes. - */ -export function useApmBreadcrumbs(routes: APMRouteDefinition[]) { - const history = useHistory(); - const location = useLocation(); - const { search } = location; - const { core } = useApmPluginContext(); - const { basePath } = core.http; - const { navigateToUrl } = core.application; - - function wrappedGetAPMHref(path: string) { - return getAPMHref({ basePath, path, search }); - } - - const breadcrumbsWithoutLinks = routeDefinitionsToBreadcrumbs({ - history, - location, - routes, - }); - const breadcrumbs = addLinksToBreadcrumbs({ - breadcrumbs: breadcrumbsWithoutLinks, - wrappedGetAPMHref, - navigateToUrl, - }); - - useBreadcrumbs(breadcrumbs); -} diff --git a/x-pack/plugins/apm/public/hooks/use_apm_params.ts b/x-pack/plugins/apm/public/hooks/use_apm_params.ts new file mode 100644 index 000000000000000..d7661dbcf4d21c6 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_apm_params.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { OutputOf, PathsOf, useParams } from '@kbn/typed-react-router-config'; +import { ApmRoutes } from '../components/routing/apm_route_config'; + +export function useApmParams>( + path: TPath +): OutputOf { + return useParams(path as never); +} diff --git a/x-pack/plugins/apm/public/hooks/use_apm_router.ts b/x-pack/plugins/apm/public/hooks/use_apm_router.ts new file mode 100644 index 000000000000000..c0ccc37cc897d61 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_apm_router.ts @@ -0,0 +1,26 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useRouter } from '@kbn/typed-react-router-config'; +import type { ApmRouter } from '../components/routing/apm_route_config'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; + +export function useApmRouter() { + const router = useRouter(); + const { core } = useApmPluginContext(); + + const link = (...args: any[]) => { + // a little too much effort needed to satisfy TS here + // @ts-ignore + return core.http.basePath.prepend('/app/apm' + router.link(...args)); + }; + + return ({ + ...router, + link, + } as unknown) as ApmRouter; +} diff --git a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index baf3eb51ae033f8..37eca08225e8f17 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { useParams } from 'react-router-dom'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { MetricsChartsByAgentAPIResponse } from '../../server/lib/metrics/get_metrics_chart_data_by_agent'; import { useUrlParams } from '../context/url_params_context/use_url_params'; @@ -24,8 +23,7 @@ export function useServiceMetricChartsFetcher({ const { urlParams: { environment, kuery, start, end }, } = useUrlParams(); - const { agentName } = useApmServiceContext(); - const { serviceName } = useParams<{ serviceName?: string }>(); + const { agentName, serviceName } = useApmServiceContext(); const { data = INITIAL_DATA, error, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/hooks/use_service_name.tsx b/x-pack/plugins/apm/public/hooks/use_service_name.tsx index c003bf5223a32b0..5e2678374c68edc 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_name.tsx +++ b/x-pack/plugins/apm/public/hooks/use_service_name.tsx @@ -5,12 +5,10 @@ * 2.0. */ -import { useRouteMatch } from 'react-router-dom'; +import { useApmParams } from './use_apm_params'; export function useServiceName(): string | undefined { - const match = useRouteMatch<{ serviceName?: string }>( - '/services/:serviceName' - ); + const { path } = useApmParams('/*'); - return match ? match.params.serviceName : undefined; + return 'serviceName' in path ? path.serviceName : undefined; } diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts index 25632d4b19cf47a..8e48f386772b3c0 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts @@ -6,12 +6,13 @@ */ import { flatten, omit, isEmpty } from 'lodash'; -import { useHistory, useParams } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { useFetcher } from './use_fetcher'; import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; import { maybe } from '../../common/utils/maybe'; import { APIReturnType } from '../services/rest/createCallApmApi'; import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; type APIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/distribution'>; @@ -21,19 +22,15 @@ const INITIAL_DATA = { bucketSize: 0, }; -export function useTransactionDistributionFetcher() { - const { serviceName } = useParams<{ serviceName?: string }>(); +export function useTransactionDistributionFetcher({ + transactionName, +}: { + transactionName: string; +}) { + const { serviceName, transactionType } = useApmServiceContext(); + const { - urlParams: { - environment, - kuery, - start, - end, - transactionType, - transactionId, - traceId, - transactionName, - }, + urlParams: { environment, kuery, start, end, transactionId, traceId }, } = useUrlParams(); const history = useHistory(); diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts index 0f1592ca2679fd0..5ae4a138608ecd3 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts @@ -6,7 +6,6 @@ */ import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; import { useFetcher } from './use_fetcher'; import { useUrlParams } from '../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; @@ -15,8 +14,7 @@ import { useTheme } from './use_theme'; import { getTimeRangeComparison } from '../components/shared/time_comparison/get_time_range_comparison'; export function useTransactionLatencyChartsFetcher() { - const { serviceName } = useParams<{ serviceName?: string }>(); - const { transactionType } = useApmServiceContext(); + const { transactionType, serviceName } = useApmServiceContext(); const theme = useTheme(); const { urlParams: { diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts index c8ae4fa5823a446..72e469178a10010 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts @@ -6,7 +6,6 @@ */ import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; import { useFetcher } from './use_fetcher'; import { useUrlParams } from '../context/url_params_context/use_url_params'; import { getThroughputChartSelector } from '../selectors/throughput_chart_selectors'; @@ -14,8 +13,7 @@ import { useTheme } from './use_theme'; import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; export function useTransactionThroughputChartsFetcher() { - const { serviceName } = useParams<{ serviceName?: string }>(); - const { transactionType } = useApmServiceContext(); + const { transactionType, serviceName } = useApmServiceContext(); const theme = useTheme(); const { urlParams: { environment, kuery, start, end, transactionName }, diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx index 9a1d4da8ece7cc8..465155dbf166bc4 100644 --- a/x-pack/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx @@ -67,13 +67,13 @@ export function mockMoment() { // Useful for getting the rendered href from any kind of link component export async function getRenderedHref(Component: React.FC, location: Location) { const el = render( - - + + - - + + ); const a = el.container.querySelector('a'); diff --git a/x-pack/plugins/apm/scripts/precommit.js b/x-pack/plugins/apm/scripts/precommit.js index 88d2e169dd542a6..89c5055c6a7f741 100644 --- a/x-pack/plugins/apm/scripts/precommit.js +++ b/x-pack/plugins/apm/scripts/precommit.js @@ -71,6 +71,8 @@ const tasks = new Listr( resolve(__dirname, '../../../../node_modules/jest-silent-reporter'), '--collect-coverage', 'false', + '--maxWorkers', + 4, ], execaOpts ), diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts index 6ce175fcb83626d..3a67076201efa94 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts @@ -13,7 +13,8 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { environmentQuery, rangeQuery } from '../../../../server/utils/queries'; +import { rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { AlertParams } from '../../../routes/alerts/chart_preview'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts index 3d64c63cb2041ce..0ead50c709083f8 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts @@ -8,7 +8,8 @@ import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { AlertParams } from '../../../routes/alerts/chart_preview'; -import { environmentQuery, rangeQuery } from '../../../../server/utils/queries'; +import { rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts index 0a6a25ad9c53329..888c929a2c7210e 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts @@ -12,7 +12,8 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { AlertParams } from '../../../routes/alerts/chart_preview'; -import { environmentQuery, rangeQuery } from '../../../../server/utils/queries'; +import { rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts index 35c80df2ca31cd0..bdd6b240c4bbcb5 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts @@ -28,7 +28,7 @@ import { SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; -import { environmentQuery } from '../../../server/utils/queries'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index ff202669fe1da54..c14675cb93987e5 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -30,7 +30,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; import { getDurationFormatter } from '../../../common/utils/formatters'; -import { environmentQuery } from '../../../server/utils/queries'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts index 36fd9c3fac58d8d..a39730265953801 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts @@ -32,7 +32,7 @@ import { import { EventOutcome } from '../../../common/event_outcome'; import { ProcessorEvent } from '../../../common/processor_event'; import { asDecimalOrInteger } from '../../../common/utils/formatters'; -import { environmentQuery } from '../../../server/utils/queries'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index d93514c42bbdda4..28f3041d65d7037 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -11,7 +11,7 @@ import { snakeCase } from 'lodash'; import Boom from '@hapi/boom'; import { ML_ERRORS } from '../../../common/anomaly_detection'; import { ProcessorEvent } from '../../../common/processor_event'; -import { environmentQuery } from '../../../server/utils/queries'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { Setup } from '../helpers/setup_request'; import { TRANSACTION_DURATION, diff --git a/x-pack/plugins/apm/server/lib/correlations/get_filters.ts b/x-pack/plugins/apm/server/lib/correlations/get_filters.ts index 61fec492ad38ec2..d6e8f3f57c91afc 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_filters.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_filters.ts @@ -7,7 +7,8 @@ import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { ESFilter } from '../../../../../../src/core/types/elasticsearch'; -import { environmentQuery, rangeQuery, kqlQuery } from '../../utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { SERVICE_NAME, TRANSACTION_NAME, diff --git a/x-pack/plugins/apm/server/lib/environments/get_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_environments.ts index c0b267f180010a1..84448b1c7c2b16f 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_environments.ts @@ -11,7 +11,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts index fa73ce8f2bc858d..48bdfd84b0443f5 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -11,11 +11,8 @@ import { SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; export async function getBuckets({ diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts index a915a4fb0330514..1a05f55dc8def5e 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts @@ -12,11 +12,8 @@ import { TRANSACTION_SAMPLED, } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getTransaction } from '../transactions/get_transaction'; diff --git a/x-pack/plugins/apm/server/lib/fleet/create_cloud_apm_package_policy.ts b/x-pack/plugins/apm/server/lib/fleet/create_cloud_apm_package_policy.ts index c336e5dc95ba657..6726865c6c3a5c0 100644 --- a/x-pack/plugins/apm/server/lib/fleet/create_cloud_apm_package_policy.ts +++ b/x-pack/plugins/apm/server/lib/fleet/create_cloud_apm_package_policy.ts @@ -10,6 +10,7 @@ import { SavedObjectsClientContract, Logger, } from 'kibana/server'; +import { PackagePolicy } from '../../../../fleet/common'; import { APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE, APM_SERVER_SCHEMA_SAVED_OBJECT_ID, @@ -19,6 +20,8 @@ import { APMPluginStartDependencies, } from '../../types'; import { getApmPackagePolicyDefinition } from './get_apm_package_policy_definition'; +import { Setup } from '../helpers/setup_request'; +import { mergePackagePolicyWithApm } from './merge_package_policy_with_apm'; export async function createCloudApmPackgePolicy({ cloudPluginSetup, @@ -26,13 +29,15 @@ export async function createCloudApmPackgePolicy({ savedObjectsClient, esClient, logger, + setup, }: { cloudPluginSetup: APMPluginSetupDependencies['cloud']; fleetPluginStart: NonNullable; savedObjectsClient: SavedObjectsClientContract; esClient: ElasticsearchClient; logger: Logger; -}) { + setup: Setup; +}): Promise { const { attributes } = await savedObjectsClient.get( APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE, APM_SERVER_SCHEMA_SAVED_OBJECT_ID @@ -40,15 +45,21 @@ export async function createCloudApmPackgePolicy({ const apmServerSchema: Record = JSON.parse( (attributes as { schemaJson: string }).schemaJson ); + // Merges agent config and source maps with the new APM cloud package policy const apmPackagePolicyDefinition = getApmPackagePolicyDefinition({ apmServerSchema, cloudPluginSetup, }); + const mergedAPMPackagePolicy = await mergePackagePolicyWithApm({ + setup, + packagePolicy: apmPackagePolicyDefinition, + fleetPluginStart, + }); logger.info(`Fleet migration on Cloud - apmPackagePolicy create start`); const apmPackagePolicy = await fleetPluginStart.packagePolicyService.create( savedObjectsClient, esClient, - apmPackagePolicyDefinition, + mergedAPMPackagePolicy, { force: true, bumpRevision: true } ); logger.info(`Fleet migration on Cloud - apmPackagePolicy create end`); diff --git a/x-pack/plugins/apm/server/lib/fleet/merge_package_policy_with_apm.ts b/x-pack/plugins/apm/server/lib/fleet/merge_package_policy_with_apm.ts new file mode 100644 index 000000000000000..fd3e3db700fd02a --- /dev/null +++ b/x-pack/plugins/apm/server/lib/fleet/merge_package_policy_with_apm.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Setup } from '../helpers/setup_request'; +import { APMPluginStartDependencies } from '../../types'; +import { listConfigurations } from '../settings/agent_configuration/list_configurations'; +import { + getPackagePolicyWithAgentConfigurations, + PackagePolicy, +} from './register_fleet_policy_callbacks'; +import { getPackagePolicyWithSourceMap, listArtifacts } from './source_maps'; + +export async function mergePackagePolicyWithApm({ + packagePolicy, + setup, + fleetPluginStart, +}: { + packagePolicy: PackagePolicy; + setup: Setup; + fleetPluginStart: NonNullable; +}) { + const agentConfigurations = await listConfigurations({ setup }); + const artifacts = await listArtifacts({ fleetPluginStart }); + return getPackagePolicyWithAgentConfigurations( + getPackagePolicyWithSourceMap({ packagePolicy, artifacts }), + agentConfigurations + ); +} diff --git a/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts b/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts index c122a5c406eab10..b0065b70673a52f 100644 --- a/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts +++ b/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts @@ -6,13 +6,12 @@ */ import { APMPlugin, APMRouteHandlerResources } from '../..'; -import { listConfigurations } from '../settings/agent_configuration/list_configurations'; -import { setupRequest } from '../helpers/setup_request'; -import { APMPluginStartDependencies } from '../../types'; import { ExternalCallback } from '../../../../fleet/server'; -import { AGENT_NAME } from '../../../common/elasticsearch_fieldnames'; import { AgentConfiguration } from '../../../common/agent_configuration/configuration_types'; -import { getPackagePolicyWithSourceMap, listArtifacts } from './source_maps'; +import { AGENT_NAME } from '../../../common/elasticsearch_fieldnames'; +import { APMPluginStartDependencies } from '../../types'; +import { setupRequest } from '../helpers/setup_request'; +import { mergePackagePolicyWithApm } from './merge_package_policy_with_apm'; export async function registerFleetPolicyCallbacks({ plugins, @@ -91,12 +90,11 @@ function registerPackagePolicyExternalCallback({ logger, ruleDataClient, }); - const agentConfigurations = await listConfigurations({ setup }); - const artifacts = await listArtifacts({ fleetPluginStart }); - return getPackagePolicyWithAgentConfigurations( - getPackagePolicyWithSourceMap({ packagePolicy, artifacts }), - agentConfigurations - ); + return await mergePackagePolicyWithApm({ + setup, + fleetPluginStart, + packagePolicy, + }); }; fleetPluginStart.registerExternalCallback(callbackName, callbackFn); diff --git a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts index 60ce36a85235ed1..d1174fcfcac6c67 100644 --- a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts @@ -6,7 +6,7 @@ */ import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions'; -import { kqlQuery, rangeQuery } from '../../../../server/utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../../observability/server'; import { ProcessorEvent } from '../../../../common/processor_event'; import { TRANSACTION_DURATION, diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts index 086516371387e6a..7bd46bfaabdd412 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts @@ -6,7 +6,7 @@ */ import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_transactions_per_minute.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_transactions_per_minute.ts index 016cb50566da0dd..c74e910e8cd27d5 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/get_transactions_per_minute.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/get_transactions_per_minute.ts @@ -10,7 +10,7 @@ import { TRANSACTION_REQUEST, } from '../../../common/transaction_types'; import { TRANSACTION_TYPE } from '../../../common/elasticsearch_fieldnames'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { calculateThroughput } from '../helpers/calculate_throughput'; diff --git a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts index fc5da4ec1d0fa67..28fab3369b1eb38 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts @@ -11,7 +11,7 @@ import { TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types'; export async function hasRumData({ diff --git a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts index 48beb9bca52413c..5f587f82e979d9b 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts @@ -11,7 +11,7 @@ import { } from '../../../../common/ux_ui_filter'; import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; import { UxUIFilters } from '../../../../typings/ui_filters'; -import { environmentQuery } from '../../../utils/queries'; +import { environmentQuery } from '../../../../common/utils/environment_query'; export function getEsFilter(uiFilters: UxUIFilters, exclude?: boolean) { const localFilterValues = uiFilters; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts index e0ddfc1b053b5f8..5d4af3e80f8bee6 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts @@ -5,19 +5,16 @@ * 2.0. */ -import { pipe } from 'fp-ts/lib/pipeable'; +import type { estypes } from '@elastic/elasticsearch'; import { getOrElse } from 'fp-ts/lib/Either'; -import { failure } from 'io-ts/lib/PathReporter'; +import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; - -import type { estypes } from '@elastic/elasticsearch'; +import { failure } from 'io-ts/lib/PathReporter'; import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types'; import { rangeRt } from '../../../routes/default_api_types'; - -import { Setup, SetupTimeRange } from '../../helpers/setup_request'; - import { getCorrelationsFilters } from '../../correlations/get_filters'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; const getPercentileThresholdValueQuery = ( percentileThresholdValue: number | undefined diff --git a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts index 6ecfe425dc8c5a8..8a934a102556e69 100644 --- a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { ProcessorEvent } from '../../../common/processor_event'; import { TRACE_ID } from '../../../common/elasticsearch_fieldnames'; import { diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 7ac56bcd9192d37..7ce4af41f4fecab 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -18,7 +18,7 @@ import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../common/transaction_types'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { withApmSpan } from '../../utils/with_apm_span'; import { getMlJobsWithAPMGroup } from '../anomaly_detection/get_ml_jobs_with_apm_group'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts index 6d50023d3fd0e77..5fc022508d0a8f8 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts @@ -15,7 +15,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { getServicesProjection } from '../../projections/services'; import { mergeProjection } from '../../projections/util/merge_projection'; -import { environmentQuery } from '../../../server/utils/queries'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { withApmSpan } from '../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts index 2e0ac303e5157d7..f9af5d227a86753 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts @@ -19,7 +19,8 @@ import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../common/transaction_types'; -import { environmentQuery, rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { withApmSpan } from '../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts index 26d7d2d1ee316f1..c97bfc1cfacc844 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts @@ -17,7 +17,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map'; -import { environmentQuery, rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; const MAX_TRACES_TO_INSPECT = 1000; diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts index 08587217980fb7b..7a36817dfc458ea 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts @@ -12,7 +12,8 @@ import { SERVICE_NAME, SERVICE_VERSION, } from '../../../../common/elasticsearch_fieldnames'; -import { environmentQuery, rangeQuery } from '../../../../server/utils/queries'; +import { rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts index 56b7aa1f465b0e9..1e8b528142936ad 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts @@ -7,7 +7,8 @@ import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { ElasticsearchClient, Logger } from 'kibana/server'; -import { environmentQuery, rangeQuery } from '../../../../server/utils/queries'; +import { rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { unwrapEsResponse, WrappedElasticsearchClientError, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts index 82147d7c94236e7..49489f2b338885d 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts @@ -10,7 +10,7 @@ import { AGENT_NAME, SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts b/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts index 2141570f521c01f..a9fe55456ad4e0b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts @@ -11,7 +11,8 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { environmentQuery, rangeQuery } from '../../utils/queries'; +import { rangeQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; export async function getServiceAlerts({ ruleDataClient, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts index 4993484f5b24003..8382ff820805e1c 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts @@ -21,7 +21,8 @@ import { SPAN_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { environmentQuery, rangeQuery } from '../../../../server/utils/queries'; +import { rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { withApmSpan } from '../../../utils/with_apm_span'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts index 1d815dd7180e3fd..52d3b41960a50b9 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts @@ -14,7 +14,8 @@ import { SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { environmentQuery, rangeQuery } from '../../../../server/utils/queries'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { rangeQuery } from '../../../../../observability/server'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { EventOutcome } from '../../../../common/event_outcome'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts index bd69bfc53db71f2..1e0c8e6f604410b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts @@ -13,11 +13,8 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_main_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_main_statistics.ts index 8168c0d5549aa44..9922cbce83dfdec 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_main_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_main_statistics.ts @@ -14,11 +14,8 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getErrorName } from '../../helpers/get_error_name'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts index b720c56464c30fd..0e9f8282301bfed 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts @@ -9,11 +9,8 @@ import { ValuesType } from 'utility-types'; import { orderBy } from 'lodash'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { PromiseReturnType } from '../../../../../observability/typings/common'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { ProcessorEvent } from '../../../../common/processor_event'; import { ERROR_EXC_MESSAGE, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instance_metadata_details.ts b/x-pack/plugins/apm/server/lib/services/get_service_instance_metadata_details.ts index bdf9530a9c0c7e3..44dfc2f600efc81 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instance_metadata_details.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instance_metadata_details.ts @@ -10,7 +10,8 @@ import { SERVICE_NODE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { environmentQuery, kqlQuery, rangeQuery } from '../../utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts index b817d4fb654ce7d..48209d98e86ce68 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts @@ -17,7 +17,8 @@ import { import { ProcessorEvent } from '../../../../common/processor_event'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; import { Coordinate } from '../../../../typings/timeseries'; -import { environmentQuery, kqlQuery, rangeQuery } from '../../../utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup } from '../../helpers/setup_request'; import { diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts index 6110ad345991114..d0f58ee8be31f68 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts @@ -14,7 +14,8 @@ import { EventOutcome } from '../../../../common/event_outcome'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; import { Coordinate } from '../../../../typings/timeseries'; -import { environmentQuery, kqlQuery, rangeQuery } from '../../../utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts index 910725b00541131..60b0628017efb2c 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts @@ -20,7 +20,7 @@ import { SERVICE_VERSION, } from '../../../common/elasticsearch_fieldnames'; import { ContainerType } from '../../../common/service_metadata'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts index 469c788a6cf17af..ce9a9f59d0e20b2 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts @@ -16,7 +16,7 @@ import { HOST_OS_PLATFORM, } from '../../../common/elasticsearch_fieldnames'; import { ContainerType } from '../../../common/service_metadata'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts index ea33c942cfc3bc8..6edec60b6f37324 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts @@ -15,11 +15,8 @@ import { import { EventOutcome } from '../../../common/event_outcome'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; -import { - environmentQuery, - kqlQuery, - rangeQuery, -} from '../../../server/utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { Coordinate } from '../../../typings/timeseries'; import { getDocumentTypeFilterForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts index a4cc27c875d731e..223abf972ee244d 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts @@ -13,11 +13,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts index f38a7fba09d967e..8d5a9248abce922 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts @@ -9,7 +9,7 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getDocumentTypeFilterForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts index f33bedb6ef4fb1f..cf8b83464b6d4c4 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { rangeQuery } from '../../../../server/utils/queries'; +import { rangeQuery } from '../../../../../observability/server'; import { ProcessorEvent } from '../../../../common/processor_event'; import { OBSERVER_VERSION_MAJOR } from '../../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts index 7f48c591521e718..c2121dbba97ef0e 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts @@ -15,11 +15,8 @@ import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../../common/transaction_types'; -import { - environmentQuery, - kqlQuery, - rangeQuery, -} from '../../../../server/utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { getDocumentTypeFilterForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts index 4692d1122b16c9e..a3bab48646eb6e9 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts @@ -11,7 +11,8 @@ import { SERVICE_ENVIRONMENT, SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; -import { environmentQuery, kqlQuery, rangeQuery } from '../../../utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { ProcessorEvent } from '../../../../common/processor_event'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_throughput.ts b/x-pack/plugins/apm/server/lib/services/get_throughput.ts index 7eacf47f15b7ab2..4004d55da79f698 100644 --- a/x-pack/plugins/apm/server/lib/services/get_throughput.ts +++ b/x-pack/plugins/apm/server/lib/services/get_throughput.ts @@ -10,11 +10,8 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { - environmentQuery, - kqlQuery, - rangeQuery, -} from '../../../server/utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts index 4e88c752aa50bcf..4434cc89d24f24f 100644 --- a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts @@ -21,11 +21,8 @@ import { PROFILE_TOP_ID, SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; -import { - rangeQuery, - environmentQuery, - kqlQuery, -} from '../../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { withApmSpan } from '../../../utils/with_apm_span'; diff --git a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_timeline.ts b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_timeline.ts index af3cd6596a8c1bf..19de91a5a105519 100644 --- a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_timeline.ts +++ b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_timeline.ts @@ -5,7 +5,6 @@ * 2.0. */ import { mapKeys, mapValues } from 'lodash'; -import { rangeQuery, environmentQuery } from '../../../../server/utils/queries'; import { ProcessorEvent } from '../../../../common/processor_event'; import { PROFILE_ID, @@ -17,7 +16,8 @@ import { } from '../../../../common/profiling'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getBucketSize } from '../../helpers/get_bucket_size'; -import { kqlQuery } from '../../../utils/queries'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { kqlQuery, rangeQuery } from '../../../../../observability/server'; const configMap = mapValues( mapKeys(ProfilingValueType, (val, key) => val), diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts index 68d316ef55df98f..026cf9dcceb7916 100644 --- a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts @@ -16,7 +16,7 @@ import { ERROR_LOG_LEVEL, } from '../../../common/elasticsearch_fieldnames'; import { APMError } from '../../../typings/es_schemas/ui/apm_error'; -import { rangeQuery } from '../../../server/utils/queries'; +import { rangeQuery } from '../../../../observability/server'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { PromiseValueType } from '../../../typings/common'; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts index cc3a13ef5c648f8..b27a54a983734bd 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts @@ -13,11 +13,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; -import { - environmentQuery, - kqlQuery, - rangeQuery, -} from '../../../server/utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../observability/server'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { Coordinate } from '../../../typings/timeseries'; import { getDocumentTypeFilterForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts index 20534a5fa7cbfc9..f5fcb3c2917ea45 100644 --- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -18,11 +18,8 @@ import { TRANSACTION_BREAKDOWN_COUNT, } from '../../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getMetricsDateHistogramParams } from '../../helpers/metrics'; import { MAX_KPIS } from './constants'; import { getVizColorForIndex } from '../../../../common/viz_colors'; diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts index 6259bb75386fb3f..62c284cd220530b 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts @@ -17,11 +17,8 @@ import { } from '../../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../../common/processor_event'; import { joinByKey } from '../../../../../common/utils/join_by_key'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../../../observability/server'; +import { environmentQuery } from '../../../../../common/utils/environment_query'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts index f3d4e8f6dd92d56..34b790a267c8326 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts @@ -15,11 +15,8 @@ import { getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, } from '../../helpers/aggregated_transactions'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; export async function getDistributionMax({ environment, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts index 558db1793935445..95cca0081e5ba6a 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts @@ -8,7 +8,7 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; import { ESSearchResponse } from '../../../../../../../src/core/types/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; -import { rangeQuery } from '../../../../server/utils/queries'; +import { rangeQuery } from '../../../../../observability/server'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index e3f59ca2e432809..183a754ea0809c7 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -14,11 +14,8 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; -import { - environmentQuery, - kqlQuery, - rangeQuery, -} from '../../../../server/utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts index ff3534159d19b6a..d5fff20496280b2 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts @@ -13,11 +13,8 @@ import { TRANSACTION_RESULT, TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; -import { - environmentQuery, - kqlQuery, - rangeQuery, -} from '../../../../server/utils/queries'; +import { kqlQuery, rangeQuery } from '../../../../../observability/server'; +import { environmentQuery } from '../../../../common/utils/environment_query'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts index c928b00cefb63c9..87d205f2bcd1173 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts @@ -9,7 +9,7 @@ import { TRACE_ID, TRANSACTION_ID, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeQuery } from '../../../../server/utils/queries'; +import { rangeQuery } from '../../../../../observability/server'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { ProcessorEvent } from '../../../../common/processor_event'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; diff --git a/x-pack/plugins/apm/server/projections/errors.ts b/x-pack/plugins/apm/server/projections/errors.ts index 341c7d13936baba..8f32dd9e5be5841 100644 --- a/x-pack/plugins/apm/server/projections/errors.ts +++ b/x-pack/plugins/apm/server/projections/errors.ts @@ -10,11 +10,8 @@ import { SERVICE_NAME, ERROR_GROUP_ID, } from '../../common/elasticsearch_fieldnames'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../observability/server'; +import { environmentQuery } from '../../common/utils/environment_query'; import { ProcessorEvent } from '../../common/processor_event'; export function getErrorGroupsProjection({ diff --git a/x-pack/plugins/apm/server/projections/metrics.ts b/x-pack/plugins/apm/server/projections/metrics.ts index 9a757893337e5a9..54bc498007cbbb1 100644 --- a/x-pack/plugins/apm/server/projections/metrics.ts +++ b/x-pack/plugins/apm/server/projections/metrics.ts @@ -11,11 +11,8 @@ import { SERVICE_NAME, SERVICE_NODE_NAME, } from '../../common/elasticsearch_fieldnames'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../observability/server'; +import { environmentQuery } from '../../common/utils/environment_query'; import { SERVICE_NODE_NAME_MISSING } from '../../common/service_nodes'; import { ProcessorEvent } from '../../common/processor_event'; diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts index ae8bb4a5f11afd8..b8cf92f15c70669 100644 --- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts +++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @@ -11,7 +11,7 @@ import { TRANSACTION_TYPE, SERVICE_LANGUAGE_NAME, } from '../../common/elasticsearch_fieldnames'; -import { rangeQuery } from '../../server/utils/queries'; +import { rangeQuery } from '../../../observability/server'; import { ProcessorEvent } from '../../common/processor_event'; import { TRANSACTION_PAGE_LOAD } from '../../common/transaction_types'; import { getEsFilter } from '../lib/rum_client/ui_filters/get_es_filter'; diff --git a/x-pack/plugins/apm/server/projections/services.ts b/x-pack/plugins/apm/server/projections/services.ts index 3509e4fa5b3393e..52992f16dac85c9 100644 --- a/x-pack/plugins/apm/server/projections/services.ts +++ b/x-pack/plugins/apm/server/projections/services.ts @@ -7,7 +7,7 @@ import { Setup, SetupTimeRange } from '../../server/lib/helpers/setup_request'; import { SERVICE_NAME } from '../../common/elasticsearch_fieldnames'; -import { rangeQuery, kqlQuery } from '../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../observability/server'; import { ProcessorEvent } from '../../common/processor_event'; import { getProcessorEventForAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; diff --git a/x-pack/plugins/apm/server/projections/transactions.ts b/x-pack/plugins/apm/server/projections/transactions.ts index 3e830403debb0df..1efd9679cae9c07 100644 --- a/x-pack/plugins/apm/server/projections/transactions.ts +++ b/x-pack/plugins/apm/server/projections/transactions.ts @@ -12,11 +12,8 @@ import { TRANSACTION_TYPE, TRANSACTION_NAME, } from '../../common/elasticsearch_fieldnames'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../server/utils/queries'; +import { rangeQuery, kqlQuery } from '../../../observability/server'; +import { environmentQuery } from '../../common/utils/environment_query'; import { getProcessorEventForAggregatedTransactions, getDocumentTypeFilterForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts index 66843f4f0df4d78..a01c9dd1579b1fa 100644 --- a/x-pack/plugins/apm/server/routes/fleet.ts +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -5,26 +5,27 @@ * 2.0. */ -import { keyBy } from 'lodash'; import Boom from '@hapi/boom'; -import * as t from 'io-ts'; import { i18n } from '@kbn/i18n'; +import * as t from 'io-ts'; +import { keyBy } from 'lodash'; import { - APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE, APM_SERVER_SCHEMA_SAVED_OBJECT_ID, + APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE, } from '../../common/apm_saved_object_constants'; +import { createCloudApmPackgePolicy } from '../lib/fleet/create_cloud_apm_package_policy'; import { getFleetAgents } from '../lib/fleet/get_agents'; import { getApmPackgePolicies } from '../lib/fleet/get_apm_package_policies'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; import { - getCloudAgentPolicy, getApmPackagePolicy, + getCloudAgentPolicy, } from '../lib/fleet/get_cloud_apm_package_policy'; -import { createCloudApmPackgePolicy } from '../lib/fleet/create_cloud_apm_package_policy'; import { getUnsupportedApmServerSchema } from '../lib/fleet/get_unsupported_apm_server_schema'; import { isSuperuser } from '../lib/fleet/is_superuser'; import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client'; +import { setupRequest } from '../lib/helpers/setup_request'; +import { createApmServerRoute } from './create_apm_server_route'; +import { createApmServerRouteRepository } from './create_apm_server_route_repository'; const hasFleetDataRoute = createApmServerRoute({ endpoint: 'GET /api/apm/fleet/has_data', @@ -172,12 +173,15 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({ throw Boom.forbidden(CLOUD_SUPERUSER_REQUIRED_MESSAGE); } + const setup = await setupRequest(resources); + const cloudApmPackagePolicy = await createCloudApmPackgePolicy({ cloudPluginSetup, fleetPluginStart, savedObjectsClient, esClient, logger, + setup, }); return { cloudApmPackagePolicy }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts index 3295332bb631658..906f1646757a7ff 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { debug } from './debug'; -import { error } from './error'; import { image } from './image'; import { markdown } from './markdown'; import { metric } from './metric'; @@ -19,8 +17,6 @@ import { table } from './table'; import { text } from './text'; export const renderFunctions = [ - debug, - error, image, markdown, metric, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx deleted file mode 100644 index 5870338ff689495..000000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/debug.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import ReactDOM from 'react-dom'; -import React from 'react'; -import { Debug } from '../../public/components/debug'; -import { RendererStrings } from '../../i18n'; -import { RendererFactory } from '../../types'; - -const { debug: strings } = RendererStrings; - -export const debug: RendererFactory = () => ({ - name: 'debug', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render(domNode, config, handlers) { - const renderDebug = () => ( -
- -
- ); - - ReactDOM.render(renderDebug(), domNode, () => handlers.done()); - - if (handlers.onResize) { - handlers.onResize(() => { - ReactDOM.render(renderDebug(), domNode, () => handlers.done()); - }); - } - - handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/error.scss b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/error.scss deleted file mode 100644 index 9229c1f88a096a8..000000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/error.scss +++ /dev/null @@ -1,16 +0,0 @@ -.canvasRenderError { - display: flex; - justify-content: center; - align-items: center; - - .canvasRenderError__icon { - opacity: .4; - stroke: $euiColorEmptyShade; - stroke-width: .2px; - - &:hover { - opacity: .6; - cursor: pointer; - } - } -} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.tsx deleted file mode 100644 index dcf83c68f0c75af..000000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import ReactDOM from 'react-dom'; -import React, { MouseEventHandler } from 'react'; -import { EuiIcon } from '@elastic/eui'; -import { Error } from '../../../public/components/error'; -import { Popover } from '../../../public/components/popover'; -import { RendererStrings } from '../../../i18n'; -import { RendererFactory } from '../../../types'; - -export interface Config { - error: Error; -} - -const { error: strings } = RendererStrings; - -export const error: RendererFactory = () => ({ - name: 'error', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render(domNode, config, handlers) { - const draw = () => { - const buttonSize = Math.min(domNode.clientHeight, domNode.clientWidth); - const button = (handleClick: MouseEventHandler) => ( - - ); - - ReactDOM.render( -
- {() => } -
, - - domNode, - () => handlers.done() - ); - }; - - draw(); - - handlers.onResize(draw); - - handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts index bf9b6a744e686e5..1d032aa829bc070 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts @@ -6,6 +6,7 @@ */ import { revealImageRenderer } from '../../../../../src/plugins/expression_reveal_image/public'; +import { errorRenderer, debugRenderer } from '../../../../../src/plugins/expression_error/public'; -export const renderFunctions = [revealImageRenderer]; +export const renderFunctions = [revealImageRenderer, errorRenderer, debugRenderer]; export const renderFunctionFactories = []; diff --git a/x-pack/plugins/canvas/i18n/renderers.ts b/x-pack/plugins/canvas/i18n/renderers.ts index 29687155818e7b8..fa1fbc063dbe6e3 100644 --- a/x-pack/plugins/canvas/i18n/renderers.ts +++ b/x-pack/plugins/canvas/i18n/renderers.ts @@ -55,16 +55,6 @@ export const RendererStrings = { defaultMessage: 'Renders an embeddable Saved Object from other parts of Kibana', }), }, - error: { - getDisplayName: () => - i18n.translate('xpack.canvas.renderer.error.displayName', { - defaultMessage: 'Error information', - }), - getHelpDescription: () => - i18n.translate('xpack.canvas.renderer.error.helpDescription', { - defaultMessage: 'Render error data in a way that is helpful to users', - }), - }, image: { getDisplayName: () => i18n.translate('xpack.canvas.renderer.image.displayName', { diff --git a/x-pack/plugins/canvas/kibana.json b/x-pack/plugins/canvas/kibana.json index 85d2e0709cb3e78..545eae742a89e5f 100644 --- a/x-pack/plugins/canvas/kibana.json +++ b/x-pack/plugins/canvas/kibana.json @@ -10,6 +10,7 @@ "charts", "data", "embeddable", + "expressionError", "expressionRevealImage", "expressions", "features", diff --git a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx index d9df1e4661fbf27..e1cd5c55393fb52 100644 --- a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx +++ b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx @@ -9,9 +9,7 @@ import React, { MouseEventHandler, FC } from 'react'; import PropTypes from 'prop-types'; import { EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - -// @ts-expect-error untyped local -import { Popover, PopoverChildrenProps } from '../popover'; +import { Popover } from '../popover'; import { ArgAdd } from '../arg_add'; // @ts-expect-error untyped local import { Arg } from '../../expression_types/arg'; @@ -49,7 +47,7 @@ export const ArgAddPopover: FC = ({ options }) => { panelPaddingSize="none" button={button} > - {({ closePopover }: PopoverChildrenProps) => + {({ closePopover }) => options.map((opt) => ( )) .add('datasource with expression arguments', () => ( @@ -90,5 +91,6 @@ storiesOf('components/datasource/DatasourceComponent', module) setPreviewing={action('setPreviewing')} isInvalid={false} setInvalid={action('setInvalid')} + renderError={action('renderError')} /> )); diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js index f09ce4c925820c0..c1df18fc06d5593 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js @@ -60,6 +60,7 @@ export class DatasourceComponent extends PureComponent { setPreviewing: PropTypes.func, isInvalid: PropTypes.bool, setInvalid: PropTypes.func, + renderError: PropTypes.func, }; state = { defaultIndex: '' }; @@ -125,6 +126,7 @@ export class DatasourceComponent extends PureComponent { setPreviewing, isInvalid, setInvalid, + renderError, } = this.props; const { defaultIndex } = this.state; @@ -155,6 +157,7 @@ export class DatasourceComponent extends PureComponent { isInvalid, setInvalid, defaultIndex, + renderError, }); const hasExpressionArgs = Object.values(stateArgs).some((a) => a && typeof a[0] === 'object'); diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js index 2eb42c5cb98dc50..a45613f4bc96b43 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js @@ -20,8 +20,11 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { withSuspense } from '../../../../../../../src/plugins/presentation_util/public'; +import { LazyErrorComponent } from '../../../../../../../src/plugins/expression_error/public'; import { Datatable } from '../../datatable'; -import { Error } from '../../error'; + +const Error = withSuspense(LazyErrorComponent); const strings = { getEmptyFirstLineDescription: () => diff --git a/x-pack/plugins/canvas/public/components/debug/index.tsx b/x-pack/plugins/canvas/public/components/debug/index.tsx deleted file mode 100644 index 8adec03e8540871..000000000000000 --- a/x-pack/plugins/canvas/public/components/debug/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { Debug } from './debug'; diff --git a/x-pack/plugins/canvas/public/components/error/index.ts b/x-pack/plugins/canvas/public/components/error/index.ts deleted file mode 100644 index 65c4af8a369aeea..000000000000000 --- a/x-pack/plugins/canvas/public/components/error/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { Error } from './error'; diff --git a/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot b/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot index 5c17eb2b681373e..99d5dc3c115be1a 100644 --- a/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot @@ -16,7 +16,18 @@ exports[`Storyshots components/ExpressionInput default 1`] = ` id="generated-id" onBlur={[Function]} onFocus={[Function]} - /> + > +
+
+
diff --git a/x-pack/plugins/canvas/public/components/home/__snapshots__/home.stories.storyshot b/x-pack/plugins/canvas/public/components/home/__snapshots__/home.stories.storyshot index 770e94ec4b174c5..02f54723abd42b9 100644 --- a/x-pack/plugins/canvas/public/components/home/__snapshots__/home.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/home/__snapshots__/home.stories.storyshot @@ -123,9 +123,22 @@ exports[`Storyshots Home Home Page 1`] = `
- +
+
+ +
+
diff --git a/x-pack/plugins/canvas/public/components/popover/popover.tsx b/x-pack/plugins/canvas/public/components/popover/popover.tsx index 275d800fe2ca1e4..a2793b3759f1e23 100644 --- a/x-pack/plugins/canvas/public/components/popover/popover.tsx +++ b/x-pack/plugins/canvas/public/components/popover/popover.tsx @@ -5,7 +5,6 @@ * 2.0. */ -/* eslint react/no-did-mount-set-state: 0, react/forbid-elements: 0 */ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { EuiPopover, EuiToolTip } from '@elastic/eui'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.component.tsx index efc7f2fae8f8b15..f501410b26a74b2 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.component.tsx @@ -9,10 +9,9 @@ import React, { Fragment, FunctionComponent, useState } from 'react'; import PropTypes from 'prop-types'; import { EuiButtonEmpty, EuiContextMenu, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - +import { Popover, ClosePopoverFn } from '../../popover'; import { ShortcutStrings } from '../../../../i18n/shortcuts'; import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; -import { Popover, ClosePopoverFn } from '../../popover'; import { CustomElementModal } from '../../custom_element_modal'; import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib/constants'; import { PositionedElement } from '../../../../types'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx index e1d69163e07619a..2907e8c4d5dd728 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx @@ -15,12 +15,11 @@ import { EuiContextMenuPanelItemDescriptor, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - +import { Popover, ClosePopoverFn } from '../../popover'; import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib'; import { ElementSpec } from '../../../../types'; import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; import { getId } from '../../../lib/get_id'; -import { Popover, ClosePopoverFn } from '../../popover'; import { AssetManager } from '../../asset_manager'; import { SavedElementsModal } from '../../saved_elements_modal'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx index e59bf284258fc46..de712b269835910 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx @@ -9,12 +9,11 @@ import React, { FunctionComponent, useState } from 'react'; import PropTypes from 'prop-types'; import { EuiButtonEmpty, EuiContextMenu, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - +import { Popover, ClosePopoverFn } from '../../popover'; import { ReportingStart } from '../../../../../reporting/public'; import { PDF, JSON } from '../../../../i18n/constants'; import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; import { usePlatformService } from '../../../services'; -import { ClosePopoverFn, Popover } from '../../popover'; import { ShareWebsiteFlyout } from './flyout'; import { CanvasWorkpadSharingData, getPdfJobParams } from './utils'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.component.tsx index b001ad04caa4418..b2c6d97a51748dc 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/view_menu.component.tsx @@ -14,7 +14,7 @@ import { EuiContextMenuPanelItemDescriptor, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - +import { Popover, ClosePopoverFn } from '../../popover'; import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL, @@ -22,7 +22,6 @@ import { } from '../../../../common/lib/constants'; import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; -import { Popover, ClosePopoverFn } from '../../popover'; import { AutoRefreshControls } from './auto_refresh_controls'; import { KioskControls } from './kiosk_controls'; diff --git a/x-pack/plugins/canvas/public/style/index.scss b/x-pack/plugins/canvas/public/style/index.scss index aac898c3dd374b3..0860bfd5afe6a09 100644 --- a/x-pack/plugins/canvas/public/style/index.scss +++ b/x-pack/plugins/canvas/public/style/index.scss @@ -19,7 +19,6 @@ @import '../components/datasource/datasource'; @import '../components/datasource/datasource_preview/datasource_preview'; @import '../components/datatable/datatable'; -@import '../components/debug/debug'; @import '../components/dom_preview/dom_preview'; @import '../components/element_card/element_card'; @import '../components/element_content/element_content'; diff --git a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js index 60987e987f63ac8..df894a65afab178 100644 --- a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js +++ b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js @@ -5,8 +5,6 @@ * 2.0. */ -import { debug } from '../canvas_plugin_src/renderers/debug'; -import { error } from '../canvas_plugin_src/renderers/error'; import { image } from '../canvas_plugin_src/renderers/image'; import { repeatImage } from '../canvas_plugin_src/renderers/repeat_image'; import { markdown } from '../canvas_plugin_src/renderers/markdown'; @@ -18,6 +16,10 @@ import { shape } from '../canvas_plugin_src/renderers/shape'; import { table } from '../canvas_plugin_src/renderers/table'; import { text } from '../canvas_plugin_src/renderers/text'; import { revealImageRenderer as revealImage } from '../../../../src/plugins/expression_reveal_image/public'; +import { + errorRenderer as error, + debugRenderer as debug, +} from '../../../../src/plugins/expression_error/public'; /** * This is a collection of renderers which are bundled with the runtime. If diff --git a/x-pack/plugins/canvas/storybook/storyshots.test.tsx b/x-pack/plugins/canvas/storybook/storyshots.test.tsx index 84ac1a26281e054..ddb52a22d2f17ad 100644 --- a/x-pack/plugins/canvas/storybook/storyshots.test.tsx +++ b/x-pack/plugins/canvas/storybook/storyshots.test.tsx @@ -6,13 +6,15 @@ */ import fs from 'fs'; -import { ReactChildren } from 'react'; +import { ReactChildren, createElement } from 'react'; import path from 'path'; import moment from 'moment'; import 'moment-timezone'; import ReactDOM from 'react-dom'; +import { shallow } from 'enzyme'; +import { create, act } from 'react-test-renderer'; -import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots'; +import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots'; // @ts-expect-error untyped library import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer'; import { addSerializer } from 'jest-specific-snapshot'; @@ -114,11 +116,24 @@ jest.mock('../public/lib/es_service', () => ({ addSerializer(styleSheetSerializer); +const converter = new Stories2SnapsConverter(); + // Initialize Storyshots and build the Jest Snapshots initStoryshots({ configPath: path.resolve(__dirname), framework: 'react', - test: multiSnapshotWithOptions(), + // test: multiSnapshotWithOptions({}), + asyncJest: true, + test: async ({ story, context, done }) => { + const renderer = create(createElement(story.render)); + // wait until the element will perform all renders and resolve all promises (lazy loading, especially) + await act(() => new Promise((resolve) => setTimeout(resolve, 0))); + // save each snapshot to a different file (similar to "multiSnapshotWithOptions") + const snapshotFileName = converter.getSnapshotFileName(context); + expect(renderer).toMatchSpecificSnapshot(snapshotFileName); + done?.(); + }, // Don't snapshot tests that start with 'redux' storyNameRegex: /^((?!.*?redux).)*$/, + renderer: shallow, }); diff --git a/x-pack/plugins/canvas/tsconfig.json b/x-pack/plugins/canvas/tsconfig.json index 84581d7be85a376..bf9544a173f16f5 100644 --- a/x-pack/plugins/canvas/tsconfig.json +++ b/x-pack/plugins/canvas/tsconfig.json @@ -31,6 +31,7 @@ { "path": "../../../src/plugins/discover/tsconfig.json" }, { "path": "../../../src/plugins/embeddable/tsconfig.json" }, { "path": "../../../src/plugins/expressions/tsconfig.json" }, + { "path": "../../../src/plugins/expression_error/tsconfig.json" }, { "path": "../../../src/plugins/expression_reveal_image/tsconfig.json" }, { "path": "../../../src/plugins/home/tsconfig.json" }, { "path": "../../../src/plugins/inspector/tsconfig.json" }, diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.test.ts similarity index 92% rename from x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.test.ts rename to x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.test.ts index 0a80f1c06998ffa..4d9ecb92217095b 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { checkNonPersistedSessions as checkNonPersistedSessions$ } from './check_non_persiseted_sessions'; +import { checkNonPersistedSessions as checkNonPersistedSessions$ } from './check_non_persisted_sessions'; import { SearchSessionStatus, SearchSessionSavedObjectAttributes, @@ -322,6 +322,46 @@ describe('checkNonPersistedSessions', () => { const { id } = mockClient.asyncSearch.delete.mock.calls[0][0]; expect(id).toBe('async-id'); }); + + test("doesn't attempt to delete errored out async search", async () => { + mockClient.asyncSearch.delete = jest.fn(); + + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [ + { + id: '123', + attributes: { + persisted: false, + status: SearchSessionStatus.ERROR, + expires: moment().add(moment.duration(3, 'm')), + created: moment().subtract(moment.duration(30, 'm')), + touched: moment().subtract(moment.duration(6, 'm')), + idMapping: { + 'map-key': { + strategy: ENHANCED_ES_SEARCH_STRATEGY, + id: 'async-id', + status: SearchStatus.ERROR, + }, + }, + }, + }, + ], + total: 1, + } as any); + + await checkNonPersistedSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).toBeCalled(); + expect(mockClient.asyncSearch.delete).not.toBeCalled(); + }); }); describe('update', () => { diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts similarity index 95% rename from x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts rename to x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts index 2115ce85eeb274e..180f276620a6cf2 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts @@ -18,7 +18,7 @@ import { KueryNode, } from '../../../../../../src/plugins/data/common'; import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page'; -import { SearchSessionsConfig, CheckSearchSessionsDeps } from './types'; +import { SearchSessionsConfig, CheckSearchSessionsDeps, SearchStatus } from './types'; import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status'; export const SEARCH_SESSIONS_CLEANUP_TASK_TYPE = 'search_sessions_cleanup'; @@ -87,6 +87,8 @@ function checkNonPersistedSessionsPage( // Send a delete request for each async search to ES Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { const searchInfo = session.attributes.idMapping[searchKey]; + if (searchInfo.status === SearchStatus.ERROR) return; // skip attempting to delete async search in case we know it has errored out + if (searchInfo.strategy === ENHANCED_ES_SEARCH_STRATEGY) { try { await client.asyncSearch.delete({ id: searchInfo.id }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 0998c1f42e1833d..af646ac3c5604af 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -57,7 +57,7 @@ import { SEARCH_SESSIONS_CLEANUP_TASK_TYPE, checkNonPersistedSessions, SEARCH_SESSIONS_CLEANUP_TASK_ID, -} from './check_non_persiseted_sessions'; +} from './check_non_persisted_sessions'; import { SEARCH_SESSIONS_EXPIRE_TASK_TYPE, SEARCH_SESSIONS_EXPIRE_TASK_ID, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx index 619d9d07bf5f355..765c8c5ea847aeb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx @@ -32,17 +32,23 @@ export const CredentialsList: React.FC = () => { const columns: Array> = [ { - name: 'Name', + name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.nameTitle', { + defaultMessage: 'Name', + }), width: '12%', render: (token: ApiToken) => token.name, }, { - name: 'Type', + name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.typeTitle', { + defaultMessage: 'Type', + }), width: '15%', render: (token: ApiToken) => TOKEN_TYPE_DISPLAY_NAMES[token.type], }, { - name: 'Key', + name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.keyTitle', { + defaultMessage: 'Key', + }), width: '36%', className: 'eui-textBreakAll', render: (token: ApiToken) => { @@ -71,12 +77,16 @@ export const CredentialsList: React.FC = () => { }, }, { - name: 'Modes', + name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.modesTitle', { + defaultMessage: 'Modes', + }), width: '10%', render: (token: ApiToken) => getModeDisplayText(token), }, { - name: 'Engines', + name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.enginesTitle', { + defaultMessage: 'Engines', + }), width: '18%', render: (token: ApiToken) => getEnginesDisplayText(token), }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx index 944c99f7c61c271..332f3a3889a3261 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx @@ -39,6 +39,7 @@ describe('SearchUIForm', () => { onSortFieldsChange: jest.fn(), onTitleFieldChange: jest.fn(), onUrlFieldChange: jest.fn(), + onThumbnailFieldChange: jest.fn(), }; beforeAll(() => { @@ -52,6 +53,7 @@ describe('SearchUIForm', () => { expect(wrapper.find('[data-test-subj="selectFilters"]').exists()).toBe(true); expect(wrapper.find('[data-test-subj="selectSort"]').exists()).toBe(true); expect(wrapper.find('[data-test-subj="selectUrl"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="selectThumbnail"]').exists()).toBe(true); }); describe('title field', () => { @@ -112,6 +114,35 @@ describe('SearchUIForm', () => { }); }); + describe('thumbnail field', () => { + beforeEach(() => jest.clearAllMocks()); + const subject = () => shallow().find('[data-test-subj="selectThumbnail"]'); + + it('renders with its value set from state', () => { + setMockValues({ + ...values, + thumbnailField: 'foo', + }); + + expect(subject().prop('value')).toBe('foo'); + }); + + it('updates state with new value when changed', () => { + subject().simulate('change', { target: { value: 'foo' } }); + expect(actions.onThumbnailFieldChange).toHaveBeenCalledWith('foo'); + }); + + it('updates active field in state on focus', () => { + subject().simulate('focus'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Thumb); + }); + + it('removes active field in state on blur', () => { + subject().simulate('blur'); + expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); + }); + }); + describe('filters field', () => { beforeEach(() => jest.clearAllMocks()); const subject = () => shallow().find('[data-test-subj="selectFilters"]'); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx index b795a46268237ec..145362812a7bfc3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx @@ -22,6 +22,8 @@ import { URL_FIELD_LABEL, URL_FIELD_HELP_TEXT, GENERATE_PREVIEW_BUTTON_LABEL, + THUMBNAIL_FIELD_LABEL, + THUMBNAIL_FIELD_HELP_TEXT, } from '../i18n'; import { SearchUILogic } from '../search_ui_logic'; import { ActiveField } from '../types'; @@ -36,6 +38,7 @@ export const SearchUIForm: React.FC = () => { validFacetFields, titleField, urlField, + thumbnailField, facetFields, sortFields, } = useValues(SearchUILogic); @@ -45,11 +48,13 @@ export const SearchUIForm: React.FC = () => { onSortFieldsChange, onTitleFieldChange, onUrlFieldChange, + onThumbnailFieldChange, } = useActions(SearchUILogic); const previewHref = generatePreviewUrl({ titleField, urlField, + thumbnailField, facets: facetFields, sortFields, }); @@ -69,6 +74,7 @@ export const SearchUIForm: React.FC = () => { const facetOptionFields = formatMultiOptions(validFacetFields); const selectedTitleOption = formatSelectOption(titleField); const selectedURLOption = formatSelectOption(urlField); + const selectedThumbnailOption = formatSelectOption(thumbnailField); const selectedSortOptions = formatMultiOptions(sortFields); const selectedFacetOptions = formatMultiOptions(facetFields); @@ -112,7 +118,6 @@ export const SearchUIForm: React.FC = () => { data-test-subj="selectSort" /> - { data-test-subj="selectUrl" /> + + onThumbnailFieldChange(e.target.value)} + fullWidth + onFocus={() => onActiveFieldChange(ActiveField.Thumb)} + onBlur={() => onActiveFieldChange(ActiveField.None)} + hasNoInitialSelection + data-test-subj="selectThumbnail" + /> + { validFacetFields: [], titleField: '', urlField: '', + thumbnailField: '', facetFields: [], sortFields: [], activeField: ActiveField.None, @@ -93,6 +94,17 @@ describe('SearchUILogic', () => { }); }); + describe('onThumbnailFieldChange', () => { + it('sets the thumbnailField value', () => { + mount({ thumbnailField: '' }); + SearchUILogic.actions.onThumbnailFieldChange('foo'); + expect(SearchUILogic.values).toEqual({ + ...DEFAULT_VALUES, + thumbnailField: 'foo', + }); + }); + }); + describe('onFacetFieldsChange', () => { it('sets the facetFields value', () => { mount({ facetFields: [] }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts index 096365f57ea36de..e6225614fdc1860 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts @@ -20,6 +20,7 @@ interface InitialFieldValues { validSortFields: string[]; validFacetFields: string[]; urlField?: string; + thumbnailField?: string; titleField?: string; } interface SearchUIActions { @@ -30,6 +31,7 @@ interface SearchUIActions { onSortFieldsChange(sortFields: string[]): { sortFields: string[] }; onTitleFieldChange(titleField: string): { titleField: string }; onUrlFieldChange(urlField: string): { urlField: string }; + onThumbnailFieldChange(thumbnailField: string): { thumbnailField: string }; } interface SearchUIValues { @@ -39,6 +41,7 @@ interface SearchUIValues { validFacetFields: string[]; titleField: string; urlField: string; + thumbnailField: string; facetFields: string[]; sortFields: string[]; activeField: ActiveField; @@ -54,6 +57,7 @@ export const SearchUILogic = kea> onSortFieldsChange: (sortFields) => ({ sortFields }), onTitleFieldChange: (titleField) => ({ titleField }), onUrlFieldChange: (urlField) => ({ urlField }), + onThumbnailFieldChange: (thumbnailField) => ({ thumbnailField }), }), reducers: () => ({ dataLoading: [ @@ -79,6 +83,12 @@ export const SearchUILogic = kea> onFieldDataLoaded: (_, { urlField }) => urlField || '', }, ], + thumbnailField: [ + '', + { + onThumbnailFieldChange: (_, { thumbnailField }) => thumbnailField, + }, + ], facetFields: [[], { onFacetFieldsChange: (_, { facetFields }) => facetFields }], sortFields: [[], { onSortFieldsChange: (_, { sortFields }) => sortFields }], activeField: [ActiveField.None, { onActiveFieldChange: (_, { activeField }) => activeField }], diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts index 224aeab05af35f1..e5b0f2ce2ccd79a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts @@ -10,5 +10,6 @@ export enum ActiveField { Filter = 'Filter', Sort = 'Sort', Url = 'Url', + Thumb = 'Thumb', None = '', } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/account_header/account_header.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/account_header/account_header.tsx index 1e6e8ec6c5e498d..4c22a234e731c3c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/account_header/account_header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/account_header/account_header.tsx @@ -68,11 +68,11 @@ export const AccountHeader: React.FC = () => { return ( - + {WORKPLACE_SEARCH_TITLE} - + {ACCOUNT_NAV.SOURCES} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts index 09ba41f81d76ac8..950412e84f87053 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -289,6 +289,7 @@ describe('AddSourceLogic', () => { describe('saveSourceParams', () => { const params = { code: 'code123', + session_state: 'session_state123', state: '{"action":"create","context":"organization","service_type":"gmail","csrf_token":"token==","index_permissions":false}', }; @@ -306,7 +307,7 @@ describe('AddSourceLogic', () => { const setAddedSourceSpy = jest.spyOn(SourcesLogic.actions, 'setAddedSource'); const { serviceName, indexPermissions, serviceType } = response; http.get.mockReturnValue(Promise.resolve(response)); - AddSourceLogic.actions.saveSourceParams(queryString); + AddSourceLogic.actions.saveSourceParams(queryString, params, true); expect(http.get).toHaveBeenCalledWith('/api/workplace_search/sources/create', { query: { ...params, @@ -324,7 +325,7 @@ describe('AddSourceLogic', () => { const accountQueryString = '?state=%7B%22action%22:%22create%22,%22context%22:%22account%22,%22service_type%22:%22gmail%22,%22csrf_token%22:%22token%3D%3D%22,%22index_permissions%22:false%7D&code=code'; - AddSourceLogic.actions.saveSourceParams(accountQueryString); + AddSourceLogic.actions.saveSourceParams(accountQueryString, params, false); await nextTick(); @@ -345,7 +346,7 @@ describe('AddSourceLogic', () => { preContentSourceId, }) ); - AddSourceLogic.actions.saveSourceParams(queryString); + AddSourceLogic.actions.saveSourceParams(queryString, params, true); expect(http.get).toHaveBeenCalledWith('/api/workplace_search/sources/create', { query: { ...params, @@ -360,28 +361,27 @@ describe('AddSourceLogic', () => { }); describe('Github error edge case', () => { + const GITHUB_ERROR = + 'The redirect_uri MUST match the registered callback URL for this application.'; + const errorParams = { ...params, error_description: GITHUB_ERROR }; const getGithubQueryString = (context: 'organization' | 'account') => `?error=redirect_uri_mismatch&error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&error_uri=https%3A%2F%2Fdocs.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-authorization-request-errors%2F%23redirect-uri-mismatch&state=%7B%22action%22%3A%22create%22%2C%22context%22%3A%22${context}%22%2C%22service_type%22%3A%22github%22%2C%22csrf_token%22%3A%22TOKEN%3D%3D%22%2C%22index_permissions%22%3Afalse%7D`; it('handles "organization" redirect and displays error', () => { const githubQueryString = getGithubQueryString('organization'); - AddSourceLogic.actions.saveSourceParams(githubQueryString); + AddSourceLogic.actions.saveSourceParams(githubQueryString, errorParams, true); expect(navigateToUrl).toHaveBeenCalledWith('/'); - expect(setErrorMessage).toHaveBeenCalledWith( - 'The redirect_uri MUST match the registered callback URL for this application.' - ); + expect(setErrorMessage).toHaveBeenCalledWith(GITHUB_ERROR); }); it('handles "account" redirect and displays error', () => { const githubQueryString = getGithubQueryString('account'); - AddSourceLogic.actions.saveSourceParams(githubQueryString); + AddSourceLogic.actions.saveSourceParams(githubQueryString, errorParams, false); expect(navigateToUrl).toHaveBeenCalledWith(PERSONAL_SOURCES_PATH); expect(setErrorMessage).toHaveBeenCalledWith( - PERSONAL_DASHBOARD_SOURCE_ERROR( - 'The redirect_uri MUST match the registered callback URL for this application.' - ) + PERSONAL_DASHBOARD_SOURCE_ERROR(GITHUB_ERROR) ); }); }); @@ -389,7 +389,7 @@ describe('AddSourceLogic', () => { it('handles error', async () => { http.get.mockReturnValue(Promise.reject('this is an error')); - AddSourceLogic.actions.saveSourceParams(queryString); + AddSourceLogic.actions.saveSourceParams(queryString, params, true); await nextTick(); expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index 81e27f07293dc25..a75e494aa2b1c3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -20,7 +20,6 @@ import { } from '../../../../../shared/flash_messages'; import { HttpLogic } from '../../../../../shared/http'; import { KibanaLogic } from '../../../../../shared/kibana'; -import { parseQueryParams } from '../../../../../shared/query_params'; import { AppLogic } from '../../../../app_logic'; import { CUSTOM_SERVICE_TYPE, WORKPLACE_SEARCH_URL_PREFIX } from '../../../../constants'; import { @@ -95,7 +94,11 @@ export interface AddSourceActions { isUpdating: boolean, successCallback?: () => void ): { isUpdating: boolean; successCallback?(): void }; - saveSourceParams(search: Search): { search: Search }; + saveSourceParams( + search: Search, + params: OauthParams, + isOrganization: boolean + ): { search: Search; params: OauthParams; isOrganization: boolean }; getSourceConfigData(serviceType: string): { serviceType: string }; getSourceConnectData( serviceType: string, @@ -206,7 +209,11 @@ export const AddSourceLogic = kea ({ search }), + saveSourceParams: (search: Search, params: OauthParams, isOrganization: boolean) => ({ + search, + params, + isOrganization, + }), createContentSource: ( serviceType: string, successCallback: () => void, @@ -500,15 +507,12 @@ export const AddSourceLogic = kea { + saveSourceParams: async ({ search, params, isOrganization }) => { const { http } = HttpLogic.values; const { navigateToUrl } = KibanaLogic.values; const { setAddedSource } = SourcesLogic.actions; - const params = (parseQueryParams(search) as unknown) as OauthParams; const query = { ...params, kibana_host: kibanaHost }; const route = '/api/workplace_search/sources/create'; - const state = JSON.parse(params.state); - const isOrganization = state.context !== 'account'; /** There is an extreme edge case where the user is trying to connect Github as source from ent-search, @@ -539,7 +543,7 @@ export const AddSourceLogic = kea { }); it('renders', () => { - const search = '?name=foo&serviceType=custom&indexPermissions=false'; + const search = + '?code=1234&state=%7B%22action%22%3A%22create%22%2C%22context%22%3A%22account%22%2C%22service_type%22%3A%22github%22%2C%22csrf_token%22%3A%22TOKEN123%3D%3D%22%2C%22index_permissions%22%3Afalse%7D'; (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx index 5b93b7a426936e3..77b393105048349 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx @@ -15,8 +15,9 @@ import { EuiPage, EuiPageBody } from '@elastic/eui'; import { KibanaLogic } from '../../../../shared/kibana'; import { Loading } from '../../../../shared/loading'; +import { parseQueryParams } from '../../../../shared/query_params'; -import { AddSourceLogic } from './add_source/add_source_logic'; +import { AddSourceLogic, OauthParams } from './add_source/add_source_logic'; /** * This component merely triggers catchs the redirect from the oauth application and initializes the saving @@ -25,14 +26,17 @@ import { AddSourceLogic } from './add_source/add_source_logic'; */ export const SourceAdded: React.FC = () => { const { search } = useLocation() as Location; + const params = (parseQueryParams(search) as unknown) as OauthParams; + const state = JSON.parse(params.state); + const isOrganization = state.context !== 'account'; const { setChromeIsVisible } = useValues(KibanaLogic); const { saveSourceParams } = useActions(AddSourceLogic); // We don't want the personal dashboard to flash the Kibana chrome, so we hide it. - setChromeIsVisible(false); + setChromeIsVisible(isOrganization); useEffect(() => { - saveSourceParams(search); + saveSourceParams(search, params, isOrganization); }, []); return ( diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index 5de4387f2c0d9c0..835ad84ef6853c0 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -135,7 +135,7 @@ export function registerAccountCreateSourceRoute({ login: schema.maybe(schema.string()), password: schema.maybe(schema.string()), organizations: schema.maybe(schema.arrayOf(schema.string())), - indexPermissions: schema.boolean(), + indexPermissions: schema.maybe(schema.boolean()), }), }, }, diff --git a/x-pack/plugins/fleet/README.md b/x-pack/plugins/fleet/README.md index 1b18b7264753785..ce8e95c83d6bdc0 100644 --- a/x-pack/plugins/fleet/README.md +++ b/x-pack/plugins/fleet/README.md @@ -48,6 +48,48 @@ This plugin follows the `common`, `server`, `public` structure from the [Archite Note: The plugin was previously named Ingest Manager it's possible that some variables are still named with that old plugin name. +### Running Fleet Server Locally in a Container + +It can be useful to run Fleet Server in a container on your local machine in order to free up your actual "bare metal" machine to run Elastic Agent for testing purposes. Otherwise, you'll only be able to a single instance of Elastic Agent dedicated to Fleet Server on your local machine, and this can make testing integrations and policies difficult. + +_The following is adapted from the Fleet Server [README](https://github.com/elastic/fleet-server#running-elastic-agent-with-fleet-server-in-container)_ + +1. Add the following configuration to your `kibana.dev.yml` + +```yml +server.host: 0.0.0.0 +``` + +2. Append the following option to the command you use to start Elasticsearch + +``` +-E http.host=0.0.0.0 +``` + +This command should look something like this: + +``` +yarn es snapshot --license trial -E xpack.security.authc.api_key.enabled=true -E path.data=/tmp/es-data -E http.host=0.0.0.0 +``` + +3. Run the Fleet Server Docker container. Make sure you include a `BASE-PATH` value if your local Kibana instance is using one. `YOUR-IP` should correspond to the IP address used by your Docker network to represent the host. For Windows and Mac machines, this should be `192.168.65.2`. If you're not sure what this IP should be, run the following to look it up: + +``` +docker run -it --rm alpine nslookup host.docker.internal +``` + +To run the Fleet Server Docker container: + +``` +docker run -e KIBANA_HOST=http://{YOUR-IP}:5601/{BASE-PATH} -e KIBANA_USERNAME=elastic -e KIBANA_PASSWORD=changeme -e ELASTICSEARCH_HOST=http://{YOUR-IP}:9200 -e ELASTICSEARCH_USERNAME=elastic -e ELASTICSEARCH_PASSWORD=changeme -e KIBANA_FLEET_SETUP=1 -e FLEET_SERVER_ENABLE=1 -e FLEET_SERVER_INSECURE_HTTP=1 -p 8220:8220 docker.elastic.co/beats/elastic-agent:{VERSION} +``` + +Ensure you provide the `-p 8220:8220` port mapping to map the Fleet Server container's port `8220` to your local machine's port `8220` in order for Fleet to communicate with Fleet Server. + +For the latest version, use `8.0.0-SNAPSHOT`. Otherwise, you can explore the available versions at https://www.docker.elastic.co/r/beats/elastic-agent. + +Once the Fleet Server container is running, you should be able to treat it as if it were a local process running on `http://localhost:8220` when configuring Fleet via the UI. You can then run `elastic-agent` on your local machine directly for testing purposes. + ### Tests #### API integration tests @@ -77,3 +119,4 @@ You need to have `docker` to run ingest manager api integration tests ``` FLEET_PACKAGE_REGISTRY_DOCKER_IMAGE='docker.elastic.co/package-registry/distribution:production' FLEET_PACKAGE_REGISTRY_PORT=12345 yarn test:ftr:runner ``` + diff --git a/x-pack/plugins/fleet/common/openapi/README.md b/x-pack/plugins/fleet/common/openapi/README.md index 4afbef549f248b2..7ccccf052f37de6 100644 --- a/x-pack/plugins/fleet/common/openapi/README.md +++ b/x-pack/plugins/fleet/common/openapi/README.md @@ -16,8 +16,8 @@ For example, online viewers for the specification like these: * It's currently generated with: ``` - npx swagger-cli bundle -o bundled.json -t json entrypoint.yaml - npx swagger-cli bundle -o bundled.yaml -t yaml entrypoint.yaml + npx @redocly/openapi-cli bundle --ext yaml --output bundled.yaml entrypoint.yaml + npx @redocly/openapi-cli bundle --ext json --output bundled.json entrypoint.yaml ``` * [Paths](paths/README.md): this defines each endpoint. A path can have one operation per http method. * [Components](components/README.md): Reusable components like [`schemas`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject), diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index b121095c8b91b36..287ec8c24bafa5c 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -1,13 +1,16 @@ { "openapi": "3.0.0", + "tags": [], "info": { - "title": "Ingest Manager", + "title": "Fleet", + "description": "OpenAPI schema for Fleet API endpoints", "version": "0.2", "contact": { - "name": "Ingest Team" + "name": "Fleet Team" }, "license": { - "name": "Elastic" + "name": "Elastic License 2.0", + "url": "https://www.elastic.co/licensing/elastic-license" } }, "servers": [ @@ -32,7 +35,7 @@ "items": { "type": "array", "items": { - "$ref": "#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item" + "$ref": "#/components/schemas/agent_policy" } }, "total": { @@ -59,31 +62,13 @@ "operationId": "agent-policy-list", "parameters": [ { - "name": "perPage", - "in": "query", - "description": "The number of items to return", - "required": false, - "schema": { - "type": "integer", - "default": 50 - } + "$ref": "#/components/parameters/page_size" }, { - "name": "page", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "default": 1 - } + "$ref": "#/components/parameters/page_index" }, { - "name": "kuery", - "in": "query", - "required": false, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/kuery" } ], "description": "" @@ -100,58 +85,7 @@ "type": "object", "properties": { "item": { - "allOf": [ - { - "$ref": "#/paths/~1agent_policies/post/requestBody/content/application~1json/schema" - }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "packagePolicies": { - "oneOf": [ - { - "items": { - "type": "string" - } - }, - { - "items": { - "$ref": "#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item" - } - } - ], - "type": "array" - }, - "updated_on": { - "type": "string", - "format": "date-time" - }, - "updated_by": { - "type": "string" - }, - "revision": { - "type": "number" - }, - "agents": { - "type": "number" - } - }, - "required": [ - "id", - "status" - ] - } - ] + "$ref": "#/components/schemas/agent_policy" } } } @@ -164,19 +98,7 @@ "content": { "application/json": { "schema": { - "title": "NewAgentPolicy", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "namespace": { - "type": "string" - }, - "description": { - "type": "string" - } - } + "$ref": "#/components/schemas/new_agent_policy" } } } @@ -184,7 +106,7 @@ "security": [], "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] } @@ -212,7 +134,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item" + "$ref": "#/components/schemas/agent_policy" } }, "required": [ @@ -239,7 +161,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item" + "$ref": "#/components/schemas/agent_policy" } }, "required": [ @@ -255,14 +177,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/paths/~1agent_policies/post/requestBody/content/application~1json/schema" + "$ref": "#/components/schemas/new_agent_policy" } } } }, "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] } @@ -290,7 +212,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item" + "$ref": "#/components/schemas/agent_policy" } }, "required": [ @@ -375,7 +297,7 @@ }, "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] }, @@ -383,9 +305,56 @@ }, "/agent-status": { "get": { - "summary": "Fleet - Agent - Status for policy", + "summary": "Fleet - Agent - Summary stats", "tags": [], - "responses": {}, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "integer" + }, + "events": { + "type": "integer" + }, + "inactive": { + "type": "integer" + }, + "offline": { + "type": "integer" + }, + "online": { + "type": "integer" + }, + "other": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "updating": { + "type": "integer" + } + }, + "required": [ + "error", + "events", + "inactive", + "offline", + "online", + "other", + "total", + "updating" + ] + } + } + } + } + }, "operationId": "get-fleet-agent-status", "parameters": [ { @@ -414,7 +383,7 @@ "list": { "type": "array", "items": { - "type": "object" + "$ref": "#/components/schemas/agent_policy" } }, "total": { @@ -460,11 +429,46 @@ "post": { "summary": "Fleet - Agent - Unenroll", "tags": [], - "responses": {}, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "400": { + "description": "BAD REQUEST", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "statusCode": { + "type": "number", + "enum": [ + 400 + ] + } + } + } + } + } + } + }, "operationId": "post-fleet-agents-unenroll", "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ], "requestBody": { @@ -506,7 +510,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema" + "$ref": "#/components/schemas/upgrade_agent" } } } @@ -516,7 +520,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema" + "$ref": "#/components/schemas/upgrade_agent" } } } @@ -525,7 +529,7 @@ "operationId": "post-fleet-agents-upgrade", "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ], "requestBody": { @@ -533,34 +537,7 @@ "content": { "application/json": { "schema": { - "title": "UpgradeAgent", - "oneOf": [ - { - "type": "object", - "properties": { - "version": { - "type": "string" - } - }, - "required": [ - "version" - ] - }, - { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "source_uri": { - "type": "string" - } - }, - "required": [ - "version" - ] - } - ] + "$ref": "#/components/schemas/upgrade_agent" } } } @@ -577,7 +554,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/paths/~1agents~1bulk_upgrade/post/requestBody/content/application~1json/schema" + "$ref": "#/components/schemas/bulk_upgrade_agents" } } } @@ -587,7 +564,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema" + "$ref": "#/components/schemas/upgrade_agent" } } } @@ -596,7 +573,7 @@ "operationId": "post-fleet-agents-bulk-upgrade", "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ], "requestBody": { @@ -604,66 +581,7 @@ "content": { "application/json": { "schema": { - "title": "BulkUpgradeAgents", - "oneOf": [ - { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "agents": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "version", - "agents" - ] - }, - { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "source_uri": { - "type": "string" - }, - "agents": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "version", - "agents" - ] - }, - { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "source_uri": { - "type": "string" - }, - "agents": { - "type": "string" - } - }, - "required": [ - "version", - "agents" - ] - } - ] + "$ref": "#/components/schemas/bulk_upgrade_agents" } } } @@ -747,7 +665,7 @@ }, "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] } @@ -756,18 +674,74 @@ "get": { "summary": "Enrollment - List", "tags": [], - "responses": {}, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/components/schemas/enrollment_api_key" + } + }, + "page": { + "type": "number" + }, + "perPage": { + "type": "number" + }, + "total": { + "type": "number" + } + }, + "required": [ + "list", + "page", + "perPage", + "total" + ] + } + } + } + } + }, "operationId": "get-fleet-enrollment-api-keys", "parameters": [] }, "post": { "summary": "Enrollment - Create", "tags": [], - "responses": {}, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "item": { + "$ref": "#/components/schemas/enrollment_api_key" + }, + "action": { + "type": "string", + "enum": [ + "created" + ] + } + } + } + } + } + } + }, "operationId": "post-fleet-enrollment-api-keys", "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] } @@ -786,17 +760,58 @@ "get": { "summary": "Enrollment - Info", "tags": [], - "responses": {}, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "item": { + "$ref": "#/components/schemas/enrollment_api_key" + } + }, + "required": [ + "item" + ] + } + } + } + } + }, "operationId": "get-fleet-enrollment-api-keys-keyId" }, "delete": { "summary": "Enrollment - Delete", "tags": [], - "responses": {}, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "deleted" + ] + } + }, + "required": [ + "action" + ] + } + } + } + } + }, "operationId": "delete-fleet-enrollment-api-keys-keyId", "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] } @@ -851,51 +866,7 @@ "schema": { "type": "array", "items": { - "title": "SearchResult", - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "download": { - "type": "string" - }, - "icons": { - "type": "string" - }, - "name": { - "type": "string" - }, - "path": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - }, - "version": { - "type": "string" - }, - "status": { - "type": "string" - }, - "savedObject": { - "type": "object" - } - }, - "required": [ - "description", - "download", - "icons", - "name", - "path", - "title", - "type", - "version", - "status" - ] + "$ref": "#/components/schemas/search_result" } } } @@ -921,182 +892,7 @@ { "properties": { "response": { - "title": "PackageInfo", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "version": { - "type": "string" - }, - "readme": { - "type": "string" - }, - "description": { - "type": "string" - }, - "type": { - "type": "string" - }, - "categories": { - "type": "array", - "items": { - "type": "string" - } - }, - "requirement": { - "oneOf": [ - { - "properties": { - "kibana": { - "type": "object", - "properties": { - "versions": { - "type": "string" - } - } - } - } - }, - { - "properties": { - "elasticsearch": { - "type": "object", - "properties": { - "versions": { - "type": "string" - } - } - } - } - } - ], - "type": "object" - }, - "screenshots": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "path": { - "type": "string" - }, - "title": { - "type": "string" - }, - "size": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src", - "path" - ] - } - }, - "icons": { - "type": "array", - "items": { - "type": "string" - } - }, - "assets": { - "type": "array", - "items": { - "type": "string" - } - }, - "internal": { - "type": "boolean" - }, - "format_version": { - "type": "string" - }, - "data_streams": { - "type": "array", - "items": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "name": { - "type": "string" - }, - "release": { - "type": "string" - }, - "ingeset_pipeline": { - "type": "string" - }, - "vars": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "default": { - "type": "string" - } - }, - "required": [ - "name", - "default" - ] - } - }, - "type": { - "type": "string" - }, - "package": { - "type": "string" - } - }, - "required": [ - "title", - "name", - "release", - "ingeset_pipeline", - "type", - "package" - ] - } - }, - "download": { - "type": "string" - }, - "path": { - "type": "string" - }, - "removable": { - "type": "boolean" - } - }, - "required": [ - "name", - "title", - "version", - "description", - "type", - "categories", - "requirement", - "assets", - "format_version", - "download", - "path" - ] + "$ref": "#/components/schemas/package_info" } } }, @@ -1183,7 +979,7 @@ "description": "", "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] }, @@ -1228,7 +1024,7 @@ "operationId": "post-epm-delete-pkgkey", "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ], "requestBody": { @@ -1270,7 +1066,7 @@ "type": "object", "properties": { "item": { - "type": "object" + "$ref": "#/components/schemas/agent" } }, "required": [ @@ -1286,44 +1082,67 @@ "put": { "summary": "Fleet - Agent - Update", "tags": [], - "responses": {}, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "item": { + "$ref": "#/components/schemas/agent" + } + }, + "required": [ + "item" + ] + } + } + } + } + }, "operationId": "put-fleet-agents-agentId", "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] }, "delete": { "summary": "Fleet - Agent - Delete", "tags": [], - "responses": {}, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "deleted" + ] + } + }, + "required": [ + "action" + ] + } + } + } + } + }, "operationId": "delete-fleet-agents-agentId", "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] } }, - "/install/{osType}": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "osType", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Fleet - Get OS install script", - "tags": [], - "responses": {}, - "operationId": "get-fleet-install-osType" - } - }, "/package_policies": { "get": { "summary": "PackagePolicies - List", @@ -1339,7 +1158,7 @@ "items": { "type": "array", "items": { - "$ref": "#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item" + "$ref": "#/components/schemas/package_policy" } }, "total": { @@ -1377,96 +1196,14 @@ "content": { "application/json": { "schema": { - "title": "NewPackagePolicy", - "type": "object", - "description": "", - "properties": { - "enabled": { - "type": "boolean" - }, - "package": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "version": { - "type": "string" - }, - "title": { - "type": "string" - } - }, - "required": [ - "name", - "version", - "title" - ] - }, - "namespace": { - "type": "string" - }, - "output_id": { - "type": "string" - }, - "inputs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "processors": { - "type": "array", - "items": { - "type": "string" - } - }, - "streams": { - "type": "array", - "items": {} - }, - "config": { - "type": "object" - }, - "vars": { - "type": "object" - } - }, - "required": [ - "type", - "enabled", - "streams" - ] - } - }, - "policy_id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": [ - "output_id", - "inputs", - "policy_id", - "name" - ] + "$ref": "#/components/schemas/new_package_policy" } } } }, "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] } @@ -1484,31 +1221,7 @@ "type": "object", "properties": { "item": { - "title": "PackagePolicy", - "allOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "revision": { - "type": "number" - }, - "inputs": { - "type": "array", - "items": {} - } - }, - "required": [ - "id", - "revision" - ] - }, - { - "$ref": "#/paths/~1package_policies/post/requestBody/content/application~1json/schema" - } - ] + "$ref": "#/components/schemas/package_policy" } }, "required": [ @@ -1538,20 +1251,7 @@ "content": { "application/json": { "schema": { - "title": "UpdatePackagePolicy", - "allOf": [ - { - "type": "object", - "properties": { - "version": { - "type": "string" - } - } - }, - { - "$ref": "#/paths/~1package_policies/post/requestBody/content/application~1json/schema" - } - ] + "$ref": "#/components/schemas/update_package_policy" } } } @@ -1565,7 +1265,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item" + "$ref": "#/components/schemas/package_policy" }, "sucess": { "type": "boolean" @@ -1582,14 +1282,14 @@ }, "parameters": [ { - "$ref": "#/paths/~1setup/post/parameters/0" + "$ref": "#/components/parameters/kbn_xsrf" } ] } }, "/setup": { "post": { - "summary": "Ingest Manager - Setup", + "summary": "Fleet - Setup", "tags": [], "responses": { "200": { @@ -1626,12 +1326,7 @@ "operationId": "post-setup", "parameters": [ { - "schema": { - "type": "string" - }, - "in": "header", - "name": "kbn-xsrf", - "required": true + "$ref": "#/components/parameters/kbn_xsrf" } ] } @@ -1643,18 +1338,694 @@ "type": "http", "scheme": "basic" }, - "Enrollment API Key": { + "Enrollment_API_Key": { "name": "Authorization", "type": "apiKey", "in": "header", "description": "e.g. Authorization: ApiKey base64EnrollmentApiKey" }, - "Access API Key": { + "Access_API_Key": { "name": "Authorization", "type": "apiKey", "in": "header", "description": "e.g. Authorization: ApiKey base64AccessApiKey" } + }, + "parameters": { + "page_size": { + "name": "perPage", + "in": "query", + "description": "The number of items to return", + "required": false, + "schema": { + "type": "integer", + "default": 50 + } + }, + "page_index": { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 1 + } + }, + "kuery": { + "name": "kuery", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + "kbn_xsrf": { + "schema": { + "type": "string" + }, + "in": "header", + "name": "kbn-xsrf", + "required": true + } + }, + "schemas": { + "new_agent_policy": { + "title": "NewAgentPolicy", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "new_package_policy": { + "title": "NewPackagePolicy", + "type": "object", + "description": "", + "properties": { + "enabled": { + "type": "boolean" + }, + "package": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "name", + "version", + "title" + ] + }, + "namespace": { + "type": "string" + }, + "output_id": { + "type": "string" + }, + "inputs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "processors": { + "type": "array", + "items": { + "type": "string" + } + }, + "streams": { + "type": "array", + "items": {} + }, + "config": { + "type": "object" + }, + "vars": { + "type": "object" + } + }, + "required": [ + "type", + "enabled", + "streams" + ] + } + }, + "policy_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "output_id", + "inputs", + "policy_id", + "name" + ] + }, + "package_policy": { + "title": "PackagePolicy", + "allOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "revision": { + "type": "number" + }, + "inputs": { + "type": "array", + "items": {} + } + }, + "required": [ + "id", + "revision" + ] + }, + { + "$ref": "#/components/schemas/new_package_policy" + } + ] + }, + "agent_policy": { + "allOf": [ + { + "$ref": "#/components/schemas/new_agent_policy" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "packagePolicies": { + "oneOf": [ + { + "items": { + "type": "string" + } + }, + { + "items": { + "$ref": "#/components/schemas/package_policy" + } + } + ], + "type": "array" + }, + "updated_on": { + "type": "string", + "format": "date-time" + }, + "updated_by": { + "type": "string" + }, + "revision": { + "type": "number" + }, + "agents": { + "type": "number" + } + }, + "required": [ + "id", + "status" + ] + } + ] + }, + "upgrade_agent": { + "title": "UpgradeAgent", + "oneOf": [ + { + "type": "object", + "properties": { + "version": { + "type": "string" + } + }, + "required": [ + "version" + ] + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "source_uri": { + "type": "string" + } + }, + "required": [ + "version" + ] + } + ] + }, + "bulk_upgrade_agents": { + "title": "BulkUpgradeAgents", + "oneOf": [ + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "agents": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "version", + "agents" + ] + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "source_uri": { + "type": "string" + }, + "agents": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "version", + "agents" + ] + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "source_uri": { + "type": "string" + }, + "agents": { + "type": "string" + } + }, + "required": [ + "version", + "agents" + ] + } + ] + }, + "enrollment_api_key": { + "title": "EnrollmentApiKey", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "api_key_id": { + "type": "string" + }, + "api_key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "policy_id": { + "type": "string" + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "id", + "api_key_id", + "api_key", + "active", + "created_at" + ] + }, + "search_result": { + "title": "SearchResult", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "download": { + "type": "string" + }, + "icons": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "version": { + "type": "string" + }, + "status": { + "type": "string" + }, + "savedObject": { + "type": "object" + } + }, + "required": [ + "description", + "download", + "icons", + "name", + "path", + "title", + "type", + "version", + "status" + ] + }, + "package_info": { + "title": "PackageInfo", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": "string" + }, + "version": { + "type": "string" + }, + "readme": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "requirement": { + "oneOf": [ + { + "properties": { + "kibana": { + "type": "object", + "properties": { + "versions": { + "type": "string" + } + } + } + } + }, + { + "properties": { + "elasticsearch": { + "type": "object", + "properties": { + "versions": { + "type": "string" + } + } + } + } + } + ], + "type": "object" + }, + "screenshots": { + "type": "array", + "items": { + "type": "object", + "properties": { + "src": { + "type": "string" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + }, + "size": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "src", + "path" + ] + } + }, + "icons": { + "type": "array", + "items": { + "type": "string" + } + }, + "assets": { + "type": "array", + "items": { + "type": "string" + } + }, + "internal": { + "type": "boolean" + }, + "format_version": { + "type": "string" + }, + "data_streams": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "name": { + "type": "string" + }, + "release": { + "type": "string" + }, + "ingeset_pipeline": { + "type": "string" + }, + "vars": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "default": { + "type": "string" + } + }, + "required": [ + "name", + "default" + ] + } + }, + "type": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "title", + "name", + "release", + "ingeset_pipeline", + "type", + "package" + ] + } + }, + "download": { + "type": "string" + }, + "path": { + "type": "string" + }, + "removable": { + "type": "boolean" + } + }, + "required": [ + "name", + "title", + "version", + "description", + "type", + "categories", + "requirement", + "assets", + "format_version", + "download", + "path" + ] + }, + "agent_type": { + "type": "string", + "title": "AgentType", + "enum": [ + "PERMANENT", + "EPHEMERAL", + "TEMPORARY" + ] + }, + "agent_metadata": { + "title": "AgentMetadata", + "type": "object" + }, + "agent_status": { + "type": "string", + "title": "AgentStatus", + "enum": [ + "offline", + "error", + "online", + "inactive", + "warning" + ] + }, + "agent": { + "title": "Agent", + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/agent_type" + }, + "active": { + "type": "boolean" + }, + "enrolled_at": { + "type": "string" + }, + "unenrolled_at": { + "type": "string" + }, + "unenrollment_started_at": { + "type": "string" + }, + "shared_id": { + "type": "string", + "deprecated": true + }, + "access_api_key_id": { + "type": "string" + }, + "default_api_key_id": { + "type": "string" + }, + "policy_id": { + "type": "string" + }, + "policy_revision": { + "type": "number" + }, + "last_checkin": { + "type": "string" + }, + "user_provided_metadata": { + "$ref": "#/components/schemas/agent_metadata" + }, + "local_metadata": { + "$ref": "#/components/schemas/agent_metadata" + }, + "id": { + "type": "string" + }, + "access_api_key": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/agent_status" + }, + "default_api_key": { + "type": "string" + } + }, + "required": [ + "type", + "active", + "enrolled_at", + "id", + "status" + ] + }, + "update_package_policy": { + "title": "UpdatePackagePolicy", + "allOf": [ + { + "type": "object", + "properties": { + "version": { + "type": "string" + } + } + }, + { + "$ref": "#/components/schemas/new_package_policy" + } + ] + } } }, "security": [ @@ -1662,4 +2033,4 @@ "basicAuth": [] } ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 537ef136c76110e..b91540f4cdeea8d 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1,11 +1,14 @@ openapi: 3.0.0 +tags: [] info: - title: Ingest Manager + title: Fleet + description: OpenAPI schema for Fleet API endpoints version: '0.2' contact: - name: Ingest Team + name: Fleet Team license: - name: Elastic + name: Elastic License 2.0 + url: 'https://www.elastic.co/licensing/elastic-license' servers: - url: 'http://localhost:5601/api/fleet' description: local @@ -25,7 +28,7 @@ paths: items: type: array items: - $ref: '#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item' + $ref: '#/components/schemas/agent_policy' total: type: number page: @@ -39,24 +42,9 @@ paths: - perPage operationId: agent-policy-list parameters: - - name: perPage - in: query - description: The number of items to return - required: false - schema: - type: integer - default: 50 - - name: page - in: query - required: false - schema: - type: integer - default: 1 - - name: kuery - in: query - required: false - schema: - type: string + - $ref: '#/components/parameters/page_size' + - $ref: '#/components/parameters/page_index' + - $ref: '#/components/parameters/kuery' description: '' post: summary: Agent policy - Create @@ -70,53 +58,16 @@ paths: type: object properties: item: - allOf: - - $ref: '#/paths/~1agent_policies/post/requestBody/content/application~1json/schema' - - type: object - properties: - id: - type: string - status: - type: string - enum: - - active - - inactive - packagePolicies: - oneOf: - - items: - type: string - - items: - $ref: '#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item' - type: array - updated_on: - type: string - format: date-time - updated_by: - type: string - revision: - type: number - agents: - type: number - required: - - id - - status + $ref: '#/components/schemas/agent_policy' operationId: post-agent-policy requestBody: content: application/json: schema: - title: NewAgentPolicy - type: object - properties: - name: - type: string - namespace: - type: string - description: - type: string + $ref: '#/components/schemas/new_agent_policy' security: [] parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' '/agent_policies/{agentPolicyId}': parameters: - schema: @@ -136,7 +87,7 @@ paths: type: object properties: item: - $ref: '#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item' + $ref: '#/components/schemas/agent_policy' required: - item operationId: agent-policy-info @@ -154,7 +105,7 @@ paths: type: object properties: item: - $ref: '#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item' + $ref: '#/components/schemas/agent_policy' required: - item operationId: put-agent-policy-agentPolicyId @@ -162,9 +113,9 @@ paths: content: application/json: schema: - $ref: '#/paths/~1agent_policies/post/requestBody/content/application~1json/schema' + $ref: '#/components/schemas/new_agent_policy' parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' '/agent_policies/{agentPolicyId}/copy': parameters: - schema: @@ -184,7 +135,7 @@ paths: type: object properties: item: - $ref: '#/paths/~1agent_policies/post/responses/200/content/application~1json/schema/properties/item' + $ref: '#/components/schemas/agent_policy' required: - item requestBody: @@ -233,13 +184,45 @@ paths: items: type: string parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' parameters: [] /agent-status: get: - summary: Fleet - Agent - Status for policy + summary: Fleet - Agent - Summary stats tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + error: + type: integer + events: + type: integer + inactive: + type: integer + offline: + type: integer + online: + type: integer + other: + type: integer + total: + type: integer + updating: + type: integer + required: + - error + - events + - inactive + - offline + - online + - other + - total + - updating operationId: get-fleet-agent-status parameters: - schema: @@ -262,7 +245,7 @@ paths: list: type: array items: - type: object + $ref: '#/components/schemas/agent_policy' total: type: number page: @@ -287,10 +270,31 @@ paths: post: summary: Fleet - Agent - Unenroll tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + '400': + description: BAD REQUEST + content: + application/json: + schema: + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + enum: + - 400 operationId: post-fleet-agents-unenroll parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' requestBody: content: application/json: @@ -317,37 +321,22 @@ paths: content: application/json: schema: - $ref: '#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema' + $ref: '#/components/schemas/upgrade_agent' '400': description: BAD REQUEST content: application/json: schema: - $ref: '#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema' + $ref: '#/components/schemas/upgrade_agent' operationId: post-fleet-agents-upgrade parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' requestBody: required: true content: application/json: schema: - title: UpgradeAgent - oneOf: - - type: object - properties: - version: - type: string - required: - - version - - type: object - properties: - version: - type: string - source_uri: - type: string - required: - - version + $ref: '#/components/schemas/upgrade_agent' /agents/bulk_upgrade: post: summary: Fleet - Agent - Bulk Upgrade @@ -358,58 +347,22 @@ paths: content: application/json: schema: - $ref: '#/paths/~1agents~1bulk_upgrade/post/requestBody/content/application~1json/schema' + $ref: '#/components/schemas/bulk_upgrade_agents' '400': description: BAD REQUEST content: application/json: schema: - $ref: '#/paths/~1agents~1%7BagentId%7D~1upgrade/post/requestBody/content/application~1json/schema' + $ref: '#/components/schemas/upgrade_agent' operationId: post-fleet-agents-bulk-upgrade parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' requestBody: required: true content: application/json: schema: - title: BulkUpgradeAgents - oneOf: - - type: object - properties: - version: - type: string - agents: - type: array - items: - type: string - required: - - version - - agents - - type: object - properties: - version: - type: string - source_uri: - type: string - agents: - type: array - items: - type: string - required: - - version - - agents - - type: object - properties: - version: - type: string - source_uri: - type: string - agents: - type: string - required: - - version - - agents + $ref: '#/components/schemas/bulk_upgrade_agents' /agents/setup: get: summary: Agents setup - Info @@ -458,21 +411,56 @@ paths: - admin_username - admin_password parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' /enrollment-api-keys: get: summary: Enrollment - List tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + list: + type: array + items: + $ref: '#/components/schemas/enrollment_api_key' + page: + type: number + perPage: + type: number + total: + type: number + required: + - list + - page + - perPage + - total operationId: get-fleet-enrollment-api-keys parameters: [] post: summary: Enrollment - Create tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: '#/components/schemas/enrollment_api_key' + action: + type: string + enum: + - created operationId: post-fleet-enrollment-api-keys parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' '/enrollment-api-keys/{keyId}': parameters: - schema: @@ -483,15 +471,39 @@ paths: get: summary: Enrollment - Info tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: '#/components/schemas/enrollment_api_key' + required: + - item operationId: get-fleet-enrollment-api-keys-keyId delete: summary: Enrollment - Delete tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + action: + type: string + enum: + - deleted + required: + - action operationId: delete-fleet-enrollment-api-keys-keyId parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' /epm/categories: get: summary: EPM - Categories @@ -529,39 +541,7 @@ paths: schema: type: array items: - title: SearchResult - type: object - properties: - description: - type: string - download: - type: string - icons: - type: string - name: - type: string - path: - type: string - title: - type: string - type: - type: string - version: - type: string - status: - type: string - savedObject: - type: object - required: - - description - - download - - icons - - name - - path - - title - - type - - version - - status + $ref: '#/components/schemas/search_result' operationId: get-epm-list parameters: [] '/epm/packages/{pkgkey}': @@ -578,124 +558,7 @@ paths: allOf: - properties: response: - title: PackageInfo - type: object - properties: - name: - type: string - title: - type: string - version: - type: string - readme: - type: string - description: - type: string - type: - type: string - categories: - type: array - items: - type: string - requirement: - oneOf: - - properties: - kibana: - type: object - properties: - versions: - type: string - - properties: - elasticsearch: - type: object - properties: - versions: - type: string - type: object - screenshots: - type: array - items: - type: object - properties: - src: - type: string - path: - type: string - title: - type: string - size: - type: string - type: - type: string - required: - - src - - path - icons: - type: array - items: - type: string - assets: - type: array - items: - type: string - internal: - type: boolean - format_version: - type: string - data_streams: - type: array - items: - type: object - properties: - title: - type: string - name: - type: string - release: - type: string - ingeset_pipeline: - type: string - vars: - type: array - items: - type: object - properties: - name: - type: string - default: - type: string - required: - - name - - default - type: - type: string - package: - type: string - required: - - title - - name - - release - - ingeset_pipeline - - type - - package - download: - type: string - path: - type: string - removable: - type: boolean - required: - - name - - title - - version - - description - - type - - categories - - requirement - - assets - - format_version - - download - - path + $ref: '#/components/schemas/package_info' - properties: status: type: string @@ -744,7 +607,7 @@ paths: operationId: post-epm-install-pkgkey description: '' parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' delete: summary: EPM - Packages - Delete tags: [] @@ -772,7 +635,7 @@ paths: - response operationId: post-epm-delete-pkgkey parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' requestBody: content: application/json: @@ -800,36 +663,48 @@ paths: type: object properties: item: - type: object + $ref: '#/components/schemas/agent' required: - item operationId: get-fleet-agents-agentId put: summary: Fleet - Agent - Update tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: '#/components/schemas/agent' + required: + - item operationId: put-fleet-agents-agentId parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' delete: summary: Fleet - Agent - Delete tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + action: + type: string + enum: + - deleted + required: + - action operationId: delete-fleet-agents-agentId parameters: - - $ref: '#/paths/~1setup/post/parameters/0' - '/install/{osType}': - parameters: - - schema: - type: string - name: osType - in: path - required: true - get: - summary: Fleet - Get OS install script - tags: [] - responses: {} - operationId: get-fleet-install-osType + - $ref: '#/components/parameters/kbn_xsrf' /package_policies: get: summary: PackagePolicies - List @@ -845,7 +720,7 @@ paths: items: type: array items: - $ref: '#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item' + $ref: '#/components/schemas/package_policy' total: type: number page: @@ -868,66 +743,9 @@ paths: content: application/json: schema: - title: NewPackagePolicy - type: object - description: '' - properties: - enabled: - type: boolean - package: - type: object - properties: - name: - type: string - version: - type: string - title: - type: string - required: - - name - - version - - title - namespace: - type: string - output_id: - type: string - inputs: - type: array - items: - type: object - properties: - type: - type: string - enabled: - type: boolean - processors: - type: array - items: - type: string - streams: - type: array - items: {} - config: - type: object - vars: - type: object - required: - - type - - enabled - - streams - policy_id: - type: string - name: - type: string - description: - type: string - required: - - output_id - - inputs - - policy_id - - name + $ref: '#/components/schemas/new_package_policy' parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' '/package_policies/{packagePolicyId}': get: summary: PackagePolicies - Info @@ -941,21 +759,7 @@ paths: type: object properties: item: - title: PackagePolicy - allOf: - - type: object - properties: - id: - type: string - revision: - type: number - inputs: - type: array - items: {} - required: - - id - - revision - - $ref: '#/paths/~1package_policies/post/requestBody/content/application~1json/schema' + $ref: '#/components/schemas/package_policy' required: - item operationId: get-packagePolicies-packagePolicyId @@ -972,13 +776,7 @@ paths: content: application/json: schema: - title: UpdatePackagePolicy - allOf: - - type: object - properties: - version: - type: string - - $ref: '#/paths/~1package_policies/post/requestBody/content/application~1json/schema' + $ref: '#/components/schemas/update_package_policy' responses: '200': description: OK @@ -988,17 +786,17 @@ paths: type: object properties: item: - $ref: '#/paths/~1package_policies~1%7BpackagePolicyId%7D/get/responses/200/content/application~1json/schema/properties/item' + $ref: '#/components/schemas/package_policy' sucess: type: boolean required: - item - sucess parameters: - - $ref: '#/paths/~1setup/post/parameters/0' + - $ref: '#/components/parameters/kbn_xsrf' /setup: post: - summary: Ingest Manager - Setup + summary: Fleet - Setup tags: [] responses: '200': @@ -1021,25 +819,470 @@ paths: type: string operationId: post-setup parameters: - - schema: - type: string - in: header - name: kbn-xsrf - required: true + - $ref: '#/components/parameters/kbn_xsrf' components: securitySchemes: basicAuth: type: http scheme: basic - Enrollment API Key: + Enrollment_API_Key: name: Authorization type: apiKey in: header description: 'e.g. Authorization: ApiKey base64EnrollmentApiKey' - Access API Key: + Access_API_Key: name: Authorization type: apiKey in: header description: 'e.g. Authorization: ApiKey base64AccessApiKey' + parameters: + page_size: + name: perPage + in: query + description: The number of items to return + required: false + schema: + type: integer + default: 50 + page_index: + name: page + in: query + required: false + schema: + type: integer + default: 1 + kuery: + name: kuery + in: query + required: false + schema: + type: string + kbn_xsrf: + schema: + type: string + in: header + name: kbn-xsrf + required: true + schemas: + new_agent_policy: + title: NewAgentPolicy + type: object + properties: + name: + type: string + namespace: + type: string + description: + type: string + new_package_policy: + title: NewPackagePolicy + type: object + description: '' + properties: + enabled: + type: boolean + package: + type: object + properties: + name: + type: string + version: + type: string + title: + type: string + required: + - name + - version + - title + namespace: + type: string + output_id: + type: string + inputs: + type: array + items: + type: object + properties: + type: + type: string + enabled: + type: boolean + processors: + type: array + items: + type: string + streams: + type: array + items: {} + config: + type: object + vars: + type: object + required: + - type + - enabled + - streams + policy_id: + type: string + name: + type: string + description: + type: string + required: + - output_id + - inputs + - policy_id + - name + package_policy: + title: PackagePolicy + allOf: + - type: object + properties: + id: + type: string + revision: + type: number + inputs: + type: array + items: {} + required: + - id + - revision + - $ref: '#/components/schemas/new_package_policy' + agent_policy: + allOf: + - $ref: '#/components/schemas/new_agent_policy' + - type: object + properties: + id: + type: string + status: + type: string + enum: + - active + - inactive + packagePolicies: + oneOf: + - items: + type: string + - items: + $ref: '#/components/schemas/package_policy' + type: array + updated_on: + type: string + format: date-time + updated_by: + type: string + revision: + type: number + agents: + type: number + required: + - id + - status + upgrade_agent: + title: UpgradeAgent + oneOf: + - type: object + properties: + version: + type: string + required: + - version + - type: object + properties: + version: + type: string + source_uri: + type: string + required: + - version + bulk_upgrade_agents: + title: BulkUpgradeAgents + oneOf: + - type: object + properties: + version: + type: string + agents: + type: array + items: + type: string + required: + - version + - agents + - type: object + properties: + version: + type: string + source_uri: + type: string + agents: + type: array + items: + type: string + required: + - version + - agents + - type: object + properties: + version: + type: string + source_uri: + type: string + agents: + type: string + required: + - version + - agents + enrollment_api_key: + title: EnrollmentApiKey + type: object + properties: + id: + type: string + api_key_id: + type: string + api_key: + type: string + name: + type: string + active: + type: boolean + policy_id: + type: string + created_at: + type: string + required: + - id + - api_key_id + - api_key + - active + - created_at + search_result: + title: SearchResult + type: object + properties: + description: + type: string + download: + type: string + icons: + type: string + name: + type: string + path: + type: string + title: + type: string + type: + type: string + version: + type: string + status: + type: string + savedObject: + type: object + required: + - description + - download + - icons + - name + - path + - title + - type + - version + - status + package_info: + title: PackageInfo + type: object + properties: + name: + type: string + title: + type: string + version: + type: string + readme: + type: string + description: + type: string + type: + type: string + categories: + type: array + items: + type: string + requirement: + oneOf: + - properties: + kibana: + type: object + properties: + versions: + type: string + - properties: + elasticsearch: + type: object + properties: + versions: + type: string + type: object + screenshots: + type: array + items: + type: object + properties: + src: + type: string + path: + type: string + title: + type: string + size: + type: string + type: + type: string + required: + - src + - path + icons: + type: array + items: + type: string + assets: + type: array + items: + type: string + internal: + type: boolean + format_version: + type: string + data_streams: + type: array + items: + type: object + properties: + title: + type: string + name: + type: string + release: + type: string + ingeset_pipeline: + type: string + vars: + type: array + items: + type: object + properties: + name: + type: string + default: + type: string + required: + - name + - default + type: + type: string + package: + type: string + required: + - title + - name + - release + - ingeset_pipeline + - type + - package + download: + type: string + path: + type: string + removable: + type: boolean + required: + - name + - title + - version + - description + - type + - categories + - requirement + - assets + - format_version + - download + - path + agent_type: + type: string + title: AgentType + enum: + - PERMANENT + - EPHEMERAL + - TEMPORARY + agent_metadata: + title: AgentMetadata + type: object + agent_status: + type: string + title: AgentStatus + enum: + - offline + - error + - online + - inactive + - warning + agent: + title: Agent + type: object + properties: + type: + $ref: '#/components/schemas/agent_type' + active: + type: boolean + enrolled_at: + type: string + unenrolled_at: + type: string + unenrollment_started_at: + type: string + shared_id: + type: string + deprecated: true + access_api_key_id: + type: string + default_api_key_id: + type: string + policy_id: + type: string + policy_revision: + type: number + last_checkin: + type: string + user_provided_metadata: + $ref: '#/components/schemas/agent_metadata' + local_metadata: + $ref: '#/components/schemas/agent_metadata' + id: + type: string + access_api_key: + type: string + status: + $ref: '#/components/schemas/agent_status' + default_api_key: + type: string + required: + - type + - active + - enrolled_at + - id + - status + update_package_policy: + title: UpdatePackagePolicy + allOf: + - type: object + properties: + version: + type: string + - $ref: '#/components/schemas/new_package_policy' security: - basicAuth: [] diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/enrollment_api_key.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/enrollment_api_key.yaml index 3efe77b3bd60655..e8491504d841686 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/enrollment_api_key.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/enrollment_api_key.yaml @@ -1,3 +1,23 @@ -type: string title: EnrollmentApiKey -format: byte +type: object +properties: + id: + type: string + api_key_id: + type: string + api_key: + type: string + name: + type: string + active: + type: boolean + policy_id: + type: string + created_at: + type: string +required: + - id + - api_key_id + - api_key + - active + - created_at diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index 6ea8ae966bdca97..f4d39287b4322f0 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -1,11 +1,14 @@ openapi: 3.0.0 +tags: [] info: - title: Ingest Manager + title: Fleet + description: OpenAPI schema for Fleet API endpoints version: '0.2' contact: - name: Ingest Team + name: Fleet Team license: - name: Elastic + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license servers: - url: 'http://localhost:5601/api/fleet' description: local @@ -42,8 +45,6 @@ paths: $ref: 'paths/epm@packages@{pkgkey}.yaml' '/agents/{agentId}': $ref: 'paths/agents@{agent_id}.yaml' - '/install/{osType}': - $ref: 'paths/install@{os_type}.yaml' /package_policies: $ref: paths/package_policies.yaml '/package_policies/{packagePolicyId}': @@ -55,12 +56,12 @@ components: basicAuth: type: http scheme: basic - Enrollment API Key: + Enrollment_API_Key: name: Authorization type: apiKey in: header description: 'e.g. Authorization: ApiKey base64EnrollmentApiKey' - Access API Key: + Access_API_Key: name: Authorization type: apiKey in: header diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml index 77ec9e85069a26e..b6dbd04c7d8ea30 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml @@ -1,7 +1,39 @@ get: - summary: Fleet - Agent - Status for policy + summary: Fleet - Agent - Summary stats tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + error: + type: integer + events: + type: integer + inactive: + type: integer + offline: + type: integer + online: + type: integer + other: + type: integer + total: + type: integer + updating: + type: integer + required: + - error + - events + - inactive + - offline + - online + - other + - total + - updating operationId: get-fleet-agent-status parameters: - schema: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml index e5039bc2caccf00..c20ba259fe01680 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml @@ -12,7 +12,7 @@ get: list: type: array items: - type: object + $ref: ../components/schemas/agent_policy.yaml total: type: number page: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml index e65c80d8fae8834..780bd085b12855f 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml @@ -16,21 +16,45 @@ get: type: object properties: item: - type: object + $ref: ../components/schemas/agent.yaml required: - item operationId: get-fleet-agents-agentId put: summary: Fleet - Agent - Update tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: ../components/schemas/agent.yaml + required: + - item operationId: put-fleet-agents-agentId parameters: - $ref: ../components/headers/kbn_xsrf.yaml delete: summary: Fleet - Agent - Delete tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + action: + type: string + enum: + - deleted + required: + - action operationId: delete-fleet-agents-agentId parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml index eccbbbfc9b8cae4..02ca76f135b2ddf 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml @@ -7,7 +7,28 @@ parameters: post: summary: Fleet - Agent - Unenroll tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + '400': + description: BAD REQUEST + content: + application/json: + schema: + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + enum: + - 400 operationId: post-fleet-agents-unenroll parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml index 22d27c0596d68c0..a954714f33d008f 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml @@ -1,13 +1,48 @@ get: summary: Enrollment - List tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + list: + type: array + items: + $ref: ../components/schemas/enrollment_api_key.yaml + page: + type: number + perPage: + type: number + total: + type: number + required: + - list + - page + - perPage + - total operationId: get-fleet-enrollment-api-keys parameters: [] post: summary: Enrollment - Create tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: ../components/schemas/enrollment_api_key.yaml + action: + type: string + enum: + - created operationId: post-fleet-enrollment-api-keys parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml index 3b43950427e82ee..3c63b7e338a6b94 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml @@ -7,12 +7,36 @@ parameters: get: summary: Enrollment - Info tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + item: + $ref: ../components/schemas/enrollment_api_key.yaml + required: + - item operationId: get-fleet-enrollment-api-keys-keyId delete: summary: Enrollment - Delete tags: [] - responses: {} + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + action: + type: string + enum: + - deleted + required: + - action operationId: delete-fleet-enrollment-api-keys-keyId parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/install@{os_type}.yaml b/x-pack/plugins/fleet/common/openapi/paths/install@{os_type}.yaml deleted file mode 100644 index 80351aa7ae119cc..000000000000000 --- a/x-pack/plugins/fleet/common/openapi/paths/install@{os_type}.yaml +++ /dev/null @@ -1,11 +0,0 @@ -parameters: - - schema: - type: string - name: osType - in: path - required: true -get: - summary: Fleet - Get OS install script - tags: [] - responses: {} - operationId: get-fleet-install-osType diff --git a/x-pack/plugins/fleet/common/openapi/paths/setup.yaml b/x-pack/plugins/fleet/common/openapi/paths/setup.yaml index 62ad2cb66dacbf8..859ea50cc25c509 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/setup.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/setup.yaml @@ -1,5 +1,5 @@ post: - summary: Ingest Manager - Setup + summary: Fleet - Setup tags: [] responses: '200': diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 36554b840936466..bbb571f963dc956 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -414,7 +414,7 @@ export interface IndexTemplateMappings { // This is an index template v2, see https://github.com/elastic/elasticsearch/issues/53101 // until "proper" documentation of the new format is available. -// Ingest Manager does not use nor support the legacy index template v1 format at all +// Fleet does not use nor support the legacy index template v1 format at all export interface IndexTemplate { priority: number; index_patterns: string[]; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/index.ts b/x-pack/plugins/fleet/common/types/rest_spec/index.ts index dfbf5b968f3c7f6..870cf3f3f1b823b 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/index.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/index.ts @@ -13,7 +13,6 @@ export * from './agent_policy'; export * from './fleet_setup'; export * from './epm'; export * from './enrollment_api_key'; -export * from './install_script'; export * from './ingest_setup'; export * from './output'; export * from './settings'; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/install_script.ts b/x-pack/plugins/fleet/common/types/rest_spec/install_script.ts deleted file mode 100644 index 4a43405e4e3bab2..000000000000000 --- a/x-pack/plugins/fleet/common/types/rest_spec/install_script.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export interface InstallScriptRequest { - params: { - osType: 'macos'; - }; -} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_cloud_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_cloud_instructions.tsx index 3b9d297f37df2e3..88590ce3ce504d9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_cloud_instructions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_cloud_instructions.tsx @@ -8,7 +8,6 @@ import React, { useEffect } from 'react'; import { EuiButton, - EuiPanel, EuiLink, EuiEmptyPrompt, EuiFlexItem, @@ -39,62 +38,54 @@ export const CloudInstructions: React.FC<{ deploymentUrl: string }> = ({ deploym return ( <> - - - - - } - body={ + - - - ), - }} + id="xpack.fleet.fleetServerSetup.cloudSetupTitle" + defaultMessage="Enable APM & Fleet" /> - } - actions={ - <> - - - - - } - /> - + + } + body={ + + + + ), + }} + /> + } + actions={ + <> + + + + + } + /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx index 0fc3821d2e3f7fb..1d43f90b80defe5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx @@ -10,7 +10,6 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, - EuiPanel, EuiSpacer, EuiText, EuiLink, @@ -737,9 +736,8 @@ export const OnPremInstructions: React.FC = () => { }, [notifications.toasts]); return ( - - - + <> +

{ : CompleteStep(), ]} /> - + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/es_requirements_page.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/es_requirements_page.tsx index b4e6f1007536f6f..9c5ec12645c1dd2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/es_requirements_page.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/es_requirements_page.tsx @@ -72,7 +72,8 @@ export const MissingESRequirementsPage: React.FunctionComponent<{ elasticsearch.yml }} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx index c79263093abeb9b..28332961f37a237 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx @@ -21,7 +21,6 @@ const FlexItemWithMinWidth = styled(EuiFlexItem)` const ContentWrapper = styled(EuiFlexGroup)` height: 100%; margin: 0 auto; - max-width: 800px; `; export const FleetServerRequirementPage = () => { diff --git a/x-pack/plugins/fleet/server/services/install_script/index.ts b/x-pack/plugins/fleet/server/services/install_script/index.ts deleted file mode 100644 index b978eddbfc6735c..000000000000000 --- a/x-pack/plugins/fleet/server/services/install_script/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { appContextService } from '../app_context'; - -import { macosInstallTemplate } from './install_templates/macos'; -import { linuxInstallTemplate } from './install_templates/linux'; - -export function getScript(osType: 'macos' | 'linux', kibanaUrl: string): string { - const variables = { kibanaUrl, kibanaVersion: appContextService.getKibanaVersion() }; - - switch (osType) { - case 'macos': - return macosInstallTemplate(variables); - case 'linux': - return linuxInstallTemplate(variables); - default: - throw new Error(`${osType} is not supported.`); - } -} diff --git a/x-pack/plugins/fleet/server/services/install_script/install_templates/linux.ts b/x-pack/plugins/fleet/server/services/install_script/install_templates/linux.ts deleted file mode 100644 index 4e31193efc0e60e..000000000000000 --- a/x-pack/plugins/fleet/server/services/install_script/install_templates/linux.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { InstallTemplateFunction } from './types'; - -export const linuxInstallTemplate: InstallTemplateFunction = (variables) => { - const artifact = `elastic-agent-${variables.kibanaVersion}-linux-x86_64`; - - return `#!/bin/sh - -set -e -curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/${artifact}.tar.gz -tar -xzvf ${artifact}.tar.gz -cd ${artifact} -./elastic-agent enroll ${variables.kibanaUrl} $API_KEY --force -./elastic-agent run -`; -}; diff --git a/x-pack/plugins/fleet/server/services/install_script/install_templates/macos.ts b/x-pack/plugins/fleet/server/services/install_script/install_templates/macos.ts deleted file mode 100644 index abe52f64d0e5c1f..000000000000000 --- a/x-pack/plugins/fleet/server/services/install_script/install_templates/macos.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { InstallTemplateFunction } from './types'; - -export const macosInstallTemplate: InstallTemplateFunction = (variables) => { - const artifact = `elastic-agent-${variables.kibanaVersion}-darwin-x86_64`; - - return `#!/bin/sh - -set -e -curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/${artifact}.tar.gz -tar -xzvf ${artifact}.tar.gz -cd ${artifact} -./elastic-agent enroll ${variables.kibanaUrl} $API_KEY --force -./elastic-agent run -`; -}; diff --git a/x-pack/plugins/fleet/server/services/install_script/install_templates/types.ts b/x-pack/plugins/fleet/server/services/install_script/install_templates/types.ts deleted file mode 100644 index e8fa001f7eb65d0..000000000000000 --- a/x-pack/plugins/fleet/server/services/install_script/install_templates/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export type InstallTemplateFunction = (variables: { - kibanaUrl: string; - kibanaVersion: string; -}) => string; diff --git a/x-pack/plugins/fleet/server/types/rest_spec/index.ts b/x-pack/plugins/fleet/server/types/rest_spec/index.ts index c4fbb05ffd42f6d..badf02e2e624220 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/index.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/index.ts @@ -11,7 +11,6 @@ export * from './agent'; export * from './package_policy'; export * from './epm'; export * from './enrollment_api_key'; -export * from './install_script'; export * from './output'; export * from './preconfiguration'; export * from './settings'; diff --git a/x-pack/plugins/fleet/server/types/rest_spec/install_script.ts b/x-pack/plugins/fleet/server/types/rest_spec/install_script.ts deleted file mode 100644 index 05ddfeb6f7c040d..000000000000000 --- a/x-pack/plugins/fleet/server/types/rest_spec/install_script.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema } from '@kbn/config-schema'; - -export const InstallScriptRequestSchema = { - params: schema.object({ - osType: schema.oneOf([schema.literal('macos'), schema.literal('linux')]), - }), -}; diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json index 981036114282ed8..1e7c747887cb1bf 100644 --- a/x-pack/plugins/infra/kibana.json +++ b/x-pack/plugins/infra/kibana.json @@ -27,5 +27,10 @@ "home", "ml", "embeddable" - ] + ], + "owner": { + "name": "Logs and Metrics UI", + "githubTeam": "logs-metrics-ui" + }, + "description": "This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions" } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index 45e7247f0bd856b..476ba381dd92e55 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -222,12 +222,11 @@ export class JobCreator { } public get description(): string { - return this._job_config.description; + return this._job_config.description ?? ''; } public get groups(): string[] { - // @ts-expect-error @elastic-elasticsearch FIXME groups is optional - return this._job_config.groups; + return this._job_config.groups ?? []; } public set groups(groups: string[]) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/detector_cards.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/detector_cards.tsx index 8da41b0e7c87509..b78970eca59d1d0 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/detector_cards.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/detector_cards.tsx @@ -29,7 +29,7 @@ export const RareCard: FC = ({ onClick, isSelected }) => ( <> } @@ -52,7 +52,7 @@ export const RareInPopulationCard: FC = ({ onClick, isSelected }) => <> } @@ -75,7 +75,7 @@ export const FrequentlyRareInPopulationCard: FC = ({ onClick, isSelec <> } diff --git a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts index 97ddefac860f2dc..7a81a7ecb1e34b0 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts @@ -504,8 +504,9 @@ export class AnomalyExplorerChartsService { const config = seriesConfigs[i]; let records; if ( - config.detectorLabel !== undefined && - config.detectorLabel.includes(ML_JOB_AGGREGATION.LAT_LONG) + (config.detectorLabel !== undefined && + config.detectorLabel.includes(ML_JOB_AGGREGATION.LAT_LONG)) || + config?.metricFunction === ML_JOB_AGGREGATION.LAT_LONG ) { if (config.entityFields.length) { records = [ diff --git a/x-pack/plugins/observability/public/components/app/fleet_panel/index.tsx b/x-pack/plugins/observability/public/components/app/fleet_panel/index.tsx index fce1cde38f58721..b6db8b2308b7854 100644 --- a/x-pack/plugins/observability/public/components/app/fleet_panel/index.tsx +++ b/x-pack/plugins/observability/public/components/app/fleet_panel/index.tsx @@ -16,9 +16,6 @@ export function FleetPanel() { return ( {i18n.translate('xpack.observability.fleet.text', { @@ -30,7 +27,7 @@ export function FleetPanel() { footer={ {i18n.translate('xpack.observability.fleet.button', { - defaultMessage: 'Try Fleet Beta', + defaultMessage: 'Try Fleet', })} } diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx index 95977c55725f69d..f713af976822993 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx @@ -15,6 +15,7 @@ import { EuiPopoverTitle, EuiSelectable, EuiSelectableOption, + EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; @@ -34,7 +35,13 @@ const formatOptions = ( excludedValues?: string[], showCount?: boolean ): EuiSelectableOption[] => { - return (values ?? []).map(({ label, count }) => ({ + const uniqueValues: Record = {}; + + values?.forEach(({ label, count }) => { + uniqueValues[label] = count; + }); + + return Object.entries(uniqueValues).map(([label, count]) => ({ label, append: showCount ? ( @@ -50,6 +57,7 @@ export function FieldValueSelection({ fullWidth, label, loading, + query, setQuery, button, width, @@ -118,7 +126,7 @@ export function FieldValueSelection({ hasActiveFilters={numOfFilters > 0} iconType="arrowDown" numActiveFilters={numOfFilters} - numFilters={values.length} + numFilters={options.length} onClick={onButtonClick} > {label} @@ -158,19 +166,28 @@ export function FieldValueSelection({ }), compressed, onInput: onValueChange, + 'data-test-subj': 'suggestionInputField', }} listProps={{ onFocusBadge: false, }} options={options} onChange={onChange} - isLoading={loading} + isLoading={loading && !query && options.length === 0} allowExclusions={true} > {(list, search) => (
{search} {list} + {loading && query && ( + + {i18n.translate('xpack.observability.fieldValueSelection.loading', { + defaultMessage: 'Loading', + })}{' '} + + + )} { + jest.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockReturnValue(1500); + jest.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(1500); + + function setupSearch(data: any) { + // @ts-ignore + jest.spyOn(searchHook, 'useEsSearch').mockReturnValue({ + data: { + took: 17, + timed_out: false, + _shards: { total: 35, successful: 35, skipped: 31, failed: 0 }, + hits: { total: { value: 15299, relation: 'eq' }, hits: [] }, + aggregations: { + values: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: data, + }, + }, + }, + loading: false, + }); + } + + it('renders a list', async () => { + setupSearch([ + { key: 'US', doc_count: 14132 }, + { key: 'Pak', doc_count: 200 }, + { key: 'Japan', doc_count: 100 }, + ]); + + render( + + {}} + selectedValue={[]} + filters={[]} + asCombobox={false} + /> + + ); + + fireEvent.click(screen.getByText('Service name')); + + expect(await screen.findByPlaceholderText('Filter Service name')).toBeInTheDocument(); + expect(await screen.findByText('Apply')).toBeInTheDocument(); + expect(await screen.findByText('US')).toBeInTheDocument(); + expect(await screen.findByText('Pak')).toBeInTheDocument(); + expect(await screen.findByText('Japan')).toBeInTheDocument(); + expect(await screen.findByText('14132')).toBeInTheDocument(); + expect(await screen.findByText('200')).toBeInTheDocument(); + expect(await screen.findByText('100')).toBeInTheDocument(); + + setupSearch([{ key: 'US', doc_count: 14132 }]); + + fireEvent.input(screen.getByTestId('suggestionInputField'), { + target: { value: 'u' }, + }); + + expect(await screen.findByDisplayValue('u')).toBeInTheDocument(); + }); + + it('calls oncChange when applied', async () => { + setupSearch([ + { key: 'US', doc_count: 14132 }, + { key: 'Pak', doc_count: 200 }, + { key: 'Japan', doc_count: 100 }, + ]); + + const onChange = jest.fn(); + + const { rerender } = render( + + + + ); + + fireEvent.click(screen.getByText('Service name')); + + fireEvent.click(await screen.findByText('US')); + fireEvent.click(await screen.findByText('Apply')); + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith(['US'], []); + + rerender( + + + + ); + + fireEvent.click(await screen.findByText('US')); + fireEvent.click(await screen.findByText('Pak')); + fireEvent.click(await screen.findByText('Apply')); + + expect(onChange).toHaveBeenCalledTimes(2); + expect(onChange).toHaveBeenLastCalledWith([], ['US']); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx index 2b4d9c0898f3e91..54114c760464407 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx @@ -6,8 +6,6 @@ */ import React, { useState } from 'react'; - -import useDebounce from 'react-use/lib/useDebounce'; import { useValuesList } from '../../../hooks/use_values_list'; import { FieldValueSelection } from './field_value_selection'; import { FieldValueSuggestionsProps } from './types'; @@ -35,7 +33,6 @@ export function FieldValueSuggestions({ onChange: onSelectionChange, }: FieldValueSuggestionsProps) { const [query, setQuery] = useState(''); - const [debouncedValue, setDebouncedValue] = useState(''); const { values, loading } = useValuesList({ indexPatternTitle, @@ -46,14 +43,6 @@ export function FieldValueSuggestions({ keepHistory: true, }); - useDebounce( - () => { - setQuery(debouncedValue); - }, - 400, - [debouncedValue] - ); - const SelectionComponent = asCombobox ? FieldValueCombobox : FieldValueSelection; return ( @@ -63,7 +52,8 @@ export function FieldValueSuggestions({ values={values} label={label} onChange={onSelectionChange} - setQuery={setDebouncedValue} + query={query} + setQuery={setQuery} loading={loading} selectedValue={selectedValue} excludedValue={excludedValue} diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts index aa8c967e31ff5fd..d857b39b074ac57 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts @@ -39,6 +39,7 @@ export type FieldValueSelectionProps = CommonProps & { loading?: boolean; onChange: (val?: string[], excludedValue?: string[]) => void; values?: ListItem[]; + query?: string; setQuery: Dispatch>; }; diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index 094b7a0f3692136..46d89a062f0722f 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -58,6 +58,13 @@ export const useValuesList = ({ [query] ); + useEffect(() => { + if (!query) { + // in case query is cleared, we don't wait for debounce + setDebounceQuery(query); + } + }, [query]); + const { data, loading } = useEsSearch( createEsParams({ index: indexPatternTitle!, diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js index 6969f98e5f092a4..5c8ef874ec7ed1c 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js @@ -494,6 +494,7 @@ export class DetailPanel extends Component { aria-labelledby="remoteClusterDetailsFlyoutTitle" size="m" maxWidth={550} + className="eui-textBreakAll" > diff --git a/x-pack/plugins/reporting/common/types.ts b/x-pack/plugins/reporting/common/types.ts index 193f3f2a971ea80..308245a696d922a 100644 --- a/x-pack/plugins/reporting/common/types.ts +++ b/x-pack/plugins/reporting/common/types.ts @@ -55,31 +55,39 @@ export interface TaskRunResult { } export interface ReportSource { - jobtype: string; - kibana_name: string; - kibana_id: string; - created_by: string | false; + /* + * Required fields: populated in enqueue_job when the request comes in to + * generate the report + */ + jobtype: string; // refers to `ExportTypeDefinition.jobType` + created_by: string | false; // username or `false` if security is disabled. Used for ensuring users can only access the reports they've created. payload: { headers: string; // encrypted headers - browserTimezone?: string; // may use timezone from advanced settings - objectType: string; - title: string; - layout?: LayoutParams; - isDeprecated?: boolean; - }; - meta: { objectType: string; layout?: string }; - browser_type: string; - migration_version: string; - max_attempts: number; - timeout: number; - + isDeprecated?: boolean; // set to true when the export type is being phased out + } & BaseParams; + meta: { objectType: string; layout?: string }; // for telemetry + migration_version: string; // for reminding the user to update their POST URL + attempts: number; // initially populated as 0 + created_at: string; // timestamp in UTC status: JobStatus; - attempts: number; + + /* + * `output` is only populated if the report job is completed or failed. + */ output: TaskRunResult | null; - started_at?: string; - completed_at?: string; - created_at: string; - process_expiration?: string | null; // must be set to null to clear the expiration + + /* + * Optional fields: populated when the job is claimed to execute, and after + * execution has finished + */ + kibana_name?: string; // for troubleshooting + kibana_id?: string; // for troubleshooting + browser_type?: string; // no longer used since chromium is the only option (used to allow phantomjs) + timeout?: number; // for troubleshooting: the actual comparison uses the config setting xpack.reporting.queue.timeout + max_attempts?: number; // for troubleshooting: the actual comparison uses the config setting xpack.reporting.capture.maxAttempts + started_at?: string; // timestamp in UTC + completed_at?: string; // timestamp in UTC + process_expiration?: string | null; // timestamp in UTC - is overwritten with `null` when the job needs a retry } /* @@ -97,48 +105,41 @@ export interface BaseParams { } export type JobId = string; + +/* + * JobStatus: + * - Begins as 'pending' + * - Changes to 'processing` when the job is claimed + * - Then 'completed' | 'failed' when execution is done + * If the job needs a retry, it reverts back to 'pending'. + */ export type JobStatus = - | 'completed' - | 'completed_with_warnings' - | 'pending' - | 'processing' - | 'failed'; + | 'completed' // Report was successful + | 'completed_with_warnings' // The download available for troubleshooting - it **should** show a meaningful error + | 'pending' // Report job is waiting to be claimed + | 'processing' // Report job has been claimed and is executing + | 'failed'; // Report was not successful, and all retries are done. Nothing to download. export interface JobContent { content: string; } -export interface ReportApiJSON { +/* + * Info API response: to avoid unnecessary large payloads on a network, the + * report query results do not include `payload.headers` or `output.content`, + * which can be long strings of meaningless text + */ +interface ReportSimple extends Omit { + payload: Omit; + output?: Omit; // is undefined for report jobs that are not completed +} + +/* + * The response format for all of the report job APIs + */ +export interface ReportApiJSON extends ReportSimple { id: string; index: string; - kibana_name: string; - kibana_id: string; - browser_type: string | undefined; - created_at: string; - jobtype: string; - created_by: string | false; - timeout?: number; - output?: { - content_type: string; - size: number; - warnings?: string[]; - }; - process_expiration?: string; - completed_at: string | undefined; - payload: { - layout?: LayoutParams; - title: string; - browserTimezone?: string; - isDeprecated?: boolean; - }; - meta: { - layout?: string; - objectType: string; - }; - max_attempts: number; - started_at: string | undefined; - attempts: number; - status: string; } export interface LicenseCheckResults { @@ -147,13 +148,14 @@ export interface LicenseCheckResults { message: string; } +/* Notifier Toasts */ export interface JobSummary { id: JobId; status: JobStatus; - title: string; - jobtype: string; - maxSizeReached?: boolean; - csvContainsFormulas?: boolean; + jobtype: ReportSource['jobtype']; + title: ReportSource['payload']['title']; + maxSizeReached: TaskRunResult['max_size_reached']; + csvContainsFormulas: TaskRunResult['csv_contains_formulas']; } export interface JobSummarySet { diff --git a/x-pack/plugins/reporting/public/lib/job.ts b/x-pack/plugins/reporting/public/lib/job.ts new file mode 100644 index 000000000000000..c882e8b92986bc6 --- /dev/null +++ b/x-pack/plugins/reporting/public/lib/job.ts @@ -0,0 +1,75 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { JobId, ReportApiJSON, ReportSource, TaskRunResult } from '../../common/types'; + +type ReportPayload = ReportSource['payload']; + +/* + * This class represents a report job for the UI + * It can be instantiated with ReportApiJSON: the response data format for the report job APIs + */ +export class Job { + public id: JobId; + public index: string; + + public objectType: ReportPayload['objectType']; + public title: ReportPayload['title']; + public isDeprecated: ReportPayload['isDeprecated']; + public browserTimezone?: ReportPayload['browserTimezone']; + public layout: ReportPayload['layout']; + + public jobtype: ReportSource['jobtype']; + public created_by: ReportSource['created_by']; + public created_at: ReportSource['created_at']; + public started_at: ReportSource['started_at']; + public completed_at: ReportSource['completed_at']; + public status: ReportSource['status']; + public attempts: ReportSource['attempts']; + public max_attempts: ReportSource['max_attempts']; + + public timeout: ReportSource['timeout']; + public kibana_name: ReportSource['kibana_name']; + public kibana_id: ReportSource['kibana_id']; + public browser_type: ReportSource['browser_type']; + + public size?: TaskRunResult['size']; + public content_type?: TaskRunResult['content_type']; + public csv_contains_formulas?: TaskRunResult['csv_contains_formulas']; + public max_size_reached?: TaskRunResult['max_size_reached']; + public warnings?: TaskRunResult['warnings']; + + constructor(report: ReportApiJSON) { + this.id = report.id; + this.index = report.index; + + this.jobtype = report.jobtype; + this.objectType = report.payload.objectType; + this.title = report.payload.title; + this.layout = report.payload.layout; + this.created_by = report.created_by; + this.created_at = report.created_at; + this.started_at = report.started_at; + this.completed_at = report.completed_at; + this.status = report.status; + this.attempts = report.attempts; + this.max_attempts = report.max_attempts; + + this.timeout = report.timeout; + this.kibana_name = report.kibana_name; + this.kibana_id = report.kibana_id; + this.browser_type = report.browser_type; + this.browserTimezone = report.payload.browserTimezone; + this.size = report.output?.size; + this.content_type = report.output?.content_type; + + this.isDeprecated = report.payload.isDeprecated || false; + this.csv_contains_formulas = report.output?.csv_contains_formulas; + this.max_size_reached = report.output?.max_size_reached; + this.warnings = report.output?.warnings; + } +} diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts index 64caac0e27bddc8..90411884332c836 100644 --- a/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.ts @@ -15,37 +15,52 @@ import { API_MIGRATE_ILM_POLICY_URL, REPORTING_MANAGEMENT_HOME, } from '../../../common/constants'; -import { - DownloadReportFn, - JobId, - ManagementLinkFn, - ReportApiJSON, - ReportDocument, - ReportSource, -} from '../../../common/types'; +import { DownloadReportFn, JobId, ManagementLinkFn, ReportApiJSON } from '../../../common/types'; import { add } from '../../notifier/job_completion_notifications'; - -export interface JobQueueEntry { - _id: string; - _source: ReportSource; -} +import { Job } from '../job'; export interface JobContent { content: string; content_type: boolean; } -interface JobParams { - [paramName: string]: any; -} - export interface DiagnoseResponse { help: string[]; success: boolean; logs: string; } -export class ReportingAPIClient { +interface JobParams { + [paramName: string]: any; +} + +interface IReportingAPI { + // Helpers + getReportURL(jobId: string): string; + getReportingJobPath(exportType: string, jobParams: JobParams): string; // Return a URL to queue a job, with the job params encoded in the query string of the URL. Used for copying POST URL + createReportingJob(exportType: string, jobParams: any): Promise; // Sends a request to queue a job, with the job params in the POST body + getServerBasePath(): string; // Provides the raw server basePath to allow it to be stripped out from relativeUrls in job params + + // CRUD + downloadReport(jobId: string): void; + deleteReport(jobId: string): Promise; + list(page: number, jobIds: string[]): Promise; // gets the first 10 report of the page + total(): Promise; + getError(jobId: string): Promise; + getInfo(jobId: string): Promise; + findForJobIds(jobIds: string[]): Promise; + + // Function props + getManagementLink: ManagementLinkFn; + getDownloadLink: DownloadReportFn; + + // Diagnostic-related API calls + verifyConfig(): Promise; + verifyBrowser(): Promise; + verifyScreenCapture(): Promise; +} + +export class ReportingAPIClient implements IReportingAPI { private http: HttpSetup; constructor(http: HttpSetup) { @@ -71,68 +86,69 @@ export class ReportingAPIClient { }); } - public list = (page = 0, jobIds: string[] = []): Promise => { + public async list(page = 0, jobIds: string[] = []) { const query = { page } as any; if (jobIds.length > 0) { // Only getting the first 10, to prevent URL overflows query.ids = jobIds.slice(0, 10).join(','); } - return this.http.get(`${API_LIST_URL}/list`, { + const jobQueueEntries: ReportApiJSON[] = await this.http.get(`${API_LIST_URL}/list`, { query, asSystemRequest: true, }); - }; - public total(): Promise { - return this.http.get(`${API_LIST_URL}/count`, { + return jobQueueEntries.map((report) => new Job(report)); + } + + public async total() { + return await this.http.get(`${API_LIST_URL}/count`, { asSystemRequest: true, }); } - public getContent(jobId: string): Promise { - return this.http.get(`${API_LIST_URL}/output/${jobId}`, { + public async getError(jobId: string) { + return await this.http.get(`${API_LIST_URL}/output/${jobId}`, { asSystemRequest: true, }); } - public getInfo(jobId: string): Promise { - return this.http.get(`${API_LIST_URL}/info/${jobId}`, { + public async getInfo(jobId: string) { + const report: ReportApiJSON = await this.http.get(`${API_LIST_URL}/info/${jobId}`, { asSystemRequest: true, }); + return new Job(report); } - public findForJobIds = (jobIds: JobId[]): Promise => { - return this.http.fetch(`${API_LIST_URL}/list`, { + public async findForJobIds(jobIds: JobId[]) { + const reports: ReportApiJSON[] = await this.http.fetch(`${API_LIST_URL}/list`, { query: { page: 0, ids: jobIds.join(',') }, method: 'GET', }); - }; + return reports.map((report) => new Job(report)); + } - /* - * Return a URL to queue a job, with the job params encoded in the query string of the URL. Used for copying POST URL - */ - public getReportingJobPath = (exportType: string, jobParams: JobParams) => { + public getReportingJobPath(exportType: string, jobParams: JobParams) { const params = stringify({ jobParams: rison.encode(jobParams) }); return `${this.http.basePath.prepend(API_BASE_GENERATE)}/${exportType}?${params}`; - }; + } - /* - * Sends a request to queue a job, with the job params in the POST body - */ - public createReportingJob = async (exportType: string, jobParams: any) => { + public async createReportingJob(exportType: string, jobParams: any) { const jobParamsRison = rison.encode(jobParams); - const resp = await this.http.post(`${API_BASE_GENERATE}/${exportType}`, { - method: 'POST', - body: JSON.stringify({ - jobParams: jobParamsRison, - }), - }); + const resp: { job: ReportApiJSON } = await this.http.post( + `${API_BASE_GENERATE}/${exportType}`, + { + method: 'POST', + body: JSON.stringify({ + jobParams: jobParamsRison, + }), + } + ); add(resp.job.id); - return resp; - }; + return new Job(resp.job); + } public getManagementLink: ManagementLinkFn = () => this.http.basePath.prepend(REPORTING_MANAGEMENT_HOME); @@ -140,36 +156,27 @@ export class ReportingAPIClient { public getDownloadLink: DownloadReportFn = (jobId: JobId) => this.http.basePath.prepend(`${API_LIST_URL}/download/${jobId}`); - /* - * provides the raw server basePath to allow it to be stripped out from relativeUrls in job params - */ public getServerBasePath = () => this.http.basePath.serverBasePath; - /* - * Diagnostic-related API calls - */ - public verifyConfig = (): Promise => - this.http.post(`${API_BASE_URL}/diagnose/config`, { + public async verifyConfig() { + return await this.http.post(`${API_BASE_URL}/diagnose/config`, { asSystemRequest: true, }); + } - /* - * Diagnostic-related API calls - */ - public verifyBrowser = (): Promise => - this.http.post(`${API_BASE_URL}/diagnose/browser`, { + public async verifyBrowser() { + return await this.http.post(`${API_BASE_URL}/diagnose/browser`, { asSystemRequest: true, }); + } - /* - * Diagnostic-related API calls - */ - public verifyScreenCapture = (): Promise => - this.http.post(`${API_BASE_URL}/diagnose/screenshot`, { + public async verifyScreenCapture() { + return await this.http.post(`${API_BASE_URL}/diagnose/screenshot`, { asSystemRequest: true, }); + } - public migrateReportingIndicesIlmPolicy = (): Promise => { - return this.http.put(`${API_MIGRATE_ILM_POLICY_URL}`); - }; + public async migrateReportingIndicesIlmPolicy() { + return await this.http.put(`${API_MIGRATE_ILM_POLICY_URL}`); + } } diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts index 7c3837486ad1d8e..58fde5cbd83adcf 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts @@ -7,7 +7,9 @@ import sinon, { stub } from 'sinon'; import { NotificationsStart } from 'src/core/public'; -import { JobSummary, ReportDocument } from '../../common/types'; +import { coreMock } from '../../../../../src/core/public/mocks'; +import { JobSummary, ReportApiJSON } from '../../common/types'; +import { Job } from './job'; import { ReportingAPIClient } from './reporting_api_client'; import { ReportingNotifierStreamHandler } from './stream_handler'; @@ -18,43 +20,20 @@ Object.defineProperty(window, 'sessionStorage', { writable: true, }); -const mockJobsFound = [ - { - _id: 'job-source-mock1', - _source: { - status: 'completed', - output: { max_size_reached: false, csv_contains_formulas: false }, - payload: { title: 'specimen' }, - }, - }, - { - _id: 'job-source-mock2', - _source: { - status: 'failed', - output: { max_size_reached: false, csv_contains_formulas: false }, - payload: { title: 'specimen' }, - }, - }, - { - _id: 'job-source-mock3', - _source: { - status: 'pending', - output: { max_size_reached: false, csv_contains_formulas: false }, - payload: { title: 'specimen' }, - }, - }, -]; +const mockJobsFound: Job[] = [ + { id: 'job-source-mock1', status: 'completed', output: { csv_contains_formulas: false, max_size_reached: false }, payload: { title: 'specimen' } }, + { id: 'job-source-mock2', status: 'failed', output: { csv_contains_formulas: false, max_size_reached: false }, payload: { title: 'specimen' } }, + { id: 'job-source-mock3', status: 'pending', output: { csv_contains_formulas: false, max_size_reached: false }, payload: { title: 'specimen' } }, +].map((j) => new Job(j as ReportApiJSON)); // prettier-ignore -const jobQueueClientMock: ReportingAPIClient = { - findForJobIds: async (jobIds: string[]) => { - return mockJobsFound as ReportDocument[]; - }, - getContent: (): Promise => { - return Promise.resolve({ content: 'this is the completed report data' }); - }, - getManagementLink: () => '/#management', - getDownloadLink: () => '/reporting/download/job-123', -} as any; +const jobQueueClientMock = new ReportingAPIClient(coreMock.createSetup().http); +jobQueueClientMock.findForJobIds = async (jobIds: string[]) => mockJobsFound; +jobQueueClientMock.getInfo = () => + Promise.resolve(({ content: 'this is the completed report data' } as unknown) as Job); +jobQueueClientMock.getError = () => + Promise.resolve({ content: 'this is the completed report data' }); +jobQueueClientMock.getManagementLink = () => '/#management'; +jobQueueClientMock.getDownloadLink = () => '/reporting/download/job-123'; const mockShowDanger = stub(); const mockShowSuccess = stub(); diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.ts b/x-pack/plugins/reporting/public/lib/stream_handler.ts index 53191cacb5ba1a8..8e41d34d054ecb6 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.ts @@ -10,7 +10,7 @@ import * as Rx from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { NotificationsSetup } from 'src/core/public'; import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JOB_STATUSES } from '../../common/constants'; -import { JobId, JobSummary, JobSummarySet, ReportDocument } from '../../common/types'; +import { JobId, JobSummary, JobSummarySet } from '../../common/types'; import { getFailureToast, getGeneralErrorToast, @@ -18,20 +18,21 @@ import { getWarningFormulasToast, getWarningMaxSizeToast, } from '../notifier'; +import { Job } from './job'; import { ReportingAPIClient } from './reporting_api_client'; function updateStored(jobIds: JobId[]): void { sessionStorage.setItem(JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JSON.stringify(jobIds)); } -function getReportStatus(src: ReportDocument): JobSummary { +function getReportStatus(src: Job): JobSummary { return { - id: src._id, - status: src._source.status, - title: src._source.payload.title, - jobtype: src._source.jobtype, - maxSizeReached: src._source.output?.max_size_reached, - csvContainsFormulas: src._source.output?.csv_contains_formulas, + id: src.id, + status: src.status, + title: src.title, + jobtype: src.jobtype, + maxSizeReached: src.max_size_reached, + csvContainsFormulas: src.csv_contains_formulas, }; } @@ -73,7 +74,7 @@ export class ReportingNotifierStreamHandler { // no download link available for (const job of failedJobs) { - const { content } = await this.apiClient.getContent(job.id); + const { content } = await this.apiClient.getError(job.id); this.notifications.toasts.addDanger( getFailureToast(content, job, this.apiClient.getManagementLink) ); @@ -90,17 +91,14 @@ export class ReportingNotifierStreamHandler { */ public findChangedStatusJobs(storedJobs: JobId[]): Rx.Observable { return Rx.from(this.apiClient.findForJobIds(storedJobs)).pipe( - map((jobs: ReportDocument[]) => { + map((jobs) => { const completedJobs: JobSummary[] = []; const failedJobs: JobSummary[] = []; const pending: JobId[] = []; // add side effects to storage for (const job of jobs) { - const { - _id: jobId, - _source: { status: jobStatus }, - } = job; + const { id: jobId, status: jobStatus } = job; if (storedJobs.includes(jobId)) { if (jobStatus === JOB_STATUSES.COMPLETED || jobStatus === JOB_STATUSES.WARNINGS) { completedJobs.push(getReportStatus(job)); diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap index cb365849608677f..3417aa59f9d7254 100644 --- a/x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap +++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap @@ -6,7 +6,9 @@ exports[`ReportInfoButton handles button click flyout on click 1`] = ` className="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall" data-test-subj="reportInfoButton" disabled={false} + onBlur={[Function]} onClick={[Function]} + onFocus={[Function]} type="button" > - - - - - + + + + + + +
@@ -1404,7 +1654,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` - - - - - - - - - - - - - - - - - - -
- - -
- + + + + + + + + + +
+
+ + + + + + +
+ + + + +
+ + +
+ - + - - - - + + + + + + +
@@ -3493,7 +4243,7 @@ exports[`ReportListing Report job listing with some items 1`] = `
- + - - - - + + + + + + + @@ -4594,7 +5594,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` - + - - - - + + + + + + + @@ -5662,7 +6912,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` - + - - - - + + + + + + + @@ -6730,7 +8230,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` - + - - - - + + + + + + + @@ -7798,7 +9548,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` - + - - - - + + + + + + + @@ -8866,7 +10866,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` - - - - - + + + + + + + diff --git a/x-pack/plugins/reporting/public/management/report_delete_button.tsx b/x-pack/plugins/reporting/public/management/report_delete_button.tsx index dfb411fc195e8ac..5200191184972f1 100644 --- a/x-pack/plugins/reporting/public/management/report_delete_button.tsx +++ b/x-pack/plugins/reporting/public/management/report_delete_button.tsx @@ -7,7 +7,8 @@ import { EuiButton, EuiConfirmModal } from '@elastic/eui'; import React, { Fragment, PureComponent } from 'react'; -import { Job, Props as ListingProps } from './report_listing'; +import { Job } from '../lib/job'; +import { Props as ListingProps } from './report_listing'; type DeleteFn = () => Promise; type Props = { jobsToDelete: Job[]; performDelete: DeleteFn } & ListingProps; @@ -46,7 +47,7 @@ export class ReportDeleteButton extends PureComponent { id: 'xpack.reporting.listing.table.deleteConfirmTitle', defaultMessage: `Delete the "{name}" report?`, }, - { name: jobsToDelete[0].object_title } + { name: jobsToDelete[0].title } ); const message = intl.formatMessage({ id: 'xpack.reporting.listing.table.deleteConfirmMessage', diff --git a/x-pack/plugins/reporting/public/management/report_download_button.tsx b/x-pack/plugins/reporting/public/management/report_download_button.tsx index 78022b85e2ff860..b4212710377224f 100644 --- a/x-pack/plugins/reporting/public/management/report_download_button.tsx +++ b/x-pack/plugins/reporting/public/management/report_download_button.tsx @@ -8,7 +8,8 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { FunctionComponent } from 'react'; import { JOB_STATUSES } from '../../common/constants'; -import { Job as ListingJob, Props as ListingProps } from './report_listing'; +import { Job as ListingJob } from '../lib/job'; +import { Props as ListingProps } from './report_listing'; type Props = { record: ListingJob } & ListingProps; diff --git a/x-pack/plugins/reporting/public/management/report_error_button.tsx b/x-pack/plugins/reporting/public/management/report_error_button.tsx index 0ebdf5ca60b5a07..ee0c0e162cb7d08 100644 --- a/x-pack/plugins/reporting/public/management/report_error_button.tsx +++ b/x-pack/plugins/reporting/public/management/report_error_button.tsx @@ -9,8 +9,8 @@ import { EuiButtonIcon, EuiCallOut, EuiPopover } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { Component } from 'react'; import { JOB_STATUSES } from '../../common/constants'; +import { Job as ListingJob } from '../lib/job'; import { JobContent, ReportingAPIClient } from '../lib/reporting_api_client'; -import { Job as ListingJob } from './report_listing'; interface Props { intl: InjectedIntl; @@ -102,7 +102,7 @@ class ReportErrorButtonUi extends Component { this.setState({ isLoading: true }); try { - const reportContent: JobContent = await apiClient.getContent(record.id); + const reportContent: JobContent = await apiClient.getError(record.id); if (this.mounted) { this.setState({ isLoading: false, error: reportContent.content }); } diff --git a/x-pack/plugins/reporting/public/management/report_info_button.tsx b/x-pack/plugins/reporting/public/management/report_info_button.tsx index 719f1ff341daf8d..92acaa386bd5680 100644 --- a/x-pack/plugins/reporting/public/management/report_info_button.tsx +++ b/x-pack/plugins/reporting/public/management/report_info_button.tsx @@ -15,14 +15,16 @@ import { EuiSpacer, EuiText, EuiTitle, + EuiToolTip, } from '@elastic/eui'; -import { get } from 'lodash'; -import React, { Component, Fragment } from 'react'; +import { injectI18n } from '@kbn/i18n/react'; +import React, { Component } from 'react'; import { USES_HEADLESS_JOB_TYPES } from '../../common/constants'; -import { ReportApiJSON } from '../../common/types'; +import { Job } from '../lib/job'; import { ReportingAPIClient } from '../lib/reporting_api_client'; +import { Props as ListingProps } from './report_listing'; -interface Props { +interface Props extends Pick { jobId: string; apiClient: ReportingAPIClient; } @@ -31,23 +33,23 @@ interface State { isLoading: boolean; isFlyoutVisible: boolean; calloutTitle: string; - info: ReportApiJSON | null; + info: Job | null; error: Error | null; } const NA = 'n/a'; const UNKNOWN = 'unknown'; -const getDimensions = (info: ReportApiJSON): string => { +const getDimensions = (info: Job): string => { const defaultDimensions = { width: null, height: null }; - const { width, height } = get(info, 'payload.layout.dimensions', defaultDimensions); + const { width, height } = info.layout?.dimensions || defaultDimensions; if (width && height) { return `Width: ${width} x Height: ${height}`; } - return NA; + return UNKNOWN; }; -export class ReportInfoButton extends Component { +class ReportInfoButtonUi extends Component { private mounted?: boolean; constructor(props: Props) { @@ -75,133 +77,60 @@ export class ReportInfoButton extends Component { } const jobType = info.jobtype || NA; - - interface JobInfo { - title: string; - description: string; - } - - interface JobInfoMap { - [thing: string]: JobInfo[]; - } - const attempts = info.attempts ? info.attempts.toString() : NA; const maxAttempts = info.max_attempts ? info.max_attempts.toString() : NA; const timeout = info.timeout ? info.timeout.toString() : NA; - const warnings = info.output && info.output.warnings ? info.output.warnings.join(',') : null; + const warnings = info.warnings?.join(',') ?? null; + + const jobInfo = [ + { title: 'Title', description: info.title || NA }, + { title: 'Created By', description: info.created_by || NA }, + { title: 'Created At', description: info.created_at || NA }, + { title: 'Timezone', description: info.browserTimezone || NA }, + { title: 'Status', description: info.status || NA }, + ]; - const jobInfoDateTimes: JobInfo[] = [ - { - title: 'Created By', - description: info.created_by || NA, - }, - { - title: 'Created At', - description: info.created_at || NA, - }, - { - title: 'Started At', - description: info.started_at || NA, - }, - { - title: 'Completed At', - description: info.completed_at || NA, - }, + const processingInfo = [ + { title: 'Started At', description: info.started_at || NA }, + { title: 'Completed At', description: info.completed_at || NA }, { title: 'Processed By', description: - info.kibana_name && info.kibana_id ? `${info.kibana_name} (${info.kibana_id})` : UNKNOWN, - }, - { - title: 'Browser Timezone', - description: get(info, 'payload.browserTimezone') || NA, - }, - ]; - const jobInfoPayload: JobInfo[] = [ - { - title: 'Title', - description: get(info, 'payload.title') || NA, - }, - { - title: 'Layout', - description: get(info, 'meta.layout') || NA, - }, - { - title: 'Dimensions', - description: getDimensions(info), - }, - { - title: 'Job Type', - description: jobType, - }, - { - title: 'Content Type', - description: get(info, 'output.content_type') || NA, - }, - { - title: 'Size in Bytes', - description: get(info, 'output.size') || NA, - }, - ]; - const jobInfoStatus: JobInfo[] = [ - { - title: 'Attempts', - description: attempts, - }, - { - title: 'Max Attempts', - description: maxAttempts, - }, - { - title: 'Timeout', - description: timeout, - }, - { - title: 'Status', - description: info.status || NA, - }, - { - title: 'Browser Type', - description: USES_HEADLESS_JOB_TYPES.includes(jobType) ? info.browser_type || UNKNOWN : NA, + info.kibana_name && info.kibana_id ? `${info.kibana_name} (${info.kibana_id})` : NA, }, + { title: 'Content Type', description: info.content_type || NA }, + { title: 'Size in Bytes', description: info.size?.toString() || NA }, + { title: 'Attempts', description: attempts }, + { title: 'Max Attempts', description: maxAttempts }, + { title: 'Timeout', description: timeout }, ]; - if (warnings) { - jobInfoStatus.push({ - title: 'Errors', - description: warnings, - }); - } + const jobScreenshot = [ + { title: 'Dimensions', description: getDimensions(info) }, + { title: 'Layout', description: info.layout?.id || UNKNOWN }, + { title: 'Browser Type', description: info.browser_type || NA }, + ]; - const jobInfoParts: JobInfoMap = { - datetimes: jobInfoDateTimes, - payload: jobInfoPayload, - status: jobInfoStatus, - }; + const warningInfo = warnings && [{ title: 'Errors', description: warnings }]; return ( - - + <> + - - - - + + {USES_HEADLESS_JOB_TYPES.includes(jobType) ? ( + <> + + + + ) : null} + {warningInfo ? ( + <> + + + + ) : null} + ); } @@ -240,23 +169,31 @@ export class ReportInfoButton extends Component { } return ( - - + <> + + + {flyout} - + ); } private loadInfo = async () => { this.setState({ isLoading: true }); try { - const info: ReportApiJSON = await this.props.apiClient.getInfo(this.props.jobId); + const info = await this.props.apiClient.getInfo(this.props.jobId); if (this.mounted) { this.setState({ isLoading: false, info }); } @@ -287,3 +224,5 @@ export class ReportInfoButton extends Component { } }; } + +export const ReportInfoButton = injectI18n(ReportInfoButtonUi); diff --git a/x-pack/plugins/reporting/public/management/report_listing.test.tsx b/x-pack/plugins/reporting/public/management/report_listing.test.tsx index 0b278cbaa0449f4..0c9b85c2f8cbb05 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.test.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.test.tsx @@ -5,24 +5,20 @@ * 2.0. */ -import React from 'react'; -import { Observable } from 'rxjs'; +import { registerTestBed } from '@kbn/test/jest'; import { UnwrapPromise } from '@kbn/utility-types'; - +import React from 'react'; import { act } from 'react-dom/test-utils'; - -import { registerTestBed } from '@kbn/test/jest'; - -import type { SharePluginSetup, LocatorPublic } from '../../../../../src/plugins/share/public'; +import { Observable } from 'rxjs'; import type { NotificationsSetup } from '../../../../../src/core/public'; import { httpServiceMock, notificationServiceMock } from '../../../../../src/core/public/mocks'; - +import type { LocatorPublic, SharePluginSetup } from '../../../../../src/plugins/share/public'; import type { ILicense } from '../../../licensing/public'; - -import { IlmPolicyMigrationStatus } from '../../common/types'; - -import { ReportingAPIClient, InternalApiClientClientProvider } from '../lib/reporting_api_client'; +import { IlmPolicyMigrationStatus, ReportApiJSON } from '../../common/types'; import { IlmPolicyStatusContextProvider } from '../lib/ilm_policy_status_context'; +import { Job } from '../lib/job'; +import { InternalApiClientClientProvider, ReportingAPIClient } from '../lib/reporting_api_client'; +import { Props, ReportListing } from './report_listing'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { return { @@ -30,21 +26,20 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { }; }); -import { ReportListing, Props } from './report_listing'; +const mockJobs: ReportApiJSON[] = [ + { id: 'k90e51pk1ieucbae0c3t8wo2', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 0, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '1970-01-01T00:00:00.000Z', status: 'pending', timeout: 300000 }, // prettier-ignore + { id: 'k90e51pk1ieucbae0c3t8wo1', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T21:06:14.526Z', started_at: '2020-04-14T21:01:14.526Z', status: 'processing', timeout: 300000 }, + { id: 'k90cmthd1gv8cbae0c2le8bo', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T20:19:14.748Z', created_at: '2020-04-14T20:19:02.977Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T20:24:04.073Z', started_at: '2020-04-14T20:19:04.073Z', status: 'completed', timeout: 300000 }, + { id: 'k906958e1d4wcbae0c9hip1a', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:21:08.223Z', created_at: '2020-04-14T17:20:27.326Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 49468, warnings: [ 'An error occurred when trying to read the page for visualization panel info. You may need to increase \'xpack.reporting.capture.timeouts.waitForElements\'. TimeoutError: waiting for selector "[data-shared-item],[data-shared-items-count]" failed: timeout 30000ms exceeded', ] }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:25:29.444Z', started_at: '2020-04-14T17:20:29.444Z', status: 'completed_with_warnings', timeout: 300000 }, + { id: 'k9067y2a1d4wcbae0cad38n0', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:53.244Z', created_at: '2020-04-14T17:19:31.379Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:24:39.883Z', started_at: '2020-04-14T17:19:39.883Z', status: 'completed', timeout: 300000 }, + { id: 'k9067s1m1d4wcbae0cdnvcms', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:36.822Z', created_at: '2020-04-14T17:19:23.578Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:24:25.247Z', started_at: '2020-04-14T17:19:25.247Z', status: 'completed', timeout: 300000 }, + { id: 'k9065q3s1d4wcbae0c00fxlh', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:18:03.910Z', created_at: '2020-04-14T17:17:47.752Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:22:50.379Z', started_at: '2020-04-14T17:17:50.379Z', status: 'completed', timeout: 300000 }, + { id: 'k905zdw11d34cbae0c3y6tzh', index: '.reporting-2020.04.12', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:13:03.719Z', created_at: '2020-04-14T17:12:51.985Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad' }, output: { content_type: 'application/pdf', size: 80262 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 720, width: 1080 }, id: 'preserve_layout' }, objectType: 'canvas workpad', title: 'My Canvas Workpad' }, process_expiration: '2020-04-14T17:17:52.431Z', started_at: '2020-04-14T17:12:52.431Z', status: 'completed', timeout: 300000 }, + { id: 'k8t4ylcb07mi9d006214ifyg', index: '.reporting-2020.04.05', migration_version: '7.15.0', attempts: 1, browser_type: 'chromium', completed_at: '2020-04-09T19:10:10.049Z', created_at: '2020-04-09T19:09:52.139Z', created_by: 'elastic', jobtype: 'PNG', kibana_id: 'f2e59b4e-f79b-4a48-8a7d-6d50a3c1d914', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'png', objectType: 'visualization' }, output: { content_type: 'image/png', size: 123456789 }, payload: { browserTimezone: 'America/Phoenix', layout: { dimensions: { height: 1575, width: 1423 }, id: 'png' }, objectType: 'visualization', title: 'count' }, process_expiration: '2020-04-09T19:14:54.570Z', started_at: '2020-04-09T19:09:54.570Z', status: 'completed', timeout: 300000 }, +]; // prettier-ignore const reportingAPIClient = { - list: () => - Promise.resolve([ - { _id: 'k90e51pk1ieucbae0c3t8wo2', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 0, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T21:01:13.062Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '1970-01-01T00:00:00.000Z', status: 'pending', timeout: 300000, }, sort: [1586898073064], }, // prettier-ignore - { _id: 'k90e51pk1ieucbae0c3t8wo1', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', created_at: '2020-04-14T21:01:13.064Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T21:01:13.062Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T21:06:14.526Z', started_at: '2020-04-14T21:01:14.526Z', status: 'processing', timeout: 300000, }, sort: [1586898073064], }, - { _id: 'k90cmthd1gv8cbae0c2le8bo', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T20:19:14.748Z', created_at: '2020-04-14T20:19:02.977Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T20:19:02.976Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T20:24:04.073Z', started_at: '2020-04-14T20:19:04.073Z', status: 'completed', timeout: 300000, }, sort: [1586895542977], }, - { _id: 'k906958e1d4wcbae0c9hip1a', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:21:08.223Z', created_at: '2020-04-14T17:20:27.326Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 49468, warnings: [ 'An error occurred when trying to read the page for visualization panel info. You may need to increase \'xpack.reporting.capture.timeouts.waitForElements\'. TimeoutError: waiting for selector "[data-shared-item],[data-shared-items-count]" failed: timeout 30000ms exceeded', ], }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:20:27.326Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e8-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:25:29.444Z', started_at: '2020-04-14T17:20:29.444Z', status: 'completed_with_warnings', timeout: 300000, }, sort: [1586884827326], }, - { _id: 'k9067y2a1d4wcbae0cad38n0', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:53.244Z', created_at: '2020-04-14T17:19:31.379Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:19:31.378Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:24:39.883Z', started_at: '2020-04-14T17:19:39.883Z', status: 'completed', timeout: 300000, }, sort: [1586884771379], }, - { _id: 'k9067s1m1d4wcbae0cdnvcms', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:19:36.822Z', created_at: '2020-04-14T17:19:23.578Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:19:23.578Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:24:25.247Z', started_at: '2020-04-14T17:19:25.247Z', status: 'completed', timeout: 300000, }, sort: [1586884763578], }, - { _id: 'k9065q3s1d4wcbae0c00fxlh', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:18:03.910Z', created_at: '2020-04-14T17:17:47.752Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:17:47.750Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:22:50.379Z', started_at: '2020-04-14T17:17:50.379Z', status: 'completed', timeout: 300000, }, sort: [1586884667752], }, - { _id: 'k905zdw11d34cbae0c3y6tzh', _index: '.reporting-2020.04.12', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-14T17:13:03.719Z', created_at: '2020-04-14T17:12:51.985Z', created_by: 'elastic', jobtype: 'printable_pdf', kibana_id: '5b2de169-2785-441b-ae8c-186a1936b17d', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'preserve_layout', objectType: 'canvas workpad', }, output: { content_type: 'application/pdf', size: 80262, }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-14T17:12:51.984Z', layout: { dimensions: { height: 720, width: 1080, }, id: 'preserve_layout', }, objectType: 'canvas workpad', relativeUrls: [ '/s/hsyjklk/app/canvas#/export/workpad/pdf/workpad-53d38306-eda4-410f-b5e7-efbeca1a8c63/page/1', ], title: 'My Canvas Workpad', }, priority: 10, process_expiration: '2020-04-14T17:17:52.431Z', started_at: '2020-04-14T17:12:52.431Z', status: 'completed', timeout: 300000, }, sort: [1586884371985], }, - { _id: 'k8t4ylcb07mi9d006214ifyg', _index: '.reporting-2020.04.05', _score: null, _source: { attempts: 1, browser_type: 'chromium', completed_at: '2020-04-09T19:10:10.049Z', created_at: '2020-04-09T19:09:52.139Z', created_by: 'elastic', jobtype: 'PNG', kibana_id: 'f2e59b4e-f79b-4a48-8a7d-6d50a3c1d914', kibana_name: 'spicy.local', max_attempts: 1, meta: { layout: 'png', objectType: 'visualization', }, output: { content_type: 'image/png', }, payload: { basePath: '/kbn', browserTimezone: 'America/Phoenix', forceNow: '2020-04-09T19:09:52.137Z', layout: { dimensions: { height: 1575, width: 1423, }, id: 'png', }, objectType: 'visualization', relativeUrl: "/s/hsyjklk/app/visualize#/edit/94d1fe40-7a94-11ea-b373-0749f92ad295?_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!((enabled:!t,id:'1',params:(),schema:metric,type:count)),params:(addLegend:!f,addTooltip:!t,metric:(colorSchema:'Green%20to%20Red',colorsRange:!((from:0,to:10000)),invertColors:!f,labels:(show:!t),metricColorMode:None,percentageMode:!f,style:(bgColor:!f,bgFill:%23000,fontSize:60,labelColor:!f,subText:''),useRanges:!f),type:metric),title:count,type:metric))&_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15y,to:now))&indexPattern=d81752b0-7434-11ea-be36-1f978cda44d4&type=metric", title: 'count', }, priority: 10, process_expiration: '2020-04-09T19:14:54.570Z', started_at: '2020-04-09T19:09:54.570Z', status: 'completed', timeout: 300000, }, sort: [1586459392139], }, - ]), // prettier-ignore + list: () => Promise.resolve(mockJobs.map((j) => new Job(j))), total: () => Promise.resolve(18), migrateReportingIndicesIlmPolicy: jest.fn(), } as any; diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx index dd41314b4883fa8..30c9325a0f34f5c 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.tsx @@ -9,15 +9,14 @@ import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, + EuiLoadingSpinner, EuiPageHeader, EuiSpacer, EuiText, EuiTextColor, - EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { get } from 'lodash'; import moment from 'moment'; import { Component, default as React, Fragment } from 'react'; import { Subscription } from 'rxjs'; @@ -26,37 +25,16 @@ import { ILicense, LicensingPluginSetup } from '../../../licensing/public'; import { JOB_STATUSES as JobStatuses } from '../../common/constants'; import { Poller } from '../../common/poller'; import { durationToNumber } from '../../common/schema_utils'; -import { checkLicense } from '../lib/license_check'; -import { - JobQueueEntry, - ReportingAPIClient, - useInternalApiClient, -} from '../lib/reporting_api_client'; import { useIlmPolicyStatus, UseIlmPolicyStatusReturn } from '../lib/ilm_policy_status_context'; -import type { SharePluginSetup } from '../shared_imports'; +import { Job } from '../lib/job'; +import { checkLicense } from '../lib/license_check'; +import { ReportingAPIClient, useInternalApiClient } from '../lib/reporting_api_client'; import { ClientConfigType } from '../plugin'; +import type { SharePluginSetup } from '../shared_imports'; import { ReportDeleteButton, ReportDownloadButton, ReportErrorButton, ReportInfoButton } from './'; -import { ReportDiagnostic } from './report_diagnostic'; -import { MigrateIlmPolicyCallOut } from './migrate_ilm_policy_callout'; import { IlmPolicyLink } from './ilm_policy_link'; - -export interface Job { - id: string; - type: string; - object_type: string; - object_title: string; - created_by?: string | false; - created_at: string; - started_at?: string; - completed_at?: string; - status: string; - statusLabel: string; - max_size_reached?: boolean; - attempts: number; - max_attempts: number; - csv_contains_formulas: boolean; - warnings?: string[]; -} +import { MigrateIlmPolicyCallOut } from './migrate_ilm_policy_callout'; +import { ReportDiagnostic } from './report_diagnostic'; export interface Props { intl: InjectedIntl; @@ -251,7 +229,7 @@ class ReportListingUi extends Component { id: 'xpack.reporting.listing.table.deleteConfim', defaultMessage: `The {reportTitle} report was deleted`, }, - { reportTitle: record.object_title } + { reportTitle: record.title } ) ); } catch (error) { @@ -293,7 +271,7 @@ class ReportListingUi extends Component { this.setState(() => ({ isLoading: true })); } - let jobs: JobQueueEntry[]; + let jobs: Job[]; let total: number; try { jobs = await this.props.apiClient.list(this.state.page); @@ -325,28 +303,7 @@ class ReportListingUi extends Component { this.setState(() => ({ isLoading: false, total, - jobs: jobs.map( - (job: JobQueueEntry): Job => { - const { _source: source } = job; - return { - id: job._id, - type: source.jobtype, - object_type: source.payload.objectType, - object_title: source.payload.title, - created_by: source.created_by, - created_at: source.created_at, - started_at: source.started_at, - completed_at: source.completed_at, - status: source.status, - statusLabel: jobStatusLabelsMap.get(source.status as JobStatuses) || source.status, - max_size_reached: source.output ? source.output.max_size_reached : false, - attempts: source.attempts, - max_attempts: source.max_attempts, - csv_contains_formulas: get(source, 'output.csv_contains_formulas'), - warnings: source.output ? source.output.warnings : undefined, - }; - } - ), + jobs, })); } }; @@ -369,7 +326,7 @@ class ReportListingUi extends Component { const tableColumns = [ { - field: 'object_title', + field: 'title', name: intl.formatMessage({ id: 'xpack.reporting.listing.tableColumns.reportTitle', defaultMessage: 'Report', @@ -379,7 +336,7 @@ class ReportListingUi extends Component {
{objectTitle}
- {record.object_type} + {record.objectType}
); diff --git a/x-pack/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/plugins/reporting/server/lib/enqueue_job.ts index 70492b415f961d7..ec2e443d86c8095 100644 --- a/x-pack/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/plugins/reporting/server/lib/enqueue_job.ts @@ -47,7 +47,6 @@ export function enqueueJobFactory( reporting.getStore(), ]); - const config = reporting.getConfig(); const job = await createJob!(jobParams, context, request); // 1. Add the report to ReportingStore to show as pending @@ -55,7 +54,6 @@ export function enqueueJobFactory( new Report({ jobtype: exportType.jobType, created_by: user ? user.username : false, - max_attempts: config.get('capture', 'maxAttempts'), // NOTE: since max attempts is stored in the document, changing the capture.maxAttempts setting does not affect existing pending reports payload: job, meta: { objectType: jobParams.objectType, diff --git a/x-pack/plugins/reporting/server/lib/store/report.test.ts b/x-pack/plugins/reporting/server/lib/store/report.test.ts index a8d14e12a738be8..4bc45fd745a567c 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.test.ts @@ -48,7 +48,7 @@ describe('Class Report', () => { index: '.reporting-test-index-12345', jobtype: 'test-report', max_attempts: 50, - payload: { headers: 'payload_test_field', objectType: 'testOt' }, + payload: { objectType: 'testOt' }, meta: { objectType: 'test' }, status: 'pending', timeout: 30000, @@ -109,7 +109,7 @@ describe('Class Report', () => { jobtype: 'test-report', max_attempts: 50, meta: { objectType: 'stange' }, - payload: { headers: 'payload_test_field', objectType: 'testOt' }, + payload: { objectType: 'testOt' }, started_at: undefined, status: 'pending', timeout: 30000, @@ -117,7 +117,7 @@ describe('Class Report', () => { }); it('throws error if converted to task JSON before being synced with ES storage', () => { - const report = new Report({} as any); + const report = new Report({ jobtype: 'spam', payload: {} } as any); expect(() => report.updateWithEsDoc(report)).toThrowErrorMatchingInlineSnapshot( `"Report object from ES has missing fields!"` ); diff --git a/x-pack/plugins/reporting/server/lib/store/report.ts b/x-pack/plugins/reporting/server/lib/store/report.ts index fa5b91527ccc471..0f970ead7c75cb8 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { omit } from 'lodash'; import moment from 'moment'; // @ts-ignore no module definition import Puid from 'puid'; @@ -33,23 +34,25 @@ export class Report implements Partial { public _primary_term?: number; // set by ES public _seq_no?: number; // set by ES - public readonly kibana_name: ReportSource['kibana_name']; - public readonly kibana_id: ReportSource['kibana_id']; public readonly jobtype: ReportSource['jobtype']; public readonly created_at: ReportSource['created_at']; public readonly created_by: ReportSource['created_by']; public readonly payload: ReportSource['payload']; public readonly meta: ReportSource['meta']; - public readonly max_attempts: ReportSource['max_attempts']; - public readonly browser_type?: ReportSource['browser_type']; + public readonly browser_type: ReportSource['browser_type']; public readonly status: ReportSource['status']; public readonly attempts: ReportSource['attempts']; - public readonly output?: ReportSource['output']; - public readonly started_at?: ReportSource['started_at']; - public readonly completed_at?: ReportSource['completed_at']; - public readonly timeout?: ReportSource['timeout']; + + // fields with undefined values exist in report jobs that have not been claimed + public readonly kibana_name: ReportSource['kibana_name']; + public readonly kibana_id: ReportSource['kibana_id']; + public readonly output: ReportSource['output']; + public readonly started_at: ReportSource['started_at']; + public readonly completed_at: ReportSource['completed_at']; + public readonly timeout: ReportSource['timeout']; + public readonly max_attempts: ReportSource['max_attempts']; public process_expiration?: ReportSource['process_expiration']; public migration_version: string; @@ -66,20 +69,29 @@ export class Report implements Partial { this.migration_version = MIGRATION_VERSION; - this.payload = opts.payload!; - this.kibana_name = opts.kibana_name!; - this.kibana_id = opts.kibana_id!; - this.jobtype = opts.jobtype!; - this.max_attempts = opts.max_attempts!; - this.attempts = opts.attempts || 0; + // see enqueue_job for all the fields that are expected to exist when adding a report + if (opts.jobtype == null) { + throw new Error(`jobtype is expected!`); + } + if (opts.payload == null) { + throw new Error(`payload is expected!`); + } - this.process_expiration = opts.process_expiration; + this.payload = opts.payload; + this.kibana_id = opts.kibana_id; + this.kibana_name = opts.kibana_name; + this.jobtype = opts.jobtype; + this.max_attempts = opts.max_attempts; + this.attempts = opts.attempts || 0; this.timeout = opts.timeout; + this.browser_type = opts.browser_type; + this.process_expiration = opts.process_expiration; + this.started_at = opts.started_at; + this.completed_at = opts.completed_at; this.created_at = opts.created_at || moment.utc().toISOString(); this.created_by = opts.created_by || false; this.meta = opts.meta || { objectType: 'unknown' }; - this.browser_type = opts.browser_type; this.status = opts.status || JOB_STATUSES.PENDING; this.output = opts.output || null; @@ -113,9 +125,9 @@ export class Report implements Partial { created_by: this.created_by, payload: this.payload, meta: this.meta, - timeout: this.timeout!, + timeout: this.timeout, max_attempts: this.max_attempts, - browser_type: this.browser_type!, + browser_type: this.browser_type, status: this.status, attempts: this.attempts, started_at: this.started_at, @@ -142,7 +154,6 @@ export class Report implements Partial { payload: this.payload, meta: this.meta, attempts: this.attempts, - max_attempts: this.max_attempts, }; } @@ -158,7 +169,6 @@ export class Report implements Partial { jobtype: this.jobtype, created_at: this.created_at, created_by: this.created_by, - payload: this.payload, meta: this.meta, timeout: this.timeout, max_attempts: this.max_attempts, @@ -167,6 +177,9 @@ export class Report implements Partial { attempts: this.attempts, started_at: this.started_at, completed_at: this.completed_at, + migration_version: this.migration_version, + payload: omit(this.payload, 'headers'), + output: omit(this.output, 'content'), }; } } diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index 7a7dd20e1b25cf6..40a73f294c5a973 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -29,6 +29,7 @@ export type ReportProcessingFields = Required<{ browser_type: Report['browser_type']; attempts: Report['attempts']; started_at: Report['started_at']; + max_attempts: Report['max_attempts']; timeout: Report['timeout']; process_expiration: Report['process_expiration']; }>; diff --git a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts index f9e2cd82b0805c5..a52f452436d6db8 100644 --- a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts +++ b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts @@ -135,9 +135,8 @@ export class ExecuteReportTask implements ReportingTask { const m = moment(); - // check if job has exceeded maxAttempts (stored in job params) and somehow hasn't been marked as failed yet - // NOTE: the max attempts value comes from the stored document, so changing the capture.maxAttempts config setting does not affect existing pending reports - const maxAttempts = task.max_attempts; + // check if job has exceeded the configured maxAttempts + const maxAttempts = this.config.capture.maxAttempts; if (report.attempts >= maxAttempts) { const err = new Error(`Max attempts reached (${maxAttempts}). Queue timeout reached.`); await this._failJob(report, err); @@ -153,6 +152,7 @@ export class ExecuteReportTask implements ReportingTask { kibana_name: this.kibanaName, browser_type: this.config.capture.browser.type, attempts: report.attempts + 1, + max_attempts: maxAttempts, started_at: startTime, timeout: queueTimeout, process_expiration: expirationTime, @@ -195,7 +195,7 @@ export class ExecuteReportTask implements ReportingTask { const completedTime = moment().toISOString(); const doc: ReportFailedFields = { completed_at: completedTime, - output: docOutput, + output: docOutput ?? null, }; return await store.setReportFailed(report, doc); @@ -306,11 +306,14 @@ export class ExecuteReportTask implements ReportingTask { } if (!report) { + this.reporting.untrackReport(jobId); errorLogger(this.logger, `Job ${jobId} could not be claimed. Exiting...`); return; } - const { jobtype: jobType, attempts, max_attempts: maxAttempts } = report; + const { jobtype: jobType, attempts } = report; + const maxAttempts = this.config.capture.maxAttempts; + this.logger.debug( `Starting ${jobType} report ${jobId}: attempt ${attempts} of ${maxAttempts}.` ); diff --git a/x-pack/plugins/reporting/server/lib/tasks/index.ts b/x-pack/plugins/reporting/server/lib/tasks/index.ts index c02b06d97adc7df..662528124e6c02b 100644 --- a/x-pack/plugins/reporting/server/lib/tasks/index.ts +++ b/x-pack/plugins/reporting/server/lib/tasks/index.ts @@ -27,7 +27,6 @@ export interface ReportTaskParams { created_at: ReportSource['created_at']; created_by: ReportSource['created_by']; jobtype: ReportSource['jobtype']; - max_attempts: ReportSource['max_attempts']; attempts: ReportSource['attempts']; meta: ReportSource['meta']; } diff --git a/x-pack/plugins/reporting/server/routes/jobs.test.ts b/x-pack/plugins/reporting/server/routes/jobs.test.ts index 3f913dfd1f32f3f..3040ea351f7d048 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.test.ts @@ -152,7 +152,10 @@ describe('GET /api/reporting/jobs/download', () => { it('returns a 401 if not a valid job type', async () => { mockEsClient.search.mockResolvedValueOnce({ - body: getHits({ jobtype: 'invalidJobType' }), + body: getHits({ + jobtype: 'invalidJobType', + payload: { title: 'invalid!' }, + }), } as any); registerJobInfoRoutes(core); @@ -163,7 +166,11 @@ describe('GET /api/reporting/jobs/download', () => { it('when a job is incomplete', async () => { mockEsClient.search.mockResolvedValueOnce({ - body: getHits({ jobtype: 'unencodedJobType', status: 'pending' }), + body: getHits({ + jobtype: 'unencodedJobType', + status: 'pending', + payload: { title: 'incomplete!' }, + }), } as any); registerJobInfoRoutes(core); @@ -182,6 +189,7 @@ describe('GET /api/reporting/jobs/download', () => { jobtype: 'unencodedJobType', status: 'failed', output: { content: 'job failure message' }, + payload: { title: 'failing job!' }, }), } as any); registerJobInfoRoutes(core); @@ -207,9 +215,7 @@ describe('GET /api/reporting/jobs/download', () => { jobtype: jobType, status: 'completed', output: { content: outputContent, content_type: outputContentType }, - payload: { - title, - }, + payload: { title }, }); }; diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts index 3f2a95a34224ce9..0d0332983d6bc39 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -71,7 +71,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { path: `${MAIN_ENTRY}/count`, validate: false, }, - userHandler(async (user, context, req, res) => { + userHandler(async (user, context, _req, res) => { // ensure the async dependencies are loaded if (!context.reporting) { return handleUnavailable(res); @@ -115,22 +115,20 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { } = await reporting.getLicenseInfo(); const jobsQuery = jobsQueryFactory(reporting); - const result = await jobsQuery.get(user, docId, { includeContent: true }); + const result = await jobsQuery.getContent(user, docId); if (!result) { throw Boom.notFound(); } - const { - _source: { jobtype: jobType, output: jobOutput }, - } = result; + const { jobtype: jobType, output } = result; if (!jobTypes.includes(jobType)) { throw Boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); } return res.ok({ - body: jobOutput || {}, + body: output?.content ?? {}, headers: { 'content-type': 'application/json', }, @@ -166,21 +164,14 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { throw Boom.notFound(); } - const { _source: job } = result; - const { jobtype: jobType, payload: jobPayload } = job; + const { jobtype: jobType } = result; if (!jobTypes.includes(jobType)) { throw Boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); } return res.ok({ - body: { - ...job, - payload: { - ...jobPayload, - headers: undefined, - }, - }, + body: result, headers: { 'content-type': 'application/json', }, diff --git a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts index 4e8e888e4e26659..2141252c70bfa05 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -7,12 +7,11 @@ // @ts-ignore import contentDisposition from 'content-disposition'; -import { get } from 'lodash'; import { CSV_JOB_TYPE, CSV_JOB_TYPE_DEPRECATED } from '../../../common/constants'; import { ExportTypesRegistry, statuses } from '../../lib'; -import { ReportDocument } from '../../lib/store'; import { TaskRunResult } from '../../lib/tasks'; import { ExportTypeDefinition } from '../../types'; +import { ReportContent } from './jobs_query'; export interface ErrorFromPayload { message: string; @@ -35,8 +34,8 @@ const getReportingHeaders = (output: TaskRunResult, exportType: ExportTypeDefini const metaDataHeaders: Record = {}; if (exportType.jobType === CSV_JOB_TYPE || exportType.jobType === CSV_JOB_TYPE_DEPRECATED) { - const csvContainsFormulas = get(output, 'csv_contains_formulas', false); - const maxSizedReach = get(output, 'max_size_reached', false); + const csvContainsFormulas = output.csv_contains_formulas ?? false; + const maxSizedReach = output.max_size_reached ?? false; metaDataHeaders['kbn-csv-contains-formulas'] = csvContainsFormulas; metaDataHeaders['kbn-max-size-reached'] = maxSizedReach; @@ -98,10 +97,12 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist }; } - return function getDocumentPayload(doc: ReportDocument): Payload { - const { status, jobtype: jobType, payload: { title } = { title: '' } } = doc._source; - const { output } = doc._source; - + return function getDocumentPayload({ + status, + jobtype: jobType, + payload: { title } = { title: 'unknown' }, + output, + }: ReportContent): Payload { if (output) { if (status === statuses.JOB_STATUS_COMPLETED || status === statuses.JOB_STATUS_WARNINGS) { return getCompleted(output, jobType, title); diff --git a/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts index 8ffefa9c8a98cf2..f9519f74060f91f 100644 --- a/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts +++ b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts @@ -35,16 +35,14 @@ export function downloadJobResponseHandlerFactory(reporting: ReportingCore) { try { const { docId } = params; - const doc = await jobsQuery.get(user, docId, { includeContent: !opts.excludeContent }); + const doc = await jobsQuery.getContent(user, docId); if (!doc) { return res.notFound(); } - const { jobtype: jobType } = doc._source; - - if (!validJobTypes.includes(jobType)) { + if (!validJobTypes.includes(doc.jobtype)) { return res.unauthorized({ - body: `Sorry, you are not authorized to download ${jobType} reports`, + body: `Sorry, you are not authorized to download ${doc.jobtype} reports`, }); } @@ -81,13 +79,13 @@ export function deleteJobResponseHandlerFactory(reporting: ReportingCore) { params: JobResponseHandlerParams ) { const { docId } = params; - const doc = await jobsQuery.get(user, docId, { includeContent: false }); + const doc = await jobsQuery.get(user, docId); if (!doc) { return res.notFound(); } - const { jobtype: jobType } = doc._source; + const { jobtype: jobType } = doc; if (!validJobTypes.includes(jobType)) { return res.unauthorized({ @@ -96,7 +94,7 @@ export function deleteJobResponseHandlerFactory(reporting: ReportingCore) { } try { - const docIndex = doc._index; + const docIndex = doc.index; await jobsQuery.delete(docIndex, docId); return res.ok({ body: { deleted: true }, diff --git a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts index 2fec34470ff1f1f..76896a7472d59d4 100644 --- a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts +++ b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts @@ -5,20 +5,19 @@ * 2.0. */ +import { ApiResponse } from '@elastic/elasticsearch'; +import { DeleteResponse, SearchHit, SearchResponse } from '@elastic/elasticsearch/api/types'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { i18n } from '@kbn/i18n'; import { UnwrapPromise } from '@kbn/utility-types'; import { ElasticsearchClient } from 'src/core/server'; import { ReportingCore } from '../../'; -import { ReportDocument } from '../../lib/store'; +import { ReportApiJSON, ReportDocument, ReportSource } from '../../../common/types'; +import { Report } from '../../lib/store'; import { ReportingUser } from '../../types'; type SearchRequest = Required>[0]; -interface GetOpts { - includeContent?: boolean; -} - const defaultSize = 10; const getUsername = (user: ReportingUser) => (user ? user.username : false); @@ -33,7 +32,25 @@ function getSearchBody(body: SearchRequest['body']): SearchRequest['body'] { }; } -export function jobsQueryFactory(reportingCore: ReportingCore) { +export type ReportContent = Pick & { + payload?: Pick; +}; + +interface JobsQueryFactory { + list( + jobTypes: string[], + user: ReportingUser, + page: number, + size: number, + jobIds: string[] | null + ): Promise; + count(jobTypes: string[], user: ReportingUser): Promise; + get(user: ReportingUser, id: string): Promise; + getContent(user: ReportingUser, id: string): Promise; + delete(deleteIndex: string, id: string): Promise>; +} + +export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory { function getIndex() { const config = reportingCore.getConfig(); @@ -57,13 +74,7 @@ export function jobsQueryFactory(reportingCore: ReportingCore) { } return { - async list( - jobTypes: string[], - user: ReportingUser, - page = 0, - size = defaultSize, - jobIds: string[] | null - ) { + async list(jobTypes, user, page = 0, size = defaultSize, jobIds) { const username = getUsername(user); const body = getSearchBody({ size, @@ -83,15 +94,23 @@ export function jobsQueryFactory(reportingCore: ReportingCore) { }, }); - const response = await execQuery((elasticsearchClient) => + const response = (await execQuery((elasticsearchClient) => elasticsearchClient.search({ body, index: getIndex() }) + )) as ApiResponse>; + + return ( + response?.body.hits?.hits.map((report: SearchHit) => { + const { _source: reportSource, ...reportHead } = report; + if (reportSource) { + const reportInstance = new Report({ ...reportSource, ...reportHead }); + return reportInstance.toApiJSON(); + } + throw new Error(`Search hit did not include _source!`); + }) ?? [] ); - - // FIXME: return the info in ReportApiJSON format; - return response?.body.hits?.hits ?? []; }, - async count(jobTypes: string[], user: ReportingUser) { + async count(jobTypes, user) { const username = getUsername(user); const body = { query: { @@ -112,14 +131,50 @@ export function jobsQueryFactory(reportingCore: ReportingCore) { return response?.body.count ?? 0; }, - async get(user: ReportingUser, id: string, opts: GetOpts = {}): Promise { + async get(user, id) { + const { logger } = reportingCore.getPluginSetupDeps(); + if (!id) { + logger.warning(`No ID provided for GET`); + return; + } + + const username = getUsername(user); + + const body = getSearchBody({ + query: { + constant_score: { + filter: { + bool: { + must: [{ term: { _id: id } }, { term: { created_by: username } }], + }, + }, + }, + }, + size: 1, + }); + + const response = await execQuery((elasticsearchClient) => + elasticsearchClient.search({ body, index: getIndex() }) + ); + + const result = response?.body.hits.hits[0] as SearchHit | undefined; + if (!result || !result._source) { + logger.warning(`No hits resulted in search`); + return; + } + + const report = new Report({ ...result, ...result._source }); + return report.toApiJSON(); + }, + + async getContent(user, id) { if (!id) { return; } const username = getUsername(user); const body: SearchRequest['body'] = { - ...(opts.includeContent ? { _source: { excludes: [] } } : {}), + _source: { excludes: ['payload.headers'] }, query: { constant_score: { filter: { @@ -140,11 +195,17 @@ export function jobsQueryFactory(reportingCore: ReportingCore) { return; } - // FIXME: return the info in ReportApiJSON format; - return response.body.hits.hits[0] as ReportDocument; + const report = response.body.hits.hits[0] as ReportDocument; + + return { + status: report._source.status, + jobtype: report._source.jobtype, + output: report._source.output, + payload: report._source.payload, + }; }, - async delete(deleteIndex: string, id: string) { + async delete(deleteIndex, id) { try { const { asInternalUser: elasticsearchClient } = await reportingCore.getEsClient(); const query = { id, index: deleteIndex, refresh: true }; diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts index e30cde498928452..10ed18eb91d0cc9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts @@ -88,6 +88,7 @@ import { import { changeRowsPerPageTo100, deleteFirstRule, + deleteRuleFromDetailsPage, deleteSelectedRules, editFirstRule, filterByCustomRules, @@ -237,8 +238,10 @@ describe('Custom detection rules deletion and edition', () => { goToManageAlertsDetectionRules(); waitForAlertsIndexToBeCreated(); createCustomRuleActivated(getNewRule(), 'rule1'); - createCustomRuleActivated(getNewOverrideRule(), 'rule2'); - createCustomRuleActivated(getExistingRule(), 'rule3'); + createCustomRuleActivated(getNewRule(), 'rule2'); + + createCustomRuleActivated(getNewOverrideRule(), 'rule3'); + createCustomRuleActivated(getExistingRule(), 'rule4'); reload(); }); @@ -292,7 +295,7 @@ describe('Custom detection rules deletion and edition', () => { }); cy.get(SHOWING_RULES_TEXT).should( 'have.text', - `Showing ${expectedNumberOfRulesAfterDeletion} rule` + `Showing ${expectedNumberOfRulesAfterDeletion} rules` ); cy.get(CUSTOM_RULES_BTN).should( 'have.text', @@ -300,6 +303,38 @@ describe('Custom detection rules deletion and edition', () => { ); }); }); + + it('Deletes one rule from detail page', () => { + cy.get(RULES_TABLE) + .find(RULES_ROW) + .then((rules) => { + const initialNumberOfRules = rules.length; + const expectedNumberOfRulesAfterDeletion = initialNumberOfRules - 1; + + goToRuleDetails(); + cy.intercept('POST', '/api/detection_engine/rules/_bulk_delete').as('deleteRule'); + + deleteRuleFromDetailsPage(); + + cy.waitFor('@deleteRule').then(() => { + cy.get(RULES_TABLE).should('exist'); + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should( + 'eql', + expectedNumberOfRulesAfterDeletion + ); + }); + cy.get(SHOWING_RULES_TEXT).should( + 'have.text', + `Showing ${expectedNumberOfRulesAfterDeletion} rules` + ); + cy.get(CUSTOM_RULES_BTN).should( + 'have.text', + `Custom rules (${expectedNumberOfRulesAfterDeletion})` + ); + }); + }); + }); }); context('Edition', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts index 9986d9d2afbd9ac..0755142fbdc5818 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts @@ -20,26 +20,6 @@ import { populateTimeline } from '../../tasks/timeline'; import { HOSTS_URL } from '../../urls/navigation'; -const RowRenderersId = [ - 'alerts', - 'auditd', - 'auditd_file', - 'library', - 'netflow', - 'plain', - 'registry', - 'suricata', - 'system', - 'system_dns', - 'system_endgame_process', - 'system_file', - 'system_fim', - 'system_security_event', - 'system_socket', - 'threat_match', - 'zeek', -]; - describe('Row renderers', () => { beforeEach(() => { cleanKibana(); @@ -100,9 +80,5 @@ describe('Row renderers', () => { .should('not.be.checked'); cy.wait('@updateTimeline').its('response.statusCode').should('eq', 200); - - cy.wait('@updateTimeline').then((interception) => { - expect(interception.request.body.timeline.excludedRowRendererIds).to.eql(RowRenderersId); - }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 0bf0e5a09e328b8..9a23d98c1e91fee 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -103,3 +103,5 @@ export const RULES_EMPTY_PROMPT = '[data-test-subj="rulesEmptyPrompt"]'; export const RULES_DELETE_CONFIRMATION_MODAL = '[data-test-subj="allRulesDeleteConfirmationModal"]'; export const MODAL_CONFIRMATION_BTN = '[data-test-subj="confirmModalConfirmButton"]'; + +export const RULE_DETAILS_DELETE_BTN = '[data-test-subj="rules-details-delete-rule"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index 78298c988107727..6dbd1ae16c4add6 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -42,6 +42,7 @@ import { ACTIVATE_RULE_BULK_BTN, DEACTIVATE_RULE_BULK_BTN, EXPORT_RULE_BULK_BTN, + RULE_DETAILS_DELETE_BTN, } from '../screens/alerts_detection_rules'; import { ALL_ACTIONS, DELETE_RULE } from '../screens/rule_details'; @@ -107,6 +108,17 @@ export const deleteSelectedRules = () => { cy.get(DELETE_RULE_BULK_BTN).click(); }; +export const deleteRuleFromDetailsPage = () => { + cy.get(ALL_ACTIONS).should('be.visible'); + cy.root() + .pipe(($el) => { + $el.find(ALL_ACTIONS).trigger('click'); + return $el.find(RULE_DETAILS_DELETE_BTN); + }) + .should(($el) => expect($el).to.be.visible); + cy.get(RULE_DETAILS_DELETE_BTN).pipe(($el) => $el.trigger('click')); +}; + export const duplicateSelectedRules = () => { cy.get(BULK_ACTIONS_BTN).click({ force: true }); cy.get(DUPLICATE_RULE_BULK_BTN).click(); @@ -143,7 +155,7 @@ export const goToCreateNewRule = () => { }; export const goToRuleDetails = () => { - cy.get(RULE_NAME).click({ force: true }); + cy.get(RULE_NAME).first().click({ force: true }); }; export const loadPrebuiltDetectionRules = () => { @@ -190,7 +202,8 @@ export const sortByActivatedRules = () => { export const waitForRulesTableToBeLoaded = () => { cy.get(RULES_TABLE_INITIAL_LOADING_INDICATOR).should('exist'); - cy.get(RULES_TABLE_INITIAL_LOADING_INDICATOR).should('not.exist'); + // Wait up to 5 minutes for the rules to load as in CI containers this can be very slow + cy.get(RULES_TABLE_INITIAL_LOADING_INDICATOR, { timeout: 300000 }).should('not.exist'); }; export const waitForRulesTableToBeRefreshed = () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index 2146123deafd58b..9f2728e0813f4e7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -16,7 +16,6 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { noop } from 'lodash/fp'; -import { useHistory } from 'react-router-dom'; import { Rule } from '../../../containers/detection_engine/rules'; import * as i18n from './translations'; import * as i18nActions from '../../../pages/detection_engine/rules/translations'; @@ -31,6 +30,7 @@ import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_d import { getToolTipContent } from '../../../../common/utils/privileges'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; import { useKibana } from '../../../../common/lib/kibana'; +import { APP_ID, SecurityPageName } from '../../../../../common/constants'; const MyEuiButtonIcon = styled(EuiButtonIcon)` &.euiButtonIcon { @@ -58,13 +58,15 @@ const RuleActionsOverflowComponent = ({ canDuplicateRuleWithActions, }: RuleActionsOverflowComponentProps) => { const [isPopoverOpen, , closePopover, togglePopover] = useBoolState(); - const history = useHistory(); const { navigateToApp } = useKibana().services.application; const [, dispatchToaster] = useStateToaster(); const onRuleDeletedCallback = useCallback(() => { - history.push(getRulesUrl()); - }, [history]); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + path: getRulesUrl(), + }); + }, [navigateToApp]); const actions = useMemo( () => diff --git a/x-pack/plugins/security_solution/server/features.ts b/x-pack/plugins/security_solution/server/features.ts new file mode 100644 index 000000000000000..1756f4cef63e5e0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/features.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +import { KibanaFeatureConfig, SubFeatureConfig } from '../../features/common'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; +import { APP_ID, SERVER_APP_ID } from '../common/constants'; +import { savedObjectTypes } from './saved_objects'; + +const CASES_SUB_FEATURE: SubFeatureConfig = { + name: 'Cases', + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + id: 'cases_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + // using variables with underscores here otherwise when we retrieve them from the kibana + // capabilities in a hook I get type errors regarding boolean | ReadOnly<{[x: string]: boolean}> + ui: ['crud_cases', 'read_cases'], // uiCapabilities.siem.crud_cases + cases: { + all: [APP_ID], + }, + }, + { + id: 'cases_read', + includeIn: 'read', + name: 'Read', + savedObject: { + all: [], + read: [], + }, + // using variables with underscores here otherwise when we retrieve them from the kibana + // capabilities in a hook I get type errors regarding boolean | ReadOnly<{[x: string]: boolean}> + ui: ['read_cases'], // uiCapabilities.siem.read_cases + cases: { + read: [APP_ID], + }, + }, + ], + }, + ], +}; + +export const getAlertsSubFeature = (ruleTypes: string[]): SubFeatureConfig => ({ + name: i18n.translate('xpack.securitySolution.featureRegistry.manageAlertsName', { + defaultMessage: 'Alerts', + }), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + id: 'alerts_all', + name: i18n.translate('xpack.securitySolution.featureRegistry.subfeature.alertsAllName', { + defaultMessage: 'All', + }), + includeIn: 'all' as 'all', + alerting: { + alert: { + all: ruleTypes, + }, + }, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + { + id: 'alerts_read', + name: i18n.translate('xpack.securitySolution.featureRegistry.subfeature.alertsReadName', { + defaultMessage: 'Read', + }), + includeIn: 'read' as 'read', + alerting: { + alert: { + read: ruleTypes, + }, + }, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + ], + }, + ], +}); + +export const getKibanaPrivilegesFeaturePrivileges = ( + ruleTypes: string[], + isRuleRegistryEnabled: boolean +): KibanaFeatureConfig => ({ + id: SERVER_APP_ID, + name: i18n.translate('xpack.securitySolution.featureRegistry.linkSecuritySolutionTitle', { + defaultMessage: 'Security', + }), + order: 1100, + category: DEFAULT_APP_CATEGORIES.security, + app: [APP_ID, 'kibana'], + catalogue: ['securitySolution'], + management: { + insightsAndAlerting: ['triggersActions'], + }, + alerting: ruleTypes, + cases: [APP_ID], + subFeatures: isRuleRegistryEnabled + ? [{ ...CASES_SUB_FEATURE }, { ...getAlertsSubFeature(ruleTypes) }] + : [{ ...CASES_SUB_FEATURE }], + privileges: { + all: { + app: [APP_ID, 'kibana'], + catalogue: ['securitySolution'], + api: ['securitySolution', 'lists-all', 'lists-read', 'rac'], + savedObject: { + all: ['alert', 'exception-list', 'exception-list-agnostic', ...savedObjectTypes], + read: [], + }, + alerting: { + rule: { + all: ruleTypes, + }, + }, + management: { + insightsAndAlerting: ['triggersActions'], + }, + ui: ['show', 'crud'], + }, + read: { + app: [APP_ID, 'kibana'], + catalogue: ['securitySolution'], + api: ['securitySolution', 'lists-read', 'rac'], + savedObject: { + all: [], + read: ['exception-list', 'exception-list-agnostic', ...savedObjectTypes], + }, + alerting: { + rule: { + read: ruleTypes, + }, + }, + management: { + insightsAndAlerting: ['triggersActions'], + }, + ui: ['show'], + }, + }, +}); diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index a8ad6c919a04d0e..07b0e2ed4b9dd8c 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -7,7 +7,6 @@ import { once } from 'lodash'; import { Observable } from 'rxjs'; -import { i18n } from '@kbn/i18n'; import LRU from 'lru-cache'; import { @@ -17,7 +16,6 @@ import { Plugin as IPlugin, PluginInitializerContext, SavedObjectsClient, - DEFAULT_APP_CATEGORIES, } from '../../../../src/core/server'; import { PluginSetup as DataPluginSetup, @@ -58,7 +56,7 @@ import { signalRulesAlertType } from './lib/detection_engine/signals/signal_rule import { rulesNotificationAlertType } from './lib/detection_engine/notifications/rules_notification_alert_type'; import { isNotificationAlertExecutor } from './lib/detection_engine/notifications/types'; import { ManifestTask } from './endpoint/lib/artifacts'; -import { initSavedObjects, savedObjectTypes } from './saved_objects'; +import { initSavedObjects } from './saved_objects'; import { AppClientFactory } from './client'; import { createConfig, ConfigType } from './config'; import { initUiSettings } from './ui_settings'; @@ -91,6 +89,7 @@ import { licenseService } from './lib/license'; import { PolicyWatcher } from './endpoint/lib/policy/license_watch'; import { parseExperimentalConfigValue } from '../common/experimental_features'; import { migrateArtifactsToFleet } from './endpoint/lib/artifacts/migrate_artifacts_to_fleet'; +import { getKibanaPrivilegesFeaturePrivileges } from './features'; export interface SetupPlugins { alerting: AlertingSetup; @@ -279,107 +278,9 @@ export class Plugin implements IPlugin - ui: ['crud_cases', 'read_cases'], // uiCapabilities.siem.crud_cases - cases: { - all: [APP_ID], - }, - }, - { - id: 'cases_read', - includeIn: 'read', - name: 'Read', - savedObject: { - all: [], - read: [], - }, - // using variables with underscores here otherwise when we retrieve them from the kibana - // capabilities in a hook I get type errors regarding boolean | ReadOnly<{[x: string]: boolean}> - ui: ['read_cases'], // uiCapabilities.siem.read_cases - cases: { - read: [APP_ID], - }, - }, - ], - }, - ], - }, - ], - privileges: { - all: { - app: [APP_ID, 'kibana'], - catalogue: ['securitySolution'], - api: ['securitySolution', 'lists-all', 'lists-read', 'rac'], - savedObject: { - all: ['alert', 'exception-list', 'exception-list-agnostic', ...savedObjectTypes], - read: [], - }, - alerting: { - rule: { - all: ruleTypes, - }, - alert: { - all: ruleTypes, - }, - }, - management: { - insightsAndAlerting: ['triggersActions'], - }, - ui: ['show', 'crud'], - }, - read: { - app: [APP_ID, 'kibana'], - catalogue: ['securitySolution'], - api: ['securitySolution', 'lists-read', 'rac'], - savedObject: { - all: [], - read: ['exception-list', 'exception-list-agnostic', ...savedObjectTypes], - }, - alerting: { - rule: { - read: ruleTypes, - }, - alert: { - read: ruleTypes, - }, - }, - management: { - insightsAndAlerting: ['triggersActions'], - }, - ui: ['show'], - }, - }, - }); + plugins.features.registerKibanaFeature( + getKibanaPrivilegesFeaturePrivileges(ruleTypes, isRuleRegistryEnabled) + ); // Continue to register legacy rules against alerting client exposed through rule-registry if (this.setupPlugins.alerting != null) { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index a1f86e25d97fd42..52303e1134f9dd8 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -13,7 +13,6 @@ import { setupEnvironment, pageHelpers, nextTick, - delay, getRandomString, findTestSubject, } from './helpers'; @@ -409,9 +408,9 @@ describe('', () => { await act(async () => { testBed.actions.selectTab('snapshots'); - await delay(100); - testBed.component.update(); }); + + testBed.component.update(); }); test('should display an empty prompt', () => { @@ -438,9 +437,8 @@ describe('', () => { await act(async () => { testBed.actions.selectTab('snapshots'); - await delay(2000); - testBed.component.update(); }); + testBed.component.update(); }); test('should display an empty prompt', () => { @@ -477,9 +475,9 @@ describe('', () => { await act(async () => { testBed.actions.selectTab('snapshots'); - await delay(2000); - testBed.component.update(); }); + + testBed.component.update(); }); test('should list them in the table', async () => { diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts index 00543d7081d3412..f71c5ec9ffc08cb 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts @@ -38,6 +38,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { const getLifecycleFn = router.getMockApiFn('slm.getLifecycle'); const getSnapshotFn = router.getMockApiFn('snapshot.get'); const deleteSnapshotFn = router.getMockApiFn('snapshot.delete'); + const getRepoFn = router.getMockApiFn('snapshot.getRepository'); beforeAll(() => { registerSnapshotsRoutes({ @@ -59,7 +60,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { }; test('combines snapshots and their repositories returned from ES', async () => { - const mockSnapshotGetPolicyEsResponse = { + const mockGetPolicyEsResponse = { fooPolicy: {}, }; @@ -70,12 +71,22 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { ], }; - getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); - getLifecycleFn.mockResolvedValue({ body: mockSnapshotGetPolicyEsResponse }); - getSnapshotFn.mockResolvedValueOnce({ body: mockGetSnapshotsResponse }); + const mockGetRepositoryEsResponse = { + fooRepository: {}, + barRepository: {}, + // Test that there may be a repository that does not yet have any snapshots associated to it + bazRepository: {}, + }; + + getClusterSettingsFn.mockResolvedValue({ + body: mockSnapshotGetManagedRepositoryEsResponse, + }); + getLifecycleFn.mockResolvedValue({ body: mockGetPolicyEsResponse }); + getRepoFn.mockResolvedValue({ body: mockGetRepositoryEsResponse }); + getSnapshotFn.mockResolvedValue({ body: mockGetSnapshotsResponse }); const expectedResponse = { - repositories: ['fooRepository', 'barRepository'], + repositories: ['fooRepository', 'barRepository', 'bazRepository'], policies: ['fooPolicy'], snapshots: [ { @@ -104,7 +115,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { }); test('returns an error object if ES request contains repository failures', async () => { - const mockSnapshotGetPolicyEsResponse = { + const mockGetPolicyEsResponse = { fooPolicy: {}, }; @@ -119,9 +130,16 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { }, }; - getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); - getLifecycleFn.mockResolvedValue({ body: mockSnapshotGetPolicyEsResponse }); - getSnapshotFn.mockResolvedValueOnce({ body: mockGetSnapshotsResponse }); + const mockGetRepositoryEsResponse = { + fooRepository: {}, + }; + + getClusterSettingsFn.mockResolvedValue({ + body: mockSnapshotGetManagedRepositoryEsResponse, + }); + getLifecycleFn.mockResolvedValue({ body: mockGetPolicyEsResponse }); + getRepoFn.mockResolvedValue({ body: mockGetRepositoryEsResponse }); + getSnapshotFn.mockResolvedValue({ body: mockGetSnapshotsResponse }); const expectedResponse = { repositories: ['fooRepository'], @@ -150,13 +168,12 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { expect(response).toEqual({ body: expectedResponse }); }); - test('returns empty arrays if no snapshots returned from ES', async () => { - const mockSnapshotGetPolicyEsResponse = {}; - const mockSnapshotGetRepositoryEsResponse = {}; - - getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); - getLifecycleFn.mockResolvedValue({ body: mockSnapshotGetPolicyEsResponse }); - getSnapshotFn.mockResolvedValue({ body: mockSnapshotGetRepositoryEsResponse }); + test('returns empty arrays if no repositories returned from ES', async () => { + getClusterSettingsFn.mockResolvedValue({ + body: mockSnapshotGetManagedRepositoryEsResponse, + }); + getLifecycleFn.mockResolvedValue({ body: {} }); + getRepoFn.mockResolvedValue({ body: {} }); const expectedResponse = { snapshots: [], @@ -168,10 +185,33 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { expect(response).toEqual({ body: expectedResponse }); }); + test('returns an empty snapshot array if no snapshots returned from ES', async () => { + const mockGetRepositoryEsResponse = { + fooRepository: {}, + }; + + getClusterSettingsFn.mockResolvedValue({ + body: mockSnapshotGetManagedRepositoryEsResponse, + }); + getLifecycleFn.mockResolvedValue({ body: {} }); + getRepoFn.mockResolvedValue({ body: mockGetRepositoryEsResponse }); + getSnapshotFn.mockResolvedValue({ body: {} }); + + const expectedResponse = { + snapshots: [], + repositories: ['fooRepository'], + policies: [], + }; + + const response = await router.runRequest(mockRequest); + expect(response).toEqual({ body: expectedResponse }); + }); + test('throws if ES error', async () => { - getClusterSettingsFn.mockRejectedValueOnce(new Error()); - getLifecycleFn.mockRejectedValueOnce(new Error()); - getSnapshotFn.mockRejectedValueOnce(new Error()); + getClusterSettingsFn.mockRejectedValue(new Error()); + getLifecycleFn.mockRejectedValue(new Error()); + getRepoFn.mockRejectedValue(new Error()); + getSnapshotFn.mockRejectedValue(new Error()); await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); @@ -197,12 +237,14 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { }; test('returns snapshot object with repository name if returned from ES', async () => { - const mockSnapshotGetEsResponse = { + const mockGetSnapshotEsResponse = { snapshots: [{ snapshot, repository }], }; - getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); - getSnapshotFn.mockResolvedValueOnce({ body: mockSnapshotGetEsResponse }); + getClusterSettingsFn.mockResolvedValue({ + body: mockSnapshotGetManagedRepositoryEsResponse, + }); + getSnapshotFn.mockResolvedValue({ body: mockGetSnapshotEsResponse }); const expectedResponse = { ...defaultSnapshot, @@ -237,8 +279,10 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { ], }; - getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); - getSnapshotFn.mockResolvedValueOnce({ body: mockSnapshotGetEsResponse }); + getClusterSettingsFn.mockResolvedValue({ + body: mockSnapshotGetManagedRepositoryEsResponse, + }); + getSnapshotFn.mockResolvedValue({ body: mockSnapshotGetEsResponse }); await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: 'Snapshot not found', diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts index 7307bad947211e6..fb68ca5c13dbe75 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -37,6 +37,25 @@ export function registerSnapshotsRoutes({ // Silently swallow error as policy names aren't required in UI } + let repositories: string[] = []; + + try { + const { + body: repositoriesByName, + } = await clusterClient.asCurrentUser.snapshot.getRepository({ + repository: '_all', + }); + repositories = Object.keys(repositoriesByName); + + if (repositories.length === 0) { + return res.ok({ + body: { snapshots: [], repositories: [], policies }, + }); + } + } catch (e) { + return handleEsError({ error: e, response: res }); + } + try { // If any of these repositories 504 they will cost the request significant time. const { body: fetchedSnapshots } = await clusterClient.asCurrentUser.snapshot.get({ @@ -51,24 +70,16 @@ export function registerSnapshotsRoutes({ size: SNAPSHOT_LIST_MAX_SIZE, }); - const allRepos: string[] = []; - // Decorate each snapshot with the repository with which it's associated. const snapshots = fetchedSnapshots?.snapshots?.map((snapshot) => { - // @ts-expect-error @elastic/elasticsearch "repository" is a new field in the response - allRepos.push(snapshot.repository); return deserializeSnapshotDetails(snapshot as SnapshotDetailsEs, managedRepository); }); - const uniqueRepos = allRepos.filter((repo, index) => { - return allRepos.indexOf(repo) === index; - }); - return res.ok({ body: { snapshots: snapshots || [], policies, - repositories: uniqueRepos, + repositories, // @ts-expect-error @elastic/elasticsearch "failures" is a new field in the response errors: fetchedSnapshots?.failures, }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2b1088d8c11aea3..1709ee6b3066545 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -262,7 +262,6 @@ "core.euiCodeEditor.stopEditing": "完了したら Esc キーで編集を終了します。", "core.euiCodeEditor.stopInteracting": "完了したら Esc キーでコードの操作を終了します。", "core.euiCollapsedItemActions.allActions": "すべてのアクション", - "core.euiCollapsibleNav.closeButtonLabel": "閉じる", "core.euiColorPicker.alphaLabel": "アルファチャネル (不透明) 値", "core.euiColorPicker.closeLabel": "下矢印キーを押すと、色オプションを含むポップオーバーが開きます", "core.euiColorPicker.colorErrorMessage": "無効な色値", @@ -337,7 +336,6 @@ "core.euiFlyout.closeAriaLabel": "このダイアログを閉じる", "core.euiForm.addressFormErrors": "ハイライトされたエラーを修正してください。", "core.euiFormControlLayoutClearButton.label": "インプットを消去", - "core.euiHeaderAlert.dismiss": "閉じる", "core.euiHeaderLinks.appNavigation": "アプリメニュー", "core.euiHeaderLinks.openNavigationMenu": "メニューを開く", "core.euiHue.label": "HSV カラーモードの「色相」値を選択", @@ -444,19 +442,12 @@ "core.euiSuperUpdateButton.refreshButtonLabel": "更新", "core.euiSuperUpdateButton.updateButtonLabel": "更新", "core.euiSuperUpdateButton.updatingButtonLabel": "更新中", - "core.euiTableHeaderCell.clickForAscending": "クリックすると、昇順で並べ替えます", - "core.euiTableHeaderCell.clickForDescending": "クリックすると、降順で並べ替えます", - "core.euiTableHeaderCell.clickForUnsort": "クリックすると、並べ替えを解除します", - "core.euiTableHeaderCell.titleTextWithSort": "{innerText}; {ariaSortValue}順に表示", "core.euiTablePagination.rowsPerPage": "ページごとの行数", "core.euiTablePagination.rowsPerPageOption": "{rowsPerPage} 行", "core.euiTableSortMobile.sorting": "並べ替え", "core.euiToast.dismissToast": "トーストを閉じる", "core.euiToast.newNotification": "新しい通知が表示されます", "core.euiToast.notification": "通知", - "core.euiTour.closeTour": "ツアーを閉じる", - "core.euiTour.endTour": "ツアーを終了", - "core.euiTour.skipTour": "ツアーをスキップ", "core.euiTourStepIndicator.ariaLabel": "ステップ{number} {status}", "core.euiTourStepIndicator.isActive": "アクティブ", "core.euiTourStepIndicator.isComplete": "完了", @@ -673,6 +664,7 @@ "dashboard.topNav.options.syncColorsBetweenPanelsSwitchLabel": "パネル全体でカラーパレットを同期", "dashboard.topNav.options.useMarginsBetweenPanelsSwitchLabel": "パネルの間に余白を使用", "dashboard.topNav.saveModal.descriptionFormRowLabel": "説明", + "dashboard.topNav.saveModal.objectType": "dashboard", "dashboard.topNav.saveModal.storeTimeWithDashboardFormRowHelpText": "有効化すると、ダッシュボードが読み込まれるごとに現在選択された時刻の時間フィルターが変更されます。", "dashboard.topNav.saveModal.storeTimeWithDashboardFormRowLabel": "ダッシュボードに時刻を保存", "dashboard.topNav.showCloneModal.dashboardCopyTitle": "{title}のコピー", @@ -1698,6 +1690,7 @@ "discover.localMenu.openTitle": "開く", "discover.localMenu.optionsDescription": "オプション", "discover.localMenu.saveSaveSearchDescription": "ビジュアライゼーションとダッシュボードで使用できるように Discover の検索を保存します", + "discover.localMenu.saveSaveSearchObjectType": "discover", "discover.localMenu.saveSearchDescription": "検索を保存します", "discover.localMenu.saveTitle": "保存", "discover.localMenu.shareSearchDescription": "検索を共有します", @@ -5138,6 +5131,7 @@ "visualize.topNavMenu.saveVisualizationButtonAriaLabel": "ビジュアライゼーションを保存", "visualize.topNavMenu.saveVisualizationButtonLabel": "保存", "visualize.topNavMenu.saveVisualizationDisabledButtonTooltip": "保存する前に変更を適用または破棄", + "visualize.topNavMenu.saveVisualizationObjectType": "visualize", "visualize.topNavMenu.saveVisualizationToLibraryButtonLabel": "ライブラリに保存", "visualize.topNavMenu.shareVisualizationButtonAriaLabel": "ビジュアライゼーションを共有", "visualize.topNavMenu.shareVisualizationButtonLabel": "共有", @@ -6100,9 +6094,8 @@ "xpack.canvas.error.useImportWorkpad.missingPropertiesErrorMessage": "{CANVAS} ワークパッドに必要なプロパティの一部が欠けています。 {JSON} ファイルを編集して正しいプロパティ値を入力し、再試行してください。", "xpack.canvas.error.workpadRoutes.createFailureErrorMessage": "ワークパッドを作成できませんでした", "xpack.canvas.error.workpadRoutes.loadFailureErrorMessage": "ID でワークパッドを読み込めませんでした", - "xpack.canvas.errorComponent.description": "表現が失敗し次のメッセージが返されました:", - "xpack.canvas.errorComponent.title": "おっと!表現が失敗しました", - "xpack.canvas.errors.useImportWorkpad.fileUploadFileWithFileNameErrorMessage": "「{fileName}」をアップロードできませんでした", + "expressionError.errorComponent.description": "表現が失敗し次のメッセージが返されました:", + "expressionError.errorComponent.title": "おっと!表現が失敗しました", "xpack.canvas.expression.cancelButtonLabel": "キャンセル", "xpack.canvas.expression.closeButtonLabel": "閉じる", "xpack.canvas.expression.learnLinkText": "表現構文の詳細", @@ -6514,15 +6507,15 @@ "xpack.canvas.renderer.advancedFilter.displayName": "高度なフィルター", "xpack.canvas.renderer.advancedFilter.helpDescription": "Canvas フィルター表現をレンダリングします。", "xpack.canvas.renderer.advancedFilter.inputPlaceholder": "フィルター表現を入力", - "xpack.canvas.renderer.debug.displayName": "デバッグ", - "xpack.canvas.renderer.debug.helpDescription": "デバッグアウトプットをフォーマットされた {JSON} としてレンダリングします", + "expressionError.renderer.debug.displayName": "デバッグ", + "expressionError.renderer.debug.helpDescription": "デバッグアウトプットをフォーマットされた {JSON} としてレンダリングします", "xpack.canvas.renderer.dropdownFilter.displayName": "ドロップダウンフィルター", "xpack.canvas.renderer.dropdownFilter.helpDescription": "「{exactly}」フィルターの値を選択できるドロップダウンです", "xpack.canvas.renderer.dropdownFilter.matchAllOptionLabel": "すべて", "xpack.canvas.renderer.embeddable.displayName": "埋め込み可能", "xpack.canvas.renderer.embeddable.helpDescription": "Kibana の他の部分から埋め込み可能な保存済みオブジェクトをレンダリングします", - "xpack.canvas.renderer.error.displayName": "エラー情報", - "xpack.canvas.renderer.error.helpDescription": "エラーデータをユーザーにわかるようにレンダリングします", + "expressionError.renderer.error.displayName": "エラー情報", + "expressionError.renderer.error.helpDescription": "エラーデータをユーザーにわかるようにレンダリングします", "xpack.canvas.renderer.image.displayName": "画像", "xpack.canvas.renderer.image.helpDescription": "画像をレンダリングします", "xpack.canvas.renderer.markdown.displayName": "マークダウン", @@ -9207,7 +9200,6 @@ "xpack.fleet.setupPage.gettingStartedText": "詳細については、{link}ガイドをお読みください。", "xpack.fleet.setupPage.missingRequirementsCalloutDescription": "Elasticエージェントの集中管理を使用するには、次のElasticsearchのセキュリティ機能を有効にする必要があります。", "xpack.fleet.setupPage.missingRequirementsCalloutTitle": "不足しているセキュリティ要件", - "xpack.fleet.setupPage.missingRequirementsElasticsearchTitle": "Elasticsearchポリシーでは、次のことができます。", "xpack.fleet.unenrollAgents.cancelButtonLabel": "キャンセル", "xpack.fleet.unenrollAgents.confirmMultipleButtonLabel": "{count}個のエージェントを登録解除", "xpack.fleet.unenrollAgents.confirmSingleButtonLabel": "エージェントの登録解除", @@ -17158,7 +17150,6 @@ "xpack.observability.feedbackMenu.appName": "オブザーバビリティ", "xpack.observability.fieldValueSelection.apply": "適用", "xpack.observability.fieldValueSelection.placeholder": "{label}をフィルタリング", - "xpack.observability.fleet.beta": "ベータ", "xpack.observability.fleet.button": "Fleet ベータを試す", "xpack.observability.fleet.text": "Elastic エージェントでは、シンプルかつ統合された方法で、ログ、メトリック、他の種類のデータの監視をホストに追加することができます。複数の Beats と他のエージェントをインストールする必要はありません。このため、インフラストラクチャ全体での構成のデプロイが簡単で高速になりました。", "xpack.observability.fleet.title": "新しい Fleet をご覧になりましたか。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 04394a1ac170474..76d6ad2f903ad3b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -265,7 +265,6 @@ "core.euiCodeEditor.stopEditing": "完成后,按 Esc 键停止编辑。", "core.euiCodeEditor.stopInteracting": "完成后,按 Esc 键停止与代码互动。", "core.euiCollapsedItemActions.allActions": "所有操作", - "core.euiCollapsibleNav.closeButtonLabel": "关闭", "core.euiColorPicker.alphaLabel": "Alpha 通道 (不透明度) 值", "core.euiColorPicker.closeLabel": "按向下箭头键可打开包含颜色选项的弹出框", "core.euiColorPicker.colorErrorMessage": "颜色值无效", @@ -340,7 +339,6 @@ "core.euiFlyout.closeAriaLabel": "关闭此对话框", "core.euiForm.addressFormErrors": "请解决突出显示的错误。", "core.euiFormControlLayoutClearButton.label": "清除输入", - "core.euiHeaderAlert.dismiss": "关闭", "core.euiHeaderLinks.appNavigation": "应用菜单", "core.euiHeaderLinks.openNavigationMenu": "打开菜单", "core.euiHue.label": "选择 HSV 颜色模式“色调”值", @@ -447,19 +445,12 @@ "core.euiSuperUpdateButton.refreshButtonLabel": "刷新", "core.euiSuperUpdateButton.updateButtonLabel": "更新", "core.euiSuperUpdateButton.updatingButtonLabel": "正在更新", - "core.euiTableHeaderCell.clickForAscending": "单击升序排序", - "core.euiTableHeaderCell.clickForDescending": "单击降序排序", - "core.euiTableHeaderCell.clickForUnsort": "单击取消排序", - "core.euiTableHeaderCell.titleTextWithSort": "{innerText};已按 {ariaSortValue} 顺序排序", "core.euiTablePagination.rowsPerPage": "每页行数", "core.euiTablePagination.rowsPerPageOption": "{rowsPerPage} 行", "core.euiTableSortMobile.sorting": "排序", "core.euiToast.dismissToast": "关闭 Toast", "core.euiToast.newNotification": "新通知出现", "core.euiToast.notification": "通知", - "core.euiTour.closeTour": "关闭教程", - "core.euiTour.endTour": "结束教程", - "core.euiTour.skipTour": "跳过教程", "core.euiTourStepIndicator.ariaLabel": "第 {number} 步{status}", "core.euiTourStepIndicator.isActive": "活动", "core.euiTourStepIndicator.isComplete": "已完成", @@ -676,6 +667,7 @@ "dashboard.topNav.options.syncColorsBetweenPanelsSwitchLabel": "在面板之间同步调色板", "dashboard.topNav.options.useMarginsBetweenPanelsSwitchLabel": "在面板间使用边距", "dashboard.topNav.saveModal.descriptionFormRowLabel": "描述", + "dashboard.topNav.saveModal.objectType": "dashboard", "dashboard.topNav.saveModal.storeTimeWithDashboardFormRowHelpText": "每次加载此仪表板时,都会将时间筛选更改为当前选定的时间。", "dashboard.topNav.saveModal.storeTimeWithDashboardFormRowLabel": "将时间随仪表板保存", "dashboard.topNav.showCloneModal.dashboardCopyTitle": "{title} 副本", @@ -1708,6 +1700,7 @@ "discover.localMenu.openTitle": "打开", "discover.localMenu.optionsDescription": "选项", "discover.localMenu.saveSaveSearchDescription": "保存您的 Discover 搜索,以便可以在可视化和仪表板中使用该搜索", + "discover.localMenu.saveSaveSearchObjectType": "discover", "discover.localMenu.saveSearchDescription": "保存搜索", "discover.localMenu.saveTitle": "保存", "discover.localMenu.shareSearchDescription": "共享搜索", @@ -5166,6 +5159,7 @@ "visualize.topNavMenu.saveVisualizationButtonAriaLabel": "保存可视化", "visualize.topNavMenu.saveVisualizationButtonLabel": "保存", "visualize.topNavMenu.saveVisualizationDisabledButtonTooltip": "保存前应用或放弃所做更改", + "visualize.topNavMenu.saveVisualizationObjectType": "visualize", "visualize.topNavMenu.saveVisualizationToLibraryButtonLabel": "保存到库", "visualize.topNavMenu.shareVisualizationButtonAriaLabel": "共享可视化", "visualize.topNavMenu.shareVisualizationButtonLabel": "共享", @@ -6139,9 +6133,8 @@ "xpack.canvas.error.useImportWorkpad.missingPropertiesErrorMessage": "{CANVAS} Workpad 所需的某些属性缺失。 编辑 {JSON} 文件以提供正确的属性值,然后重试。", "xpack.canvas.error.workpadRoutes.createFailureErrorMessage": "无法创建 Workpad", "xpack.canvas.error.workpadRoutes.loadFailureErrorMessage": "无法加载具有以下 ID 的 Workpad", - "xpack.canvas.errorComponent.description": "表达式失败,并显示消息:", - "xpack.canvas.errorComponent.title": "哎哟!表达式失败", - "xpack.canvas.errors.useImportWorkpad.fileUploadFileWithFileNameErrorMessage": "无法上传“{fileName}”", + "expressionError.errorComponent.description": "表达式失败,并显示消息:", + "expressionError.errorComponent.title": "哎哟!表达式失败", "xpack.canvas.expression.cancelButtonLabel": "取消", "xpack.canvas.expression.closeButtonLabel": "关闭", "xpack.canvas.expression.learnLinkText": "学习表达式语法", @@ -6554,15 +6547,15 @@ "xpack.canvas.renderer.advancedFilter.displayName": "高级筛选", "xpack.canvas.renderer.advancedFilter.helpDescription": "呈现 Canvas 筛选表达式", "xpack.canvas.renderer.advancedFilter.inputPlaceholder": "输入筛选表达式", - "xpack.canvas.renderer.debug.displayName": "故障排查", - "xpack.canvas.renderer.debug.helpDescription": "将故障排查输出呈现为带格式的 {JSON}", + "expressionError.renderer.debug.displayName": "故障排查", + "expressionError.renderer.debug.helpDescription": "将故障排查输出呈现为带格式的 {JSON}", "xpack.canvas.renderer.dropdownFilter.displayName": "下拉列表筛选", "xpack.canvas.renderer.dropdownFilter.helpDescription": "可以从其中为“{exactly}”筛选选择值的下拉列表", "xpack.canvas.renderer.dropdownFilter.matchAllOptionLabel": "任意", "xpack.canvas.renderer.embeddable.displayName": "可嵌入", "xpack.canvas.renderer.embeddable.helpDescription": "从 Kibana 的其他部分呈现可嵌入的已保存对象", - "xpack.canvas.renderer.error.displayName": "错误信息", - "xpack.canvas.renderer.error.helpDescription": "以用户友好的方式呈现错误数据", + "expressionError.renderer.error.displayName": "错误信息", + "expressionError.renderer.error.helpDescription": "以用户友好的方式呈现错误数据", "xpack.canvas.renderer.image.displayName": "图像", "xpack.canvas.renderer.image.helpDescription": "呈现图像", "xpack.canvas.renderer.markdown.displayName": "Markdown", @@ -9293,7 +9286,6 @@ "xpack.fleet.setupPage.gettingStartedText": "有关更多信息,请阅读我们的{link}指南。", "xpack.fleet.setupPage.missingRequirementsCalloutDescription": "要对 Elastic 代理使用集中管理,请启用下面的 Elasticsearch 安全功能。", "xpack.fleet.setupPage.missingRequirementsCalloutTitle": "缺失安全性要求", - "xpack.fleet.setupPage.missingRequirementsElasticsearchTitle": "在 Elasticsearch 策略中,启用:", "xpack.fleet.unenrollAgents.cancelButtonLabel": "取消", "xpack.fleet.unenrollAgents.confirmMultipleButtonLabel": "取消注册 {count} 个代理", "xpack.fleet.unenrollAgents.confirmSingleButtonLabel": "取消注册代理", @@ -17394,7 +17386,6 @@ "xpack.observability.feedbackMenu.appName": "可观测性", "xpack.observability.fieldValueSelection.apply": "应用", "xpack.observability.fieldValueSelection.placeholder": "筛选 {label}", - "xpack.observability.fleet.beta": "公测版", "xpack.observability.fleet.button": "试用 Fleet 公测版", "xpack.observability.fleet.text": "通过 Elastic 代理,可以简单统一的方式将日志、指标和其他类型数据的监测添加到主机。您无需安装多个 Beats 和其他代理,以令其更为方便快捷地在基础结构中部署配置。", "xpack.observability.fleet.title": "您是否了解我们的全新 Fleet?", diff --git a/x-pack/plugins/uptime/kibana.json b/x-pack/plugins/uptime/kibana.json index 5aa3c80610d035c..97d154843231239 100644 --- a/x-pack/plugins/uptime/kibana.json +++ b/x-pack/plugins/uptime/kibana.json @@ -20,5 +20,5 @@ "name": "Uptime", "githubTeam": "uptime" }, - "description": "This plugin visualizes data from from Synthetics and Heartbeat, and integrates with other Observability solutions." + "description": "This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions." } diff --git a/x-pack/test/accessibility/apps/ml.ts b/x-pack/test/accessibility/apps/ml.ts index 382086728da019f..4babe0bd6ff8847 100644 --- a/x-pack/test/accessibility/apps/ml.ts +++ b/x-pack/test/accessibility/apps/ml.ts @@ -59,8 +59,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/103538 - describe.skip('with data loaded', function () { + describe('with data loaded', function () { const adJobId = 'fq_single_a11y'; const dfaOutlierJobId = 'iph_outlier_a11y'; const calendarId = 'calendar_a11y'; diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get_spaces.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get_spaces.ts index 33dcec6264501b4..b6509438831964c 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get_spaces.ts @@ -45,6 +45,19 @@ export default ({ getService }: FtrProviderContext) => { return body; } + async function runMapRequest(space: string, expectedStatusCode: number, jobId: string) { + const { body } = await supertest + .get(`/s/${space}/api/ml/data_frame/analytics/map/${jobId}`) + .auth( + USER.ML_VIEWER_ALL_SPACES, + ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES) + ) + .set(COMMON_REQUEST_HEADERS) + .expect(expectedStatusCode); + + return body; + } + describe('GET data_frame/analytics with spaces', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ihp_outlier'); @@ -140,5 +153,62 @@ export default ({ getService }: FtrProviderContext) => { it('should fail to list jobs stats by job ids if one of them is in a different space', async () => { await runRequest(idSpace1, 404, true, `${jobIdSpace1},${jobIdSpace2}`); }); + + describe('GetDataFrameAnalyticsIdMap with spaces', () => { + it(`should return a map of objects from ${idSpace1} leading up to analytics job id created in ${idSpace1}`, async () => { + const body = await runMapRequest(idSpace1, 200, jobIdSpace1); + + expect(body).to.have.keys('elements', 'details', 'error'); + // Index node, 2 job nodes (with same source index), and 2 edge nodes to connect them + expect(body.elements.length).to.eql( + 5, + `Expected 5 map elements, got ${body.elements.length}` + ); + expect(body.error).to.be(null); + // No space2 related job ids should be returned + for (const detailsId in body.details) { + if (detailsId.includes('analytics')) { + expect(body.details[detailsId].id.includes(idSpace2)).to.eql( + false, + `No space2 related job ids should be returned, got ${body.details[detailsId].id}` + ); + } + } + }); + + it(`should return a map of objects from ${idSpace2} leading up to analytics job id created in ${idSpace2}`, async () => { + const body = await runMapRequest(idSpace2, 200, jobIdSpace2); + + expect(body).to.have.keys('elements', 'details', 'error'); + // Index node, 1 job node and 2 edge nodes to connect them + expect(body.elements.length).to.eql( + 3, + `Expected 3 map elements, got ${body.elements.length}` + ); + expect(body.error).to.be(null); + // No space1 related job ids should be returned + for (const detailsId in body.details) { + if (detailsId.includes('analytics')) { + expect(body.details[detailsId].id.includes(idSpace1)).to.eql( + false, + `No space1 related job ids should be returned, got ${body.details[detailsId].id}` + ); + } + } + }); + + it(`should fail to return a map of objects from one space when requesting with analytics job id created in a different space`, async () => { + const body = await runMapRequest(idSpace2, 200, jobIdSpace1); + + expect(body).to.have.keys('elements', 'details', 'error'); + + expect(body.elements.length).to.eql( + 0, + `Expected 0 map elements, got ${body.elements.length}` + ); + expect(body.details).to.eql({}); + expect(body.error).to.eql(`No known job with id '${jobIdSpace1}'`); + }); + }); }); }; diff --git a/x-pack/test/functional/apps/discover/async_scripted_fields.js b/x-pack/test/functional/apps/discover/async_scripted_fields.js index 427d8c21635c4f7..2c1805140596476 100644 --- a/x-pack/test/functional/apps/discover/async_scripted_fields.js +++ b/x-pack/test/functional/apps/discover/async_scripted_fields.js @@ -19,8 +19,7 @@ export default function ({ getService, getPageObjects }) { const queryBar = getService('queryBar'); const security = getService('security'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104362 - describe.skip('async search with scripted fields', function () { + describe('async search with scripted fields', function () { this.tags(['skipFirefox']); before(async function () { diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts index de0dc459b63953a..650d67f05129c3f 100644 --- a/x-pack/test/functional/apps/discover/visualize_field.ts +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -28,8 +28,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); } - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104469 - describe.skip('discover field visualize button', () => { + describe('discover field visualize button', () => { beforeEach(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/lens/basic'); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts index fbd6cc70418d34c..91d7b0a9347e285 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/feature_importance.ts @@ -183,7 +183,8 @@ export default function ({ getService }: FtrProviderContext) { }); for (const testData of testDataList) { - describe(`${testData.suiteTitle}`, function () { + // FLAKY: https://github.com/elastic/kibana/issues/93188 + describe.skip(`${testData.suiteTitle}`, function () { before(async () => { await ml.navigation.navigateToMl(); await ml.navigation.navigateToDataFrameAnalytics(); diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts index 031074876f39c7d..2e88198adbc089a 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts @@ -496,7 +496,8 @@ export default function ({ getService }: FtrProviderContext) { }); } - describe('index based', function () { + // FLAKY: https://github.com/elastic/kibana/issues/105087 + describe.skip('index based', function () { this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts index 577afb200d4a1a8..06f3756593d767f 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts @@ -7,18 +7,13 @@ import expect from '@kbn/expect'; import { pick } from 'lodash'; -import { - ReportApiJSON, - ReportDocument, - ReportSource, -} from '../../../plugins/reporting/common/types'; +import { ReportApiJSON } from '../../../plugins/reporting/common/types'; import { FtrProviderContext } from '../ftr_provider_context'; const apiResponseFields = [ 'attempts', 'created_by', 'jobtype', - 'max_attempts', 'meta', 'payload.isDeprecated', 'payload.title', @@ -26,26 +21,13 @@ const apiResponseFields = [ 'status', ]; -// TODO: clean up the /list and /info endpoints to return ReportApiJSON interface data -const documentResponseFields = [ - '_source.attempts', - '_source.created_by', - '_source.jobtype', - '_source.max_attempts', - '_source.meta', - '_source.payload.isDeprecated', - '_source.payload.title', - '_source.payload.type', - '_source.status', -]; - // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertestNoAuth = getService('supertestWithoutAuth'); const reportingAPI = getService('reportingAPI'); - const postJobCSV = async () => { + const postJobCSV = async (): Promise<{ job: ReportApiJSON; path: string }> => { const jobParams = `(browserTimezone:UTC,columns:!('@timestamp',clientip,extension),` + `objectType:search,searchSource:(fields:!((field:'*',include_unmapped:true)),filter:!((meta:(index:'logstash-*',params:()),` + @@ -81,13 +63,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('Posted CSV job is visible in the job count', async () => { - const { job, path }: { job: ReportApiJSON; path: string } = await postJobCSV(); + const { job, path } = await postJobCSV(); expectSnapshot(pick(job, apiResponseFields)).toMatchInline(` Object { "attempts": 0, "created_by": false, "jobtype": "csv_searchsource", - "max_attempts": 1, "meta": Object { "objectType": "search", }, @@ -110,13 +91,12 @@ export default function ({ getService }: FtrProviderContext) { it('Posted CSV job is visible in the status check', async () => { // post a job - const { job, path }: { job: ReportApiJSON; path: string } = await postJobCSV(); + const { job, path } = await postJobCSV(); expectSnapshot(pick(job, apiResponseFields)).toMatchInline(` Object { "attempts": 0, "created_by": false, "jobtype": "csv_searchsource", - "max_attempts": 1, "meta": Object { "objectType": "search", }, @@ -133,24 +113,21 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx'); // verify the top item in the list - const listingJobs: ReportDocument[] = JSON.parse(listText); - expect(listingJobs[0]._id).to.be(job.id); - expectSnapshot(listingJobs.map((j) => pick(j, documentResponseFields))).toMatchInline(` + const listingJobs: ReportApiJSON[] = JSON.parse(listText); + expect(listingJobs[0].id).to.be(job.id); + expectSnapshot(listingJobs.map((j) => pick(j, apiResponseFields))).toMatchInline(` Array [ Object { - "_source": Object { - "attempts": 0, - "created_by": false, - "jobtype": "csv_searchsource", - "max_attempts": 1, - "meta": Object { - "objectType": "search", - }, - "payload": Object { - "title": "A Saved Search With a DATE FILTER", - }, - "status": "pending", + "attempts": 0, + "created_by": false, + "jobtype": "csv_searchsource", + "meta": Object { + "objectType": "search", + }, + "payload": Object { + "title": "A Saved Search With a DATE FILTER", }, + "status": "pending", }, ] `); @@ -161,13 +138,12 @@ export default function ({ getService }: FtrProviderContext) { it('Posted CSV job is visible in the first page of jobs listing', async () => { // post a job - const { job, path }: { job: ReportApiJSON; path: string } = await postJobCSV(); + const { job, path } = await postJobCSV(); expectSnapshot(pick(job, apiResponseFields)).toMatchInline(` Object { "attempts": 0, "created_by": false, "jobtype": "csv_searchsource", - "max_attempts": 1, "meta": Object { "objectType": "search", }, @@ -179,29 +155,27 @@ export default function ({ getService }: FtrProviderContext) { `); // call the listing api - const { text: listText } = await supertestNoAuth + const { text: listText, status } = await supertestNoAuth .get(`/api/reporting/jobs/list?page=0`) .set('kbn-xsrf', 'xxx'); + expect(status).to.be(200); // verify the top item in the list - const listingJobs: ReportDocument[] = JSON.parse(listText); - expect(listingJobs[0]._id).to.be(job.id); - expectSnapshot(listingJobs.map((j) => pick(j, documentResponseFields))).toMatchInline(` + const listingJobs: ReportApiJSON[] = JSON.parse(listText); + expect(listingJobs[0].id).to.be(job.id); + expectSnapshot(listingJobs.map((j) => pick(j, apiResponseFields))).toMatchInline(` Array [ Object { - "_source": Object { - "attempts": 0, - "created_by": false, - "jobtype": "csv_searchsource", - "max_attempts": 1, - "meta": Object { - "objectType": "search", - }, - "payload": Object { - "title": "A Saved Search With a DATE FILTER", - }, - "status": "pending", + "attempts": 0, + "created_by": false, + "jobtype": "csv_searchsource", + "meta": Object { + "objectType": "search", + }, + "payload": Object { + "title": "A Saved Search With a DATE FILTER", }, + "status": "pending", }, ] `); @@ -212,13 +186,12 @@ export default function ({ getService }: FtrProviderContext) { it('Posted CSV job details are visible in the info API', async () => { // post a job - const { job, path }: { job: ReportApiJSON; path: string } = await postJobCSV(); + const { job, path } = await postJobCSV(); expectSnapshot(pick(job, apiResponseFields)).toMatchInline(` Object { "attempts": 0, "created_by": false, "jobtype": "csv_searchsource", - "max_attempts": 1, "meta": Object { "objectType": "search", }, @@ -229,17 +202,17 @@ export default function ({ getService }: FtrProviderContext) { } `); - const { text: infoText } = await supertestNoAuth + const { text: infoText, status } = await supertestNoAuth .get(`/api/reporting/jobs/info/${job.id}`) .set('kbn-xsrf', 'xxx'); + expect(status).to.be(200); - const info: ReportSource = JSON.parse(infoText); + const info = JSON.parse(infoText); expectSnapshot(pick(info, apiResponseFields)).toMatchInline(` Object { "attempts": 0, "created_by": false, "jobtype": "csv_searchsource", - "max_attempts": 1, "meta": Object { "objectType": "search", }, diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts index 5aafcfb9d29a1ac..2d62725e23989a3 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts @@ -7,11 +7,7 @@ import expect from '@kbn/expect'; import { pick } from 'lodash'; -import { - ReportApiJSON, - ReportDocument, - ReportSource, -} from '../../../plugins/reporting/common/types'; +import { ReportApiJSON } from '../../../plugins/reporting/common/types'; import { FtrProviderContext } from '../ftr_provider_context'; import { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../services/fixtures'; @@ -19,7 +15,6 @@ const apiResponseFields = [ 'attempts', 'created_by', 'jobtype', - 'max_attempts', 'meta', 'payload.isDeprecated', 'payload.title', @@ -27,18 +22,8 @@ const apiResponseFields = [ 'status', ]; -// TODO: clean up the /list and /info endpoints to return ReportApiJSON interface data -const documentResponseFields = [ - '_source.attempts', - '_source.created_by', - '_source.jobtype', - '_source.max_attempts', - '_source.meta', - '_source.payload.isDeprecated', - '_source.payload.title', - '_source.payload.type', - '_source.status', -]; +const parseApiJSON = (apiResponseText: string): { job: ReportApiJSON; path: string } => + JSON.parse(apiResponseText); // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { @@ -68,13 +53,12 @@ export default function ({ getService }: FtrProviderContext) { .send({ jobParams: JOB_PARAMS_RISON_CSV_DEPRECATED }); expect(resStatus).to.be(200); - const { job, path }: { job: ReportApiJSON; path: string } = JSON.parse(resText); + const { job, path } = parseApiJSON(resText); expectSnapshot(pick(job, apiResponseFields)).toMatchInline(` Object { "attempts": 0, "created_by": false, "jobtype": "csv", - "max_attempts": 1, "meta": Object {}, "payload": Object { "isDeprecated": true, @@ -103,30 +87,27 @@ export default function ({ getService }: FtrProviderContext) { .send({ jobParams: JOB_PARAMS_RISON_CSV_DEPRECATED }); expect(resStatus).to.be(200); - const { job, path }: { job: ReportApiJSON; path: string } = JSON.parse(resText); + const { job, path } = parseApiJSON(resText); // call the single job listing api (status check) const { text: listText } = await supertestNoAuth .get(`/api/reporting/jobs/list?page=0&ids=${job.id}`) .set('kbn-xsrf', 'xxx'); - const listingJobs: ReportDocument[] = JSON.parse(listText); - expect(listingJobs[0]._id).to.be(job.id); - expectSnapshot(listingJobs.map((j) => pick(j, documentResponseFields))).toMatchInline(` + const listingJobs: ReportApiJSON[] = JSON.parse(listText); + expect(listingJobs[0].id).to.be(job.id); + expectSnapshot(listingJobs.map((j) => pick(j, apiResponseFields))).toMatchInline(` Array [ Object { - "_source": Object { - "attempts": 0, - "created_by": false, - "jobtype": "csv", - "max_attempts": 1, - "meta": Object {}, - "payload": Object { - "isDeprecated": true, - "title": "A Saved Search With a DATE FILTER", - "type": "search", - }, - "status": "pending", + "attempts": 0, + "created_by": false, + "jobtype": "csv", + "meta": Object {}, + "payload": Object { + "isDeprecated": true, + "title": "A Saved Search With a DATE FILTER", + "type": "search", }, + "status": "pending", }, ] `); @@ -141,30 +122,27 @@ export default function ({ getService }: FtrProviderContext) { .send({ jobParams: JOB_PARAMS_RISON_CSV_DEPRECATED }); expect(resStatus).to.be(200); - const { job, path }: { job: ReportApiJSON; path: string } = JSON.parse(resText); + const { job, path } = parseApiJSON(resText); // call the ALL job listing api const { text: listText } = await supertestNoAuth .get(`/api/reporting/jobs/list?page=0`) .set('kbn-xsrf', 'xxx'); - const listingJobs: ReportDocument[] = JSON.parse(listText); - expect(listingJobs[0]._id).to.eql(job.id); - expectSnapshot(listingJobs.map((j) => pick(j, documentResponseFields))).toMatchInline(` + const listingJobs: ReportApiJSON[] = JSON.parse(listText); + expect(listingJobs[0].id).to.eql(job.id); + expectSnapshot(listingJobs.map((j) => pick(j, apiResponseFields))).toMatchInline(` Array [ Object { - "_source": Object { - "attempts": 0, - "created_by": false, - "jobtype": "csv", - "max_attempts": 1, - "meta": Object {}, - "payload": Object { - "isDeprecated": true, - "title": "A Saved Search With a DATE FILTER", - "type": "search", - }, - "status": "pending", + "attempts": 0, + "created_by": false, + "jobtype": "csv", + "meta": Object {}, + "payload": Object { + "isDeprecated": true, + "title": "A Saved Search With a DATE FILTER", + "type": "search", }, + "status": "pending", }, ] `); @@ -179,18 +157,17 @@ export default function ({ getService }: FtrProviderContext) { .send({ jobParams: JOB_PARAMS_RISON_CSV_DEPRECATED }); expect(resStatus).to.be(200); - const { job, path }: { job: ReportApiJSON; path: string } = JSON.parse(resText); + const { job, path } = parseApiJSON(resText); const { text: infoText } = await supertestNoAuth .get(`/api/reporting/jobs/info/${job.id}`) .set('kbn-xsrf', 'xxx'); - const info: ReportSource = JSON.parse(infoText); + const info = JSON.parse(infoText); expectSnapshot(pick(info, apiResponseFields)).toMatchInline(` Object { "attempts": 0, "created_by": false, "jobtype": "csv", - "max_attempts": 1, "meta": Object {}, "payload": Object { "isDeprecated": true, diff --git a/x-pack/test/rule_registry/common/config.ts b/x-pack/test/rule_registry/common/config.ts index 8d1b3807a245b0b..487af84141d2003 100644 --- a/x-pack/test/rule_registry/common/config.ts +++ b/x-pack/test/rule_registry/common/config.ts @@ -80,6 +80,9 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, '--xpack.eventLog.logEntries=true', ...disabledPlugins.map((key) => `--xpack.${key}.enabled=false`), + // TO DO: Remove feature flags once we're good to go + '--xpack.securitySolution.enableExperimental=["ruleRegistryEnabled"]', + '--xpack.ruleRegistry.write.enabled=true', `--server.xsrf.whitelist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`, ...(ssl ? [ diff --git a/x-pack/test/rule_registry/common/lib/authentication/roles.ts b/x-pack/test/rule_registry/common/lib/authentication/roles.ts index e38378dcfc8f213..098bb649ccb1a8f 100644 --- a/x-pack/test/rule_registry/common/lib/authentication/roles.ts +++ b/x-pack/test/rule_registry/common/lib/authentication/roles.ts @@ -24,10 +24,7 @@ export const globalRead: Role = { }, kibana: [ { - feature: { - siem: ['read'], - apm: ['read'], - }, + base: ['read'], spaces: ['*'], }, ], diff --git a/yarn.lock b/yarn.lock index 7a70953379f54e4..0199fabf8204320 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1466,10 +1466,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@34.5.2": - version "34.5.2" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-34.5.2.tgz#6aad49945a894fb77785a48c281cd0bd5e9e87ba" - integrity sha512-+ColXEaZ8Oa8lJ/ixayiLuWlYUggoTTW0Q5sWXOolR94PlekxsdSpu5f0kVyxlz7ECdkHz3ttOu9RYM7Z6ARyA== +"@elastic/eui@35.0.0": + version "35.0.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-35.0.0.tgz#e270b4f4ce216b8bba7dc874464bfc76c242e8ce" + integrity sha512-btjkhFb017iY5OM60ka0w+N4TqNeEpsXfaVmvPB5AzYMCT4wLC1L4I2xBD7W2xQbk5fTpKuh9Q6fOk3hmv3iKQ== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" @@ -1495,7 +1495,7 @@ react-is "~16.3.0" react-virtualized-auto-sizer "^1.0.2" react-window "^1.8.5" - refractor "^3.3.1" + refractor "^3.4.0" rehype-raw "^5.0.0" rehype-react "^6.0.0" rehype-stringify "^8.0.0" @@ -2931,6 +2931,10 @@ version "0.0.0" uid "" +"@kbn/typed-react-router-config@link:bazel-bin/packages/kbn-typed-react-router-config": + version "0.0.0" + uid "" + "@kbn/ui-framework@link:bazel-bin/packages/kbn-ui-framework": version "0.0.0" uid "" @@ -5879,6 +5883,15 @@ dependencies: "@types/react" "*" +"@types/react-router-config@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.2.tgz#4d3b52e71ed363a1976a12321e67b09a99ad6d10" + integrity sha512-WOSetDV3YPxbkVJAdv/bqExJjmcdCi/vpCJh3NfQOy1X15vHMSiMioXIcGekXDJJYhqGUMDo9e337mh508foAA== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + "@types/react-router-dom@^5.1.5": version "5.1.5" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090" @@ -22286,7 +22299,7 @@ printj@~1.1.0: resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@1.24.0, prismjs@^1.22.0, prismjs@~1.23.0: +prismjs@^1.22.0, prismjs@~1.24.0: version "1.24.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.24.0.tgz#0409c30068a6c52c89ef7f1089b3ca4de56be2ac" integrity sha512-SqV5GRsNqnzCL8k5dfAjCNhUrF3pR0A9lTDSCUZeh/LIshheXJEaP0hwLz2t4XHivd2J/v2HR+gRnigzeKe3cQ== @@ -23280,6 +23293,13 @@ react-reverse-portal@^1.0.4: resolved "https://registry.yarnpkg.com/react-reverse-portal/-/react-reverse-portal-1.0.4.tgz#d127d2c9147549b25c4959aba1802eca4b144cd4" integrity sha512-WESex/wSjxHwdG7M0uwPNkdQXaLauXNHi4INQiRybmFIXVzAqgf/Ak2OzJ4MLf4UuCD/IzEwJOkML2SxnnontA== +react-router-config@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" + integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg== + dependencies: + "@babel/runtime" "^7.1.2" + react-router-dom@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" @@ -23872,14 +23892,14 @@ reflect.ownkeys@^0.2.0: resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= -refractor@^3.2.0, refractor@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.3.1.tgz#ebbc04b427ea81dc25ad333f7f67a0b5f4f0be3a" - integrity sha512-vaN6R56kLMuBszHSWlwTpcZ8KTMG6aUCok4GrxYDT20UIOXxOc5o6oDc8tNTzSlH3m2sI+Eu9Jo2kVdDcUTWYw== +refractor@^3.2.0, refractor@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.4.0.tgz#62bd274b06c942041f390c371b676eb67cb0a678" + integrity sha512-dBeD02lC5eytm9Gld2Mx0cMcnR+zhSnsTfPpWqFaMgUMJfC9A6bcN3Br/NaXrnBJcuxnLFR90k1jrkaSyV8umg== dependencies: hastscript "^6.0.0" parse-entities "^2.0.0" - prismjs "~1.23.0" + prismjs "~1.24.0" regedit@^3.0.3: version "3.0.3"