diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index ebaeb6c3692b7..f16b0e46ada95 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -129,6 +129,7 @@ enabled: - x-pack/test/banners_functional/config.ts - x-pack/test/cases_api_integration/security_and_spaces/config_basic.ts - x-pack/test/cases_api_integration/security_and_spaces/config_trial.ts + - x-pack/test/cases_api_integration/security_and_spaces/config_no_public_base_url.ts - x-pack/test/cases_api_integration/spaces_only/config.ts - x-pack/test/detection_engine_api_integration/basic/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group1/config.ts diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 98475403ca32b..eb1487ddb610d 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index ae099907eaf10..8efa7f8781595 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 7683a670ad0cc..c39e3fbf400c7 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index b6223f469ee75..35b9e4b271e82 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 03b40689a968d..d268ae24dbc35 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 9237485502816..646e7c8219680 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index ab75b3c20bcbf..41bbb3e7fde44 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 6cbfe95fcdb59..bd967866d8230 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index e1c0468fdc4c0..d8a87f238d651 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index e5a37209f9534..b954be6c09277 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 8ce2184ab234a..d9469e759a288 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_chat.mdx b/api_docs/cloud_chat.mdx index 89a97f55ad913..4f569492a3ce5 100644 --- a/api_docs/cloud_chat.mdx +++ b/api_docs/cloud_chat.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChat title: "cloudChat" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChat plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 5e5b0d5fed32f..54eb31e82e81a 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index e965852141d18..5cc76805706d6 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 850f02ae5bd07..ba26675b39cbd 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index e18730f0d1032..d6b0f70a9cf33 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index df4c2daee6312..6ef18a3a1a34a 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -520,6 +520,10 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -978,6 +982,14 @@ "plugin": "@kbn/core-status-server-internal", "path": "packages/core/status/core-status-server-internal/src/status_service.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" @@ -11082,18 +11094,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/saved_objects/migrations.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/saved_objects/migrations.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/saved_objects/migrations.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/types.ts" @@ -11274,6 +11274,22 @@ "plugin": "ml", "path": "x-pack/plugins/ml/common/types/modules.ts" }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.15/index.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.15/index.ts" + }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" @@ -19275,6 +19291,10 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -19733,6 +19753,14 @@ "plugin": "@kbn/core-status-server-internal", "path": "packages/core/status/core-status-server-internal/src/status_service.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" @@ -40746,18 +40774,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/saved_objects/migrations.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/saved_objects/migrations.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/saved_objects/migrations.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/types.ts" @@ -40938,6 +40954,22 @@ "plugin": "ml", "path": "x-pack/plugins/ml/common/types/modules.ts" }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.15/index.ts" + }, + { + "plugin": "alerting", + "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.15/index.ts" + }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/save_dashboard_state_to_saved_object.ts" diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 35cc636d8da18..4a9b93624ce46 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 8cf8442b3a671..1a241c5d3afef 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 71e3c9ae67d08..6daeff8f09e23 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 5c1fa56b3e93d..32db4218f4f97 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index abb89969d739c..174bf75e5af79 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index eb8e3e42c061a..dd592bf2517b5 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 291a7a6b787ba..f5a60d70bb2c6 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 03ade23298bb1..9c86d87a5f88a 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 9b5f32daa5cde..a5f257464b327 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index f3cf02d01efb5..0d2ae84687987 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 9be1ce0c9d549..a4a8fb7164d13 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index e1c15f0b36c71..509f317201b63 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index e65b6f2b8d747..1a6c8659439f8 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index bed0c87d8dc72..1bae8a7b2bc20 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -146,8 +146,8 @@ so TS and code-reference navigation might not highlight them. | | | [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.test.ts#:~:text=getKibanaFeatures) | 8.8.0 | | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24) | 8.8.0 | | | [task.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/usage/task.ts#:~:text=index) | - | -| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/migrations.ts#:~:text=SavedObjectAttributes)+ 10 more | - | -| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/migrations.ts#:~:text=SavedObjectAttributes)+ 10 more | - | +| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/types.ts#:~:text=SavedObjectAttributes), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/types.ts#:~:text=SavedObjectAttributes)+ 11 more | - | +| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/types.ts#:~:text=SavedObjectAttributes), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/types.ts#:~:text=SavedObjectAttributes)+ 11 more | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index dfc99dbe7718f..e0042adee26db 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 3c1c313bb4e64..d9e9568875adb 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 392dfd744fed4..495a0e8ec6e81 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index b21117ef64068..3dcadce3585c0 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 1bc67b83f88e5..a23b5b4de8375 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index f763f8878f9a9..90995e3bfa964 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index c1664e7a0488d..34c1ea59c2abb 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 8434b169b1685..343431e4c5c21 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 2414f2f989d1f..5b014c7c0694a 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index fc4cd7075c95c..020dd9437577f 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 87eb0ca4239e5..0439d22f3e1b0 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 8d383dd04bc63..07f25b5592b84 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 0110beea0a830..b97850f178671 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 70498224fe725..8ee4ab363e88f 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 7f75a856a9f8a..01b9779c303de 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index d915562f517ed..77e866e689778 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index fc56222c21303..1011437ae1e1d 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index 4f0f35468da93..27aec8dea2ffd 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 6ea78a426b00f..836e7bedb1e6a 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index cbcdc174a0bd2..faeeacfabfd3f 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 7d49523bfcede..94e1f192e8026 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index d185ed80d375f..d1b8304c51d39 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index a75a5099d8cbf..fd04d9f42ce63 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index d5e95d79161de..7f06be4f1b41f 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index fb122a1a0d183..12dabf8516cfe 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index cb4f5a47beb92..fd6c3ab75b9cf 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 4dd5241b7e864..f413678b536a7 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index d8ae8de7c78a5..1bdd39c60beb8 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 18dd2dbbd8326..d81441b17ea2d 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index d773a828ed5c0..c0447d71298df 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 9210e5f155a06..8e9ba7f45684c 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.devdocs.json b/api_docs/guided_onboarding.devdocs.json index 5b5809dbbe1e8..701721e92edd3 100644 --- a/api_docs/guided_onboarding.devdocs.json +++ b/api_docs/guided_onboarding.devdocs.json @@ -108,7 +108,7 @@ "label": "status", "description": [], "signature": [ - "\"complete\" | \"in_progress\" | \"inactive\" | \"active\"" + "\"complete\" | \"in_progress\" | \"ready_to_complete\" | \"inactive\" | \"active\"" ], "path": "src/plugins/guided_onboarding/common/types.ts", "deprecated": false, diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 68dbb0f83f797..10b43de72130a 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 479a0c9fedc0a..4310a630f34da 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 29afac21e3b8f..186519279e1b8 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 7e1db0a05745c..0434e0878974b 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index f8ed2d3c0f53e..ccd47d562583e 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 0f1b903f620d1..e26714eb8cafb 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 3a9118e7bbf8b..1b9eafd7f4838 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index c2b1768cd69af..bd1891303fc21 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 4b950ba190115..ac428f3150d40 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 65bc270d4af3f..e95ee7e70935d 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 9b4555587996f..7f7b670e95ea4 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 24973a77dda65..8bb00f0d887ae 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 6ddd79212358e..f876f523e5273 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 0dc05aac8b78d..0f201ca949aa1 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 4932a398ad9e4..cb8d81d380d24 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 65b1a4f1ba756..2d1e94bb29c62 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index c1be6165b238c..afe87b71afc62 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index e3bb48e2e0811..619782af2efab 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 5486e5f333bdb..11c063f49dba3 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index b218432f5ebcc..ac9994663428f 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index c1e0fe3aaf76e..6174a4bf432e3 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 1ce366a32d4fe..819ace3145acf 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index ad4519607eb28..216a6888c4a0a 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index a141e3ec816a0..a2f5fba60997c 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 257d08713f24c..3cbbf3a8b0e6e 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index efd3e4379a169..63f7c5fae7aaf 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index d801f0e8cfbd5..ec95959f24487 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 9f87e2c6acc73..8639ce062be28 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 0dbc5adbc45d7..6f026440a3126 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 1713e0a26faa8..42f1850973a8a 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index ff6a1d93cee14..ede74c3e33ab1 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index f28c1ca301628..b408f084be878 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 932ae557c8c82..b68658aa33831 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 0845bc6eb1dbc..ec73e5d071ff2 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 280eb28952d1c..eda9625a5344e 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index dda783350fca2..071292bce9026 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index d60c3c52b3775..5a8e77d56a499 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index ac55c2cde88e7..5141496a4b30f 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 7c6e4368f3095..1237be00f25ce 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index f18c33093816a..e231b5edd7cc8 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index f1bf63e29b8c9..c897daef7c921 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index f42ea015aee0b..b09c0628adaca 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index b4608b508393a..aaf3d23b63cd9 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 92bd848fa3899..2ac525d842e1c 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 0660c21f48732..212ab7afa2907 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 65aafe170374e..430cf23529165 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 81469e6c906aa..ba28547698c99 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 7eedeaf9b0e97..582e110116765 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 99414d1b3963d..307034fa3741a 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 21aefe6dc31cb..1e1ed829a7891 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 2b01f8b025569..f177dad9092f6 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 1837d37440d5e..c07122276c046 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 27376ddc167e0..372d4c67d5bb4 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 9f3577505fe74..7ec82e6355cee 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 387fed6489ed4..9ab241ae433ac 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index e3ed025d9d87a..4eb03664d6c5b 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 4dba90aff09b8..159aa0c1fc1a9 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index dcf3b45235e27..ebaf334c48b94 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 2910167c137a7..f9057f5617270 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 4dbab16afe44c..bfcf35dc3f578 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 23d22f0347e6f..fa2b0932de510 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 305bc4011d3c5..55915243e1632 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index f8522fa3b54f0..f7645609a4c44 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index c5e253666a8e0..ae0ec6faa002d 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 829547015add7..20d0bb33fa8c9 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index 93de60b0f64de..f32d204f036f7 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index f40347c481362..13bd5b1e73932 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 303c757685192..34a3abf15fd6e 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 307aba572f572..51f73e69599eb 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index f381780ff1621..0abdf0e9e12ed 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index ec0275f4c068e..f0166494018c2 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index ce0878b4721ab..4c12a773e7265 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 6997ccab732e1..8177c47a3b86f 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index d64b35a667e94..1b165cc132ac4 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 15b397cdae940..69d2180d5740a 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 916f093b1befa..53a67b3a1663a 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index bf2d6d4324b9c..d78889dd618f8 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 8acfac015abee..f6651cfd2048b 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index b1b4ca0e49e2c..f3f4fe7b53773 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 528059e177bfa..f7f115f1566c5 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index f6f80612ce791..0ba4297747ace 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 1d663390a388c..bbbd96bbe494b 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 299c82e402225..c5f57a6c883ef 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index c3ed353ec4826..e9b0aaddfb97d 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 4d2a276cf7f3f..0dbce22cc47cb 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 711953a27ec5a..2aeb4f6165558 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 7c6d4b3835839..5123215a0efc0 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index 43204a6a9cd59..daf40fe512ebe 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index 1fb1fb2786666..3d9e99a97a464 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 9faa5bc2b6137..4c03c0c1a4724 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index ea177fc4d5002..775edabfb9835 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 4c0525bce9ec5..14d93e4105fc7 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 55a5d1f070aae..3841d7d285ff0 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 620a1388e6792..714a76ef55feb 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index ad1750c44201d..8c5cbd86552d8 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index b3a4024ae81d9..946c471ad20a3 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 2e2244224cff6..f2fc8743dec08 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 605c21ac98c3c..a714e9cc6f4ec 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index 885ae3946f5bd..b24ccb40a6e7e 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 013d7c1bec5bf..bf364845ce652 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 0e1a372fdefe0..afb3e93c55935 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index aa64438c1777b..22801f223555e 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 24007898d118c..c5cb736d207b8 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index a0bee88b6c19c..93b45bd1db2f7 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index bbd6d0002396b..9ae29f42cc95d 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 85dfaec1eddc8..c1709d33ce0d7 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index a49bb6261ca60..e28cba7d2425c 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 20173c54048b5..db8b4462a2abb 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index b9d975fc0423b..87c1f449de2be 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index fadc64dd7abaa..87f27b7f1a2d0 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index f114fcb08c417..22ee987b7efbe 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index 395eb493b24c6..0aad11d9f2137 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 32af8a0882a53..ced6cbacf088f 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 7daf0893bdae7..af35d94e359e8 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index dfe7c445c0bd5..05c9718c7d43a 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 14c4b8a5e1551..931eabbbe138a 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 738442e3af703..b0474d94827d6 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 073dfd67249bb..884ae287885d2 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 4e7500ed54889..d20d963d710f6 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 9336d4959948c..7a9006903b785 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 95c2da9061225..0b23f381804d9 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index 04def10325f6e..ffcc433bf6af5 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index ffebaf144c84a..9036867be221f 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 6c23d5d025f75..712f8251d5c61 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 865366e453cfc..70ce98466d4fe 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index f3abb5bbfb8ad..b9c3a94cd202c 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 6798150ce6006..69b37646668bc 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index dc8687236c9bc..333a24c202e44 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 47b3483514524..1bc472c28dd8b 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index e11e48a77fb0f..e05036c1c725e 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index 5fae41f10a9e5..31008adb80bcf 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index 6ebdeb956fd4e..fea85ac884713 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 20aa7441881a6..7a8c95a97d676 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index ad9055802199a..fa83aa76f9fb5 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index e64e5b5541764..3c02a543f729d 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 056df9040ed7d..68c6108d53597 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 7e2b57df7c7e0..b14db41024525 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 54d5fd1407508..67606ebbf5179 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index a77d40386164e..fb5ed53a336a6 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 65b694d6cac23..dcd94fad9e5d5 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 228b2a5142725..d3f0ceaed3cce 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index d0ebea2b1f95a..76dca2436e020 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 0571269bebd9f..d4f9ecf0cd3a0 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index bad24d39666a1..cb9441dd59b52 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index b76039518582c..a13434c01d4a5 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index d59c7e5124283..e52f41b3be8f7 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 4bd70290bfb2f..31c988d7f9a4e 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index b5f3421a03f60..01dbefbe3a456 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index f8225a2d1b36f..7610eb55d8c14 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 9b6a366a54049..ddb72189808d4 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 5bdae98802672..6a8f1775f9938 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 23c6303f781f1..d53c91895c42a 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index af239cf48b3ae..decce4c4ad401 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index e634e94f5669b..32298ea4b7bd8 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 56fb65934aeaa..09e9a962fb073 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index 73c8ed98bb41b..01caa1df40885 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 0fe20d9a77ca8..831d549444af9 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index dbb8bee66ea0b..3812354e12b63 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 17905a7849ba5..3f6a67a95736e 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index ab66657998592..dffcc6b101cfa 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 1f2bb0c19706c..dfb5b9f8354ec 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index 27cd800984c75..38f797b362de5 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index b29904c0c8674..60c14feeb7f07 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 6f1b46931ad27..03176e8c91ba1 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 112a31dcee24e..d34af650d8dd9 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index f158c9e813034..402fcdccd5caf 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index c38ae7d4118a3..c5b88a3421e8b 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index a6465f693e820..f211bb66b2f4f 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index c89744f9f5778..7252e46db7ebc 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index f9f13dce813b2..52add676219f3 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index b0701abb5118d..24b67e6c76f81 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 0f71192d008a6..fbd6a4d3cdad7 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 1d91ad2e67db3..46233e26fbab7 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index d1fa9d3786b45..58b37afc7897a 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index e9db73c80e329..235c80480f1d4 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 26b1c70d9bced..0338ad7ba8da2 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 79686f7850b40..a86c1995e87be 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index b05bec4988f0d..e3363f9927c87 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 57eeb56777e11..8b783d63b1e70 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index d62ab83ebcff7..20d1b9e0144ce 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 59d99761b93f3..86b7e9647bc46 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 1dc26531545a1..d7d13318bb297 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 3b9c570b113b1..85e98cbdeb15c 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 7b1c1717ee7d5..8cb400d9399d2 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 3fa373a22ad1a..0e9025ba267fe 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 0c359f4f7e8e5..bf4fa667858a6 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 41119a9d00319..b0c70bec6cc0c 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index fa6ddcdd6d95c..0bbbda976916e 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 340a7c896ee06..e8619d48201fa 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 6a0a3f8e6726d..c52b256a5819b 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index 4e168ef4af173..fe2e42a922202 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 2c7e2090a91b1..b845293fa719a 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 71e9a366cbbbb..2280cea95ea08 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index d8bbdacea2d21..7ec6d9da1d167 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 30eef0c1196cb..72ac2e99478bd 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 7cfcdca31f79e..1998d0d5520c7 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index cd5e46f0c4235..1e1265090924e 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 09b9919f46e5f..c3621ff358455 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index b77b43358fb51..75e43e83fe804 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index d080e4dc7fc69..2c58a2a334895 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 921263ac94423..90b5f681cacb7 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 936419c6b285d..32e15e0991aaf 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index f1d9eaca02302..9c95fef63a351 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 5028fe685abf5..cba8c0fc1853f 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index ad72a93059af3..28c61e54ec2d9 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 796c0a11d338e..46629467a890e 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 909d35424ccd7..83afc2c06b970 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index babfb00112ec5..d81c1f5608dc0 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 07438956c25f5..590a62891fa6f 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 5fd2150c55e3d..fe0437ddecca7 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index af5e2740c0191..aa3ac9977962f 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 36d6d11d964c6..5358c1464d3b0 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 0cc36ab118d0f..ce5c1d380fea3 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index e0eafa025e1bf..139dc072af92f 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 5e8e571e096f7..92791b020dfaf 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 3454c24690a27..c61e40ce4d5c1 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 83ce9bc555f44..88461caa1d77a 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 8edaf1daf0abb..9f0541f5a55fd 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 7b33a739414ef..a874a65d25126 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index c74bb8b86f6b1..9c925b1338223 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index bb07f02b58305..39bc3999fafc6 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 0be6ec752a657..9807bfb87229a 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 7f63210b061fe..a9042d2232d08 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index acf6f43badaee..5a9cd38f09b49 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 3962948d0e0ae..2cd4406035039 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 04ee34ca4c3e7..72cacb9ce2b3c 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 4fc89c7ec606c..8576083e813f0 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index e47c6f19d24af..4ac93e2d25960 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index b41f1d06177e0..f14d26d2edf22 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 909fe3ea66e3d..bd5855d0cd746 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index d231fffd94181..78b6e7b88d4c7 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 93a012258b767..0eba790a12640 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 094976c30d2d9..503b13efc68d1 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index dbb6c96a59e58..31c15c4a52037 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 14e588f8b50f5..76930deff7aec 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 7fbebab171a67..e81b04b3ae9eb 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index dda901559468e..5d388d1b3c74e 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 61160386978e9..aa45daa55d3cf 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 9f89aa73432d5..0e4323167397b 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 77f2fc244841c..2a75e4ca26742 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 62bb89a7a9b24..6cb107b6b7685 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 88d539bbba324..0718bbf83840c 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index be2afdf972f3b..d9d770d2cfe89 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 780780183872b..b760c93c1987a 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index ce9b8887ff99d..13c52febec86c 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index cfd332cb2372d..ec6631008e9fb 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 8a599cba5bc63..7e20d5a92be16 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index e7bcd04174597..4a55d033fb255 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 1caabc1075553..cace3c480477c 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 489a3c7b274c2..6b7dc5b1d516d 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index 9312225be586e..332c17f903579 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 608c69bd1e2f9..60dd7a32b04a8 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index 5d9359b6a226b..c43d04bd26a11 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index e765b7acfc139..ecbf3828a144c 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 9291b410c219b..214205caf4b4e 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 0f9a76875a8b0..8e7a998461cee 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index b1c4bcca84749..045727e8f9622 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index c2f8e1f83d46b..3e3407f1bbf06 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 509eaaf0fec48..286d22c37bfd9 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 826c8d4022f01..3a8e6c7e31c3b 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 69619984c403f..7c57fe2abda11 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 31a5f5fb60edc..3bc5fbf1173c1 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index 62dab760e4a5b..bc2fbf01f59d1 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 01b6a220bf865..97e2338d71629 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index b87735f14e680..8666564c891e0 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 959d827c97d4b..e6ce5d7e8246f 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 11b8436e5b02f..61c00ac271eca 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 90a8a2253ff33..5c52d6c402e32 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index c300b5f2b166e..bf22a5ec555d8 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 3ac807b4a2f2a..86f65530886b3 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index c61e10e481d27..f1b1798fab1b8 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index db7d2d2208749..db8d1a8022b31 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 0fabbc396e628..ad788a66355bb 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index afd067b54c86f..99f07cca5249f 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index b1fba7a953f3c..d361266f4b112 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index edcf2a9ecf49f..2d3efbab3fe7a 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 706b250ca9c30..d91016709162d 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 9e2f55d44256b..6cd910e0cbd30 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 24a679d142a6f..d282fab4950b8 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 9395a8b06dc2f..c20fcee717e8b 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 6218edc4db488..6dbe8ec638069 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 20ca815e9fe48..fe99963d954ae 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 1ae0f55ee8b47..5f72dee209292 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index b614a9cef39d5..4ea45e2d6386d 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 29bd92f4db760..d19ce3adcaead 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index fc163ff44614d..383b3680cbafb 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 0889602483dfd..a62e0b65cbab9 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 35dbfba586fb5..9703f660b6515 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index d1a0beb34b1bf..30d4ce56cb586 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index b2935e8899670..4dd4ec5e1eab8 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.devdocs.json b/api_docs/maps.devdocs.json index d40b99e5b5795..752becaa2ceb6 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -4470,7 +4470,7 @@ "StyleDescriptor", " | null | undefined; query?: ", "Query", - " | undefined; includeInFitToBounds?: boolean | undefined; }" + " | undefined; includeInFitToBounds?: boolean | undefined; parent?: string | undefined; }" ], "path": "x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts", "deprecated": false, diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index da7f8d0f22b80..70ac834551a4e 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index d4352b0b1b887..9a6033e47298d 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 74a1ec483cc1f..70d2472f68011 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 280dde24913c9..ffb8396bac5cf 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 71fe97576094d..19006dba40a9f 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index b304315815ff7..b2e3637164e81 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 71fe49172d536..fd6c0f9d3f4e8 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 2617e4c3ed390..f541a56f52200 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index cbaaf7fb4735f..d9d8bb530bef2 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 281fb6de01efa..dfcb932773724 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 32302 | 179 | 21767 | 1023 | +| 32303 | 179 | 21767 | 1023 | ## Plugin Directory @@ -155,7 +155,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 452 | 1 | 346 | 33 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [Kibana Localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 517 | 1 | 489 | 49 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 518 | 1 | 489 | 49 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 133 | 0 | 92 | 11 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 206 | 0 | 142 | 9 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 122 | 0 | 117 | 2 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 7c78b017a7cbc..2d2bd5f7cab65 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 03fd68b4dace8..a2ab179291b93 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 09f7279acc3b0..64db429de0000 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 84e0a16d1857b..3addd6c04342c 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index d4be3ea7797a2..2d950f366d145 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 1a5cdf684f518..b8177a5349db5 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 6a63f3a173cc8..b950aff4a9dc1 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 73a219490d72b..e42617d41f680 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 4cda9e6326e05..272497ed10c72 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 3b088d075d9fe..2669f7ef767e9 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 917393033e0e7..e92816f0b4452 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 1c2c4cafda3cd..da0c09083fca1 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 6bbebd91df07c..6f7d29c36ed28 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 968e4bdfb7d2f..85551152dec24 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index cb762b0c57883..c8d8da4c7e433 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 6dbc5b530e8e1..d2c74e5c35cb5 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index f535c6aca25b3..054308e191638 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 0d114ac2efa70..69b68c2eba9a8 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 246bd975b6244..3b436f7e11ab5 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 778e9f2631c91..0e71d2a44587c 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index a08d22082148e..a1e9bb5e5f4c0 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 818254d238eb3..8da0b5dfe30ab 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 48c732f3e1cdf..9c0b90d429892 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index b920c33a9fb49..37c810bccb7fb 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 50d9e28747563..6a9da2e3d8f30 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 49c39896be569..90931f6a3d4e5 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 6742dca554e95..fa8c62da9a483 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 71170397dd8e1..5ee8669701c62 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index fae7d35b28b7d..6dbf8f218887f 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 197ea0b93a34a..dc77ccda78c8e 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 27e02053609af..ff96d1a3e72ed 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index e9e5908c5ee26..1b74e68bed0b8 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -8164,7 +8164,7 @@ "label": "CoreQueryParams", "description": [], "signature": [ - "{ readonly aggField?: string | undefined; readonly termSize?: number | undefined; readonly termField?: string | undefined; readonly index: string | string[]; readonly timeField: string; readonly aggType: string; readonly timeWindowSize: number; readonly timeWindowUnit: string; readonly groupBy: string; }" + "{ readonly aggField?: string | undefined; readonly termSize?: number | undefined; readonly termField?: string | undefined; readonly filterKuery?: string | undefined; readonly index: string | string[]; readonly timeField: string; readonly aggType: string; readonly timeWindowSize: number; readonly timeWindowUnit: string; readonly groupBy: string; }" ], "path": "x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts", "deprecated": false, @@ -8236,7 +8236,7 @@ "label": "TimeSeriesQuery", "description": [], "signature": [ - "{ readonly interval?: string | undefined; readonly aggField?: string | undefined; readonly termSize?: number | undefined; readonly termField?: string | undefined; readonly dateStart?: string | undefined; readonly dateEnd?: string | undefined; readonly index: string | string[]; readonly timeField: string; readonly aggType: string; readonly timeWindowSize: number; readonly timeWindowUnit: string; readonly groupBy: string; }" + "{ readonly interval?: string | undefined; readonly aggField?: string | undefined; readonly termSize?: number | undefined; readonly termField?: string | undefined; readonly dateStart?: string | undefined; readonly dateEnd?: string | undefined; readonly filterKuery?: string | undefined; readonly index: string | string[]; readonly timeField: string; readonly aggType: string; readonly timeWindowSize: number; readonly timeWindowUnit: string; readonly groupBy: string; }" ], "path": "x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts", "deprecated": false, @@ -8358,6 +8358,23 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-server.CoreQueryParamsSchemaProperties.filterKuery", + "type": "Object", + "tags": [], + "label": "filterKuery", + "description": [ + "// filter field" + ], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-server.CoreQueryParamsSchemaProperties.termSize", diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 16885f04df0fd..2148e1939ea24 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Response Ops](https://github.com/orgs/elastic/teams/response-ops) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 517 | 1 | 489 | 49 | +| 518 | 1 | 489 | 49 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 0e6e8ffe105cf..4ec201d732105 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 863b74f7a45b2..5563c08371882 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 24f2e54fef747..27c55ce6e644b 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index a49ee1e7fafee..2c426b911f3d7 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 3f8c6f31330dd..f0067d2093f5c 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 36639a61773b1..f6b97fd615cad 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index e3fe9d4770da4..55882ead7e50e 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index b01561a87beb1..27b6bb43e27c8 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 149ff1941dd59..2b38a0015b600 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 0d8940eee6aff..55c0ddf204fc2 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index 50a3d43aaf024..7184c050b5be0 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index f983d47428d5d..269a7121e31a4 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index df710abda7b34..e236f9b93e075 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 194ef7640a2ad..f4efbf7f4bfd5 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 3c94789158f65..ccbb3a90eb417 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index d8780d991a35d..1cd99fa5393d5 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 4d7e6813b86da..59bf0ca2e9847 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 572e99ae1f73a..13d2599680d2d 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 5f2ca49cbd46d..8862a4dc51714 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2022-10-13 +date: 2022-10-14 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/examples/response_stream/common/api/reducer_stream/request_body_schema.ts b/examples/response_stream/common/api/reducer_stream/request_body_schema.ts index 58ea487a8a539..8318a411ab86a 100644 --- a/examples/response_stream/common/api/reducer_stream/request_body_schema.ts +++ b/examples/response_stream/common/api/reducer_stream/request_body_schema.ts @@ -13,5 +13,7 @@ export const reducerStreamRequestBodySchema = schema.object({ simulateErrors: schema.maybe(schema.boolean()), /** Maximum timeout between streaming messages. */ timeout: schema.maybe(schema.number()), + /** Setting to override headers derived compression */ + compressResponse: schema.maybe(schema.boolean()), }); export type ReducerStreamRequestBodySchema = TypeOf; diff --git a/examples/response_stream/common/api/simple_string_stream/request_body_schema.ts b/examples/response_stream/common/api/simple_string_stream/request_body_schema.ts index 8e891395e5dc8..3a9d0f4d7e225 100644 --- a/examples/response_stream/common/api/simple_string_stream/request_body_schema.ts +++ b/examples/response_stream/common/api/simple_string_stream/request_body_schema.ts @@ -11,6 +11,8 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const simpleStringStreamRequestBodySchema = schema.object({ /** Maximum timeout between streaming messages. */ timeout: schema.number(), + /** Setting to override headers derived compression */ + compressResponse: schema.maybe(schema.boolean()), }); export type SimpleStringStreamRequestBodySchema = TypeOf< typeof simpleStringStreamRequestBodySchema diff --git a/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx b/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx index 0755765374330..913f8b4064440 100644 --- a/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx +++ b/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx @@ -44,13 +44,14 @@ export const PageReducerStream: FC = () => { const basePath = http?.basePath.get() ?? ''; const [simulateErrors, setSimulateErrors] = useState(false); + const [compressResponse, setCompressResponse] = useState(true); const { dispatch, start, cancel, data, errors, isCancelled, isRunning } = useFetchStream< ApiReducerStream, typeof basePath >( `${basePath}/internal/response_stream/reducer_stream`, - { simulateErrors }, + { compressResponse, simulateErrors }, { reducer: reducerStreamReducer, initialState } ); @@ -144,6 +145,13 @@ export const PageReducerStream: FC = () => { onChange={(e) => setSimulateErrors(!simulateErrors)} compressed /> + setCompressResponse(!compressResponse)} + compressed + /> ); diff --git a/examples/response_stream/public/containers/app/pages/page_simple_string_stream/index.tsx b/examples/response_stream/public/containers/app/pages/page_simple_string_stream/index.tsx index cfa76688d9701..a305e907cfb45 100644 --- a/examples/response_stream/public/containers/app/pages/page_simple_string_stream/index.tsx +++ b/examples/response_stream/public/containers/app/pages/page_simple_string_stream/index.tsx @@ -6,9 +6,17 @@ * Side Public License, v 1. */ -import React, { FC } from 'react'; +import React, { useState, FC } from 'react'; -import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiCheckbox, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, +} from '@elastic/eui'; import { useFetchStream } from '@kbn/aiops-utils'; @@ -21,10 +29,15 @@ export const PageSimpleStringStream: FC = () => { const { core } = useDeps(); const basePath = core.http?.basePath.get() ?? ''; + const [compressResponse, setCompressResponse] = useState(true); + const { dispatch, errors, start, cancel, data, isRunning } = useFetchStream< ApiSimpleStringStream, typeof basePath - >(`${basePath}/internal/response_stream/simple_string_stream`, { timeout: 500 }); + >(`${basePath}/internal/response_stream/simple_string_stream`, { + compressResponse, + timeout: 500, + }); const onClickHandler = async () => { if (isRunning) { @@ -58,6 +71,14 @@ export const PageSimpleStringStream: FC = () => { + setCompressResponse(!compressResponse)} + compressed + /> +

{data}

diff --git a/examples/response_stream/server/routes/reducer_stream.ts b/examples/response_stream/server/routes/reducer_stream.ts index 7cc02d9b1a80f..e9fe6d02b68f6 100644 --- a/examples/response_stream/server/routes/reducer_stream.ts +++ b/examples/response_stream/server/routes/reducer_stream.ts @@ -31,17 +31,29 @@ export const defineReducerStreamRoute = (router: IRouter, logger: Logger) => { const maxTimeoutMs = request.body.timeout ?? 250; const simulateError = request.body.simulateErrors ?? false; + let logMessageCounter = 1; + + function logDebugMessage(msg: string) { + logger.debug(`Response Stream Example #${logMessageCounter}: ${msg}`); + logMessageCounter++; + } + + logDebugMessage('Starting stream.'); + let shouldStop = false; request.events.aborted$.subscribe(() => { + logDebugMessage('aborted$ subscription trigger.'); shouldStop = true; }); request.events.completed$.subscribe(() => { + logDebugMessage('completed$ subscription trigger.'); shouldStop = true; }); const { end, push, responseWithHeaders } = streamFactory( request.headers, - logger + logger, + request.body.compressResponse ); const entities = [ diff --git a/examples/response_stream/server/routes/single_string_stream.ts b/examples/response_stream/server/routes/single_string_stream.ts index dd3a784314c96..fde4947746dbf 100644 --- a/examples/response_stream/server/routes/single_string_stream.ts +++ b/examples/response_stream/server/routes/single_string_stream.ts @@ -35,7 +35,11 @@ export const defineSimpleStringStreamRoute = (router: IRouter, logger: Logger) = shouldStop = true; }); - const { end, push, responseWithHeaders } = streamFactory(request.headers, logger); + const { end, push, responseWithHeaders } = streamFactory( + request.headers, + logger, + request.body.compressResponse + ); const text = 'Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents. Elasticsearch is developed in Java and is dual-licensed under the source-available Server Side Public License and the Elastic license, while other parts fall under the proprietary (source-available) Elastic License. Official clients are available in Java, .NET (C#), PHP, Python, Apache Groovy, Ruby and many other languages. According to the DB-Engines ranking, Elasticsearch is the most popular enterprise search engine.'; diff --git a/package.json b/package.json index 6b72c857c310c..140727a894ca5 100644 --- a/package.json +++ b/package.json @@ -585,7 +585,7 @@ "react-fast-compare": "^2.0.4", "react-focus-on": "^3.6.0", "react-grid-layout": "^1.3.4", - "react-hook-form": "^7.36.1", + "react-hook-form": "^7.37.0", "react-intl": "^2.8.0", "react-is": "^17.0.2", "react-markdown": "^6.0.3", @@ -1450,7 +1450,7 @@ "webpack-dev-server": "^4.9.3", "webpack-merge": "^4.2.2", "webpack-sources": "^1.4.1", - "xml-crypto": "^2.1.4", + "xml-crypto": "^3.0.0", "xmlbuilder": "13.0.2", "yargs": "^15.4.1" } diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index fde4a364947cc..41cf7d3b45681 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -110,7 +110,7 @@ pageLoadAssetSize: share: 71239 snapshotRestore: 79032 spaces: 57868 - stackAlerts: 29684 + stackAlerts: 58316 stackConnectors: 36314 synthetics: 40958 telemetry: 51957 @@ -140,6 +140,6 @@ pageLoadAssetSize: visTypeTimeseries: 55203 visTypeVega: 153573 visTypeVislib: 242838 - visTypeXy: 30000 + visTypeXy: 31800 visualizations: 90000 watcher: 43598 diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts index 4664902d13876..3976f6977e2be 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts @@ -28,6 +28,7 @@ describe('layeredXyVis', () => { args: { ...rest, layers: [sampleExtendedLayer] }, syncColors: false, syncTooltips: false, + canNavigateToLens: false, }, }); }); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts index 8dcc58cda01a8..d6553ef3dd7b3 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts @@ -61,6 +61,7 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers) (handlers.variables?.embeddableTitle as string) ?? handlers.getExecutionContext?.()?.description, }, + canNavigateToLens: Boolean(handlers.variables.canNavigateToLens), syncColors: handlers?.isSyncColorsEnabled?.() ?? false, syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false, }, diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts index 67c7ab8d1e294..a09c5f05adb08 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts @@ -38,6 +38,7 @@ describe('xyVis', () => { }, ], }, + canNavigateToLens: false, syncColors: false, syncTooltips: false, }, @@ -346,6 +347,7 @@ describe('xyVis', () => { }, ], }, + canNavigateToLens: false, syncColors: false, syncTooltips: false, }, diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts index e29f1e5ffff3c..849f2030a4697 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts @@ -136,6 +136,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => { (handlers.variables?.embeddableTitle as string) ?? handlers.getExecutionContext?.()?.description, }, + canNavigateToLens: Boolean(handlers.variables.canNavigateToLens), syncColors: handlers?.isSyncColorsEnabled?.() ?? false, syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false, }, diff --git a/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts index de387b4113373..94567f563cdb1 100644 --- a/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts @@ -18,6 +18,7 @@ export interface XYChartProps { args: XYProps; syncTooltips: boolean; syncColors: boolean; + canNavigateToLens?: boolean; } export interface XYRender { diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx index 45eca06c670b0..0d88480b00342 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx @@ -110,7 +110,7 @@ declare global { } } -export type XYChartRenderProps = XYChartProps & { +export type XYChartRenderProps = Omit & { chartsThemeService: ChartsPluginSetup['theme']; chartsActiveCursorService: ChartsPluginStart['activeCursor']; data: DataPublicPluginStart; diff --git a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx index 5606bad9050c7..e00f5b04bd590 100644 --- a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx @@ -52,6 +52,7 @@ interface XyChartRendererDeps { const extractCounterEvents = ( originatingApp: string, { layers, yAxisConfigs }: XYChartProps['args'], + canNavigateToLens: boolean, services: { getDataLayers: typeof getDataLayers; } @@ -149,6 +150,7 @@ const extractCounterEvents = ( (aggregateLayers.length === 1 && dataLayer.splitAccessors?.length) ? 'aggregate_bucket' : undefined, + canNavigateToLens ? `render_${byTypes.mixedXY ? 'mixed_xy' : type}_convertable` : undefined, ] .filter(Boolean) .map((item) => `render_${originatingApp}_${item}`); @@ -188,9 +190,14 @@ export const getXyChartRenderer = ({ const visualizationType = extractVisualizationType(executionContext); if (deps.usageCollection && containerType && visualizationType) { - const uiEvents = extractCounterEvents(visualizationType, config.args, { - getDataLayers, - }); + const uiEvents = extractCounterEvents( + visualizationType, + config.args, + Boolean(config.canNavigateToLens), + { + getDataLayers, + } + ); if (uiEvents) { deps.usageCollection.reportUiCounter(containerType, METRIC_TYPE.COUNT, uiEvents); diff --git a/src/plugins/controls/common/options_list/ip_search.test.ts b/src/plugins/controls/common/options_list/ip_search.test.ts new file mode 100644 index 0000000000000..1c935b1875311 --- /dev/null +++ b/src/plugins/controls/common/options_list/ip_search.test.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { getIpRangeQuery, getIpSegments, getMinMaxIp } from './ip_search'; + +describe('test IP search functionality', () => { + test('get IP segments', () => { + expect(getIpSegments('')).toStrictEqual({ segments: [''], type: 'unknown' }); + expect(getIpSegments('test')).toStrictEqual({ segments: ['test'], type: 'unknown' }); + expect(getIpSegments('123.456')).toStrictEqual({ segments: ['123', '456'], type: 'ipv4' }); + expect(getIpSegments('123..456...')).toStrictEqual({ segments: ['123', '456'], type: 'ipv4' }); + expect(getIpSegments('abc:def:')).toStrictEqual({ segments: ['abc', 'def'], type: 'ipv6' }); + expect(getIpSegments(':::x:::abc:::def:::')).toStrictEqual({ + segments: ['x', 'abc', 'def'], + type: 'ipv6', + }); + }); + + test('get min/max IP', () => { + expect(getMinMaxIp('ipv4', ['123'])).toStrictEqual({ + min: '123.0.0.0', + max: '123.255.255.255', + }); + expect(getMinMaxIp('ipv4', ['123', '456', '789'])).toStrictEqual({ + min: '123.456.789.0', + max: '123.456.789.255', + }); + expect(getMinMaxIp('ipv6', ['abc', 'def'])).toStrictEqual({ + min: 'abc:def::', + max: 'abc:def:ffff:ffff:ffff:ffff:ffff:ffff', + }); + expect(getMinMaxIp('ipv6', ['a', 'b', 'c', 'd', 'e', 'f', 'g'])).toStrictEqual({ + min: 'a:b:c:d:e:f:g::', + max: 'a:b:c:d:e:f:g:ffff', + }); + }); + + test('get IP range query', () => { + // invalid searches + expect(getIpRangeQuery('xyz')).toStrictEqual({ + validSearch: false, + }); + expect(getIpRangeQuery('123.456.OVER 9000')).toStrictEqual({ + validSearch: false, + }); + expect(getIpRangeQuery('abc:def:ghi')).toStrictEqual({ + validSearch: false, + }); + + // full IP searches + expect(getIpRangeQuery('1.2.3.4')).toStrictEqual({ + validSearch: true, + rangeQuery: [ + { + key: 'ipv4', + mask: '1.2.3.4/32', + }, + ], + }); + expect(getIpRangeQuery('1.2.3.256')).toStrictEqual({ + validSearch: false, + rangeQuery: undefined, + }); + expect(getIpRangeQuery('fbbe:a363:9e14:987c:49cf:d4d0:d8c8:bc42')).toStrictEqual({ + validSearch: true, + rangeQuery: [ + { + key: 'ipv6', + mask: 'fbbe:a363:9e14:987c:49cf:d4d0:d8c8:bc42/128', + }, + ], + }); + + // partial IP searches - ipv4 + const partialIpv4 = getIpRangeQuery('12.34.'); + expect(partialIpv4.validSearch).toBe(true); + expect(partialIpv4.rangeQuery?.[0]).toStrictEqual({ + key: 'ipv4', + from: '12.34.0.0', + to: '12.34.255.255', + }); + expect(getIpRangeQuery('123.456.7')).toStrictEqual({ + validSearch: false, + rangeQuery: [], + }); + expect(getIpRangeQuery('12:34.56')).toStrictEqual({ + validSearch: false, + rangeQuery: [], + }); + + // partial IP searches - ipv6 + const partialIpv6 = getIpRangeQuery('fbbe:a363:9e14:987c:49cf'); + expect(partialIpv6.validSearch).toBe(true); + expect(partialIpv6.rangeQuery?.[0]).toStrictEqual({ + key: 'ipv6', + from: 'fbbe:a363:9e14:987c:49cf::', + to: 'fbbe:a363:9e14:987c:49cf:ffff:ffff:ffff', + }); + + // partial IP searches - unknown type + let partialUnknownIp = getIpRangeQuery('1234'); + expect(partialUnknownIp.validSearch).toBe(true); + expect(partialUnknownIp.rangeQuery?.length).toBe(1); + expect(partialUnknownIp.rangeQuery?.[0]).toStrictEqual({ + key: 'ipv6', + from: '1234::', + to: '1234:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + }); + + partialUnknownIp = getIpRangeQuery('123'); + expect(partialUnknownIp.validSearch).toBe(true); + expect(partialUnknownIp.rangeQuery?.length).toBe(2); + expect(partialUnknownIp.rangeQuery?.[0]).toStrictEqual({ + key: 'ipv4', + from: '123.0.0.0', + to: '123.255.255.255', + }); + expect(partialUnknownIp.rangeQuery?.[1]).toStrictEqual({ + key: 'ipv6', + from: '123::', + to: '123:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + }); + }); +}); diff --git a/src/plugins/controls/common/options_list/ip_search.ts b/src/plugins/controls/common/options_list/ip_search.ts new file mode 100644 index 0000000000000..f371fbb1f6506 --- /dev/null +++ b/src/plugins/controls/common/options_list/ip_search.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 ipaddr from 'ipaddr.js'; + +export interface IpRangeQuery { + validSearch: boolean; + rangeQuery?: Array<{ key: string; from: string; to: string } | { key: string; mask: string }>; +} +interface IpSegments { + segments: string[]; + type: 'ipv4' | 'ipv6' | 'unknown'; +} + +export const getIpSegments = (searchString: string): IpSegments => { + if (searchString.indexOf('.') !== -1) { + // ipv4 takes priority - so if search string contains both `.` and `:` then it will just be an invalid ipv4 search + const ipv4Segments = searchString.split('.').filter((segment) => segment !== ''); + return { segments: ipv4Segments, type: 'ipv4' }; + } else if (searchString.indexOf(':') !== -1) { + // note that currently, because of the logic of splitting here, searching for shorthand IPv6 IPs is not supported (for example, + // must search for `59fb:0:0:0:0:1005:cc57:6571` and not `59fb::1005:cc57:6571` to get the expected match) + const ipv6Segments = searchString.split(':').filter((segment) => segment !== ''); + return { segments: ipv6Segments, type: 'ipv6' }; + } + return { segments: [searchString], type: 'unknown' }; +}; + +export const getMinMaxIp = ( + type: 'ipv4' | 'ipv6', + segments: IpSegments['segments'] +): { min: string; max: string } => { + const isIpv4 = type === 'ipv4'; + const minIp = isIpv4 + ? segments.concat(Array(4 - segments.length).fill('0')).join('.') + : segments.join(':') + '::'; + const maxIp = isIpv4 + ? segments.concat(Array(4 - segments.length).fill('255')).join('.') + : segments.concat(Array(8 - segments.length).fill('ffff')).join(':'); + return { + min: minIp, + max: maxIp, + }; +}; + +const buildFullIpSearchRangeQuery = (segments: IpSegments): IpRangeQuery['rangeQuery'] => { + const { type: ipType, segments: ipSegments } = segments; + + const isIpv4 = ipType === 'ipv4'; + const searchIp = ipSegments.join(isIpv4 ? '.' : ':'); + if (ipaddr.isValid(searchIp)) { + return [ + { + key: ipType, + mask: isIpv4 ? searchIp + '/32' : searchIp + '/128', + }, + ]; + } + return undefined; +}; + +const buildPartialIpSearchRangeQuery = (segments: IpSegments): IpRangeQuery['rangeQuery'] => { + const { type: ipType, segments: ipSegments } = segments; + + const ranges = []; + if (ipType === 'unknown' || ipType === 'ipv4') { + const { min: minIpv4, max: maxIpv4 } = getMinMaxIp('ipv4', ipSegments); + + if (ipaddr.isValid(minIpv4) && ipaddr.isValid(maxIpv4)) { + ranges.push({ + key: 'ipv4', + from: minIpv4, + to: maxIpv4, + }); + } + } + + if (ipType === 'unknown' || ipType === 'ipv6') { + const { min: minIpv6, max: maxIpv6 } = getMinMaxIp('ipv6', ipSegments); + + if (ipaddr.isValid(minIpv6) && ipaddr.isValid(maxIpv6)) { + ranges.push({ + key: 'ipv6', + from: minIpv6, + to: maxIpv6, + }); + } + } + + return ranges; +}; + +export const getIpRangeQuery = (searchString: string): IpRangeQuery => { + if (searchString.match(/^[A-Fa-f0-9.:]*$/) === null) { + return { validSearch: false }; + } + + const ipSegments = getIpSegments(searchString); + if (ipSegments.type === 'ipv4' && ipSegments.segments.length === 4) { + const ipv4RangeQuery = buildFullIpSearchRangeQuery(ipSegments); + return { validSearch: Boolean(ipv4RangeQuery), rangeQuery: ipv4RangeQuery }; + } + if (ipSegments.type === 'ipv6' && ipSegments.segments.length === 8) { + const ipv6RangeQuery = buildFullIpSearchRangeQuery(ipSegments); + return { validSearch: Boolean(ipv6RangeQuery), rangeQuery: ipv6RangeQuery }; + } + + const partialRangeQuery = buildPartialIpSearchRangeQuery(ipSegments); + return { + validSearch: !(partialRangeQuery?.length === 0), + rangeQuery: partialRangeQuery, + }; +}; diff --git a/src/plugins/controls/common/options_list/mocks.tsx b/src/plugins/controls/common/options_list/mocks.tsx index 2fd4d31a54b21..09f6d9caa33b4 100644 --- a/src/plugins/controls/common/options_list/mocks.tsx +++ b/src/plugins/controls/common/options_list/mocks.tsx @@ -18,7 +18,7 @@ const mockOptionsListComponentState = { availableOptions: ['woof', 'bark', 'meow', 'quack', 'moo'], invalidSelections: [], validSelections: [], - searchString: '', + searchString: { value: '', valid: true }, } as OptionsListComponentState; const mockOptionsListEmbeddableInput = { diff --git a/src/plugins/controls/public/options_list/components/options_list_popover.tsx b/src/plugins/controls/public/options_list/components/options_list_popover.tsx index 0cdfcefb9baa5..8863a3d1978a3 100644 --- a/src/plugins/controls/public/options_list/components/options_list_popover.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_popover.tsx @@ -26,8 +26,8 @@ import { import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public'; import { optionsListReducers } from '../options_list_reducers'; -import { OptionsListStrings } from './options_list_strings'; import { OptionsListReduxState } from '../types'; +import { OptionsListStrings } from './options_list_strings'; export interface OptionsListPopoverProps { width: number; @@ -80,11 +80,12 @@ export const OptionsListPopover = ({ width, updateSearchString }: OptionsListPop > updateSearchString(event.target.value)} - value={searchString} + value={searchString.value} data-test-subj="optionsList-control-search-input" placeholder={ totalCardinality diff --git a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx index 0ccd5d49cabc2..292e7cb6b0597 100644 --- a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx +++ b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx @@ -35,7 +35,7 @@ import { OptionsListEmbeddableInput, OPTIONS_LIST_CONTROL, } from '../..'; -import { optionsListReducers } from '../options_list_reducers'; +import { getDefaultComponentState, optionsListReducers } from '../options_list_reducers'; import { OptionsListControl } from '../components/options_list_control'; import { ControlsDataViewsService } from '../../services/data_views/types'; import { ControlsOptionsListService } from '../../services/options_list/types'; @@ -105,6 +105,7 @@ export class OptionsListEmbeddable extends Embeddable({ embeddable: this, reducers: optionsListReducers, + initialComponentState: getDefaultComponentState(), }); this.initialize(); @@ -277,73 +278,87 @@ export class OptionsListEmbeddable extends Embeddable { + dispatch(setLoading(false)); + dispatch(publishFilters(newFilters)); + }); } else { - const valid: string[] = []; - const invalid: string[] = []; - - for (const selectedOption of selectedOptions) { - if (invalidSelections?.includes(selectedOption)) invalid.push(selectedOption); - else valid.push(selectedOption); - } - dispatch( - updateQueryResults({ - availableOptions: suggestions, - invalidSelections: invalid, - validSelections: valid, - totalCardinality, - }) - ); + batch(() => { + dispatch( + updateQueryResults({ + availableOptions: [], + }) + ); + dispatch(setLoading(false)); + }); } - - // publish filter - const newFilters = await this.buildFilter(); - batch(() => { - dispatch(setLoading(false)); - dispatch(publishFilters(newFilters)); - }); }; private buildFilter = async () => { diff --git a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable_factory.tsx b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable_factory.tsx index d0b779e566c31..ea36ede0e1c9d 100644 --- a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable_factory.tsx +++ b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable_factory.tsx @@ -57,7 +57,8 @@ export class OptionsListEmbeddableFactory public isFieldCompatible = (dataControlField: DataControlField) => { if ( (dataControlField.field.aggregatable && dataControlField.field.type === 'string') || - dataControlField.field.type === 'boolean' + dataControlField.field.type === 'boolean' || + dataControlField.field.type === 'ip' ) { dataControlField.compatibleControlTypes.push(this.type); } diff --git a/src/plugins/controls/public/options_list/options_list_reducers.ts b/src/plugins/controls/public/options_list/options_list_reducers.ts index 3b48e6f989f6c..2a0c69126c135 100644 --- a/src/plugins/controls/public/options_list/options_list_reducers.ts +++ b/src/plugins/controls/public/options_list/options_list_reducers.ts @@ -12,6 +12,11 @@ import { Filter } from '@kbn/es-query'; import { OptionsListReduxState, OptionsListComponentState } from './types'; import { OptionsListField } from '../../common/options_list/types'; +import { getIpRangeQuery } from '../../common/options_list/ip_search'; + +export const getDefaultComponentState = (): OptionsListReduxState['componentState'] => ({ + searchString: { value: '', valid: true }, +}); export const optionsListReducers = { deselectOption: (state: WritableDraft, action: PayloadAction) => { @@ -38,7 +43,13 @@ export const optionsListReducers = { } }, setSearchString: (state: WritableDraft, action: PayloadAction) => { - state.componentState.searchString = action.payload; + state.componentState.searchString.value = action.payload; + if ( + action.payload !== '' && // empty string search is never invalid + state.componentState.field?.type === 'ip' // only IP searches can currently be invalid + ) { + state.componentState.searchString.valid = getIpRangeQuery(action.payload).validSearch; + } }, selectOption: (state: WritableDraft, action: PayloadAction) => { if (!state.explicitInput.selectedOptions) state.explicitInput.selectedOptions = []; diff --git a/src/plugins/controls/public/options_list/types.ts b/src/plugins/controls/public/options_list/types.ts index aca0c05fe0893..4001299a9ab53 100644 --- a/src/plugins/controls/public/options_list/types.ts +++ b/src/plugins/controls/public/options_list/types.ts @@ -10,6 +10,11 @@ import { ReduxEmbeddableState } from '@kbn/presentation-util-plugin/public'; import { ControlOutput } from '../types'; import { OptionsListEmbeddableInput, OptionsListField } from '../../common/options_list/types'; +interface SearchString { + value: string; + valid: boolean; +} + // Component state is only used by public components. export interface OptionsListComponentState { field?: OptionsListField; @@ -17,7 +22,7 @@ export interface OptionsListComponentState { availableOptions?: string[]; invalidSelections?: string[]; validSelections?: string[]; - searchString: string; + searchString: SearchString; } // public only - redux embeddable state type diff --git a/src/plugins/controls/server/options_list/options_list_queries.test.ts b/src/plugins/controls/server/options_list/options_list_queries.test.ts index 399154c5518aa..40536822833da 100644 --- a/src/plugins/controls/server/options_list/options_list_queries.test.ts +++ b/src/plugins/controls/server/options_list/options_list_queries.test.ts @@ -102,53 +102,55 @@ describe('options list queries', () => { }); }); - describe('suggestion aggregation and parsing', () => { - test('creates case insensitive aggregation for a text / keyword field with a search string', () => { - const optionsListRequestBodyMock: OptionsListRequestBody = { - fieldName: 'coolTestField.keyword', - textFieldName: 'coolTestField', - searchString: 'cooool', - fieldSpec: { aggregatable: true } as unknown as FieldSpec, - }; - const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); - expect(suggestionAggBuilder.buildAggregation(optionsListRequestBodyMock)) - .toMatchInlineSnapshot(` - Object { - "aggs": Object { - "keywordSuggestions": Object { - "terms": Object { - "field": "coolTestField.keyword", - "shard_size": 10, + describe('suggestion aggregation', () => { + describe('text / keyword field', () => { + test('with a search string, creates case insensitive aggregation', () => { + const optionsListRequestBodyMock: OptionsListRequestBody = { + fieldName: 'coolTestField.keyword', + textFieldName: 'coolTestField', + searchString: 'cooool', + fieldSpec: { aggregatable: true } as unknown as FieldSpec, + }; + const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); + expect(suggestionAggBuilder.buildAggregation(optionsListRequestBodyMock)) + .toMatchInlineSnapshot(` + Object { + "aggs": Object { + "keywordSuggestions": Object { + "terms": Object { + "field": "coolTestField.keyword", + "shard_size": 10, + }, }, }, - }, - "filter": Object { - "match_phrase_prefix": Object { - "coolTestField": "cooool", + "filter": Object { + "match_phrase_prefix": Object { + "coolTestField": "cooool", + }, }, - }, - } - `); - }); + } + `); + }); - test('creates keyword aggregation for a text / keyword field without a search string', () => { - const optionsListRequestBodyMock: OptionsListRequestBody = { - fieldName: 'coolTestField.keyword', - textFieldName: 'coolTestField', - fieldSpec: { aggregatable: true } as unknown as FieldSpec, - }; - const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); - expect(suggestionAggBuilder.buildAggregation(optionsListRequestBodyMock)) - .toMatchInlineSnapshot(` - Object { - "terms": Object { - "execution_hint": "map", - "field": "coolTestField.keyword", - "include": ".*", - "shard_size": 10, - }, - } - `); + test('without a search string, creates keyword aggregation', () => { + const optionsListRequestBodyMock: OptionsListRequestBody = { + fieldName: 'coolTestField.keyword', + textFieldName: 'coolTestField', + fieldSpec: { aggregatable: true } as unknown as FieldSpec, + }; + const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); + expect(suggestionAggBuilder.buildAggregation(optionsListRequestBodyMock)) + .toMatchInlineSnapshot(` + Object { + "terms": Object { + "execution_hint": "map", + "field": "coolTestField.keyword", + "include": ".*", + "shard_size": 10, + }, + } + `); + }); }); test('creates boolean aggregation for boolean field', () => { @@ -216,6 +218,177 @@ describe('options list queries', () => { `); }); + describe('IP field', () => { + test('without a search string, creates IP range aggregation with default range', () => { + const optionsListRequestBodyMock: OptionsListRequestBody = { + fieldName: 'clientip', + fieldSpec: { type: 'ip' } as unknown as FieldSpec, + }; + const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); + expect(suggestionAggBuilder.buildAggregation(optionsListRequestBodyMock)) + .toMatchInlineSnapshot(` + Object { + "aggs": Object { + "filteredSuggestions": Object { + "terms": Object { + "execution_hint": "map", + "field": "clientip", + "shard_size": 10, + }, + }, + }, + "ip_range": Object { + "field": "clientip", + "keyed": true, + "ranges": Array [ + Object { + "from": "::", + "key": "ipv6", + "to": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + }, + ], + }, + } + `); + }); + + test('full IPv4 in the search string, creates IP range aggregation with CIDR mask', () => { + const optionsListRequestBodyMock: OptionsListRequestBody = { + fieldName: 'clientip', + fieldSpec: { type: 'ip' } as unknown as FieldSpec, + searchString: '41.77.243.255', + }; + const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); + expect(suggestionAggBuilder.buildAggregation(optionsListRequestBodyMock)) + .toMatchInlineSnapshot(` + Object { + "aggs": Object { + "filteredSuggestions": Object { + "terms": Object { + "execution_hint": "map", + "field": "clientip", + "shard_size": 10, + }, + }, + }, + "ip_range": Object { + "field": "clientip", + "keyed": true, + "ranges": Array [ + Object { + "key": "ipv4", + "mask": "41.77.243.255/32", + }, + ], + }, + } + `); + }); + + test('full IPv6 in the search string, creates IP range aggregation with CIDR mask', () => { + const optionsListRequestBodyMock: OptionsListRequestBody = { + fieldName: 'clientip', + fieldSpec: { type: 'ip' } as unknown as FieldSpec, + searchString: 'f688:fb50:6433:bba2:604:f2c:194a:d3c5', + }; + const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); + expect(suggestionAggBuilder.buildAggregation(optionsListRequestBodyMock)) + .toMatchInlineSnapshot(` + Object { + "aggs": Object { + "filteredSuggestions": Object { + "terms": Object { + "execution_hint": "map", + "field": "clientip", + "shard_size": 10, + }, + }, + }, + "ip_range": Object { + "field": "clientip", + "keyed": true, + "ranges": Array [ + Object { + "key": "ipv6", + "mask": "f688:fb50:6433:bba2:604:f2c:194a:d3c5/128", + }, + ], + }, + } + `); + }); + + test('partial IPv4 in the search string, creates IP range aggregation with min and max', () => { + const optionsListRequestBodyMock: OptionsListRequestBody = { + fieldName: 'clientip', + fieldSpec: { type: 'ip' } as unknown as FieldSpec, + searchString: '41.77', + }; + const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); + expect(suggestionAggBuilder.buildAggregation(optionsListRequestBodyMock)) + .toMatchInlineSnapshot(` + Object { + "aggs": Object { + "filteredSuggestions": Object { + "terms": Object { + "execution_hint": "map", + "field": "clientip", + "shard_size": 10, + }, + }, + }, + "ip_range": Object { + "field": "clientip", + "keyed": true, + "ranges": Array [ + Object { + "from": "41.77.0.0", + "key": "ipv4", + "to": "41.77.255.255", + }, + ], + }, + } + `); + }); + + test('partial IPv46 in the search string, creates IP range aggregation with min and max', () => { + const optionsListRequestBodyMock: OptionsListRequestBody = { + fieldName: 'clientip', + fieldSpec: { type: 'ip' } as unknown as FieldSpec, + searchString: 'cdb6:', + }; + const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); + expect(suggestionAggBuilder.buildAggregation(optionsListRequestBodyMock)) + .toMatchInlineSnapshot(` + Object { + "aggs": Object { + "filteredSuggestions": Object { + "terms": Object { + "execution_hint": "map", + "field": "clientip", + "shard_size": 10, + }, + }, + }, + "ip_range": Object { + "field": "clientip", + "keyed": true, + "ranges": Array [ + Object { + "from": "cdb6::", + "key": "ipv6", + "to": "cdb6:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + }, + ], + }, + } + `); + }); + }); + }); + + describe('suggestion parsing', () => { test('parses keyword / text result', () => { const optionsListRequestBodyMock: OptionsListRequestBody = { fieldName: 'coolTestField.keyword', @@ -318,4 +491,60 @@ describe('options list queries', () => { `); }); }); + + test('parses mixed IPv4 and IPv6 result', () => { + const optionsListRequestBodyMock: OptionsListRequestBody = { + fieldName: 'clientip', + fieldSpec: { type: 'ip' } as unknown as FieldSpec, + }; + const suggestionAggBuilder = getSuggestionAggregationBuilder(optionsListRequestBodyMock); + rawSearchResponseMock.aggregations = { + suggestions: { + buckets: { + ipv4: { + from: '0.0.0.0', + to: '255.255.255.255', + filteredSuggestions: { + buckets: [ + { doc_count: 8, key: '21.35.91.62' }, + { doc_count: 8, key: '21.35.91.61' }, + { doc_count: 11, key: '111.52.174.2' }, + { doc_count: 1, key: '56.73.58.63' }, + { doc_count: 9, key: '23.216.241.120' }, + { doc_count: 10, key: '196.162.13.39' }, + { doc_count: 7, key: '203.88.33.151' }, + ], + }, + }, + ipv6: { + from: '::', + to: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + filteredSuggestions: { + buckets: [ + { doc_count: 12, key: '52:ae76:5947:5e2a:551:fe6a:712a:c72' }, + { doc_count: 1, key: 'fd:4aa0:c27c:b04:997f:2de1:51b4:8418' }, + { doc_count: 9, key: '28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172' }, + { doc_count: 6, key: '1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8' }, + { doc_count: 10, key: 'f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63' }, + ], + }, + }, + }, + }, + }; + expect(suggestionAggBuilder.parse(rawSearchResponseMock)).toMatchInlineSnapshot(` + Array [ + "52:ae76:5947:5e2a:551:fe6a:712a:c72", + "111.52.174.2", + "196.162.13.39", + "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63", + "23.216.241.120", + "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172", + "21.35.91.62", + "21.35.91.61", + "203.88.33.151", + "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8", + ] + `); + }); }); diff --git a/src/plugins/controls/server/options_list/options_list_queries.ts b/src/plugins/controls/server/options_list/options_list_queries.ts index 4e88c449a7e8e..d1fa89bbc9358 100644 --- a/src/plugins/controls/server/options_list/options_list_queries.ts +++ b/src/plugins/controls/server/options_list/options_list_queries.ts @@ -7,17 +7,21 @@ */ import { get, isEmpty } from 'lodash'; - import { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import { getFieldSubtypeNested } from '@kbn/data-views-plugin/common'; import { OptionsListRequestBody } from '../../common/options_list/types'; - +import { getIpRangeQuery, type IpRangeQuery } from '../../common/options_list/ip_search'; export interface OptionsListAggregationBuilder { buildAggregation: (req: OptionsListRequestBody) => unknown; parse: (response: SearchResponse) => string[]; } +interface EsBucket { + key: string; + doc_count: number; +} + /** * Validation aggregations */ @@ -62,6 +66,9 @@ export const getSuggestionAggregationBuilder = ({ if (fieldSpec?.type === 'boolean') { return suggestionAggSubtypes.boolean; } + if (fieldSpec?.type === 'ip') { + return suggestionAggSubtypes.ip; + } if (fieldSpec && getFieldSubtypeNested(fieldSpec)) { return suggestionAggSubtypes.subtypeNested; } @@ -71,6 +78,16 @@ export const getSuggestionAggregationBuilder = ({ const getEscapedQuery = (q: string = '') => q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`); +const getIpBuckets = (rawEsResult: any, combinedBuckets: EsBucket[], type: 'ipv4' | 'ipv6') => { + const results = get( + rawEsResult, + `aggregations.suggestions.buckets.${type}.filteredSuggestions.buckets` + ); + if (results) { + results.forEach((suggestion: EsBucket) => combinedBuckets.push(suggestion)); + } +}; + const suggestionAggSubtypes: { [key: string]: OptionsListAggregationBuilder } = { /** * the "Keyword only" query / parser should be used when the options list is built on a field which has only keyword mappings. @@ -139,6 +156,65 @@ const suggestionAggSubtypes: { [key: string]: OptionsListAggregationBuilder } = ), }, + /** + * the "IP" query / parser should be used when the options list is built on a field of type IP. + */ + ip: { + buildAggregation: ({ fieldName, searchString }: OptionsListRequestBody) => { + let ipRangeQuery: IpRangeQuery = { + validSearch: true, + rangeQuery: [ + { + key: 'ipv6', + from: '::', + to: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + }, + ], + }; + + if (searchString) { + ipRangeQuery = getIpRangeQuery(searchString); + if (!ipRangeQuery.validSearch) { + // ideally should be prevented on the client side but, if somehow an invalid search gets through to the server, + // simply don't return an aggregation query for the ES search request + return undefined; + } + } + + return { + ip_range: { + field: fieldName, + ranges: ipRangeQuery.rangeQuery, + keyed: true, + }, + aggs: { + filteredSuggestions: { + terms: { + field: fieldName, + execution_hint: 'map', + shard_size: 10, + }, + }, + }, + }; + }, + parse: (rawEsResult) => { + if (!Boolean(rawEsResult.aggregations?.suggestions)) { + // if this is happens, that means there is an invalid search that snuck through to the server side code; + // so, might as well early return with no suggestions + return []; + } + + const buckets: EsBucket[] = []; + getIpBuckets(rawEsResult, buckets, 'ipv4'); // modifies buckets array directly, i.e. "by reference" + getIpBuckets(rawEsResult, buckets, 'ipv6'); + return buckets + .sort((bucketA: EsBucket, bucketB: EsBucket) => bucketB.doc_count - bucketA.doc_count) + .slice(0, 10) // only return top 10 results + .map((bucket: EsBucket) => bucket.key); + }, + }, + /** * the "Subtype Nested" query / parser should be used when the options list is built on a field with subtype nested. */ diff --git a/src/plugins/controls/server/options_list/options_list_suggestions_route.ts b/src/plugins/controls/server/options_list/options_list_suggestions_route.ts index 0d7654f318f31..c9af30bb07b82 100644 --- a/src/plugins/controls/server/options_list/options_list_suggestions_route.ts +++ b/src/plugins/controls/server/options_list/options_list_suggestions_route.ts @@ -95,9 +95,12 @@ export const setupOptionsListSuggestionsRoute = ( const suggestionBuilder = getSuggestionAggregationBuilder(request); const validationBuilder = getValidationAggregationBuilder(); - const suggestionAggregations = { - suggestions: suggestionBuilder.buildAggregation(request), - }; + const builtSuggestionAggregation = suggestionBuilder.buildAggregation(request); + const suggestionAggregation = builtSuggestionAggregation + ? { + suggestions: builtSuggestionAggregation, + } + : {}; const builtValidationAggregation = validationBuilder.buildAggregation(request); const validationAggregations = builtValidationAggregation ? { @@ -114,7 +117,7 @@ export const setupOptionsListSuggestionsRoute = ( }, }, aggs: { - ...suggestionAggregations, + ...suggestionAggregation, ...validationAggregations, unique_terms: { cardinality: { diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 36b5d16e68a78..38a4f027765df 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -762,7 +762,13 @@ describe('SearchSessionService', () => { expect(savedObjectsClient.update).toHaveBeenCalledTimes(2); // 3 trackIds calls batched into 2 update calls (2 different sessions) expect(savedObjectsClient.create).not.toHaveBeenCalled(); - const [type1, id1, callAttributes1] = savedObjectsClient.update.mock.calls[0]; + const sessionId1UpdateCallArgs = savedObjectsClient.update.mock.calls.find( + (args) => args[1] === sessionId1 + ); + + expect(sessionId1UpdateCallArgs).toBeDefined(); + + const [type1, id1, callAttributes1] = sessionId1UpdateCallArgs!; expect(type1).toBe(SEARCH_SESSION_TYPE); expect(id1).toBe(sessionId1); expect(callAttributes1).toHaveProperty('idMapping', { @@ -776,7 +782,11 @@ describe('SearchSessionService', () => { }, }); - const [type2, id2, callAttributes2] = savedObjectsClient.update.mock.calls[1]; + const sessionId2UpdateCallArgs = savedObjectsClient.update.mock.calls.find( + (args) => args[1] === sessionId2 + ); + expect(sessionId2UpdateCallArgs).toBeDefined(); + const [type2, id2, callAttributes2] = sessionId2UpdateCallArgs!; expect(type2).toBe(SEARCH_SESSION_TYPE); expect(id2).toBe(sessionId2); expect(callAttributes2).toHaveProperty('idMapping', { diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/search.ts b/src/plugins/guided_onboarding/public/constants/guides_config/search.ts index f91c0af1ba446..3c083a3bd96c1 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/search.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/search.ts @@ -25,8 +25,8 @@ export const searchConfig: GuideConfig = { 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', ], location: { - appID: 'guidedOnboardingExample', - path: 'stepOne', + appID: 'enterpriseSearch', + path: '', }, }, { diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.test.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.test.ts index ce59f275bedfb..a8070e90449ec 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.test.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.test.ts @@ -60,7 +60,7 @@ describe('getConfiguration', () => { const metricAccessor = 'metric-id'; const breakdownByAccessor = 'bucket-id'; const metrics = [metricAccessor]; - const buckets = [breakdownByAccessor]; + const buckets = { all: [breakdownByAccessor], customBuckets: {} }; const maxAccessor = 'max-accessor-id'; const collapseFn = 'sum'; expect( @@ -69,7 +69,7 @@ describe('getConfiguration', () => { buckets, maxAccessor, columnsWithoutReferenced: [], - bucketCollapseFn: { [metricAccessor]: collapseFn }, + bucketCollapseFn: { [collapseFn]: [breakdownByAccessor] }, }) ).toEqual({ breakdownByAccessor, diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.ts index d27ae644ea480..995dc5302288e 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.ts @@ -22,14 +22,22 @@ export const getConfiguration = ( bucketCollapseFn, }: { metrics: string[]; - buckets: string[]; + buckets: { + all: string[]; + customBuckets: Record; + }; maxAccessor: string; columnsWithoutReferenced: Column[]; - bucketCollapseFn?: Record; + bucketCollapseFn?: Record; } ): MetricVisConfiguration => { const [metricAccessor] = metrics; - const [breakdownByAccessor] = buckets; + const [breakdownByAccessor] = buckets.all; + const collapseFn = bucketCollapseFn + ? Object.keys(bucketCollapseFn).find((key) => + bucketCollapseFn[key].includes(breakdownByAccessor) + ) + : undefined; return { layerId, layerType: 'data', @@ -38,7 +46,7 @@ export const getConfiguration = ( breakdownByAccessor, maxAccessor, showBar: Boolean(maxAccessor), - collapseFn: Object.values(bucketCollapseFn ?? {})[0], + collapseFn, subtitle: gauge.labels.show && gauge.style.subText ? gauge.style.subText : undefined, }; }; diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.test.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.test.ts index f7c07dafd85c6..0da750bfdcef0 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.test.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.test.ts @@ -104,22 +104,26 @@ describe('convertToLens', () => { }); test('should return null if metrics count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1', '2'], - buckets: [], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1', '2'], + buckets: { all: [] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if metric column data type is different from number', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: [], - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: [] }, + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); @@ -129,15 +133,17 @@ describe('convertToLens', () => { layerType: 'data', }; - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: [], - columns: [{ columnId: '1', dataType: 'number' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: [] }, + columns: [{ columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); mockGetConfiguration.mockReturnValue(config); const result = await convertToLens(vis, timefilter); diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts index be664d3500a7c..080d5e84561a9 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts @@ -53,7 +53,7 @@ export const convertToLens: ConvertGaugeVisToLensVisualization = async (vis, tim const percentageModeConfig = getPercentageModeConfig(vis.params.gauge, false); - const result = getColumnsFromVis( + const layers = getColumnsFromVis( vis, timefilter, dataView, @@ -63,17 +63,19 @@ export const convertToLens: ConvertGaugeVisToLensVisualization = async (vis, tim { dropEmptyRowsInDateHistogram: true, ...percentageModeConfig } ); - if (result === null) { + if (layers === null) { return null; } + const [layerConfig] = layers; + // for now, multiple metrics are not supported - if (result.metrics.length > 1 || result.buckets.length) { + if (layerConfig.metrics.length > 1 || layerConfig.buckets.all.length) { return null; } - if (result.metrics[0]) { - const metric = result.columns.find(({ columnId }) => columnId === result.metrics[0]); + if (layerConfig.metrics[0]) { + const metric = layerConfig.columns.find(({ columnId }) => columnId === layerConfig.metrics[0]); if (metric?.dataType !== 'number') { return null; } @@ -82,11 +84,11 @@ export const convertToLens: ConvertGaugeVisToLensVisualization = async (vis, tim const layerId = uuid(); const indexPatternId = dataView.id!; - const metricAccessor = result.metrics[0]; + const metricAccessor = layerConfig.metrics[0]; const { min, max, isPercentageMode } = percentageModeConfig as PercentageModeConfigWithMinMax; const minColumn = createStaticValueColumn(isPercentageMode ? 0 : min); const maxColumn = createStaticValueColumn(isPercentageMode ? 1 : max); - const columns = [...result.columns, minColumn, maxColumn]; + const columns = [...layerConfig.columns, minColumn, maxColumn]; return { type: 'lnsGauge', diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.test.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.test.ts index 4d9247293e7a8..88566694f55bf 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.test.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.test.ts @@ -104,31 +104,37 @@ describe('convertToLens', () => { }); test('should return null if metrics count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1', '2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1', '2'], + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if buckets count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: [], - buckets: ['1', '2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: [], + buckets: { all: ['1', '2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if metric column data type is different from number', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: ['2'], - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); @@ -139,15 +145,17 @@ describe('convertToLens', () => { metricAccessor: '1', }; - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: ['2'], - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'number' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); mockGetConfiguration.mockReturnValue(config); const result = await convertToLens(vis, timefilter); diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts index a57dfedb02581..624ce45b3e848 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts @@ -53,7 +53,7 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time const percentageModeConfig = getPercentageModeConfig(vis.params.gauge, false); - const result = getColumnsFromVis( + const layers = getColumnsFromVis( vis, timefilter, dataView, @@ -63,17 +63,19 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time { dropEmptyRowsInDateHistogram: true, ...percentageModeConfig } ); - if (result === null) { + if (layers === null) { return null; } + const [layerConfig] = layers; + // for now, multiple metrics are not supported - if (result.metrics.length > 1 || result.buckets.length > 1) { + if (layerConfig.metrics.length > 1 || layerConfig.buckets.all.length > 1) { return null; } - if (result.metrics[0]) { - const metric = result.columns.find(({ columnId }) => columnId === result.metrics[0]); + if (layerConfig.metrics[0]) { + const metric = layerConfig.columns.find(({ columnId }) => columnId === layerConfig.metrics[0]); if (metric?.dataType !== 'number') { return null; } @@ -81,7 +83,7 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time const { isPercentageMode, max } = percentageModeConfig as PercentageModeConfigWithMinMax; const maxColumn = createStaticValueColumn(isPercentageMode ? 1 : max); - const columns = [...result.columns, maxColumn]; + const columns = [...layerConfig.columns, maxColumn]; const layerId = uuid(); const indexPatternId = dataView.id!; @@ -100,7 +102,7 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time vis.params, getPalette(vis.params.gauge, percentageModeConfig, true), { - ...result, + ...layerConfig, maxAccessor: maxColumn.columnId, } ), diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts index 29669c0286529..5ccfb169c91ae 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts @@ -48,9 +48,9 @@ describe('getConfiguration', () => { expect( getConfiguration(layerId, params, palette, { metrics: [metric], - buckets: [bucket], + buckets: { all: [bucket], customBuckets: { metric: bucket } }, columnsWithoutReferenced: [], - bucketCollapseFn: { [metric]: collapseFn }, + bucketCollapseFn: { [collapseFn]: [bucket] }, }) ).toEqual({ breakdownByAccessor: bucket, diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts index ae62b82408eeb..7b1b42a0211f5 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts @@ -21,19 +21,27 @@ export const getConfiguration = ( bucketCollapseFn, }: { metrics: string[]; - buckets: string[]; + buckets: { + all: string[]; + customBuckets: Record; + }; columnsWithoutReferenced: Column[]; - bucketCollapseFn?: Record; + bucketCollapseFn?: Record; } ): MetricVisConfiguration => { const [metricAccessor] = metrics; - const [breakdownByAccessor] = buckets; + const [breakdownByAccessor] = buckets.all; + const collapseFn = bucketCollapseFn + ? Object.keys(bucketCollapseFn).find((key) => + bucketCollapseFn[key].includes(breakdownByAccessor) + ) + : undefined; return { layerId, layerType: 'data', palette: params.metric.metricColorMode !== 'None' ? palette : undefined, metricAccessor, breakdownByAccessor, - collapseFn: Object.values(bucketCollapseFn ?? {})[0], + collapseFn, }; }; diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts index e0c50cad16719..b3ad4ccf6be59 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts @@ -83,31 +83,37 @@ describe('convertToLens', () => { }); test('should return null if metrics count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1', '2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1', '2'], + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if buckets count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: [], - buckets: ['1', '2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: [], + buckets: { all: ['1', '2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if metric column data type is different from number', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: ['2'], - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); @@ -118,15 +124,17 @@ describe('convertToLens', () => { metricAccessor: '1', }; - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: ['2'], - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'number' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); mockGetConfiguration.mockReturnValue(config); const result = await convertToLens(vis, timefilter); diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts index 55f9e500b0a29..5b9cb985a2799 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts @@ -46,7 +46,7 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti await Promise.all([convertToLensModule, import('./configurations')]); const percentageModeConfig = getPercentageModeConfig(vis.params.metric); - const result = getColumnsFromVis( + const layers = getColumnsFromVis( vis, timefilter, dataView, @@ -56,17 +56,19 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti { dropEmptyRowsInDateHistogram: true, ...percentageModeConfig } ); - if (result === null) { + if (layers === null) { return null; } + const [layerConfig] = layers; + // for now, multiple metrics are not supported - if (result.metrics.length > 1 || result.buckets.length > 1) { + if (layerConfig.metrics.length > 1 || layerConfig.buckets.all.length > 1) { return null; } - if (result.metrics[0]) { - const metric = result.columns.find(({ columnId }) => columnId === result.metrics[0]); + if (layerConfig.metrics[0]) { + const metric = layerConfig.columns.find(({ columnId }) => columnId === layerConfig.metrics[0]); if (metric?.dataType !== 'number') { return null; } @@ -81,7 +83,7 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti { indexPatternId, layerId, - columns: result.columns.map(excludeMetaFromColumn), + columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], }, ], @@ -89,7 +91,7 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti layerId, vis.params, getPalette(vis.params.metric, percentageModeConfig), - result + layerConfig ), indexPatternIds: [indexPatternId], }; diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts index 0a10a5bd7c0c0..87ec0d3b57b3f 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts @@ -19,7 +19,7 @@ describe('getConfiguration', () => { expect( getConfiguration('test1', samplePieVis as any, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: {} }, }) ).toEqual({ layers: [ @@ -55,7 +55,7 @@ describe('getConfiguration', () => { { ...samplePieVis, params: { ...samplePieVis.params, legendDisplay: 'hide' } } as any, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: {} }, } ) ).toEqual({ @@ -73,7 +73,7 @@ describe('getConfiguration', () => { { ...samplePieVis, params: { ...samplePieVis.params, legendDisplay: 'show' } } as any, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: {} }, } ) ).toEqual({ @@ -92,7 +92,7 @@ describe('getConfiguration', () => { { ...samplePieVis, params: { ...samplePieVis.params, legendDisplay } } as any, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: {} }, } ) ).toEqual({ diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts index 9a3420581c1fd..d1d1daf9fe009 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts @@ -68,12 +68,15 @@ export const getConfiguration = ( buckets, }: { metrics: string[]; - buckets: string[]; + buckets: { + all: string[]; + customBuckets: Record; + }; } ): PartitionVisConfiguration => { return { shape: vis.params.isDonut ? 'donut' : 'pie', - layers: getLayers(layerId, vis, metrics, buckets), + layers: getLayers(layerId, vis, metrics, buckets.all), palette: vis.params.palette, }; }; diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts index c1e39d741f84d..bf4dfbd3ffc72 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts @@ -40,28 +40,34 @@ describe('convertToLens', () => { }); test('should return null if more than three split slice levels', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: ['1', '2', '3', '4'], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['1', '2', '3', '4'] }, + }, + ]); const result = await convertToLens(samplePieVis as any, {} as any); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if no one split slices', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: [], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: [] }, + }, + ]); const result = await convertToLens(samplePieVis as any, {} as any); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should state for valid vis', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: ['2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(samplePieVis as any, {} as any); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(mockGetConfiguration).toBeCalledTimes(1); diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts index 5b1973507c7df..c7231af7098c8 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts @@ -44,19 +44,21 @@ export const convertToLens: ConvertPieToLensVisualization = async (vis, timefilt } const { getColumnsFromVis } = await convertToLensModule; - const result = getColumnsFromVis(vis, timefilter, dataView, { + const layers = getColumnsFromVis(vis, timefilter, dataView, { buckets: [], splits: ['segment'], unsupported: ['split_row', 'split_column'], }); - if (result === null) { + if (layers === null) { return null; } + const [layerConfig] = layers; + // doesn't support more than three split slice levels // doesn't support pie without at least one split slice - if (result.buckets.length > 3 || !result.buckets.length) { + if (layerConfig.buckets.all.length > 3 || !layerConfig.buckets.all.length) { return null; } @@ -69,11 +71,11 @@ export const convertToLens: ConvertPieToLensVisualization = async (vis, timefilt { indexPatternId, layerId, - columns: result.columns.map(excludeMetaFromColumn), + columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], }, ], - configuration: getConfiguration(layerId, vis, result), + configuration: getConfiguration(layerId, vis, layerConfig), indexPatternIds: [indexPatternId], }; }; diff --git a/src/plugins/vis_types/table/public/components/table_vis_options.tsx b/src/plugins/vis_types/table/public/components/table_vis_options.tsx index 7023716025b21..9d6b1d9a7454c 100644 --- a/src/plugins/vis_types/table/public/components/table_vis_options.tsx +++ b/src/plugins/vis_types/table/public/components/table_vis_options.tsx @@ -133,6 +133,7 @@ function TableOptions({ paramName="showTotal" value={stateParams.showTotal} setValue={setValue} + data-test-subj="showTotal" /> { expect( getConfiguration('test1', params, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: { 'metric-1': 'bucket-1' } }, columnsWithoutReferenced: [ { columnId: 'metric-1', @@ -48,7 +48,7 @@ describe('getConfiguration', () => { }, }, ], - bucketCollapseFn: { 'bucket-1': 'sum' }, + bucketCollapseFn: { sum: ['bucket-1'] }, }) ).toEqual({ columns: [ diff --git a/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts index d98cb917b40ac..5079b25258a75 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts @@ -12,19 +12,21 @@ import { TableVisParams } from '../../../common'; const getColumns = ( params: TableVisParams, metrics: string[], - buckets: string[], columns: Column[], - bucketCollapseFn?: Record + bucketCollapseFn?: Record ) => { const { showTotal, totalFunc } = params; - return columns.map(({ columnId }) => ({ - columnId, - alignment: 'left' as const, - ...(showTotal && metrics.includes(columnId) ? { summaryRow: totalFunc } : {}), - ...(bucketCollapseFn && bucketCollapseFn[columnId] - ? { collapseFn: bucketCollapseFn[columnId] } - : {}), - })); + return columns.map(({ columnId }) => { + const collapseFn = bucketCollapseFn + ? Object.keys(bucketCollapseFn).find((key) => bucketCollapseFn[key].includes(columnId)) + : undefined; + return { + columnId, + alignment: 'left' as const, + ...(showTotal && metrics.includes(columnId) ? { summaryRow: totalFunc } : {}), + ...(collapseFn ? { collapseFn } : {}), + }; + }); }; const getPagination = ({ perPage }: TableVisParams): PagingState => { @@ -54,15 +56,18 @@ export const getConfiguration = ( bucketCollapseFn, }: { metrics: string[]; - buckets: string[]; + buckets: { + all: string[]; + customBuckets: Record; + }; columnsWithoutReferenced: Column[]; - bucketCollapseFn?: Record; + bucketCollapseFn?: Record; } ): TableVisConfiguration => { return { layerId, layerType: 'data', - columns: getColumns(params, metrics, buckets, columnsWithoutReferenced, bucketCollapseFn), + columns: getColumns(params, metrics, columnsWithoutReferenced, bucketCollapseFn), paging: getPagination(params), ...getRowHeight(params), }; diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts index 5c1ad0578be11..f11f18f754eb9 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts @@ -63,14 +63,16 @@ describe('convertToLens', () => { }); test('should return null if can not build percentage column', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: ['2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); mockGetVisSchemas.mockReturnValue({ metric: [{ label: 'Count', aggId: 'agg-1' }], }); @@ -83,14 +85,16 @@ describe('convertToLens', () => { }); test('should return correct state for valid vis', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: ['2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); mockGetVisSchemas.mockReturnValue({ metric: [{ label: 'Count', aggId: 'agg-1' }], }); diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.ts index 1b37e36f1d982..e69faccbfd7ec 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.ts @@ -46,7 +46,7 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi } const { getColumnsFromVis, getPercentageColumnFormulaColumn } = await convertToLensModule; - const result = getColumnsFromVis( + const layers = getColumnsFromVis( vis, timefilter, dataView, @@ -57,10 +57,12 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi { dropEmptyRowsInDateHistogram: true, isPercentageMode: false } ); - if (result === null) { + if (layers === null) { return null; } + const [layerConfig] = layers; + if (vis.params.percentageCol) { const visSchemas = getVisSchemas(vis, { timefilter, @@ -78,12 +80,12 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi if (!percentageColumn) { return null; } - result.columns.splice( - result.columnsWithoutReferenced.findIndex((c) => c.meta.aggId === metricAgg.aggId) + 1, + layerConfig.columns.splice( + layerConfig.columnsWithoutReferenced.findIndex((c) => c.meta.aggId === metricAgg.aggId) + 1, 0, percentageColumn ); - result.columnsWithoutReferenced.push(percentageColumn); + layerConfig.columnsWithoutReferenced.push(percentageColumn); } const layerId = uuid(); @@ -94,11 +96,11 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi { indexPatternId, layerId, - columns: result.columns.map(excludeMetaFromColumn), + columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], }, ], - configuration: getConfiguration(layerId, vis.params, result), + configuration: getConfiguration(layerId, vis.params, layerConfig), indexPatternIds: [indexPatternId], }; }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts index b1b15339b37a6..dc4005c6649c1 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/index.ts @@ -31,5 +31,6 @@ export { convertToDateHistogramColumn } from './date_histogram'; export { convertToTermsColumn } from './terms'; export { convertToCounterRateColumn } from './counter_rate'; export { convertToStandartDeviationColumn } from './std_deviation'; +export { convertVarianceToFormulaColumn } from './variance'; export * from './types'; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/parent_pipeline.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/parent_pipeline.ts index 522585c5c568a..2fcba37bf0d03 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/parent_pipeline.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/parent_pipeline.ts @@ -237,7 +237,11 @@ const convertMovingAvgOrDerivativeToColumns = ( const [nestedFieldId, _] = subMetricField?.split('[') ?? []; // support nested aggs with formula const additionalSubFunction = metrics.find(({ id }) => id === nestedFieldId); - if (additionalSubFunction || pipelineAgg.name === 'counter_rate') { + if ( + additionalSubFunction || + pipelineAgg.name === 'counter_rate' || + subFunctionMetric.type === 'variance' + ) { const formula = getPipelineSeriesFormula(metric, metrics, subFunctionMetric, { metaValue, reducedTimeRange, diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/variance.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/variance.test.ts new file mode 100644 index 0000000000000..e4695f23b742e --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/variance.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { createSeries } from '../__mocks__'; +import { FormulaColumn } from './types'; +import { Metric } from '../../../../common/types'; +import { TSVB_METRIC_TYPES } from '../../../../common/enums'; +import { convertVarianceToFormulaColumn } from './variance'; + +describe('convertVarianceToFormulaColumn', () => { + const series = createSeries(); + const dataView = stubLogstashDataView; + const metric: Metric = { + id: 'some-id', + type: TSVB_METRIC_TYPES.VARIANCE, + }; + const field = dataView.fields[0].name; + + test.each< + [string, Parameters, Partial | null] + >([ + ['null if field is not provided', [{ series, metrics: [metric], dataView }], null], + [ + 'correct formula column', + [{ series, metrics: [{ ...metric, field }], dataView }], + { + meta: { metricId: 'some-id' }, + operationType: 'formula', + params: { + formula: 'pow(standard_deviation(bytes), 2)', + }, + }, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertVarianceToFormulaColumn(...input)).toBeNull(); + } else { + expect(convertVarianceToFormulaColumn(...input)).toEqual(expect.objectContaining(expected)); + } + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/variance.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/variance.ts new file mode 100644 index 0000000000000..920317e65c224 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/variance.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { getFormulaEquivalent } from '../metrics'; +import { createFormulaColumn } from './formula'; +import { CommonColumnsConverterArgs } from './types'; + +export const convertVarianceToFormulaColumn = ( + { series, metrics, dataView }: CommonColumnsConverterArgs, + reducedTimeRange?: string +) => { + const metric = metrics[metrics.length - 1]; + + const field = metric.field ? dataView.getFieldByName(metric.field) : undefined; + if (!field) { + return null; + } + + const script = getFormulaEquivalent(metric, metrics, { + reducedTimeRange, + timeShift: series.offset_time, + }); + if (!script) return null; + return createFormulaColumn(script, { series, metric, dataView }); +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/metrics_helpers.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/metrics_helpers.test.ts index 447312786ee02..944727505f682 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/metrics_helpers.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/metrics_helpers.test.ts @@ -62,6 +62,12 @@ describe('getFormulaEquivalent', () => { mode: 'upper', }; + const variance: Metric = { + id: 'some-random-value', + type: TSVB_METRIC_TYPES.VARIANCE, + field: 'test-1', + }; + const sibblingPipelineMetric: Metric[] = [ { id: 'test-1', @@ -141,6 +147,11 @@ describe('getFormulaEquivalent', () => { [stdDeviationMetricWithUpperMode, [stdDeviationMetricWithUpperMode], {}], 'average(test-1) + 1.5 * standard_deviation(test-1)', ], + [ + 'correct formula if metric is variance', + [variance, [variance], {}], + 'pow(standard_deviation(test-1), 2)', + ], [ 'correct formula if metric is supported', [supportedMetric, [supportedMetric], {}], diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/metrics_helpers.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/metrics_helpers.ts index eda3ec437fc8a..8d970f2f7262d 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/metrics_helpers.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/metrics_helpers.ts @@ -144,6 +144,12 @@ export const getFormulaEquivalent = ( case 'static': { return `${currentMetric.value}`; } + case 'variance': { + return `${aggFormula}(standard_deviation(${currentMetric.field}${addAdditionalArgs({ + reducedTimeRange, + timeShift, + })}), 2)`; + } case 'std_deviation': { if (currentMetric.mode === 'lower') { return `average(${currentMetric.field}${addAdditionalArgs({ diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts index 8be4b444be099..debe064940c8e 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts @@ -55,6 +55,7 @@ interface LocalSupportedMetrics { [TSVB_METRIC_TYPES.STATIC]: Metric; [TSVB_METRIC_TYPES.POSITIVE_RATE]: Metric; [TSVB_METRIC_TYPES.MOVING_AVERAGE]: Metric; + [TSVB_METRIC_TYPES.VARIANCE]: Metric; } type UnsupportedSupportedMetrics = Exclude; @@ -250,6 +251,15 @@ export const SUPPORTED_METRICS: SupportedMetrics = { supportedPanelTypes, supportedTimeRangeModes, }, + variance: { + name: 'formula', + isFormula: true, + formula: 'pow', + isFullReference: false, + isFieldRequired: true, + supportedPanelTypes, + supportedTimeRangeModes, + }, } as const; type SupportedMetricsKeys = keyof LocalSupportedMetrics; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.test.ts index 4461072c8df62..58cf955dab83a 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.test.ts @@ -23,6 +23,7 @@ const mockConvertToStaticValueColumn = jest.fn(); const mockConvertStaticValueToFormulaColumn = jest.fn(); const mockConvertToStandartDeviationColumn = jest.fn(); const mockConvertMetricAggregationColumnWithoutSpecialParams = jest.fn(); +const mockConvertVarianceToFormulaColumn = jest.fn(); jest.mock('../convert', () => ({ convertMathToFormulaColumn: jest.fn(() => mockConvertMathToFormulaColumn()), @@ -38,6 +39,7 @@ jest.mock('../convert', () => ({ convertMetricAggregationColumnWithoutSpecialParams: jest.fn(() => mockConvertMetricAggregationColumnWithoutSpecialParams() ), + convertVarianceToFormulaColumn: jest.fn(() => mockConvertVarianceToFormulaColumn()), })); describe('getMetricsColumns', () => { @@ -163,6 +165,11 @@ describe('getMetricsColumns', () => { ], mockConvertToStandartDeviationColumn, ], + [ + 'call convertVarianceToFormulaColumn if metric type is variance', + [createSeries({ metrics: [{ type: TSVB_METRIC_TYPES.VARIANCE, id: '1' }] }), dataView, 1], + mockConvertVarianceToFormulaColumn, + ], [ 'call convertMetricAggregationColumnWithoutSpecialParams if metric type is another supported type', [createSeries({ metrics: [{ type: METRIC_TYPES.AVG, id: '1' }] }), dataView, 1], diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.ts index 8f7d4ded0d076..6ec978e4a685a 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/metrics_columns.ts @@ -25,6 +25,7 @@ import { convertMetricAggregationColumnWithoutSpecialParams, convertToCounterRateColumn, convertToStandartDeviationColumn, + convertVarianceToFormulaColumn, } from '../convert'; import { getValidColumns } from './columns'; @@ -120,6 +121,10 @@ export const getMetricsColumns = ( const column = convertToLastValueColumn(columnsConverterArgs, reducedTimeRange); return getValidColumns(column); } + case 'variance': { + const column = convertVarianceToFormulaColumn(columnsConverterArgs, reducedTimeRange); + return getValidColumns(column); + } case 'static': { const column = isStaticValueColumnSupported ? convertToStaticValueColumn(columnsConverterArgs, { diff --git a/src/plugins/vis_types/xy/kibana.json b/src/plugins/vis_types/xy/kibana.json index fa942c1530142..474a70431fc73 100644 --- a/src/plugins/vis_types/xy/kibana.json +++ b/src/plugins/vis_types/xy/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "ui": true, "server": true, - "requiredPlugins": ["charts", "visualizations", "data", "expressions"], + "requiredPlugins": ["charts", "visualizations", "data", "expressions", "dataViews"], "requiredBundles": ["kibanaUtils", "visDefaultEditor"], "extraPublicDirs": ["common/index"], "owner": { diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts new file mode 100644 index 0000000000000..2d8c7da9ba801 --- /dev/null +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts @@ -0,0 +1,160 @@ +/* + * 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 { Column } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { getConfiguration } from '.'; +import { Layer } from '..'; +import { ChartType } from '../..'; +import { sampleAreaVis } from '../../sample_vis.test.mocks'; +import { ChartMode, InterpolationMode } from '../../types'; + +describe('getConfiguration', () => { + const layers: Layer[] = [ + { + indexPatternId: '', + layerId: 'layer-1', + columns: [ + { columnId: '1', isBucketed: false }, + { columnId: '2', isBucketed: true, isSplit: false, operationType: 'date_histogram' }, + { columnId: '3', isBucketed: true, isSplit: true }, + ] as Column[], + metrics: ['1'], + columnOrder: [], + seriesIdsMap: { 1: '1' }, + collapseFn: 'max', + isReferenceLineLayer: false, + }, + { + indexPatternId: '', + layerId: 'layer-2', + columns: [ + { columnId: '4', isBucketed: false }, + { columnId: '5', isBucketed: true, isSplit: false, operationType: 'date_histogram' }, + ] as Column[], + metrics: ['4'], + columnOrder: [], + seriesIdsMap: { 4: '2' }, + collapseFn: undefined, + isReferenceLineLayer: false, + }, + { + indexPatternId: '', + layerId: 'layer-3', + columns: [{ columnId: '7', isBucketed: false }] as Column[], + columnOrder: [], + metrics: ['7'], + seriesIdsMap: {}, + collapseFn: undefined, + isReferenceLineLayer: true, + }, + ]; + const series = [ + { + show: true, + type: ChartType.Area, + mode: ChartMode.Stacked, + data: { + label: 'Sum of total_quantity', + id: '1', + }, + drawLinesBetweenPoints: true, + showCircles: true, + circlesRadius: 5, + interpolate: InterpolationMode.Linear, + valueAxis: 'ValueAxis-1', + }, + { + show: true, + type: ChartType.Line, + mode: ChartMode.Stacked, + data: { + label: 'Sum of total_quantity 1', + id: '2', + }, + drawLinesBetweenPoints: true, + showCircles: true, + circlesRadius: 5, + interpolate: InterpolationMode.Linear, + valueAxis: 'ValueAxis-1', + }, + ]; + + test('should return correct configuration', () => { + expect(getConfiguration(layers, series, sampleAreaVis as any)).toEqual({ + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + curveType: 'LINEAR', + fillOpacity: 0.5, + fittingFunction: undefined, + gridlinesVisibilitySettings: { x: false, yLeft: false, yRight: true }, + labelsOrientation: { x: -0, yLeft: -0, yRight: -90 }, + layers: [ + { + accessors: ['1'], + collapseFn: 'max', + isHistogram: true, + layerId: 'layer-1', + layerType: 'data', + palette: { name: 'default' }, + seriesType: 'area_stacked', + simpleView: false, + splitAccessor: '3', + xAccessor: '2', + xScaleType: 'ordinal', + yConfig: [{ axisMode: 'left', forAccessor: '1' }], + }, + { + accessors: ['4'], + collapseFn: undefined, + isHistogram: true, + layerId: 'layer-2', + layerType: 'data', + palette: { name: 'default' }, + seriesType: 'area_stacked', + simpleView: false, + splitAccessor: undefined, + xAccessor: '5', + xScaleType: 'ordinal', + yConfig: [{ axisMode: 'left', forAccessor: '4' }], + }, + { + accessors: ['7'], + layerId: 'layer-3', + layerType: 'referenceLine', + yConfig: [ + { + axisMode: 'left', + color: '#E7664C', + forAccessor: '7', + lineStyle: 'solid', + lineWidth: 1, + }, + ], + }, + ], + legend: { + isVisible: true, + legendSize: 'small', + maxLines: 1, + position: 'top', + shouldTruncate: true, + showSingleSeries: true, + }, + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + valuesInLegend: false, + xTitle: undefined, + yLeftExtent: { enforce: true, lowerBound: undefined, mode: 'full', upperBound: undefined }, + yLeftScale: 'linear', + yRightExtent: undefined, + yRightScale: 'linear', + yRightTitle: undefined, + yTitle: 'Sum of total_quantity', + showCurrentTimeMarker: false, + }); + }); +}); diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts new file mode 100644 index 0000000000000..fa9cc01c6a7ca --- /dev/null +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -0,0 +1,277 @@ +/* + * 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 { Position, ScaleType as ECScaleType } from '@elastic/charts'; +import { + SeriesTypes, + Column, + XYConfiguration, + XYDataLayerConfig, + XYReferenceLineLayerConfig, +} from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { Layer } from '..'; +import { ChartType } from '../../../common'; +import { + CategoryAxis, + ChartMode, + InterpolationMode, + Scale, + ScaleType, + SeriesParam, + ValueAxis, + VisParams, +} from '../../types'; +import { getCurveType, getLineStyle, getMode, getYAxisPosition } from '../../utils/common'; + +function getYScaleType(scale?: Scale): XYConfiguration['yLeftScale'] | undefined { + const type = scale?.type; + if (type === ScaleType.SquareRoot) { + return ECScaleType.Sqrt; + } + + return type; +} + +function getXScaleType(xColumn?: Column) { + if (xColumn?.dataType === 'date') return ECScaleType.Time; + + if (xColumn?.dataType !== 'number') { + return ECScaleType.Ordinal; + } + + return ECScaleType.Linear; +} + +function getLabelOrientation(data?: CategoryAxis, isTimeChart = false) { + // lens doesn't support 75 as rotate option, we should use 45 instead + return -(data?.labels.rotate === 75 ? 45 : data?.labels.rotate ?? (isTimeChart ? 0 : 90)); +} + +function getExtents(axis: ValueAxis, series: SeriesParam[]) { + // for area and bar charts we should include 0 to bounds + const isAssignedToAreaOrBar = series.some( + (s) => s.valueAxis === axis.id && (s.type === 'histogram' || s.type === 'area') + ); + return { + mode: getMode(axis.scale), + lowerBound: + axis.scale.min !== null + ? isAssignedToAreaOrBar && axis.scale.min && axis.scale.min > 0 + ? 0 + : axis.scale.min + : undefined, + upperBound: + axis.scale.max !== null + ? isAssignedToAreaOrBar && axis.scale.max && axis.scale.max < 0 + ? 0 + : axis.scale.max + : undefined, + enforce: true, + }; +} + +function getSeriesType( + type?: ChartType, + mode?: ChartMode, + isHorizontal?: boolean, + isPercentage?: boolean +): XYDataLayerConfig['seriesType'] { + let seriesType: XYDataLayerConfig['seriesType'] = + type === 'histogram' ? SeriesTypes.BAR : type ?? SeriesTypes.AREA; + + // only bar chart supports horizontal mode + if (isHorizontal && seriesType === SeriesTypes.BAR) { + seriesType = (seriesType + '_horizontal') as XYDataLayerConfig['seriesType']; + } + + // line percentage should convert to area percentage + if (isPercentage) { + seriesType = ((seriesType !== SeriesTypes.LINE ? seriesType : SeriesTypes.AREA) + + '_percentage') as XYDataLayerConfig['seriesType']; + } + + // percentage chart should be stacked + // line stacked should convert to area stacked + if (isPercentage || mode === 'stacked') { + seriesType = ((seriesType !== SeriesTypes.LINE ? seriesType : SeriesTypes.AREA) + + '_stacked') as XYDataLayerConfig['seriesType']; + } + + return seriesType; +} + +function getDataLayers( + layers: Layer[], + series: SeriesParam[], + vis: Vis +): XYDataLayerConfig[] { + const overwriteColors: Record = vis.uiState.get('vis.colors', {}); + return layers.map((layer) => { + const xColumn = layer.columns.find((c) => c.isBucketed && !c.isSplit); + const splitAccessor = layer.columns.find( + (column) => column.isBucketed && column.isSplit + )?.columnId; + // as type and mode will be the same for all metrics we can use first to define it + const firstSeries = series.find((s) => s.data.id === layer.seriesIdsMap[layer.metrics[0]]); + const isHistogram = + xColumn?.operationType === 'date_histogram' || + (xColumn?.operationType === 'range' && xColumn.params.type === 'histogram'); + const firstYAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( + (axis) => axis.id === firstSeries?.valueAxis + ); + const isPercentage = firstYAxis?.scale.mode === 'percentage'; + const isHorizontal = + firstYAxis?.position !== Position.Left && firstYAxis?.position !== Position.Right; + const seriesType = getSeriesType( + firstSeries?.type, + firstSeries?.mode, + isHorizontal, + isPercentage + ); + + return { + layerId: layer.layerId, + accessors: layer.metrics, + layerType: 'data', + seriesType, + xAccessor: xColumn?.columnId, + simpleView: false, + splitAccessor, + palette: vis.params.palette ?? vis.type.visConfig.defaults.palette, + yConfig: layer.metrics.map((metricId) => { + const serie = series.find((s) => s.data.id === layer.seriesIdsMap[metricId]); + const yAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( + (axis) => axis.id === serie?.valueAxis + ); + return { + forAccessor: metricId, + axisMode: getYAxisPosition(yAxis?.position ?? 'left'), + color: + !splitAccessor && serie?.data.label ? overwriteColors[serie?.data.label] : undefined, + }; + }), + xScaleType: getXScaleType(xColumn), + isHistogram, + collapseFn: layer.collapseFn, + }; + }); +} + +function getReferenceLineLayers( + layers: Layer[], + vis: Vis +): XYReferenceLineLayerConfig[] { + const thresholdLineConfig = vis.params.thresholdLine ?? vis.type.visConfig.defaults.thresholdLine; + // threshold line is always assigned to the first value axis + const yAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes)[0]; + return layers.map((layer) => { + return { + layerType: 'referenceLine', + layerId: layer.layerId, + accessors: layer.metrics, + yConfig: layer.metrics.map((metricId) => { + return { + forAccessor: metricId, + axisMode: getYAxisPosition(yAxis?.position ?? 'left'), + color: thresholdLineConfig.color, + lineWidth: thresholdLineConfig.width !== null ? thresholdLineConfig.width : undefined, + lineStyle: getLineStyle(thresholdLineConfig.style), + }; + }), + }; + }); +} + +export const getConfiguration = ( + layers: Layer[], + series: SeriesParam[], + vis: Vis +): XYConfiguration => { + const legendDisplayFromUiState = vis.uiState.get('vis.legendOpen') ?? true; + const yRightAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( + (axis) => getYAxisPosition(axis.position) === Position.Right + ); + const yLeftAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( + (axis) => getYAxisPosition(axis.position) === Position.Left + ); + // as we have only one x-axis + const xAxis = (vis.params.categoryAxes ?? vis.type.visConfig.defaults.categoryAxes)[0]; + const axisTitlesVisibilitySettings = { + x: xAxis.show, + yLeft: yLeftAxis?.show ?? true, + yRight: yRightAxis?.show ?? true, + }; + const xColumn = layers[0].columns.find((c) => c.isBucketed && !c.isSplit); + const isTimeChart = xColumn?.operationType === 'date_histogram'; + const fittingFunction = vis.params.fittingFunction ?? vis.type.visConfig.defaults.fittingFunction; + return { + layers: [ + ...getDataLayers( + layers.filter((l) => !l.isReferenceLineLayer), + series, + vis + ), + ...getReferenceLineLayers( + layers.filter((l) => l.isReferenceLineLayer), + vis + ), + ], + legend: { + isVisible: + legendDisplayFromUiState && (vis.params.addLegend ?? vis.type.visConfig.defaults.addLegend), + position: vis.params.legendPosition ?? vis.type.visConfig.defaults.legendPosition, + legendSize: vis.params.legendSize ?? vis.type.visConfig.defaults.legendSize, + shouldTruncate: vis.params.truncateLegend ?? vis.type.visConfig.defaults.truncateLegend, + maxLines: vis.params.maxLegendLines ?? vis.type.visConfig.defaults.maxLegendLines, + showSingleSeries: true, + }, + fittingFunction: fittingFunction + ? fittingFunction[0].toUpperCase() + fittingFunction.slice(1) + : undefined, + fillOpacity: vis.params.fillOpacity ?? vis.type.visConfig.defaults.fillOpacity, + gridlinesVisibilitySettings: { + x: vis.params.grid.categoryLines ?? vis.type.visConfig.defaults.grid?.categoryLines, + yLeft: + (vis.params.grid.valueAxis ?? vis.type.visConfig.defaults.grid?.valueAxis) === + yLeftAxis?.id, + yRight: + (vis.params.grid.valueAxis ?? vis.type.visConfig.defaults.grid?.valueAxis) === + yRightAxis?.id, + }, + axisTitlesVisibilitySettings, + tickLabelsVisibilitySettings: { + x: axisTitlesVisibilitySettings.x && (xAxis.labels.show ?? true), + yLeft: axisTitlesVisibilitySettings.yLeft && (yLeftAxis?.labels.show ?? true), + yRight: axisTitlesVisibilitySettings.yRight && (yRightAxis?.labels.show ?? true), + }, + labelsOrientation: { + x: getLabelOrientation(xAxis, isTimeChart), + yLeft: getLabelOrientation(yLeftAxis), + yRight: getLabelOrientation(yRightAxis), + }, + yLeftScale: getYScaleType(yLeftAxis?.scale) ?? ECScaleType.Linear, + yRightScale: getYScaleType(yRightAxis?.scale) ?? ECScaleType.Linear, + yLeftExtent: yLeftAxis?.scale ? getExtents(yLeftAxis, series) : undefined, + yRightExtent: yRightAxis?.scale ? getExtents(yRightAxis, series) : undefined, + yTitle: yLeftAxis?.title.text, + yRightTitle: yRightAxis?.title.text, + xTitle: xAxis.title.text, + valueLabels: + vis.params.labels.show ?? vis.type.visConfig.defaults.labels?.show ? 'show' : 'hide', + valuesInLegend: Boolean(vis.params.labels.show ?? vis.type.visConfig.defaults.labels?.show), + showCurrentTimeMarker: isTimeChart + ? Boolean(vis.params.addTimeMarker ?? vis.type.visConfig.defaults.addTimeMarker) + : undefined, + curveType: getCurveType( + series[0].interpolate === InterpolationMode.StepAfter + ? InterpolationMode.Linear + : series[0].interpolate + ), + }; +}; diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts new file mode 100644 index 0000000000000..c77794e22ab78 --- /dev/null +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { convertToLens } from '.'; +import { sampleAreaVis } from '../sample_vis.test.mocks'; + +const mockGetColumnsFromVis = jest.fn(); +const mockCreateStaticValueColumn = jest.fn().mockReturnValue({ operationType: 'static_value' }); +const mockGetVisSchemas = jest.fn().mockReturnValue({ + metric: [{ aggId: '1' }], +}); +const mockGetConfiguration = jest.fn().mockReturnValue({}); + +jest.mock('../services', () => ({ + getDataViewsStart: jest.fn(() => ({ get: () => ({}), getDefault: () => ({}) })), +})); + +jest.mock('../utils/get_series_params', () => ({ + getSeriesParams: jest.fn(() => undefined), +})); + +jest.mock('@kbn/visualizations-plugin/public', () => ({ + convertToLensModule: Promise.resolve({ + getColumnsFromVis: jest.fn(() => mockGetColumnsFromVis()), + createStaticValueColumn: jest.fn(() => mockCreateStaticValueColumn()), + }), + getDataViewByIndexPatternId: jest.fn(() => ({ id: 'index-pattern' })), + getVisSchemas: jest.fn(() => mockGetVisSchemas()), +})); + +jest.mock('./configurations', () => ({ + getConfiguration: jest.fn(() => mockGetConfiguration()), +})); + +describe('convertToLens', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null if getColumnsFromVis returns null', async () => { + mockGetColumnsFromVis.mockReturnValue(null); + const result = await convertToLens(sampleAreaVis as any, { getAbsoluteTime: () => {} } as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if multi split series defined', async () => { + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }], + group: [{}, {}], + }); + const result = await convertToLens(sampleAreaVis as any, { getAbsoluteTime: () => {} } as any); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if sibling pipeline agg defined together with split series', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['1'], customBuckets: { metric1: '2' } }, + }, + ]); + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }], + group: [{}], + }); + const result = await convertToLens(sampleAreaVis as any, { getAbsoluteTime: () => {} } as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if defined several layers with terms split series which uses one of the metrics as order agg', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['1'], customBuckets: { metric1: '2' } }, + columns: [{ isSplit: true, params: { orderBy: { type: 'column' } } }], + }, + { + buckets: { all: ['2'], customBuckets: { metric1: '2' } }, + columns: [{}], + }, + ]); + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }, { aggId: '2' }], + }); + const result = await convertToLens(sampleAreaVis as any, { getAbsoluteTime: () => {} } as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if more than one axis left/right/top/bottom defined', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['1'], customBuckets: {} }, + columns: [], + }, + ]); + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }, { aggId: '2' }], + }); + const result = await convertToLens( + { + ...sampleAreaVis, + params: { + ...sampleAreaVis.params, + valueAxes: [ + ...sampleAreaVis.params.valueAxes, + { + id: 'ValueAxis-2', + name: 'LeftAxis-2', + type: 'value', + position: 'left', + data: { + id: '2', + }, + }, + ], + seriesParams: [ + ...sampleAreaVis.params.seriesParams, + { show: true, valueAxis: 'ValueAxis-2', data: { id: '2' } }, + ], + }, + } as any, + { getAbsoluteTime: () => {} } as any + ); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should state for valid vis', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['2', '3'], customBuckets: { 1: '3' } }, + columns: [ + { columnId: '2', isBucketed: true }, + { columnId: '1', meta: { aggId: '1' } }, + { columnId: '3', isBucketed: true }, + ], + bucketCollapseFn: { sum: ['3'] }, + metrics: ['1'], + }, + { + buckets: { all: ['2'], customBuckets: {} }, + columns: [ + { columnId: '2', isBucketed: true }, + { columnId: '1', meta: { aggId: '2' } }, + ], + metrics: ['1'], + bucketCollapseFn: {}, + }, + ]); + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }], + }); + const result = await convertToLens( + { + ...sampleAreaVis, + params: { + ...sampleAreaVis.params, + valueAxes: [ + ...sampleAreaVis.params.valueAxes, + { + id: 'ValueAxis-2', + name: 'LeftAxis-2', + type: 'value', + position: 'left', + data: { + id: '2', + }, + }, + ], + seriesParams: [ + ...sampleAreaVis.params.seriesParams, + { show: true, valueAxis: 'ValueAxis-2', data: { id: '2' } }, + ], + thresholdLine: { ...sampleAreaVis.params.thresholdLine, show: true }, + }, + } as any, + { getAbsoluteTime: () => {} } as any + ); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(mockGetConfiguration).toBeCalledTimes(1); + expect(mockCreateStaticValueColumn).toBeCalledTimes(1); + expect(result?.type).toEqual('lnsXY'); + expect(result?.layers.length).toEqual(3); + expect(result?.layers[0].columns).toEqual([ + { columnId: '2', isBucketed: true }, + { columnId: '1' }, + { columnId: '3', isBucketed: true }, + ]); + expect(result?.layers[1].columns).toEqual([ + { columnId: '2', isBucketed: true }, + { columnId: '1' }, + ]); + expect(result?.layers[2].columns).toEqual([{ operationType: 'static_value' }]); + }); +}); diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts new file mode 100644 index 0000000000000..3b4339828c6d5 --- /dev/null +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -0,0 +1,214 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { METRIC_TYPES } from '@kbn/data-plugin/public'; +import { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; +import { + convertToLensModule, + getVisSchemas, + getDataViewByIndexPatternId, +} from '@kbn/visualizations-plugin/public'; +import { getDataViewsStart } from '../services'; +import { getSeriesParams } from '../utils/get_series_params'; +import { ConvertXYToLensVisualization } from './types'; + +export interface Layer { + indexPatternId: string; + layerId: string; + columns: Column[]; + metrics: string[]; + columnOrder: never[]; + seriesIdsMap: Record; + isReferenceLineLayer: boolean; + collapseFn?: string; +} + +const SIBBLING_PIPELINE_AGGS: string[] = [ + METRIC_TYPES.AVG_BUCKET, + METRIC_TYPES.SUM_BUCKET, + METRIC_TYPES.MAX_BUCKET, + METRIC_TYPES.MIN_BUCKET, +]; + +export const isColumnWithMeta = (column: Column): column is ColumnWithMeta => { + if ((column as ColumnWithMeta).meta) { + return true; + } + return false; +}; + +export const excludeMetaFromColumn = (column: Column) => { + if (isColumnWithMeta(column)) { + const { meta, ...rest } = column; + return rest; + } + return column; +}; + +export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilter) => { + if (!timefilter) { + return null; + } + + const dataViews = getDataViewsStart(); + const dataView = await getDataViewByIndexPatternId(vis.data.indexPattern?.id, dataViews); + + if (!dataView) { + return null; + } + + const visSchemas = getVisSchemas(vis, { + timefilter, + timeRange: timefilter.getAbsoluteTime(), + }); + + // doesn't support multi split series + if (visSchemas.group && visSchemas.group.length > 1) { + return null; + } + + const firstValueAxesId = vis.params.valueAxes[0].id; + const updatedSeries = getSeriesParams( + vis.data.aggs, + vis.params.seriesParams, + 'metric', + firstValueAxesId + ); + + const finalSeriesParams = updatedSeries ?? vis.params.seriesParams; + const visibleSeries = finalSeriesParams.filter( + (param) => param.show && visSchemas.metric.some((m) => m.aggId?.split('.')[0] === param.data.id) + ); + + const [{ getColumnsFromVis, createStaticValueColumn }, { getConfiguration }] = await Promise.all([ + convertToLensModule, + import('./configurations'), + ]); + const dataLayers = getColumnsFromVis( + vis, + timefilter, + dataView, + { + buckets: ['segment'], + splits: ['group'], + unsupported: ['split_row', 'split_column', 'radius'], + }, + { + dropEmptyRowsInDateHistogram: true, + supportMixedSiblingPipelineAggs: true, + isPercentageMode: false, + }, + visibleSeries + .reduce>((acc, s) => { + const series = acc.find(({ type, mode }) => type === s.type && mode === s.mode); + // sibling pipeline agg always generate new layer because of custom bucket + if ( + series && + visSchemas.metric.some( + (m) => + m.aggId?.split('.')[0] === s.data.id && !SIBBLING_PIPELINE_AGGS.includes(m.aggType) + ) + ) { + series.metrics.push(s.data.id); + } else { + acc.push({ metrics: [s.data.id], type: s.type, mode: s.mode }); + } + return acc; + }, []) + .map(({ metrics }) => ({ metrics })) + ); + + if (dataLayers === null) { + return null; + } + + // doesn't support several layers with terms split series which uses one of the metrics as order agg + if ( + dataLayers.length > 1 && + dataLayers.some((l) => + l.columns.some( + (c) => c.isSplit && 'orderBy' in c.params && c.params.orderBy.type === 'column' + ) + ) + ) { + return null; + } + + // doesn't support sibling pipeline aggs and split series together + if ( + visSchemas.group?.length && + dataLayers.some((l) => Object.keys(l.buckets.customBuckets).length) + ) { + return null; + } + + const visibleYAxes = vis.params.valueAxes.filter((axis) => + visibleSeries.some((seriesParam) => seriesParam.valueAxis === axis.id) + ); + + const positions = visibleYAxes.map((axis) => axis.position); + const uniqPoisitions = new Set(positions); + + // doesn't support more than one axis left/right/top/bottom + if (visibleYAxes.length > 1 && uniqPoisitions.size !== positions.length) { + return null; + } + + const indexPatternId = dataView.id!; + + const uuid = await import('uuid/v4'); + + const layers = dataLayers.map((l) => { + const layerId = uuid.default(); + const seriesIdsMap: Record = {}; + visibleSeries.forEach((s) => { + const column = l.columns.find( + (c) => !c.isBucketed && c.meta.aggId.split('.')[0] === s.data.id + ); + if (column) { + seriesIdsMap[column.columnId] = s.data.id; + } + }); + const collapseFn = l.bucketCollapseFn + ? Object.keys(l.bucketCollapseFn).find((key) => + l.bucketCollapseFn[key].includes(l.buckets.customBuckets[l.metrics[0]]) + ) + : undefined; + return { + indexPatternId, + layerId, + columns: l.columns.map(excludeMetaFromColumn), + metrics: l.metrics, + columnOrder: [], + seriesIdsMap, + collapseFn, + isReferenceLineLayer: false, + }; + }); + + if (vis.params.thresholdLine.show) { + const staticValueColumn = createStaticValueColumn(vis.params.thresholdLine.value || 0); + layers.push({ + indexPatternId, + layerId: uuid.default(), + columns: [staticValueColumn], + columnOrder: [], + metrics: [staticValueColumn.columnId], + isReferenceLineLayer: true, + collapseFn: undefined, + seriesIdsMap: {}, + }); + } + + return { + type: 'lnsXY', + layers: layers.map(({ seriesIdsMap, collapseFn, isReferenceLineLayer, ...rest }) => rest), + configuration: getConfiguration(layers, visibleSeries, vis), + indexPatternIds: [indexPatternId], + }; +}; diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/types.ts b/src/plugins/vis_types/xy/public/convert_to_lens/types.ts new file mode 100644 index 0000000000000..5fa52b4221107 --- /dev/null +++ b/src/plugins/vis_types/xy/public/convert_to_lens/types.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 { TimefilterContract } from '@kbn/data-plugin/public'; +import { NavigateToLensContext, XYConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { VisParams } from '../types'; + +export type ConvertXYToLensVisualization = ( + vis: Vis, + timefilter?: TimefilterContract +) => Promise | null>; diff --git a/src/plugins/vis_types/xy/public/plugin.ts b/src/plugins/vis_types/xy/public/plugin.ts index 4561006e43e92..ad75af4dfffdb 100644 --- a/src/plugins/vis_types/xy/public/plugin.ts +++ b/src/plugins/vis_types/xy/public/plugin.ts @@ -6,10 +6,11 @@ * Side Public License, v 1. */ -import type { CoreSetup, Plugin } from '@kbn/core/public'; +import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; -import { setUISettings, setPalettesService } from './services'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { setUISettings, setPalettesService, setDataViewsStart } from './services'; import { visTypesDefinitions } from './vis_types'; @@ -24,6 +25,11 @@ export interface VisTypeXyPluginSetupDependencies { charts: ChartsPluginSetup; } +/** @internal */ +export interface VisTypeXyPluginStartDependencies { + dataViews: DataViewsPublicPluginStart; +} + type VisTypeXyCoreSetup = CoreSetup<{}, VisTypeXyPluginStart>; /** @internal */ @@ -42,7 +48,8 @@ export class VisTypeXyPlugin return {}; } - public start() { + public start(core: CoreStart, { dataViews }: VisTypeXyPluginStartDependencies) { + setDataViewsStart(dataViews); return {}; } } diff --git a/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts index e55debd7c77ba..b2660b7c66551 100644 --- a/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts @@ -8,6 +8,8 @@ import { LegendSize } from '@kbn/visualizations-plugin/common'; +const mockUiStateGet = jest.fn().mockReturnValue(() => false); + export const sampleAreaVis = { type: { name: 'area', @@ -1918,5 +1920,10 @@ export const sampleAreaVis = { }, }, isHierarchical: () => false, - uiState: {}, + uiState: { + vis: { + legendOpen: false, + }, + get: mockUiStateGet, + }, }; diff --git a/src/plugins/vis_types/xy/public/services.ts b/src/plugins/vis_types/xy/public/services.ts index 2358bcb5ede2e..7513f6188ef0e 100644 --- a/src/plugins/vis_types/xy/public/services.ts +++ b/src/plugins/vis_types/xy/public/services.ts @@ -8,6 +8,7 @@ import type { CoreSetup } from '@kbn/core/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; @@ -16,3 +17,6 @@ export const [getUISettings, setUISettings] = export const [getPalettesService, setPalettesService] = createGetterSetter('xy charts.palette'); + +export const [getDataViewsStart, setDataViewsStart] = + createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/xy/public/to_ast.ts b/src/plugins/vis_types/xy/public/to_ast.ts index 4041075b98c4d..b584fbac26bba 100644 --- a/src/plugins/vis_types/xy/public/to_ast.ts +++ b/src/plugins/vis_types/xy/public/to_ast.ts @@ -18,8 +18,8 @@ import { } from '@kbn/visualizations-plugin/public'; import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/public'; import { BUCKET_TYPES } from '@kbn/data-plugin/public'; -import { TimeRangeBounds } from '@kbn/data-plugin/common'; -import { PaletteOutput } from '@kbn/charts-plugin/common/expressions/palette/types'; +import type { TimeRangeBounds } from '@kbn/data-plugin/common'; +import type { PaletteOutput } from '@kbn/charts-plugin/common/expressions/palette/types'; import { Dimensions, Dimension, @@ -30,32 +30,15 @@ import { ValueAxis, Scale, ChartMode, - InterpolationMode, ScaleType, } from './types'; import { ChartType } from '../common'; import { getSeriesParams } from './utils/get_series_params'; import { getSafeId } from './utils/accessors'; - -interface Bounds { - min?: string | number; - max?: string | number; -} +import { Bounds, getCurveType, getLineStyle, getMode, getYAxisPosition } from './utils/common'; type YDimension = Omit & { accessor: string }; -const getCurveType = (type?: InterpolationMode) => { - switch (type) { - case 'cardinal': - return 'CURVE_MONOTONE_X'; - case 'step-after': - return 'CURVE_STEP_AFTER'; - case 'linear': - default: - return 'LINEAR'; - } -}; - const prepareLengend = (params: VisParams, legendSize?: LegendSize) => { const legend = buildExpressionFunction('legendConfig', { isVisible: params.addLegend, @@ -162,16 +145,6 @@ const prepareLayers = ( return buildExpression([dataLayer]); }; -const getMode = (scale: Scale, bounds?: Bounds) => { - if (scale.defaultYExtents) { - return 'dataBounds'; - } - - if (scale.setYExtents || bounds) { - return 'custom'; - } -}; - const getLabelArgs = (data: CategoryAxis, isTimeChart?: boolean) => { return { truncate: data.labels.truncate, @@ -215,18 +188,6 @@ function getScaleType( return type; } -function getYAxisPosition(position: Position) { - if (position === Position.Top) { - return Position.Right; - } - - if (position === Position.Bottom) { - return Position.Left; - } - - return position; -} - function getXAxisPosition(position: Position) { if (position === Position.Left) { return Position.Bottom; @@ -274,16 +235,6 @@ const prepareYAxis = (data: ValueAxis, showGridLines?: boolean) => { return buildExpression([yAxisConfig]); }; -const getLineStyle = (style: ThresholdLine['style']) => { - switch (style) { - case 'full': - return 'solid'; - case 'dashed': - case 'dot-dashed': - return style; - } -}; - const prepareReferenceLine = (thresholdLine: ThresholdLine, axisId: string) => { const referenceLine = buildExpressionFunction('referenceLine', { value: thresholdLine.value, @@ -483,6 +434,7 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params splitColumnAccessor: dimensions.splitColumn?.map(prepareVisDimension), splitRowAccessor: dimensions.splitRow?.map(prepareVisDimension), valueLabels: vis.params.labels.show ? 'show' : 'hide', + valuesInLegend: vis.params.labels.show, singleTable: true, }); diff --git a/src/plugins/vis_types/xy/public/utils/common.ts b/src/plugins/vis_types/xy/public/utils/common.ts new file mode 100644 index 0000000000000..522cac2aa00ac --- /dev/null +++ b/src/plugins/vis_types/xy/public/utils/common.ts @@ -0,0 +1,62 @@ +/* + * 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 { Position } from '@elastic/charts'; +import type { AxisExtentConfig } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import type { InterpolationMode, Scale, ThresholdLine } from '../types'; + +export interface Bounds { + min?: string | number; + max?: string | number; +} + +export const getCurveType = (type?: InterpolationMode) => { + switch (type) { + case 'cardinal': + return 'CURVE_MONOTONE_X'; + case 'step-after': + return 'CURVE_STEP_AFTER'; + case 'linear': + default: + return 'LINEAR'; + } +}; + +export const getMode = (scale: Scale, bounds?: Bounds): AxisExtentConfig['mode'] => { + if (scale.defaultYExtents) { + return 'dataBounds'; + } + + if (scale.setYExtents || bounds) { + return 'custom'; + } + + return 'full'; +}; + +export const getYAxisPosition = (position: Position) => { + if (position === Position.Top) { + return Position.Right; + } + + if (position === Position.Bottom) { + return Position.Left; + } + + return position; +}; + +export const getLineStyle = (style: ThresholdLine['style']) => { + switch (style) { + case 'full': + return 'solid'; + case 'dashed': + case 'dot-dashed': + return style; + } +}; diff --git a/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts b/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts index c1f79b041e807..1f7ead86843d9 100644 --- a/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts +++ b/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { VisParams } from '@kbn/visualizations-plugin/common'; +import type { VisParams } from '@kbn/visualizations-plugin/common'; export const getVisTypeFromParams = (params?: VisParams) => { let type = params?.seriesParams?.[0]?.type; diff --git a/src/plugins/vis_types/xy/public/vis_types/index.ts b/src/plugins/vis_types/xy/public/vis_types/index.ts index 93c973b5316c9..2f7a03b6aaf1c 100644 --- a/src/plugins/vis_types/xy/public/vis_types/index.ts +++ b/src/plugins/vis_types/xy/public/vis_types/index.ts @@ -6,14 +6,27 @@ * Side Public License, v 1. */ +import type { VisTypeDefinition } from '@kbn/visualizations-plugin/public'; +import type { VisParams } from '../types'; import { areaVisTypeDefinition } from './area'; import { lineVisTypeDefinition } from './line'; import { histogramVisTypeDefinition } from './histogram'; import { horizontalBarVisTypeDefinition } from './horizontal_bar'; +import { convertToLens } from '../convert_to_lens'; export const visTypesDefinitions = [ areaVisTypeDefinition, lineVisTypeDefinition, histogramVisTypeDefinition, horizontalBarVisTypeDefinition, -]; +].map>((defenition) => { + return { + ...defenition, + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, + }; +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts index 03e1d955dd045..a8389cb8601e4 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts @@ -13,7 +13,7 @@ import { convertToSchemaConfig } from '../../../vis_schemas'; export const convertToSiblingPipelineColumns = ( columnConverterArgs: ExtendedColumnConverterArgs ): AggBasedColumn | null => { - const { aggParams, label } = columnConverterArgs.agg; + const { aggParams, label, aggId } = columnConverterArgs.agg; if (!aggParams) { return null; } @@ -23,7 +23,7 @@ export const convertToSiblingPipelineColumns = ( } const customMetricColumn = convertMetricToColumns( - { ...convertToSchemaConfig(aggParams.customMetric), label }, + { ...convertToSchemaConfig(aggParams.customMetric), label, aggId }, columnConverterArgs.dataView, columnConverterArgs.aggs ); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts index 3dfaee67a61e0..8e6f9ec9443bb 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts @@ -58,7 +58,7 @@ export type SiblingPipelineMetric = | METRIC_TYPES.MIN_BUCKET | METRIC_TYPES.MAX_BUCKET; -export type BucketColumn = DateHistogramColumn | TermsColumn | FiltersColumn; +export type BucketColumn = DateHistogramColumn | TermsColumn | FiltersColumn | RangeColumn; export interface CommonColumnConverterArgs< Agg extends SupportedAggregation = SupportedAggregation > { diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts index 73118b6ad4f03..1f91201aff503 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts @@ -365,7 +365,7 @@ describe('getCustomBucketsFromSiblingAggs', () => { }, params: {}, aggType: METRIC_TYPES.AVG_BUCKET, - aggId: 'some-agg-id', + aggId: 'some-agg-id-1', aggParams: { customBucket: bucketWithSerialize1, }, @@ -381,7 +381,7 @@ describe('getCustomBucketsFromSiblingAggs', () => { }, params: {}, aggType: METRIC_TYPES.AVG_BUCKET, - aggId: 'some-agg-id', + aggId: 'some-agg-id-2', aggParams: { customBucket: bucketWithSerialize2, }, @@ -399,7 +399,7 @@ describe('getCustomBucketsFromSiblingAggs', () => { }, params: {}, aggType: METRIC_TYPES.AVG_BUCKET, - aggId: 'some-agg-id', + aggId: 'some-agg-id-3', aggParams: { customBucket: bucketWithSerialize3, }, @@ -407,8 +407,8 @@ describe('getCustomBucketsFromSiblingAggs', () => { test("should filter out duplicated custom buckets, ignoring id's", () => { expect(getCustomBucketsFromSiblingAggs([metric1, metric2, metric3])).toEqual([ - bucketWithSerialize1, - bucketWithSerialize2, + { customBucket: bucketWithSerialize1, metricIds: ['some-agg-id-1', 'some-agg-id-3'] }, + { customBucket: bucketWithSerialize2, metricIds: ['some-agg-id-2'] }, ]); }); }); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts index c4e5c5474bf0c..ce50312d92cf3 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts @@ -151,19 +151,19 @@ export const isStdDevAgg = (metric: SchemaConfig): metric is SchemaConfig { - return metrics.reduce((acc, metric) => { - if ( - isSiblingPipeline(metric) && - metric.aggParams?.customBucket && - acc.every( - (bucket) => - !isEqual( - omit(metric.aggParams?.customBucket?.serialize(), ['id']), - omit(bucket.serialize(), ['id']) - ) - ) - ) { - acc.push(metric.aggParams.customBucket); + return metrics.reduce>((acc, metric) => { + if (isSiblingPipeline(metric) && metric.aggParams?.customBucket && metric.aggId) { + const customBucket = acc.find((bucket) => + isEqual( + omit(metric.aggParams?.customBucket?.serialize(), ['id']), + omit(bucket.customBucket.serialize(), ['id']) + ) + ); + if (customBucket) { + customBucket.metricIds.push(metric.aggId); + } else { + acc.push({ customBucket: metric.aggParams.customBucket, metricIds: [metric.aggId] }); + } } return acc; diff --git a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts index 44a56411d8ea5..fcda8568a93c0 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -162,6 +162,7 @@ export interface XYConfiguration { fillOpacity?: number; hideEndzones?: boolean; valuesInLegend?: boolean; + showCurrentTimeMarker?: boolean; } export interface SortingState { diff --git a/src/plugins/visualizations/common/convert_to_lens/utils.ts b/src/plugins/visualizations/common/convert_to_lens/utils.ts index 3536282d830d3..51c7e8239a439 100644 --- a/src/plugins/visualizations/common/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/utils.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { DataViewField } from '@kbn/data-views-plugin/common'; -import { SupportedMetric } from './lib/convert/supported_metrics'; -import { Layer, XYAnnotationsLayerConfig, XYLayerConfig } from './types'; +import type { DataViewField } from '@kbn/data-views-plugin/common'; +import type { SupportedMetric } from './lib/convert/supported_metrics'; +import type { Layer, XYAnnotationsLayerConfig, XYLayerConfig } from './types'; export const isAnnotationsLayer = ( layer: Pick diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts index 5b8b7832730b9..54975d08b8486 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts @@ -21,6 +21,7 @@ import { getColumnsFromVis } from './schemas'; const mockConvertMetricToColumns = jest.fn(); const mockConvertBucketToColumns = jest.fn(); const mockGetCutomBucketsFromSiblingAggs = jest.fn(); +const mockGetCustomBucketColumns = jest.fn(); const mockGetVisSchemas = jest.fn(); const mockGetBucketCollapseFn = jest.fn(); @@ -55,6 +56,7 @@ jest.mock('./utils', () => ({ getMetricsWithoutDuplicates: jest.fn(() => mockGetMetricsWithoutDuplicates()), isValidVis: jest.fn(() => mockIsValidVis()), sortColumns: jest.fn(() => mockSortColumns()), + getCustomBucketColumns: jest.fn(() => mockGetCustomBucketColumns()), })); describe('getColumnsFromVis', () => { @@ -73,6 +75,7 @@ describe('getColumnsFromVis', () => { jest.clearAllMocks(); mockGetVisSchemas.mockReturnValue({}); mockIsValidVis.mockReturnValue(true); + mockGetCustomBucketColumns.mockReturnValue({ customBucketColumns: [], customBucketsMap: {} }); }); test('should return null if vis is not valid', () => { @@ -107,7 +110,10 @@ describe('getColumnsFromVis', () => { test('should return null if one sibling agg was provided and it is not supported', () => { const buckets: AggConfig[] = [aggConfig]; mockGetCutomBucketsFromSiblingAggs.mockReturnValue(buckets); - mockConvertBucketToColumns.mockReturnValue(null); + mockGetCustomBucketColumns.mockReturnValue({ + customBucketColumns: [null], + customBucketsMap: {}, + }); mockGetMetricsWithoutDuplicates.mockReturnValue([{}]); const result = getColumnsFromVis(vis, dataServiceMock.query.timefilter.timefilter, dataView, { @@ -120,7 +126,7 @@ describe('getColumnsFromVis', () => { expect(mockIsValidVis).toBeCalledTimes(1); expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(1); - expect(mockConvertBucketToColumns).toBeCalledTimes(1); + expect(mockGetCustomBucketColumns).toBeCalledTimes(1); expect(mockGetBucketColumns).toBeCalledTimes(0); }); @@ -190,7 +196,7 @@ describe('getColumnsFromVis', () => { expect(mockSortColumns).toBeCalledTimes(0); }); - test('should return columns', () => { + test('should return one layer with columns', () => { const buckets: AggConfig[] = [aggConfig]; const bucketColumns = [ { @@ -238,13 +244,18 @@ describe('getColumnsFromVis', () => { buckets: [], }); - expect(result).toEqual({ - bucketCollapseFn, - buckets: [bucketId], - columns: [...metrics, ...buckets], - columnsWithoutReferenced, - metrics: [metricId], - }); + expect(result).toEqual([ + { + bucketCollapseFn, + buckets: { + all: [bucketId], + customBuckets: {}, + }, + columns: [...metrics, ...buckets], + columnsWithoutReferenced, + metrics: [metricId], + }, + ]); expect(mockGetVisSchemas).toBeCalledTimes(1); expect(mockIsValidVis).toBeCalledTimes(1); expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); @@ -254,4 +265,84 @@ describe('getColumnsFromVis', () => { expect(mockSortColumns).toBeCalledTimes(1); expect(mockGetColumnsWithoutReferenced).toBeCalledTimes(1); }); + + test('should return several layer with columns if series is provided', () => { + const buckets: AggConfig[] = [aggConfig]; + const bucketColumns = [ + { + sourceField: 'some-field', + columnId: 'col3', + operationType: 'date_histogram', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: { interval: '1h' }, + meta: { aggId: 'agg-id-1' }, + }, + ]; + const mectricAggs = [{ aggId: 'col-id-3' }, { aggId: 'col-id-4' }]; + const metrics = [ + { + sourceField: 'some-field', + columnId: 'col2', + operationType: 'max', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'col-id-3' }, + }, + { + sourceField: 'some-field', + columnId: 'col3', + operationType: 'max', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'col-id-4' }, + }, + ]; + + const columnsWithoutReferenced = ['col2']; + const metricId = 'metric1'; + const bucketId = 'bucket1'; + const bucketCollapseFn = 'max'; + + mockGetCutomBucketsFromSiblingAggs.mockReturnValue([]); + mockGetMetricsWithoutDuplicates.mockReturnValue(mectricAggs); + mockConvertMetricToColumns.mockReturnValue(metrics); + mockConvertBucketToColumns.mockReturnValue(bucketColumns); + mockGetBucketColumns.mockReturnValue(bucketColumns); + mockGetColumnsWithoutReferenced.mockReturnValue(columnsWithoutReferenced); + mockSortColumns.mockReturnValue([...metrics, ...buckets]); + mockGetColumnIds.mockReturnValueOnce([metricId]); + mockGetColumnIds.mockReturnValueOnce([bucketId]); + mockGetColumnIds.mockReturnValueOnce([metricId]); + mockGetColumnIds.mockReturnValueOnce([bucketId]); + mockGetBucketCollapseFn.mockReturnValueOnce(bucketCollapseFn); + mockGetBucketCollapseFn.mockReturnValueOnce(bucketCollapseFn); + + const result = getColumnsFromVis( + vis, + dataServiceMock.query.timefilter.timefilter, + dataView, + { + splits: [], + buckets: [], + }, + undefined, + [{ metrics: ['col-id-3'] }, { metrics: ['col-id-4'] }] + ); + + expect(result?.length).toEqual(2); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockIsValidVis).toBeCalledTimes(1); + expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); + expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(1); + expect(mockConvertMetricToColumns).toBeCalledTimes(2); + expect(mockGetBucketColumns).toBeCalledTimes(4); + expect(mockSortColumns).toBeCalledTimes(2); + expect(mockGetColumnsWithoutReferenced).toBeCalledTimes(2); + }); }); diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index ecfbbf34ad9c9..3a225e540faae 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -7,16 +7,17 @@ */ import type { DataView } from '@kbn/data-views-plugin/common'; -import { METRIC_TYPES, TimefilterContract } from '@kbn/data-plugin/public'; +import { IAggConfig, METRIC_TYPES, TimefilterContract } from '@kbn/data-plugin/public'; import { AggBasedColumn, PercentageModeConfig, SchemaConfig } from '../../common'; import { convertMetricToColumns } from '../../common/convert_to_lens/lib/metrics'; -import { convertBucketToColumns } from '../../common/convert_to_lens/lib/buckets'; import { getCustomBucketsFromSiblingAggs } from '../../common/convert_to_lens/lib/utils'; +import { BucketColumn } from '../../common/convert_to_lens/lib'; import type { Vis } from '../types'; import { getVisSchemas, Schemas } from '../vis_schemas'; import { getBucketCollapseFn, getBucketColumns, + getCustomBucketColumns, getColumnIds, getColumnsWithoutReferenced, getMetricsWithoutDuplicates, @@ -31,66 +32,44 @@ const areVisSchemasValid = (visSchemas: Schemas, unsupported: Array( - vis: Vis, - timefilter: TimefilterContract, +const createLayer = ( + visSchemas: Schemas, + allMetrics: Array>, + metricsForLayer: Array>, + customBucketsWithMetricIds: Array<{ + customBucket: IAggConfig; + metricIds: string[]; + }>, dataView: DataView, { splits = [], buckets = [], - unsupported = [], }: { splits?: Array; buckets?: Array; - unsupported?: Array; } = {}, - config?: { - dropEmptyRowsInDateHistogram?: boolean; - } & (PercentageModeConfig | void) + percentageModeConfig: PercentageModeConfig, + dropEmptyRowsInDateHistogram?: boolean ) => { - const { dropEmptyRowsInDateHistogram, ...percentageModeConfig } = config ?? { - isPercentageMode: false, - }; - const visSchemas = getVisSchemas(vis, { - timefilter, - timeRange: timefilter.getAbsoluteTime(), - }); - - if (!isValidVis(visSchemas) || !areVisSchemasValid(visSchemas, unsupported)) { - return null; - } - - const customBuckets = getCustomBucketsFromSiblingAggs(visSchemas.metric); - - // doesn't support sibbling pipeline aggs with different bucket aggs - if (customBuckets.length > 1) { + const metricColumns = metricsForLayer.flatMap((m) => + convertMetricToColumns(m, dataView, allMetrics, percentageModeConfig) + ); + if (metricColumns.includes(null)) { return null; } + const metricColumnsWithoutNull = metricColumns as AggBasedColumn[]; - const metricsWithoutDuplicates = getMetricsWithoutDuplicates(visSchemas.metric); - const aggs = metricsWithoutDuplicates as Array>; - - const metricColumns = aggs.flatMap((m) => - convertMetricToColumns(m, dataView, aggs, percentageModeConfig) + const { customBucketColumns, customBucketsMap } = getCustomBucketColumns( + customBucketsWithMetricIds, + metricColumnsWithoutNull, + dataView, + allMetrics, + dropEmptyRowsInDateHistogram ); - if (metricColumns.includes(null)) { + if (customBucketColumns.includes(null)) { return null; } - const metrics = metricColumns as AggBasedColumn[]; - const customBucketColumns = []; - - if (customBuckets.length) { - const customBucketColumn = convertBucketToColumns( - { agg: customBuckets[0], dataView, metricColumns: metrics, aggs }, - false, - dropEmptyRowsInDateHistogram - ); - if (!customBucketColumn) { - return null; - } - customBucketColumns.push(customBucketColumn); - } const bucketColumns = getBucketColumns( visSchemas, @@ -117,19 +96,121 @@ export const getColumnsFromVis = ( } const columns = sortColumns( - [...metrics, ...bucketColumns, ...splitBucketColumns, ...customBucketColumns], + [ + ...metricColumnsWithoutNull, + ...bucketColumns, + ...splitBucketColumns, + ...(customBucketColumns as BucketColumn[]), + ], visSchemas, [...buckets, ...splits], - metricsWithoutDuplicates + metricsForLayer ); const columnsWithoutReferenced = getColumnsWithoutReferenced(columns); return { metrics: getColumnIds(columnsWithoutReferenced.filter((с) => !с.isBucketed)), - buckets: getColumnIds(columnsWithoutReferenced.filter((c) => c.isBucketed)), - bucketCollapseFn: getBucketCollapseFn(visSchemas.metric, customBucketColumns), + buckets: { + all: getColumnIds(columnsWithoutReferenced.filter((c) => c.isBucketed)), + customBuckets: customBucketsMap, + }, + bucketCollapseFn: getBucketCollapseFn( + visSchemas.metric, + customBucketColumns as BucketColumn[], + customBucketsMap, + metricColumnsWithoutNull + ), columnsWithoutReferenced, columns, }; }; + +export const getColumnsFromVis = ( + vis: Vis, + timefilter: TimefilterContract, + dataView: DataView, + { + splits = [], + buckets = [], + unsupported = [], + }: { + splits?: Array; + buckets?: Array; + unsupported?: Array; + } = {}, + config?: { + dropEmptyRowsInDateHistogram?: boolean; + supportMixedSiblingPipelineAggs?: boolean; + } & (PercentageModeConfig | void), + series?: Array<{ metrics: string[] }> +) => { + const { dropEmptyRowsInDateHistogram, supportMixedSiblingPipelineAggs, ...percentageModeConfig } = + config ?? { + isPercentageMode: false, + }; + const visSchemas = getVisSchemas(vis, { + timefilter, + timeRange: timefilter.getAbsoluteTime(), + }); + + if ( + !isValidVis(visSchemas, supportMixedSiblingPipelineAggs) || + !areVisSchemasValid(visSchemas, unsupported) + ) { + return null; + } + + const customBucketsWithMetricIds = getCustomBucketsFromSiblingAggs(visSchemas.metric); + + // doesn't support sibbling pipeline aggs with different bucket aggs + if (!supportMixedSiblingPipelineAggs && customBucketsWithMetricIds.length > 1) { + return null; + } + + const metricsWithoutDuplicates = getMetricsWithoutDuplicates(visSchemas.metric); + const aggs = metricsWithoutDuplicates as Array>; + const layers = []; + + if (series && series.length) { + for (const { metrics: metricAggIds } of series) { + const metrics = aggs.filter( + (agg) => agg.aggId && metricAggIds.includes(agg.aggId.split('.')[0]) + ); + const customBucketsForLayer = customBucketsWithMetricIds.filter((c) => + c.metricIds.some((m) => metricAggIds.includes(m)) + ); + const layer = createLayer( + visSchemas, + aggs, + metrics, + customBucketsForLayer, + dataView, + { splits, buckets }, + percentageModeConfig, + dropEmptyRowsInDateHistogram + ); + if (!layer) { + return null; + } + layers.push(layer); + } + } else { + const layer = createLayer( + visSchemas, + aggs, + aggs, + customBucketsWithMetricIds, + dataView, + { splits, buckets }, + percentageModeConfig, + dropEmptyRowsInDateHistogram + ); + if (!layer) { + return null; + } + layers.push(layer); + } + + return layers; +}; diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts index 734a250a2972c..50f667430a8cb 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { BUCKET_TYPES, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { BUCKET_TYPES, IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { AggBasedColumn, @@ -27,6 +27,7 @@ import { getBucketColumns, getColumnIds, getColumnsWithoutReferenced, + getCustomBucketColumns, getMetricsWithoutDuplicates, isReferenced, isValidVis, @@ -104,6 +105,7 @@ describe('getColumnsWithoutReferenced', () => { describe('getBucketCollapseFn', () => { const metric1: SchemaConfig = { accessor: 0, + aggId: '1', label: '', format: { id: undefined, @@ -115,21 +117,25 @@ describe('getBucketCollapseFn', () => { const metric2: SchemaConfig = { ...metric1, + aggId: '2', aggType: METRIC_TYPES.AVG_BUCKET, }; const metric3: SchemaConfig = { ...metric1, + aggId: '3', aggType: METRIC_TYPES.MAX_BUCKET, }; const metric4: SchemaConfig = { ...metric1, + aggId: '4', aggType: METRIC_TYPES.MIN_BUCKET, }; const metric5: SchemaConfig = { ...metric1, + aggId: '5', aggType: METRIC_TYPES.SUM_BUCKET, }; @@ -151,18 +157,54 @@ describe('getBucketCollapseFn', () => { test.each< [ string, - [Array>, AggBasedColumn[]], - Record + [ + Array>, + AggBasedColumn[], + Record, + AggBasedColumn[] + ], + Record ] >([ - ['avg', [[metric1, metric2], [customBucketColum]], { [customBucketColum.columnId]: 'avg' }], - ['max', [[metric1, metric3], [customBucketColum]], { [customBucketColum.columnId]: 'max' }], - ['min', [[metric1, metric4], [customBucketColum]], { [customBucketColum.columnId]: 'min' }], - ['sum', [[metric1, metric5], [customBucketColum]], { [customBucketColum.columnId]: 'sum' }], [ - 'undefined if no sibling pipeline agg is provided', - [[metric1], [customBucketColum]], - { [customBucketColum.columnId]: undefined }, + 'avg', + [ + [metric1, metric2], + [customBucketColum], + { test: 'bucket-1' }, + [{ columnId: 'test', meta: { aggId: metric2.aggId } } as AggBasedColumn], + ], + { sum: [], min: [], max: [], avg: [customBucketColum.columnId] }, + ], + [ + 'max', + [ + [metric1, metric3], + [customBucketColum], + { test: 'bucket-1' }, + [{ columnId: 'test', meta: { aggId: metric3.aggId } } as AggBasedColumn], + ], + { sum: [], min: [], max: [customBucketColum.columnId], avg: [] }, + ], + [ + 'min', + [ + [metric1, metric4], + [customBucketColum], + { test: 'bucket-1' }, + [{ columnId: 'test', meta: { aggId: metric4.aggId } } as AggBasedColumn], + ], + { sum: [], min: [customBucketColum.columnId], max: [], avg: [] }, + ], + [ + 'sum', + [ + [metric1, metric5], + [customBucketColum], + { test: 'bucket-1' }, + [{ columnId: 'test', meta: { aggId: metric5.aggId } } as AggBasedColumn], + ], + { sum: [customBucketColum.columnId], min: [], max: [], avg: [] }, ], ])('should return%s', (_, input, expected) => { expect(getBucketCollapseFn(...input)).toEqual(expected); @@ -607,4 +649,77 @@ describe('getColumnIds', () => { colId4, ]); }); + + describe('getCustomBucketColumns', () => { + const dataView = stubLogstashDataView; + const baseMetric = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + const metric1: SchemaConfig = { + ...baseMetric, + accessor: 2, + aggType: METRIC_TYPES.COUNT, + aggId: '3', + }; + const metric2: SchemaConfig = { + ...baseMetric, + accessor: 3, + aggType: METRIC_TYPES.MAX, + aggId: '4', + }; + const customBucketsWithMetricIds = [ + { + customBucket: {} as IAggConfig, + metricIds: ['3', '4'], + }, + { + customBucket: {} as IAggConfig, + metricIds: ['5'], + }, + ]; + test('return custom buckets columns and map', () => { + mockConvertBucketToColumns.mockReturnValueOnce({ + columnId: 'col-1', + operationType: 'date_histogram', + }); + mockConvertBucketToColumns.mockReturnValueOnce({ + columnId: 'col-2', + operationType: 'terms', + }); + expect( + getCustomBucketColumns( + customBucketsWithMetricIds, + [ + { columnId: 'col-3', meta: { aggId: '3' } }, + { columnId: 'col-4', meta: { aggId: '4' } }, + { columnId: 'col-5', meta: { aggId: '5' } }, + ] as AggBasedColumn[], + dataView, + [metric1, metric2] + ) + ).toEqual({ + customBucketColumns: [ + { + columnId: 'col-1', + operationType: 'date_histogram', + }, + { + columnId: 'col-2', + operationType: 'terms', + }, + ], + customBucketsMap: { + 'col-3': 'col-1', + 'col-4': 'col-1', + 'col-5': 'col-2', + }, + }); + }); + }); }); diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.ts b/src/plugins/visualizations/public/convert_to_lens/utils.ts index a5337568b5568..0cab4f698fb2f 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.ts @@ -7,10 +7,11 @@ */ import type { DataView } from '@kbn/data-views-plugin/common'; -import { METRIC_TYPES } from '@kbn/data-plugin/public'; +import { IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/public'; import { AggBasedColumn, SchemaConfig, SupportedAggregation } from '../../common'; import { convertBucketToColumns } from '../../common/convert_to_lens/lib/buckets'; import { isSiblingPipeline } from '../../common/convert_to_lens/lib/utils'; +import { BucketColumn } from '../../common/convert_to_lens/lib'; import { Schemas } from '../vis_schemas'; export const isReferenced = (columnId: string, references: string[]) => @@ -25,14 +26,31 @@ export const getColumnsWithoutReferenced = (columns: AggBasedColumn[]) => { export const getBucketCollapseFn = ( metrics: Array>, - customBucketColumns: AggBasedColumn[] + customBucketColumns: AggBasedColumn[], + customBucketsMap: Record, + metricColumns: AggBasedColumn[] ) => { - const collapseFn = metrics.find((m) => isSiblingPipeline(m))?.aggType.split('_')[0]; - return customBucketColumns.length - ? { - [customBucketColumns[0].columnId]: collapseFn, + const collapseFnMap: Record = { + min: [], + max: [], + sum: [], + avg: [], + }; + customBucketColumns.forEach((bucket) => { + const metricColumnsIds = Object.keys(customBucketsMap).filter( + (key) => customBucketsMap[key] === bucket.columnId + ); + metricColumnsIds.forEach((metricColumnsId) => { + const metricColumn = metricColumns.find((c) => c.columnId === metricColumnsId)!; + const collapseFn = metrics + .find((m) => m.aggId === metricColumn.meta.aggId) + ?.aggType.split('_')[0]; + if (collapseFn) { + collapseFnMap[collapseFn].push(bucket.columnId); } - : {}; + }); + }); + return collapseFnMap; }; export const getBucketColumns = ( @@ -67,7 +85,7 @@ export const getBucketColumns = ( return columns; }; -export const isValidVis = (visSchemas: Schemas) => { +export const isValidVis = (visSchemas: Schemas, supportMixedSiblingPipelineAggs?: boolean) => { const { metric } = visSchemas; const siblingPipelineAggs = metric.filter((m) => isSiblingPipeline(m)); @@ -76,7 +94,10 @@ export const isValidVis = (visSchemas: Schemas) => { } // doesn't support mixed sibling pipeline aggregations - if (siblingPipelineAggs.some((agg) => agg.aggType !== siblingPipelineAggs[0].aggType)) { + if ( + siblingPipelineAggs.some((agg) => agg.aggType !== siblingPipelineAggs[0].aggType) && + !supportMixedSiblingPipelineAggs + ) { return false; } @@ -103,7 +124,8 @@ export const sortColumns = ( ...acc, ...(key === 'metric' ? metricsWithoutDuplicates : visSchemas[key])?.reduce( (newAcc, schema) => { - newAcc[schema.aggId] = schema.accessor; + // metrics should always have sort more than buckets + newAcc[schema.aggId] = key === 'metric' ? schema.accessor : 1000 + schema.accessor; return newAcc; }, {} @@ -121,3 +143,36 @@ export const sortColumns = ( }; export const getColumnIds = (columns: AggBasedColumn[]) => columns.map(({ columnId }) => columnId); + +export const getCustomBucketColumns = ( + customBucketsWithMetricIds: Array<{ + customBucket: IAggConfig; + metricIds: string[]; + }>, + metricColumns: AggBasedColumn[], + dataView: DataView, + aggs: Array>, + dropEmptyRowsInDateHistogram?: boolean +) => { + const customBucketColumns: Array = []; + const customBucketsMap: Record = {}; + customBucketsWithMetricIds.forEach((customBucketWithMetricIds) => { + const customBucketColumn = convertBucketToColumns( + { agg: customBucketWithMetricIds.customBucket, dataView, metricColumns, aggs }, + true, + dropEmptyRowsInDateHistogram + ); + customBucketColumns.push(customBucketColumn); + if (customBucketColumn) { + customBucketWithMetricIds.metricIds.forEach((metricAggId) => { + const metricColumnId = metricColumns.find( + (metricColumn) => metricColumn?.meta.aggId === metricAggId + )?.columnId; + if (metricColumnId) { + customBucketsMap[metricColumnId] = customBucketColumn.columnId; + } + }); + } + }); + return { customBucketColumns, customBucketsMap }; +}; diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index be13c856c3f02..edbd53e80d8c5 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -418,8 +418,6 @@ export class VisualBuilderPageObject extends FtrService { } public async createColorRule(nth = 0) { - await this.clickPanelOptions('metric'); - const elements = await this.testSubjects.findAll('AddAddBtn'); await elements[nth].click(); await this.visChart.waitForVisualizationRenderingStabilized(); @@ -710,16 +708,16 @@ export class VisualBuilderPageObject extends FtrService { public async setColorRuleOperator(condition: string): Promise { await this.retry.try(async () => { - await this.comboBox.clearInputField('colorRuleOperator'); - await this.comboBox.set('colorRuleOperator', condition); + await this.comboBox.clearLastInputField('colorRuleOperator'); + await this.comboBox.setForLastInput('colorRuleOperator', condition); }); } - public async setColorRuleValue(value: number): Promise { + public async setColorRuleValue(value: number, nth: number = 0): Promise { await this.retry.try(async () => { - const colorRuleValueInput = await this.find.byCssSelector( - '[data-test-subj="colorRuleValue"]' - ); + const colorRuleValueInput = ( + await this.find.allByCssSelector('[data-test-subj="colorRuleValue"]') + )[nth]; await colorRuleValueInput.type(value.toString()); }); } diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index 1700b82782580..5b22ccc27a8fa 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -147,7 +147,12 @@ export class VisualizeEditorPageObject extends FtrService { await this.testSubjects.click('visEditorAddFilterButton'); } - public async selectField(fieldValue: string, groupName = 'buckets', isChildAggregation = false) { + public async selectField( + fieldValue: string, + groupName = 'buckets', + isChildAggregation = false, + aggregationIndex = 0 + ) { this.log.debug(`selectField ${fieldValue}`); const selector = ` [data-test-subj="${groupName}AggGroup"] @@ -156,8 +161,8 @@ export class VisualizeEditorPageObject extends FtrService { ${isChildAggregation ? '.visEditorAgg__subAgg' : ''} [data-test-subj="visDefaultEditorField"] `; - const fieldEl = await this.find.byCssSelector(selector); - await this.comboBox.setElement(fieldEl, fieldValue); + const fieldEls = await this.find.allByCssSelector(selector); + await this.comboBox.setElement(fieldEls[aggregationIndex], fieldValue); } public async selectOrderByMetric(aggNth: number, metric: string) { @@ -175,16 +180,17 @@ export class VisualizeEditorPageObject extends FtrService { public async selectAggregation( aggValue: string, groupName = 'buckets', - isChildAggregation = false + isChildAggregation = false, + aggregationIndex = 0 ) { - const comboBoxElement = await this.find.byCssSelector(` + const comboBoxElements = await this.find.allByCssSelector(` [data-test-subj="${groupName}AggGroup"] [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen ${isChildAggregation ? '.visEditorAgg__subAgg' : ''} [data-test-subj="defaultEditorAggSelect"] `); - await this.comboBox.setElement(comboBoxElement, aggValue); + await this.comboBox.setElement(comboBoxElements[aggregationIndex], aggValue); await this.common.sleep(500); } diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 634c9f4bc5005..6dd17c9c13e1c 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -148,6 +148,15 @@ export class VisualizePageObject extends FtrService { await this.waitForVisualizationSelectPage(); } + public async navigateToLensFromAnotherVisulization() { + const button = await this.testSubjects.find('visualizeEditInLensButton'); + await button.click(); + } + + public async hasNavigateToLensButton() { + return await this.testSubjects.exists('visualizeEditInLensButton'); + } + public async hasVisType(type: string) { return await this.testSubjects.exists(`visType-${type}`); } diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts index 98b04f83aa47f..96d51cd24611a 100644 --- a/test/functional/services/combo_box.ts +++ b/test/functional/services/combo_box.ts @@ -36,6 +36,13 @@ export class ComboBoxService extends FtrService { await this.setElement(comboBox, value); } + public async setForLastInput(comboBoxSelector: string, value: string): Promise { + this.log.debug(`comboBox.set, comboBoxSelector: ${comboBoxSelector}`); + const comboBoxes = await this.testSubjects.findAll(comboBoxSelector); + const comboBox = comboBoxes[comboBoxes.length - 1]; + await this.setElement(comboBox, value); + } + /** * Clicks option in combobox dropdown * @@ -308,4 +315,12 @@ export class ComboBoxService extends FtrService { const input = await comboBoxElement.findByTagName('input'); await input.clearValueWithKeyboard(); } + + public async clearLastInputField(comboBoxSelector: string): Promise { + this.log.debug(`comboBox.clearInputField, comboBoxSelector:${comboBoxSelector}`); + const comboBoxElements = await this.testSubjects.findAll(comboBoxSelector); + const comboBoxElement = comboBoxElements[comboBoxElements.length - 1]; + const input = await comboBoxElement.findByTagName('input'); + await input.clearValueWithKeyboard(); + } } diff --git a/x-pack/packages/ml/aiops_utils/src/stream_factory.test.ts b/x-pack/packages/ml/aiops_utils/src/stream_factory.test.ts index 1e6d7b40b22d0..27751b7dc3fd1 100644 --- a/x-pack/packages/ml/aiops_utils/src/stream_factory.test.ts +++ b/x-pack/packages/ml/aiops_utils/src/stream_factory.test.ts @@ -29,7 +29,7 @@ describe('streamFactory', () => { let mockLogger: Logger; beforeEach(() => { - mockLogger = { error: jest.fn() } as unknown as Logger; + mockLogger = { debug: jest.fn(), error: jest.fn(), info: jest.fn() } as unknown as Logger; }); it('should encode and receive an uncompressed string based stream', async () => { diff --git a/x-pack/packages/ml/aiops_utils/src/stream_factory.ts b/x-pack/packages/ml/aiops_utils/src/stream_factory.ts index fa455a04c23f1..d6e60e76efaa9 100644 --- a/x-pack/packages/ml/aiops_utils/src/stream_factory.ts +++ b/x-pack/packages/ml/aiops_utils/src/stream_factory.ts @@ -14,12 +14,15 @@ import type { Headers, ResponseHeaders } from '@kbn/core-http-server'; import { acceptCompression } from './accept_compression'; -// We need this otherwise Kibana server will crash with a 'ERR_METHOD_NOT_IMPLEMENTED' error. -class ResponseStream extends Stream.PassThrough { - flush() {} - _read() {} +// type guard to identify compressed stream +function isCompressedSream(arg: unknown): arg is zlib.Gzip { + return typeof arg === 'object' && arg !== null && typeof (arg as zlib.Gzip).flush === 'function'; } +const FLUSH_PAYLOAD_SIZE = 4 * 1024; + +class UncompressedResponseStream extends Stream.PassThrough {} + const DELIMITER = '\n'; type StreamType = 'string' | 'ndjson'; @@ -27,9 +30,9 @@ type StreamType = 'string' | 'ndjson'; interface StreamFactoryReturnType { DELIMITER: string; end: () => void; - push: (d: T) => void; + push: (d: T, drain?: boolean) => void; responseWithHeaders: { - body: zlib.Gzip | ResponseStream; + body: zlib.Gzip | UncompressedResponseStream; headers?: ResponseHeaders; }; } @@ -39,11 +42,16 @@ interface StreamFactoryReturnType { * for gzip compression depending on provided request headers. * * @param headers - Request headers. + * @param logger - Kibana logger. + * @param compressOverride - Optional flag to override header based compression setting. + * @param flushFix - Adds an attribute with a random string payload to overcome buffer flushing with certain proxy configurations. + * * @returns An object with stream attributes and methods. */ export function streamFactory( headers: Headers, logger: Logger, + compressOverride?: boolean, flushFix?: boolean ): StreamFactoryReturnType; /** @@ -51,27 +59,72 @@ export function streamFactory( * request headers. Any non-string data pushed to the stream will be stream as NDJSON. * * @param headers - Request headers. + * @param logger - Kibana logger. + * @param compressOverride - Optional flag to override header based compression setting. + * @param flushFix - Adds an attribute with a random string payload to overcome buffer flushing with certain proxy configurations. + * * @returns An object with stream attributes and methods. */ export function streamFactory( headers: Headers, logger: Logger, + compressOverride: boolean = true, flushFix: boolean = false ): StreamFactoryReturnType { let streamType: StreamType; - const isCompressed = acceptCompression(headers); + const isCompressed = compressOverride && acceptCompression(headers); + + const stream = isCompressed ? zlib.createGzip() : new UncompressedResponseStream(); + + // If waiting for draining of the stream, items will be added to this buffer. + const backPressureBuffer: T[] = []; - const stream = isCompressed ? zlib.createGzip() : new ResponseStream(); + // Flag will be set when the "drain" listener is active so we can avoid setting multiple listeners. + let waitForDrain = false; + + // Instead of a flag this is an array where we check if we are waiting on any callback from writing to the stream. + // It needs to be an array to avoid running into race conditions. + const waitForCallbacks: number[] = []; + + // Flag to set if the stream should be ended. Because there could be items in the backpressure buffer, we might + // not want to end the stream right away. Once the backpressure buffer is cleared, we'll end the stream eventually. + let tryToEnd = false; + + function logDebugMessage(msg: string) { + logger.debug(`HTTP Response Stream: ${msg}`); + } function end() { - stream.end(); + tryToEnd = true; + + logDebugMessage(`backPressureBuffer size on end(): ${backPressureBuffer.length}`); + logDebugMessage(`waitForCallbacks size on end(): ${waitForCallbacks.length}`); + + // Before ending the stream, we need to empty the backPressureBuffer + if (backPressureBuffer.length > 0) { + const el = backPressureBuffer.shift(); + if (el !== undefined) { + push(el, true); + } + return; + } + + if (waitForCallbacks.length === 0) { + logDebugMessage('All backPressureBuffer and waitForCallbacks cleared, ending the stream.'); + stream.end(); + } } - function push(d: T) { + function push(d: T, drain = false) { + logDebugMessage( + `Push to stream. Current backPressure buffer size: ${backPressureBuffer.length}, drain flag: ${drain}` + ); + if (d === undefined) { logger.error('Stream chunk must not be undefined.'); return; } + // Initialize the stream type with the first push to the stream, // otherwise check the integrity of the data to be pushed. if (streamType === undefined) { @@ -84,26 +137,69 @@ export function streamFactory( return; } + if ((!drain && waitForDrain) || (!drain && backPressureBuffer.length > 0)) { + logDebugMessage('Adding item to backpressure buffer.'); + backPressureBuffer.push(d); + return; + } + try { const line = streamType === 'ndjson' ? `${JSON.stringify({ ...d, // This is a temporary fix for response streaming with proxy configurations that buffer responses up to 4KB in size. - ...(flushFix ? { flushPayload: crypto.randomBytes(4096).toString('hex') } : {}), + ...(flushFix + ? { flushPayload: crypto.randomBytes(FLUSH_PAYLOAD_SIZE).toString('hex') } + : {}), })}${DELIMITER}` : d; - stream.write(line); + + waitForCallbacks.push(1); + const writeOk = stream.write(line, () => { + waitForCallbacks.pop(); + // Calling .flush() on a compression stream will + // make zlib return as much output as currently possible. + if (isCompressedSream(stream)) { + stream.flush(); + } + + if (tryToEnd && waitForCallbacks.length === 0) { + end(); + } + }); + + logDebugMessage(`Ok to write to the stream again? ${writeOk}`); + + if (!writeOk) { + logDebugMessage(`Should we add the "drain" listener?: ${!waitForDrain}`); + if (!waitForDrain) { + waitForDrain = true; + stream.once('drain', () => { + logDebugMessage( + 'The "drain" listener triggered, we can continue pushing to the stream.' + ); + + waitForDrain = false; + if (backPressureBuffer.length > 0) { + const el = backPressureBuffer.shift(); + if (el !== undefined) { + push(el, true); + } + } + }); + } + } else if (writeOk && drain && backPressureBuffer.length > 0) { + logDebugMessage('Continue clearing the backpressure buffer.'); + const el = backPressureBuffer.shift(); + if (el !== undefined) { + push(el, true); + } + } } catch (e) { logger.error(`Could not serialize or stream data chunk: ${e.toString()}`); return; } - - // Calling .flush() on a compression stream will - // make zlib return as much output as currently possible. - if (isCompressed) { - stream.flush(); - } } const responseWithHeaders: StreamFactoryReturnType['responseWithHeaders'] = { diff --git a/x-pack/plugins/actions/server/feature.ts b/x-pack/plugins/actions/server/feature.ts index c176e9f261551..aa0b88dbee633 100644 --- a/x-pack/plugins/actions/server/feature.ts +++ b/x-pack/plugins/actions/server/feature.ts @@ -28,7 +28,7 @@ export const ACTIONS_FEATURE = { app: [], order: FEATURE_ORDER, management: { - insightsAndAlerting: ['triggersActions'], + insightsAndAlerting: ['triggersActions', 'triggersActionsConnectors'], }, privileges: { all: { @@ -36,7 +36,7 @@ export const ACTIONS_FEATURE = { api: [], catalogue: [], management: { - insightsAndAlerting: ['triggersActions'], + insightsAndAlerting: ['triggersActions', 'triggersActionsConnectors'], }, savedObject: { all: [ @@ -53,7 +53,7 @@ export const ACTIONS_FEATURE = { api: [], catalogue: [], management: { - insightsAndAlerting: ['triggersActions'], + insightsAndAlerting: ['triggersActions', 'triggersActionsConnectors'], }, savedObject: { // action execution requires 'read' over `actions`, but 'all' over `action_task_params` diff --git a/x-pack/plugins/actions/server/saved_objects/get_import_warnings.ts b/x-pack/plugins/actions/server/saved_objects/get_import_warnings.ts index 2c8eef94f9a4d..ce2b290ed2ab4 100644 --- a/x-pack/plugins/actions/server/saved_objects/get_import_warnings.ts +++ b/x-pack/plugins/actions/server/saved_objects/get_import_warnings.ts @@ -29,7 +29,7 @@ export function getImportWarnings( { type: 'action_required', message, - actionPath: '/app/management/insightsAndAlerting/triggersActions/connectors', + actionPath: '/app/management/insightsAndAlerting/triggersActionsConnectors', buttonLabel: GO_TO_CONNECTORS_BUTTON_LABLE, } as SavedObjectsImportWarning, ]; diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/schema.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/schema.ts index f816af06f5324..a5d48462c5170 100644 --- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/schema.ts +++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/schema.ts @@ -21,6 +21,9 @@ export const aiopsExplainLogRateSpikesSchema = schema.object({ deviationMax: schema.number(), /** The index to query for log rate spikes */ index: schema.string(), + /** Settings to override headers derived compression and flush fix */ + compressResponse: schema.maybe(schema.boolean()), + flushFix: schema.maybe(schema.boolean()), }); export type AiopsExplainLogRateSpikesSchema = TypeOf; diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx index 9949ec537b77a..74be1a0f048fc 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx @@ -95,6 +95,7 @@ export const ExplainLogRateSpikesAnalysis: FC timeFieldName: dataView.timeFieldName ?? '', index: dataView.title, grouping: true, + flushFix: true, ...windowParameters, }, { reducer: streamReducer, initialState } diff --git a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts index 949b535ca16fb..67cbcce241401 100644 --- a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts +++ b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts @@ -51,6 +51,9 @@ import { markDuplicates, } from './queries/get_simple_hierarchical_tree'; +// 10s ping frequency to keep the stream alive. +const PING_FREQUENCY = 10000; + // Overall progress is a float from 0 to 1. const LOADED_FIELD_CANDIDATES = 0.2; const PROGRESS_STEP_P_VALUES = 0.5; @@ -77,12 +80,12 @@ export const defineExplainLogRateSpikesRoute = ( let logMessageCounter = 1; - function logInfoMessage(msg: string) { - logger.info(`Explain Log Rate Spikes #${logMessageCounter}: ${msg}`); + function logDebugMessage(msg: string) { + logger.debug(`Explain Log Rate Spikes #${logMessageCounter}: ${msg}`); logMessageCounter++; } - logInfoMessage('Starting analysis.'); + logDebugMessage('Starting analysis.'); const groupingEnabled = !!request.body.grouping; @@ -90,15 +93,16 @@ export const defineExplainLogRateSpikesRoute = ( const controller = new AbortController(); + let isRunning = false; let loaded = 0; let shouldStop = false; request.events.aborted$.subscribe(() => { - logInfoMessage('aborted$ subscription trigger.'); + logDebugMessage('aborted$ subscription trigger.'); shouldStop = true; controller.abort(); }); request.events.completed$.subscribe(() => { - logInfoMessage('completed$ subscription trigger.'); + logDebugMessage('completed$ subscription trigger.'); shouldStop = true; controller.abort(); }); @@ -107,17 +111,26 @@ export const defineExplainLogRateSpikesRoute = ( end: streamEnd, push, responseWithHeaders, - } = streamFactory(request.headers, logger, true); - - function pushPing() { - push(pingAction()); + } = streamFactory( + request.headers, + logger, + request.body.compressResponse, + request.body.flushFix + ); + + function pushPingWithTimeout() { + setTimeout(() => { + if (isRunning) { + logDebugMessage('Ping message.'); + push(pingAction()); + pushPingWithTimeout(); + } + }, PING_FREQUENCY); } - const pingInterval = setInterval(pushPing, 1000); - function end() { - logInfoMessage('Ending analysis.'); - clearInterval(pingInterval); + isRunning = false; + logDebugMessage('Ending analysis.'); streamEnd(); } @@ -139,15 +152,16 @@ export const defineExplainLogRateSpikesRoute = ( } function pushError(m: string) { - logInfoMessage('Push error.'); + logDebugMessage('Push error.'); push(addErrorAction(m)); } - // Async IIFE to run the analysis while not blocking returning `responseWithHeaders`. - (async () => { - logInfoMessage('Reset.'); + async function runAnalysis() { + isRunning = true; + logDebugMessage('Reset.'); push(resetAction()); - logInfoMessage('Load field candidates.'); + pushPingWithTimeout(); + logDebugMessage('Load field candidates.'); push( updateLoadingStateAction({ ccsWarning: false, @@ -204,11 +218,11 @@ export const defineExplainLogRateSpikesRoute = ( const fieldCandidatesChunks = chunk(fieldCandidates, chunkSize); - logInfoMessage('Fetch p-values.'); + logDebugMessage('Fetch p-values.'); for (const fieldCandidatesChunk of fieldCandidatesChunks) { chunkCount++; - logInfoMessage(`Fetch p-values. Chunk ${chunkCount} of ${fieldCandidatesChunks.length}`); + logDebugMessage(`Fetch p-values. Chunk ${chunkCount} of ${fieldCandidatesChunks.length}`); let pValues: Awaited>; try { pValues = await fetchChangePointPValues( @@ -258,7 +272,7 @@ export const defineExplainLogRateSpikesRoute = ( ); if (shouldStop) { - logInfoMessage('shouldStop fetching p-values.'); + logDebugMessage('shouldStop fetching p-values.'); end(); return; @@ -266,7 +280,7 @@ export const defineExplainLogRateSpikesRoute = ( } if (changePoints?.length === 0) { - logInfoMessage('Stopping analysis, did not find change points.'); + logDebugMessage('Stopping analysis, did not find change points.'); endWithUpdatedLoadingState(); return; } @@ -275,7 +289,7 @@ export const defineExplainLogRateSpikesRoute = ( { fieldName: request.body.timeFieldName, type: KBN_FIELD_TYPES.DATE }, ]; - logInfoMessage('Fetch overall histogram.'); + logDebugMessage('Fetch overall histogram.'); let overallTimeSeries: NumericChartData | undefined; try { @@ -313,7 +327,7 @@ export const defineExplainLogRateSpikesRoute = ( } if (groupingEnabled) { - logInfoMessage('Group results.'); + logDebugMessage('Group results.'); push( updateLoadingStateAction({ @@ -498,7 +512,7 @@ export const defineExplainLogRateSpikesRoute = ( pushHistogramDataLoadingState(); - logInfoMessage('Fetch group histograms.'); + logDebugMessage('Fetch group histograms.'); await asyncForEach(changePointGroups, async (cpg) => { if (overallTimeSeries !== undefined) { @@ -577,7 +591,7 @@ export const defineExplainLogRateSpikesRoute = ( loaded += PROGRESS_STEP_HISTOGRAMS_GROUPS; - logInfoMessage('Fetch field/value histograms.'); + logDebugMessage('Fetch field/value histograms.'); // time series filtered by fields if (changePoints && overallTimeSeries !== undefined) { @@ -661,7 +675,10 @@ export const defineExplainLogRateSpikesRoute = ( } endWithUpdatedLoadingState(); - })(); + } + + // Do not call this using `await` so it will run asynchronously while we return the stream already. + runAnalysis(); return response.ok(responseWithHeaders); } diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts deleted file mode 100644 index 20ebb7b4eb8e9..0000000000000 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ /dev/null @@ -1,1082 +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 { isRuleType, ruleTypeMappings } from '@kbn/securitysolution-rules'; -import { isString } from 'lodash/fp'; -import { omit, pick } from 'lodash'; -import moment from 'moment-timezone'; -import { gte } from 'semver'; -import { - LogMeta, - SavedObjectMigrationMap, - SavedObjectUnsanitizedDoc, - SavedObjectMigrationFn, - SavedObjectMigrationContext, - SavedObjectAttributes, - SavedObjectAttribute, - SavedObjectReference, -} from '@kbn/core/server'; -import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; -import type { IsMigrationNeededPredicate } from '@kbn/encrypted-saved-objects-plugin/server'; -import { MigrateFunctionsObject, MigrateFunction } from '@kbn/kibana-utils-plugin/common'; -import { mergeSavedObjectMigrationMaps } from '@kbn/core/server'; -import { isSerializedSearchSource, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; -import { extractRefsFromGeoContainmentAlert } from './geo_containment/migrations'; -import { RawRule, RawRuleAction, RawRuleExecutionStatus } from '../types'; -import { getMappedParams } from '../rules_client/lib/mapped_params_utils'; - -const SIEM_APP_ID = 'securitySolution'; -const SIEM_SERVER_APP_ID = 'siem'; -const MINIMUM_SS_MIGRATION_VERSION = '8.3.0'; -export const LEGACY_LAST_MODIFIED_VERSION = 'pre-7.10.0'; -export const FILEBEAT_7X_INDICATOR_PATH = 'threatintel.indicator'; - -interface AlertLogMeta extends LogMeta { - migrations: { alertDocument: SavedObjectUnsanitizedDoc }; -} - -type AlertMigration = ( - doc: SavedObjectUnsanitizedDoc, - context: SavedObjectMigrationContext -) => SavedObjectUnsanitizedDoc; - -function createEsoMigration( - encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, - isMigrationNeededPredicate: IsMigrationNeededPredicate, - migrationFunc: AlertMigration -) { - return encryptedSavedObjects.createMigration({ - isMigrationNeededPredicate, - migration: migrationFunc, - shouldMigrateIfDecryptionFails: true, // shouldMigrateIfDecryptionFails flag that applies the migration to undecrypted document if decryption fails - }); -} - -const SUPPORT_INCIDENTS_ACTION_TYPES = ['.servicenow', '.jira', '.resilient']; - -export const isAnyActionSupportIncidents = (doc: SavedObjectUnsanitizedDoc): boolean => - doc.attributes.actions.some((action) => - SUPPORT_INCIDENTS_ACTION_TYPES.includes(action.actionTypeId) - ); - -// Deprecated in 8.0 -export const isSiemSignalsRuleType = (doc: SavedObjectUnsanitizedDoc): boolean => - doc.attributes.alertTypeId === 'siem.signals'; - -export const isEsQueryRuleType = (doc: SavedObjectUnsanitizedDoc) => - doc.attributes.alertTypeId === '.es-query'; - -export const isDetectionEngineAADRuleType = (doc: SavedObjectUnsanitizedDoc): boolean => - (Object.values(ruleTypeMappings) as string[]).includes(doc.attributes.alertTypeId); - -/** - * Returns true if the alert type is that of "siem.notifications" which is a legacy notification system that was deprecated in 7.16.0 - * in favor of using the newer alerting notifications system. - * @param doc The saved object alert type document - * @returns true if this is a legacy "siem.notifications" rule, otherwise false - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - */ -export const isSecuritySolutionLegacyNotification = ( - doc: SavedObjectUnsanitizedDoc -): boolean => doc.attributes.alertTypeId === 'siem.notifications'; - -export function getMigrations( - encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, - searchSourceMigrations: MigrateFunctionsObject, - isPreconfigured: (connectorId: string) => boolean -): SavedObjectMigrationMap { - const migrationWhenRBACWasIntroduced = createEsoMigration( - encryptedSavedObjects, - // migrate all documents in 7.10 in order to add the "meta" RBAC field - (doc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations( - markAsLegacyAndChangeConsumer, - setAlertIdAsDefaultDedupkeyOnPagerDutyActions, - initializeExecutionStatus - ) - ); - - const migrationAlertUpdatedAtAndNotifyWhen = createEsoMigration( - encryptedSavedObjects, - // migrate all documents in 7.11 in order to add the "updatedAt" and "notifyWhen" fields - (doc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(setAlertUpdatedAtDate, setNotifyWhen) - ); - - const migrationActions7112 = createEsoMigration( - encryptedSavedObjects, - (doc): doc is SavedObjectUnsanitizedDoc => isAnyActionSupportIncidents(doc), - pipeMigrations(restructureConnectorsThatSupportIncident) - ); - - const migrationSecurityRules713 = createEsoMigration( - encryptedSavedObjects, - (doc): doc is SavedObjectUnsanitizedDoc => isSiemSignalsRuleType(doc), - pipeMigrations(removeNullsFromSecurityRules) - ); - - const migrationSecurityRules714 = createEsoMigration( - encryptedSavedObjects, - (doc): doc is SavedObjectUnsanitizedDoc => isSiemSignalsRuleType(doc), - pipeMigrations(removeNullAuthorFromSecurityRules) - ); - - const migrationSecurityRules715 = createEsoMigration( - encryptedSavedObjects, - (doc): doc is SavedObjectUnsanitizedDoc => isSiemSignalsRuleType(doc), - pipeMigrations(addExceptionListsToReferences) - ); - - const migrateRules716 = createEsoMigration( - encryptedSavedObjects, - (doc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations( - setLegacyId, - getRemovePreconfiguredConnectorsFromReferencesFn(isPreconfigured), - addRuleIdsToLegacyNotificationReferences, - extractRefsFromGeoContainmentAlert - ) - ); - - const migrationRules800 = createEsoMigration( - encryptedSavedObjects, - (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations( - addThreatIndicatorPathToThreatMatchRules, - addSecuritySolutionAADRuleTypes, - fixInventoryThresholdGroupId - ) - ); - - const migrationRules801 = createEsoMigration( - encryptedSavedObjects, - (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(addSecuritySolutionAADRuleTypeTags) - ); - - const migrationRules820 = createEsoMigration( - encryptedSavedObjects, - (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(addMappedParams) - ); - - const migrationRules830 = createEsoMigration( - encryptedSavedObjects, - (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(addSearchType, removeInternalTags, convertSnoozes) - ); - - const migrationRules841 = createEsoMigration( - encryptedSavedObjects, - (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(removeIsSnoozedUntil) - ); - - const migrationRules850 = createEsoMigration( - encryptedSavedObjects, - (doc): doc is SavedObjectUnsanitizedDoc => isEsQueryRuleType(doc), - pipeMigrations(stripOutRuntimeFieldsInOldESQuery) - ); - - return mergeSavedObjectMigrationMaps( - { - '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), - '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtAndNotifyWhen, '7.11.0'), - '7.11.2': executeMigrationWithErrorHandling(migrationActions7112, '7.11.2'), - '7.13.0': executeMigrationWithErrorHandling(migrationSecurityRules713, '7.13.0'), - '7.14.1': executeMigrationWithErrorHandling(migrationSecurityRules714, '7.14.1'), - '7.15.0': executeMigrationWithErrorHandling(migrationSecurityRules715, '7.15.0'), - '7.16.0': executeMigrationWithErrorHandling(migrateRules716, '7.16.0'), - '8.0.0': executeMigrationWithErrorHandling(migrationRules800, '8.0.0'), - '8.0.1': executeMigrationWithErrorHandling(migrationRules801, '8.0.1'), - '8.2.0': executeMigrationWithErrorHandling(migrationRules820, '8.2.0'), - '8.3.0': executeMigrationWithErrorHandling(migrationRules830, '8.3.0'), - '8.4.1': executeMigrationWithErrorHandling(migrationRules841, '8.4.1'), - '8.5.0': executeMigrationWithErrorHandling(migrationRules850, '8.5.0'), - }, - getSearchSourceMigrations(encryptedSavedObjects, searchSourceMigrations) - ); -} - -function executeMigrationWithErrorHandling( - migrationFunc: SavedObjectMigrationFn, - version: string -) { - return (doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) => { - try { - return migrationFunc(doc, context); - } catch (ex) { - context.log.error( - `encryptedSavedObject ${version} migration failed for alert ${doc.id} with error: ${ex.message}`, - { - migrations: { - alertDocument: doc, - }, - } - ); - throw ex; - } - }; -} - -const setAlertUpdatedAtDate = ( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc => { - const updatedAt = doc.updated_at || doc.attributes.createdAt; - return { - ...doc, - attributes: { - ...doc.attributes, - updatedAt, - }, - }; -}; - -const setNotifyWhen = ( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc => { - const notifyWhen = doc.attributes.throttle ? 'onThrottleInterval' : 'onActiveAlert'; - return { - ...doc, - attributes: { - ...doc.attributes, - notifyWhen, - }, - }; -}; - -const consumersToChange: Map = new Map( - Object.entries({ - alerting: 'alerts', - metrics: 'infrastructure', - [SIEM_APP_ID]: SIEM_SERVER_APP_ID, - }) -); - -function markAsLegacyAndChangeConsumer( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { - attributes: { consumer }, - } = doc; - return { - ...doc, - attributes: { - ...doc.attributes, - consumer: consumersToChange.get(consumer) ?? consumer, - // mark any alert predating 7.10 as a legacy alert - meta: { - versionApiKeyLastmodified: LEGACY_LAST_MODIFIED_VERSION, - }, - }, - }; -} - -function setAlertIdAsDefaultDedupkeyOnPagerDutyActions( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { attributes } = doc; - return { - ...doc, - attributes: { - ...attributes, - ...(attributes.actions - ? { - actions: attributes.actions.map((action) => { - if (action.actionTypeId !== '.pagerduty' || action.params.eventAction === 'trigger') { - return action; - } - return { - ...action, - params: { - ...action.params, - dedupKey: action.params.dedupKey ?? '{{alertId}}', - }, - }; - }), - } - : {}), - }, - }; -} - -function initializeExecutionStatus( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { attributes } = doc; - return { - ...doc, - attributes: { - ...attributes, - executionStatus: { - status: 'pending', - lastExecutionDate: new Date().toISOString(), - error: null, - } as RawRuleExecutionStatus, - }, - }; -} - -function isEmptyObject(obj: {}) { - for (const attr in obj) { - if (Object.prototype.hasOwnProperty.call(obj, attr)) { - return false; - } - } - return true; -} - -function restructureConnectorsThatSupportIncident( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { actions } = doc.attributes; - const newActions = actions.reduce((acc, action) => { - if ( - ['.servicenow', '.jira', '.resilient'].includes(action.actionTypeId) && - action.params.subAction === 'pushToService' - ) { - // Future developer, we needed to do that because when we created this migration - // we forget to think about user already using 7.11.0 and having an incident attribute build the right way - // IMPORTANT -> if you change this code please do the same inside of this file - // x-pack/plugins/alerting/server/saved_objects/migrations.ts - const subActionParamsIncident = - (action.params?.subActionParams as SavedObjectAttributes)?.incident ?? null; - if (subActionParamsIncident != null && !isEmptyObject(subActionParamsIncident)) { - return [...acc, action]; - } - if (action.actionTypeId === '.servicenow') { - const { - title, - comments, - comment, - description, - severity, - urgency, - impact, - short_description: shortDescription, - } = action.params.subActionParams as { - title: string; - description?: string; - severity?: string; - urgency?: string; - impact?: string; - comment?: string; - comments?: Array<{ commentId: string; comment: string }>; - short_description?: string; - }; - return [ - ...acc, - { - ...action, - params: { - subAction: 'pushToService', - subActionParams: { - incident: { - short_description: shortDescription ?? title, - description, - severity, - urgency, - impact, - }, - comments: [ - ...(comments ?? []), - ...(comment != null ? [{ commentId: '1', comment }] : []), - ], - }, - }, - }, - ] as RawRuleAction[]; - } else if (action.actionTypeId === '.jira') { - const { title, comments, description, issueType, priority, labels, parent, summary } = - action.params.subActionParams as { - title: string; - description: string; - issueType: string; - priority?: string; - labels?: string[]; - parent?: string; - comments?: unknown[]; - summary?: string; - }; - return [ - ...acc, - { - ...action, - params: { - subAction: 'pushToService', - subActionParams: { - incident: { - summary: summary ?? title, - description, - issueType, - priority, - labels, - parent, - }, - comments, - }, - }, - }, - ] as RawRuleAction[]; - } else if (action.actionTypeId === '.resilient') { - const { title, comments, description, incidentTypes, severityCode, name } = action.params - .subActionParams as { - title: string; - description: string; - incidentTypes?: number[]; - severityCode?: number; - comments?: unknown[]; - name?: string; - }; - return [ - ...acc, - { - ...action, - params: { - subAction: 'pushToService', - subActionParams: { - incident: { - name: name ?? title, - description, - incidentTypes, - severityCode, - }, - comments, - }, - }, - }, - ] as RawRuleAction[]; - } - } - - return [...acc, action]; - }, [] as RawRuleAction[]); - - return { - ...doc, - attributes: { - ...doc.attributes, - actions: newActions, - }, - }; -} - -function convertNullToUndefined(attribute: SavedObjectAttribute) { - return attribute != null ? attribute : undefined; -} - -function removeNullsFromSecurityRules( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { - attributes: { params }, - } = doc; - return { - ...doc, - attributes: { - ...doc.attributes, - params: { - ...params, - buildingBlockType: convertNullToUndefined(params.buildingBlockType), - note: convertNullToUndefined(params.note), - index: convertNullToUndefined(params.index), - language: convertNullToUndefined(params.language), - license: convertNullToUndefined(params.license), - outputIndex: convertNullToUndefined(params.outputIndex), - savedId: convertNullToUndefined(params.savedId), - timelineId: convertNullToUndefined(params.timelineId), - timelineTitle: convertNullToUndefined(params.timelineTitle), - meta: convertNullToUndefined(params.meta), - query: convertNullToUndefined(params.query), - filters: convertNullToUndefined(params.filters), - riskScoreMapping: params.riskScoreMapping != null ? params.riskScoreMapping : [], - ruleNameOverride: convertNullToUndefined(params.ruleNameOverride), - severityMapping: params.severityMapping != null ? params.severityMapping : [], - threat: params.threat != null ? params.threat : [], - threshold: - params.threshold != null && - typeof params.threshold === 'object' && - !Array.isArray(params.threshold) - ? { - field: Array.isArray(params.threshold.field) - ? params.threshold.field - : params.threshold.field === '' || params.threshold.field == null - ? [] - : [params.threshold.field], - value: params.threshold.value, - cardinality: - params.threshold.cardinality != null ? params.threshold.cardinality : [], - } - : undefined, - timestampOverride: convertNullToUndefined(params.timestampOverride), - exceptionsList: - params.exceptionsList != null - ? params.exceptionsList - : params.exceptions_list != null - ? params.exceptions_list - : params.lists != null - ? params.lists - : [], - threatFilters: convertNullToUndefined(params.threatFilters), - machineLearningJobId: - params.machineLearningJobId == null - ? undefined - : Array.isArray(params.machineLearningJobId) - ? params.machineLearningJobId - : [params.machineLearningJobId], - }, - }, - }; -} - -/** - * The author field was introduced later and was not part of the original rules. We overlooked - * the filling in the author field as an empty array in an earlier upgrade routine from - * 'removeNullsFromSecurityRules' during the 7.13.0 upgrade. Since we don't change earlier migrations, - * but rather only move forward with the "arrow of time" we are going to upgrade and fix - * it if it is missing for anyone in 7.14.0 and above release. Earlier releases if we want to fix them, - * would have to be modified as a "7.13.1", etc... if we want to fix it there. - * @param doc The document that is not migrated and contains a "null" or "undefined" author field - * @returns The document with the author field fleshed in. - */ -function removeNullAuthorFromSecurityRules( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { - attributes: { params }, - } = doc; - return { - ...doc, - attributes: { - ...doc.attributes, - params: { - ...params, - author: params.author != null ? params.author : [], - }, - }, - }; -} - -/** - * This migrates exception list containers to saved object references on an upgrade. - * We only migrate if we find these conditions: - * - exceptionLists are an array and not null, undefined, or malformed data. - * - The exceptionList item is an object and id is a string and not null, undefined, or malformed data - * - The existing references do not already have an exceptionItem reference already found within it. - * Some of these issues could crop up during either user manual errors of modifying things, earlier migration - * issues, etc... - * @param doc The document that might have exceptionListItems to migrate - * @returns The document migrated with saved object references - */ -function addExceptionListsToReferences( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { - attributes: { - params: { exceptionsList }, - }, - references, - } = doc; - if (!Array.isArray(exceptionsList)) { - // early return if we are not an array such as being undefined or null or malformed. - return doc; - } else { - const exceptionsToTransform = removeMalformedExceptionsList(exceptionsList); - const newReferences = exceptionsToTransform.flatMap( - (exceptionItem, index) => { - const existingReferenceFound = references?.find((reference) => { - return ( - reference.id === exceptionItem.id && - ((reference.type === 'exception-list' && exceptionItem.namespace_type === 'single') || - (reference.type === 'exception-list-agnostic' && - exceptionItem.namespace_type === 'agnostic')) - ); - }); - if (existingReferenceFound) { - // skip if the reference already exists for some uncommon reason so we do not add an additional one. - // This enables us to be idempotent and you can run this migration multiple times and get the same output. - return []; - } else { - return [ - { - name: `param:exceptionsList_${index}`, - id: String(exceptionItem.id), - type: - exceptionItem.namespace_type === 'agnostic' - ? 'exception-list-agnostic' - : 'exception-list', - }, - ]; - } - } - ); - if (references == null && newReferences.length === 0) { - // Avoid adding an empty references array if the existing saved object never had one to begin with - return doc; - } else { - return { ...doc, references: [...(references ?? []), ...newReferences] }; - } - } -} - -/** - * This will do a flatMap reduce where we only return exceptionsLists and their items if: - * - exceptionLists are an array and not null, undefined, or malformed data. - * - The exceptionList item is an object and id is a string and not null, undefined, or malformed data - * - * Some of these issues could crop up during either user manual errors of modifying things, earlier migration - * issues, etc... - * @param exceptionsList The list of exceptions - * @returns The exception lists if they are a valid enough shape - */ -function removeMalformedExceptionsList( - exceptionsList: SavedObjectAttribute -): SavedObjectAttributes[] { - if (!Array.isArray(exceptionsList)) { - // early return if we are not an array such as being undefined or null or malformed. - return []; - } else { - return exceptionsList.flatMap((exceptionItem) => { - if (!(exceptionItem instanceof Object) || !isString(exceptionItem.id)) { - // return early if we are not an object such as being undefined or null or malformed - // or the exceptionItem.id is not a string from being malformed - return []; - } else { - return [exceptionItem]; - } - }); - } -} - -/** - * This migrates rule_id's within the legacy siem.notification to saved object references on an upgrade. - * We only migrate if we find these conditions: - * - ruleAlertId is a string and not null, undefined, or malformed data. - * - The existing references do not already have a ruleAlertId found within it. - * Some of these issues could crop up during either user manual errors of modifying things, earlier migration - * issues, etc... so we are safer to check them as possibilities - * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function - * @param doc The document that might have "ruleAlertId" to migrate into the references - * @returns The document migrated with saved object references - */ -function addRuleIdsToLegacyNotificationReferences( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { - attributes: { - params: { ruleAlertId }, - }, - references, - } = doc; - if (!isSecuritySolutionLegacyNotification(doc) || !isString(ruleAlertId)) { - // early return if we are not a string or if we are not a security solution notification saved object. - return doc; - } else { - const existingReferences = references ?? []; - const existingReferenceFound = existingReferences.find((reference) => { - return reference.id === ruleAlertId && reference.type === 'alert'; - }); - if (existingReferenceFound) { - // skip this if the references already exists for some uncommon reason so we do not add an additional one. - return doc; - } else { - const savedObjectReference: SavedObjectReference = { - id: ruleAlertId, - name: 'param:alert_0', - type: 'alert', - }; - const newReferences = [...existingReferences, savedObjectReference]; - return { ...doc, references: newReferences }; - } - } -} - -function setLegacyId(doc: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc { - const { id } = doc; - return { - ...doc, - attributes: { - ...doc.attributes, - legacyId: id, - }, - }; -} - -function addSecuritySolutionAADRuleTypes( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const ruleType = doc.attributes.params.type; - return isSiemSignalsRuleType(doc) && isRuleType(ruleType) - ? { - ...doc, - attributes: { - ...doc.attributes, - alertTypeId: ruleTypeMappings[ruleType], - enabled: false, - params: { - ...doc.attributes.params, - outputIndex: '', - }, - }, - } - : doc; -} - -function addSearchType(doc: SavedObjectUnsanitizedDoc) { - const searchType = doc.attributes.params.searchType; - - return isEsQueryRuleType(doc) && !searchType - ? { - ...doc, - attributes: { - ...doc.attributes, - params: { - ...doc.attributes.params, - searchType: 'esQuery', - }, - }, - } - : doc; -} - -function addSecuritySolutionAADRuleTypeTags( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const ruleType = doc.attributes.params.type; - return isDetectionEngineAADRuleType(doc) && isRuleType(ruleType) - ? { - ...doc, - attributes: { - ...doc.attributes, - // If the rule is disabled at this point, then the rule has not been re-enabled after - // running the 8.0.0 migrations. If `doc.attributes.scheduledTaskId` exists, then the - // rule was enabled prior to running the migration. Thus we know we should add the - // tag to indicate it was auto-disabled. - tags: - !doc.attributes.enabled && doc.attributes.scheduledTaskId - ? [...(doc.attributes.tags ?? []), 'auto_disabled_8.0'] - : doc.attributes.tags ?? [], - }, - } - : doc; -} - -function stripOutRuntimeFieldsInOldESQuery( - doc: SavedObjectUnsanitizedDoc, - context: SavedObjectMigrationContext -): SavedObjectUnsanitizedDoc { - const isESDSLrule = - isEsQueryRuleType(doc) && !isSerializedSearchSource(doc.attributes.params.searchConfiguration); - - if (isESDSLrule) { - try { - const parsedQuery = JSON.parse(doc.attributes.params.esQuery as string); - // parsing and restringifying will cause us to lose the formatting so we only do so if this rule has - // fields other than `query` which is the only valid field at this stage - const hasFieldsOtherThanQuery = Object.keys(parsedQuery).some((key) => key !== 'query'); - return hasFieldsOtherThanQuery - ? { - ...doc, - attributes: { - ...doc.attributes, - params: { - ...doc.attributes.params, - esQuery: JSON.stringify(pick(parsedQuery, 'query'), null, 4), - }, - }, - } - : doc; - } catch (err) { - // Instead of failing the upgrade when an unparsable rule is encountered, we log that the rule caouldn't be migrated and - // as a result legacy parameters might cause the rule to behave differently if it is, in fact, still running at all - context.log.error( - `unable to migrate and remove legacy runtime fields in rule ${doc.id} due to invalid query: "${doc.attributes.params.esQuery}" - query must be JSON`, - { - migrations: { - alertDocument: doc, - }, - } - ); - } - } - return doc; -} - -function addThreatIndicatorPathToThreatMatchRules( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - return isSiemSignalsRuleType(doc) && - doc.attributes.params?.type === 'threat_match' && - !doc.attributes.params.threatIndicatorPath - ? { - ...doc, - attributes: { - ...doc.attributes, - params: { - ...doc.attributes.params, - threatIndicatorPath: FILEBEAT_7X_INDICATOR_PATH, - }, - }, - } - : doc; -} - -function getRemovePreconfiguredConnectorsFromReferencesFn( - isPreconfigured: (connectorId: string) => boolean -) { - return (doc: SavedObjectUnsanitizedDoc) => { - return removePreconfiguredConnectorsFromReferences(doc, isPreconfigured); - }; -} - -function removePreconfiguredConnectorsFromReferences( - doc: SavedObjectUnsanitizedDoc, - isPreconfigured: (connectorId: string) => boolean -): SavedObjectUnsanitizedDoc { - const { - attributes: { actions }, - references, - } = doc; - - // Look for connector references - const connectorReferences = (references ?? []).filter((ref: SavedObjectReference) => - ref.name.startsWith('action_') - ); - if (connectorReferences.length > 0) { - const restReferences = (references ?? []).filter( - (ref: SavedObjectReference) => !ref.name.startsWith('action_') - ); - - const updatedConnectorReferences: SavedObjectReference[] = []; - const updatedActions: RawRule['actions'] = []; - - // For each connector reference, check if connector is preconfigured - // If yes, we need to remove from the references array and update - // the corresponding action so it directly references the preconfigured connector id - connectorReferences.forEach((connectorRef: SavedObjectReference) => { - // Look for the corresponding entry in the actions array - const correspondingAction = getCorrespondingAction(actions, connectorRef.name); - if (correspondingAction) { - if (isPreconfigured(connectorRef.id)) { - updatedActions.push({ - ...correspondingAction, - actionRef: `preconfigured:${connectorRef.id}`, - }); - } else { - updatedActions.push(correspondingAction); - updatedConnectorReferences.push(connectorRef); - } - } else { - // Couldn't find the matching action, leave as is - updatedConnectorReferences.push(connectorRef); - } - }); - - return { - ...doc, - attributes: { - ...doc.attributes, - actions: [...updatedActions], - }, - references: [...updatedConnectorReferences, ...restReferences], - }; - } - return doc; -} - -// This fixes an issue whereby metrics.alert.inventory.threshold rules had the -// group for actions incorrectly spelt as metrics.invenotry_threshold.fired vs metrics.inventory_threshold.fired -function fixInventoryThresholdGroupId( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - if (doc.attributes.alertTypeId === 'metrics.alert.inventory.threshold') { - const { - attributes: { actions }, - } = doc; - - const updatedActions = actions - ? actions.map((action) => { - // Wrong spelling - if (action.group === 'metrics.invenotry_threshold.fired') { - return { - ...action, - group: 'metrics.inventory_threshold.fired', - }; - } else { - return action; - } - }) - : []; - - return { - ...doc, - attributes: { - ...doc.attributes, - actions: updatedActions, - }, - }; - } else { - return doc; - } -} - -function addMappedParams( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { - attributes: { params }, - } = doc; - - const mappedParams = getMappedParams(params); - - if (Object.keys(mappedParams).length) { - return { - ...doc, - attributes: { - ...doc.attributes, - mapped_params: mappedParams, - }, - }; - } - - return doc; -} - -function convertSnoozes( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - const { - attributes: { snoozeEndTime }, - } = doc; - - return { - ...doc, - attributes: { - ...(omit(doc.attributes, ['snoozeEndTime']) as RawRule), - snoozeSchedule: snoozeEndTime - ? [ - { - duration: Date.parse(snoozeEndTime as string) - Date.now(), - rRule: { - dtstart: new Date().toISOString(), - tzid: moment.tz.guess(), - count: 1, - }, - }, - ] - : [], - }, - }; -} - -function getCorrespondingAction( - actions: SavedObjectAttribute, - connectorRef: string -): RawRuleAction | null { - if (!Array.isArray(actions)) { - return null; - } else { - return actions.find( - (action) => (action as RawRuleAction)?.actionRef === connectorRef - ) as RawRuleAction; - } -} -/** - * removes internal tags(starts with '__internal') from Security Solution rules - * @param doc rule to be migrated - * @returns migrated rule if it's Security Solution rule or unchanged if not - */ -function removeInternalTags( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - if (!isDetectionEngineAADRuleType(doc)) { - return doc; - } - - const { - attributes: { tags }, - } = doc; - - const filteredTags = (tags ?? []).filter((tag) => !tag.startsWith('__internal_')); - - return { - ...doc, - attributes: { - ...doc.attributes, - tags: filteredTags, - }, - }; -} - -function pipeMigrations(...migrations: AlertMigration[]): AlertMigration { - return (doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) => - migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc, context), doc); -} - -function mapSearchSourceMigrationFunc( - migrateSerializedSearchSourceFields: MigrateFunction -): MigrateFunction { - return (doc) => { - const _doc = doc as { attributes: RawRule }; - - const serializedSearchSource = _doc.attributes.params.searchConfiguration; - - if (isSerializedSearchSource(serializedSearchSource)) { - return { - ..._doc, - attributes: { - ..._doc.attributes, - params: { - ..._doc.attributes.params, - searchConfiguration: migrateSerializedSearchSourceFields(serializedSearchSource), - }, - }, - }; - } - return _doc; - }; -} - -/** - * This creates a migration map that applies search source migrations to legacy es query rules. - * It doesn't modify existing migrations. The following migrations will occur at minimum version of 8.3+. - */ -function getSearchSourceMigrations( - encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, - searchSourceMigrations: MigrateFunctionsObject -) { - const filteredMigrations: SavedObjectMigrationMap = {}; - for (const versionKey in searchSourceMigrations) { - if (gte(versionKey, MINIMUM_SS_MIGRATION_VERSION)) { - const migrateSearchSource = mapSearchSourceMigrationFunc( - searchSourceMigrations[versionKey] - ) as unknown as AlertMigration; - - filteredMigrations[versionKey] = executeMigrationWithErrorHandling( - createEsoMigration( - encryptedSavedObjects, - (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => - isEsQueryRuleType(doc), - pipeMigrations(migrateSearchSource) - ), - versionKey - ); - } - } - return filteredMigrations; -} - -function removeIsSnoozedUntil( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc { - return { - ...doc, - attributes: { - ...(omit(doc.attributes, ['isSnoozedUntil']) as RawRule), - }, - }; -} diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/7.10/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/7.10/index.ts new file mode 100644 index 0000000000000..b432ff01618df --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/7.10/index.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { RawRule, RawRuleExecutionStatus } from '../../../types'; +import { LEGACY_LAST_MODIFIED_VERSION, SIEM_APP_ID, SIEM_SERVER_APP_ID } from '../constants'; +import { createEsoMigration, pipeMigrations } from '../utils'; + +const consumersToChange: Map = new Map( + Object.entries({ + alerting: 'alerts', + metrics: 'infrastructure', + [SIEM_APP_ID]: SIEM_SERVER_APP_ID, + }) +); + +function markAsLegacyAndChangeConsumer( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { consumer }, + } = doc; + return { + ...doc, + attributes: { + ...doc.attributes, + consumer: consumersToChange.get(consumer) ?? consumer, + // mark any alert predating 7.10 as a legacy alert + meta: { + versionApiKeyLastmodified: LEGACY_LAST_MODIFIED_VERSION, + }, + }, + }; +} + +function setAlertIdAsDefaultDedupkeyOnPagerDutyActions( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { attributes } = doc; + return { + ...doc, + attributes: { + ...attributes, + ...(attributes.actions + ? { + actions: attributes.actions.map((action) => { + if (action.actionTypeId !== '.pagerduty' || action.params.eventAction === 'trigger') { + return action; + } + return { + ...action, + params: { + ...action.params, + dedupKey: action.params.dedupKey ?? '{{alertId}}', + }, + }; + }), + } + : {}), + }, + }; +} + +function initializeExecutionStatus( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { attributes } = doc; + return { + ...doc, + attributes: { + ...attributes, + executionStatus: { + status: 'pending', + lastExecutionDate: new Date().toISOString(), + error: null, + } as RawRuleExecutionStatus, + }, + }; +} + +export const getMigrations7100 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + // migrate all documents in 7.10 in order to add the "meta" RBAC field + (doc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations( + markAsLegacyAndChangeConsumer, + setAlertIdAsDefaultDedupkeyOnPagerDutyActions, + initializeExecutionStatus + ) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts new file mode 100644 index 0000000000000..186b20679c00d --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts @@ -0,0 +1,200 @@ +/* + * 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 { SavedObjectAttributes } from '@kbn/core-saved-objects-common'; +import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { RawRule, RawRuleAction } from '../../../types'; +import { createEsoMigration, pipeMigrations } from '../utils'; + +const SUPPORT_INCIDENTS_ACTION_TYPES = ['.servicenow', '.jira', '.resilient']; +export const isAnyActionSupportIncidents = (doc: SavedObjectUnsanitizedDoc): boolean => + doc.attributes.actions.some((action) => + SUPPORT_INCIDENTS_ACTION_TYPES.includes(action.actionTypeId) + ); + +function isEmptyObject(obj: {}) { + for (const attr in obj) { + if (Object.prototype.hasOwnProperty.call(obj, attr)) { + return false; + } + } + return true; +} + +function setAlertUpdatedAtDate( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const updatedAt = doc.updated_at || doc.attributes.createdAt; + return { + ...doc, + attributes: { + ...doc.attributes, + updatedAt, + }, + }; +} + +function setNotifyWhen( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const notifyWhen = doc.attributes.throttle ? 'onThrottleInterval' : 'onActiveAlert'; + return { + ...doc, + attributes: { + ...doc.attributes, + notifyWhen, + }, + }; +} + +function restructureConnectorsThatSupportIncident( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { actions } = doc.attributes; + const newActions = actions.reduce((acc, action) => { + if ( + ['.servicenow', '.jira', '.resilient'].includes(action.actionTypeId) && + action.params.subAction === 'pushToService' + ) { + // Future developer, we needed to do that because when we created this migration + // we forget to think about user already using 7.11.0 and having an incident attribute build the right way + const subActionParamsIncident = + (action.params?.subActionParams as SavedObjectAttributes)?.incident ?? null; + if (subActionParamsIncident != null && !isEmptyObject(subActionParamsIncident)) { + return [...acc, action]; + } + if (action.actionTypeId === '.servicenow') { + const { + title, + comments, + comment, + description, + severity, + urgency, + impact, + short_description: shortDescription, + } = action.params.subActionParams as { + title: string; + description?: string; + severity?: string; + urgency?: string; + impact?: string; + comment?: string; + comments?: Array<{ commentId: string; comment: string }>; + short_description?: string; + }; + return [ + ...acc, + { + ...action, + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + short_description: shortDescription ?? title, + description, + severity, + urgency, + impact, + }, + comments: [ + ...(comments ?? []), + ...(comment != null ? [{ commentId: '1', comment }] : []), + ], + }, + }, + }, + ] as RawRuleAction[]; + } else if (action.actionTypeId === '.jira') { + const { title, comments, description, issueType, priority, labels, parent, summary } = + action.params.subActionParams as { + title: string; + description: string; + issueType: string; + priority?: string; + labels?: string[]; + parent?: string; + comments?: unknown[]; + summary?: string; + }; + return [ + ...acc, + { + ...action, + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + summary: summary ?? title, + description, + issueType, + priority, + labels, + parent, + }, + comments, + }, + }, + }, + ] as RawRuleAction[]; + } else if (action.actionTypeId === '.resilient') { + const { title, comments, description, incidentTypes, severityCode, name } = action.params + .subActionParams as { + title: string; + description: string; + incidentTypes?: number[]; + severityCode?: number; + comments?: unknown[]; + name?: string; + }; + return [ + ...acc, + { + ...action, + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + name: name ?? title, + description, + incidentTypes, + severityCode, + }, + comments, + }, + }, + }, + ] as RawRuleAction[]; + } + } + + return [...acc, action]; + }, [] as RawRuleAction[]); + + return { + ...doc, + attributes: { + ...doc.attributes, + actions: newActions, + }, + }; +} + +export const getMigrations7110 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(setAlertUpdatedAtDate, setNotifyWhen) + ); + +export const getMigrations7112 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc): doc is SavedObjectUnsanitizedDoc => isAnyActionSupportIncidents(doc), + pipeMigrations(restructureConnectorsThatSupportIncident) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/7.13/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/7.13/index.ts new file mode 100644 index 0000000000000..67b595531dfdb --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/7.13/index.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectAttribute } from '@kbn/core-saved-objects-common'; +import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { RawRule } from '../../../types'; +import { createEsoMigration, isSiemSignalsRuleType, pipeMigrations } from '../utils'; + +function convertNullToUndefined(attribute: SavedObjectAttribute) { + return attribute != null ? attribute : undefined; +} + +function removeNullsFromSecurityRules( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { params }, + } = doc; + return { + ...doc, + attributes: { + ...doc.attributes, + params: { + ...params, + buildingBlockType: convertNullToUndefined(params.buildingBlockType), + note: convertNullToUndefined(params.note), + index: convertNullToUndefined(params.index), + language: convertNullToUndefined(params.language), + license: convertNullToUndefined(params.license), + outputIndex: convertNullToUndefined(params.outputIndex), + savedId: convertNullToUndefined(params.savedId), + timelineId: convertNullToUndefined(params.timelineId), + timelineTitle: convertNullToUndefined(params.timelineTitle), + meta: convertNullToUndefined(params.meta), + query: convertNullToUndefined(params.query), + filters: convertNullToUndefined(params.filters), + riskScoreMapping: params.riskScoreMapping != null ? params.riskScoreMapping : [], + ruleNameOverride: convertNullToUndefined(params.ruleNameOverride), + severityMapping: params.severityMapping != null ? params.severityMapping : [], + threat: params.threat != null ? params.threat : [], + threshold: + params.threshold != null && + typeof params.threshold === 'object' && + !Array.isArray(params.threshold) + ? { + field: Array.isArray(params.threshold.field) + ? params.threshold.field + : params.threshold.field === '' || params.threshold.field == null + ? [] + : [params.threshold.field], + value: params.threshold.value, + cardinality: + params.threshold.cardinality != null ? params.threshold.cardinality : [], + } + : undefined, + timestampOverride: convertNullToUndefined(params.timestampOverride), + exceptionsList: + params.exceptionsList != null + ? params.exceptionsList + : params.exceptions_list != null + ? params.exceptions_list + : params.lists != null + ? params.lists + : [], + threatFilters: convertNullToUndefined(params.threatFilters), + machineLearningJobId: + params.machineLearningJobId == null + ? undefined + : Array.isArray(params.machineLearningJobId) + ? params.machineLearningJobId + : [params.machineLearningJobId], + }, + }, + }; +} + +export const getMigrations7130 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc): doc is SavedObjectUnsanitizedDoc => isSiemSignalsRuleType(doc), + pipeMigrations(removeNullsFromSecurityRules) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/7.14/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/7.14/index.ts new file mode 100644 index 0000000000000..8a4aa555127ce --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/7.14/index.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { RawRule } from '../../../types'; +import { createEsoMigration, isSiemSignalsRuleType, pipeMigrations } from '../utils'; + +/** + * The author field was introduced later and was not part of the original rules. We overlooked + * the filling in the author field as an empty array in an earlier upgrade routine from + * 'removeNullsFromSecurityRules' during the 7.13.0 upgrade. Since we don't change earlier migrations, + * but rather only move forward with the "arrow of time" we are going to upgrade and fix + * it if it is missing for anyone in 7.14.0 and above release. Earlier releases if we want to fix them, + * would have to be modified as a "7.13.1", etc... if we want to fix it there. + * @param doc The document that is not migrated and contains a "null" or "undefined" author field + * @returns The document with the author field fleshed in. + */ +function removeNullAuthorFromSecurityRules( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { params }, + } = doc; + return { + ...doc, + attributes: { + ...doc.attributes, + params: { + ...params, + author: params.author != null ? params.author : [], + }, + }, + }; +} + +export const getMigrations7140 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc): doc is SavedObjectUnsanitizedDoc => isSiemSignalsRuleType(doc), + pipeMigrations(removeNullAuthorFromSecurityRules) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/7.15/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/7.15/index.ts new file mode 100644 index 0000000000000..a7b90ab347a71 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/7.15/index.ts @@ -0,0 +1,115 @@ +/* + * 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 { + SavedObjectAttribute, + SavedObjectAttributes, + SavedObjectReference, +} from '@kbn/core-saved-objects-common'; +import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { isString } from 'lodash/fp'; +import { RawRule } from '../../../types'; +import { createEsoMigration, isSiemSignalsRuleType, pipeMigrations } from '../utils'; + +/** + * This will do a flatMap reduce where we only return exceptionsLists and their items if: + * - exceptionLists are an array and not null, undefined, or malformed data. + * - The exceptionList item is an object and id is a string and not null, undefined, or malformed data + * + * Some of these issues could crop up during either user manual errors of modifying things, earlier migration + * issues, etc... + * @param exceptionsList The list of exceptions + * @returns The exception lists if they are a valid enough shape + */ +function removeMalformedExceptionsList( + exceptionsList: SavedObjectAttribute +): SavedObjectAttributes[] { + if (!Array.isArray(exceptionsList)) { + // early return if we are not an array such as being undefined or null or malformed. + return []; + } else { + return exceptionsList.flatMap((exceptionItem) => { + if (!(exceptionItem instanceof Object) || !isString(exceptionItem.id)) { + // return early if we are not an object such as being undefined or null or malformed + // or the exceptionItem.id is not a string from being malformed + return []; + } else { + return [exceptionItem]; + } + }); + } +} + +/** + * This migrates exception list containers to saved object references on an upgrade. + * We only migrate if we find these conditions: + * - exceptionLists are an array and not null, undefined, or malformed data. + * - The exceptionList item is an object and id is a string and not null, undefined, or malformed data + * - The existing references do not already have an exceptionItem reference already found within it. + * Some of these issues could crop up during either user manual errors of modifying things, earlier migration + * issues, etc... + * @param doc The document that might have exceptionListItems to migrate + * @returns The document migrated with saved object references + */ +function addExceptionListsToReferences( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { + params: { exceptionsList }, + }, + references, + } = doc; + if (!Array.isArray(exceptionsList)) { + // early return if we are not an array such as being undefined or null or malformed. + return doc; + } else { + const exceptionsToTransform = removeMalformedExceptionsList(exceptionsList); + const newReferences = exceptionsToTransform.flatMap( + (exceptionItem, index) => { + const existingReferenceFound = references?.find((reference) => { + return ( + reference.id === exceptionItem.id && + ((reference.type === 'exception-list' && exceptionItem.namespace_type === 'single') || + (reference.type === 'exception-list-agnostic' && + exceptionItem.namespace_type === 'agnostic')) + ); + }); + if (existingReferenceFound) { + // skip if the reference already exists for some uncommon reason so we do not add an additional one. + // This enables us to be idempotent and you can run this migration multiple times and get the same output. + return []; + } else { + return [ + { + name: `param:exceptionsList_${index}`, + id: String(exceptionItem.id), + type: + exceptionItem.namespace_type === 'agnostic' + ? 'exception-list-agnostic' + : 'exception-list', + }, + ]; + } + } + ); + if (references == null && newReferences.length === 0) { + // Avoid adding an empty references array if the existing saved object never had one to begin with + return doc; + } else { + return { ...doc, references: [...(references ?? []), ...newReferences] }; + } + } +} + +export const getMigrations7150 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc): doc is SavedObjectUnsanitizedDoc => isSiemSignalsRuleType(doc), + pipeMigrations(addExceptionListsToReferences) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/7.16/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/7.16/index.ts new file mode 100644 index 0000000000000..35aded8311803 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/7.16/index.ts @@ -0,0 +1,159 @@ +/* + * 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 { SavedObjectAttribute, SavedObjectReference } from '@kbn/core-saved-objects-common'; +import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { isString } from 'lodash/fp'; +import { RawRule, RawRuleAction } from '../../../types'; +import { extractRefsFromGeoContainmentAlert } from '../../geo_containment/migrations'; +import { createEsoMigration, isSecuritySolutionLegacyNotification, pipeMigrations } from '../utils'; + +function setLegacyId(doc: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc { + const { id } = doc; + return { + ...doc, + attributes: { + ...doc.attributes, + legacyId: id, + }, + }; +} + +function getRemovePreconfiguredConnectorsFromReferencesFn( + isPreconfigured: (connectorId: string) => boolean +) { + return (doc: SavedObjectUnsanitizedDoc) => { + return removePreconfiguredConnectorsFromReferences(doc, isPreconfigured); + }; +} + +function getCorrespondingAction( + actions: SavedObjectAttribute, + connectorRef: string +): RawRuleAction | null { + if (!Array.isArray(actions)) { + return null; + } else { + return actions.find( + (action) => (action as RawRuleAction)?.actionRef === connectorRef + ) as RawRuleAction; + } +} + +function removePreconfiguredConnectorsFromReferences( + doc: SavedObjectUnsanitizedDoc, + isPreconfigured: (connectorId: string) => boolean +): SavedObjectUnsanitizedDoc { + const { + attributes: { actions }, + references, + } = doc; + + // Look for connector references + const connectorReferences = (references ?? []).filter((ref: SavedObjectReference) => + ref.name.startsWith('action_') + ); + if (connectorReferences.length > 0) { + const restReferences = (references ?? []).filter( + (ref: SavedObjectReference) => !ref.name.startsWith('action_') + ); + + const updatedConnectorReferences: SavedObjectReference[] = []; + const updatedActions: RawRule['actions'] = []; + + // For each connector reference, check if connector is preconfigured + // If yes, we need to remove from the references array and update + // the corresponding action so it directly references the preconfigured connector id + connectorReferences.forEach((connectorRef: SavedObjectReference) => { + // Look for the corresponding entry in the actions array + const correspondingAction = getCorrespondingAction(actions, connectorRef.name); + if (correspondingAction) { + if (isPreconfigured(connectorRef.id)) { + updatedActions.push({ + ...correspondingAction, + actionRef: `preconfigured:${connectorRef.id}`, + }); + } else { + updatedActions.push(correspondingAction); + updatedConnectorReferences.push(connectorRef); + } + } else { + // Couldn't find the matching action, leave as is + updatedConnectorReferences.push(connectorRef); + } + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + actions: [...updatedActions], + }, + references: [...updatedConnectorReferences, ...restReferences], + }; + } + return doc; +} + +/** + * This migrates rule_id's within the legacy siem.notification to saved object references on an upgrade. + * We only migrate if we find these conditions: + * - ruleAlertId is a string and not null, undefined, or malformed data. + * - The existing references do not already have a ruleAlertId found within it. + * Some of these issues could crop up during either user manual errors of modifying things, earlier migration + * issues, etc... so we are safer to check them as possibilities + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * @param doc The document that might have "ruleAlertId" to migrate into the references + * @returns The document migrated with saved object references + */ +function addRuleIdsToLegacyNotificationReferences( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { + params: { ruleAlertId }, + }, + references, + } = doc; + if (!isSecuritySolutionLegacyNotification(doc) || !isString(ruleAlertId)) { + // early return if we are not a string or if we are not a security solution notification saved object. + return doc; + } else { + const existingReferences = references ?? []; + const existingReferenceFound = existingReferences.find((reference) => { + return reference.id === ruleAlertId && reference.type === 'alert'; + }); + if (existingReferenceFound) { + // skip this if the references already exists for some uncommon reason so we do not add an additional one. + return doc; + } else { + const savedObjectReference: SavedObjectReference = { + id: ruleAlertId, + name: 'param:alert_0', + type: 'alert', + }; + const newReferences = [...existingReferences, savedObjectReference]; + return { ...doc, references: newReferences }; + } + } +} + +export const getMigrations7160 = ( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + isPreconfigured: (connectorId: string) => boolean +) => + createEsoMigration( + encryptedSavedObjects, + (doc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations( + setLegacyId, + getRemovePreconfiguredConnectorsFromReferencesFn(isPreconfigured), + addRuleIdsToLegacyNotificationReferences, + extractRefsFromGeoContainmentAlert + ) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/8.0/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/8.0/index.ts new file mode 100644 index 0000000000000..5d8efd4585af7 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/8.0/index.ts @@ -0,0 +1,133 @@ +/* + * 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 { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { isRuleType, ruleTypeMappings } from '@kbn/securitysolution-rules'; +import { RawRule } from '../../../types'; +import { FILEBEAT_7X_INDICATOR_PATH } from '../constants'; +import { + createEsoMigration, + isDetectionEngineAADRuleType, + isSiemSignalsRuleType, + pipeMigrations, +} from '../utils'; + +function addThreatIndicatorPathToThreatMatchRules( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + return isSiemSignalsRuleType(doc) && + doc.attributes.params?.type === 'threat_match' && + !doc.attributes.params.threatIndicatorPath + ? { + ...doc, + attributes: { + ...doc.attributes, + params: { + ...doc.attributes.params, + threatIndicatorPath: FILEBEAT_7X_INDICATOR_PATH, + }, + }, + } + : doc; +} + +function addSecuritySolutionAADRuleTypes( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const ruleType = doc.attributes.params.type; + return isSiemSignalsRuleType(doc) && isRuleType(ruleType) + ? { + ...doc, + attributes: { + ...doc.attributes, + alertTypeId: ruleTypeMappings[ruleType], + enabled: false, + params: { + ...doc.attributes.params, + outputIndex: '', + }, + }, + } + : doc; +} + +function addSecuritySolutionAADRuleTypeTags( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const ruleType = doc.attributes.params.type; + return isDetectionEngineAADRuleType(doc) && isRuleType(ruleType) + ? { + ...doc, + attributes: { + ...doc.attributes, + // If the rule is disabled at this point, then the rule has not been re-enabled after + // running the 8.0.0 migrations. If `doc.attributes.scheduledTaskId` exists, then the + // rule was enabled prior to running the migration. Thus we know we should add the + // tag to indicate it was auto-disabled. + tags: + !doc.attributes.enabled && doc.attributes.scheduledTaskId + ? [...(doc.attributes.tags ?? []), 'auto_disabled_8.0'] + : doc.attributes.tags ?? [], + }, + } + : doc; +} + +// This fixes an issue whereby metrics.alert.inventory.threshold rules had the +// group for actions incorrectly spelt as metrics.invenotry_threshold.fired vs metrics.inventory_threshold.fired +function fixInventoryThresholdGroupId( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + if (doc.attributes.alertTypeId === 'metrics.alert.inventory.threshold') { + const { + attributes: { actions }, + } = doc; + + const updatedActions = actions + ? actions.map((action) => { + // Wrong spelling + if (action.group === 'metrics.invenotry_threshold.fired') { + return { + ...action, + group: 'metrics.inventory_threshold.fired', + }; + } else { + return action; + } + }) + : []; + + return { + ...doc, + attributes: { + ...doc.attributes, + actions: updatedActions, + }, + }; + } else { + return doc; + } +} + +export const getMigrations800 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations( + addThreatIndicatorPathToThreatMatchRules, + addSecuritySolutionAADRuleTypes, + fixInventoryThresholdGroupId + ) + ); + +export const getMigrations801 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(addSecuritySolutionAADRuleTypeTags) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/8.2/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/8.2/index.ts new file mode 100644 index 0000000000000..6de67875ba2eb --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/8.2/index.ts @@ -0,0 +1,41 @@ +/* + * 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 { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { getMappedParams } from '../../../rules_client/lib/mapped_params_utils'; +import { RawRule } from '../../../types'; +import { createEsoMigration, pipeMigrations } from '../utils'; + +function addMappedParams( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { params }, + } = doc; + + const mappedParams = getMappedParams(params); + + if (Object.keys(mappedParams).length) { + return { + ...doc, + attributes: { + ...doc.attributes, + mapped_params: mappedParams, + }, + }; + } + + return doc; +} + +export const getMigrations820 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(addMappedParams) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/8.3/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/8.3/index.ts new file mode 100644 index 0000000000000..833971a71dbbe --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/8.3/index.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { omit } from 'lodash'; +import moment from 'moment-timezone'; +import { RawRule } from '../../../types'; +import { + createEsoMigration, + isDetectionEngineAADRuleType, + isEsQueryRuleType, + pipeMigrations, +} from '../utils'; + +function addSearchType(doc: SavedObjectUnsanitizedDoc) { + const searchType = doc.attributes.params.searchType; + + return isEsQueryRuleType(doc) && !searchType + ? { + ...doc, + attributes: { + ...doc.attributes, + params: { + ...doc.attributes.params, + searchType: 'esQuery', + }, + }, + } + : doc; +} + +/** + * removes internal tags(starts with '__internal') from Security Solution rules + * @param doc rule to be migrated + * @returns migrated rule if it's Security Solution rule or unchanged if not + */ +function removeInternalTags( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + if (!isDetectionEngineAADRuleType(doc)) { + return doc; + } + + const { + attributes: { tags }, + } = doc; + + const filteredTags = (tags ?? []).filter((tag) => !tag.startsWith('__internal_')); + + return { + ...doc, + attributes: { + ...doc.attributes, + tags: filteredTags, + }, + }; +} + +function convertSnoozes( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { snoozeEndTime }, + } = doc; + + return { + ...doc, + attributes: { + ...(omit(doc.attributes, ['snoozeEndTime']) as RawRule), + snoozeSchedule: snoozeEndTime + ? [ + { + duration: Date.parse(snoozeEndTime as string) - Date.now(), + rRule: { + dtstart: new Date().toISOString(), + tzid: moment.tz.guess(), + count: 1, + }, + }, + ] + : [], + }, + }; +} + +export const getMigrations830 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(addSearchType, removeInternalTags, convertSnoozes) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/8.4/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/8.4/index.ts new file mode 100644 index 0000000000000..3a02425a7c194 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/8.4/index.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { omit } from 'lodash'; +import { RawRule } from '../../../types'; +import { createEsoMigration, pipeMigrations } from '../utils'; + +function removeIsSnoozedUntil( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + return { + ...doc, + attributes: { + ...(omit(doc.attributes, ['isSnoozedUntil']) as RawRule), + }, + }; +} + +export const getMigrations841 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(removeIsSnoozedUntil) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/8.5/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/8.5/index.ts new file mode 100644 index 0000000000000..a40bcafd0bf35 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/8.5/index.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + SavedObjectMigrationContext, + SavedObjectUnsanitizedDoc, +} from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { isSerializedSearchSource } from '@kbn/data-plugin/common'; +import { pick } from 'lodash'; +import { RawRule } from '../../../types'; +import { createEsoMigration, isEsQueryRuleType, pipeMigrations } from '../utils'; +import { AlertLogMeta } from '../types'; + +function stripOutRuntimeFieldsInOldESQuery( + doc: SavedObjectUnsanitizedDoc, + context: SavedObjectMigrationContext +): SavedObjectUnsanitizedDoc { + const isESDSLrule = + isEsQueryRuleType(doc) && !isSerializedSearchSource(doc.attributes.params.searchConfiguration); + + if (isESDSLrule) { + try { + const parsedQuery = JSON.parse(doc.attributes.params.esQuery as string); + // parsing and restringifying will cause us to lose the formatting so we only do so if this rule has + // fields other than `query` which is the only valid field at this stage + const hasFieldsOtherThanQuery = Object.keys(parsedQuery).some((key) => key !== 'query'); + return hasFieldsOtherThanQuery + ? { + ...doc, + attributes: { + ...doc.attributes, + params: { + ...doc.attributes.params, + esQuery: JSON.stringify(pick(parsedQuery, 'query'), null, 4), + }, + }, + } + : doc; + } catch (err) { + // Instead of failing the upgrade when an unparsable rule is encountered, we log that the rule caouldn't be migrated and + // as a result legacy parameters might cause the rule to behave differently if it is, in fact, still running at all + context.log.error( + `unable to migrate and remove legacy runtime fields in rule ${doc.id} due to invalid query: "${doc.attributes.params.esQuery}" - query must be JSON`, + { + migrations: { + alertDocument: doc, + }, + } + ); + } + } + return doc; +} + +export const getMigrations850 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => + createEsoMigration( + encryptedSavedObjects, + (doc): doc is SavedObjectUnsanitizedDoc => isEsQueryRuleType(doc), + pipeMigrations(stripOutRuntimeFieldsInOldESQuery) + ); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/constants.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/constants.ts new file mode 100644 index 0000000000000..849021f3c58c3 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/constants.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const SIEM_APP_ID = 'securitySolution'; +export const SIEM_SERVER_APP_ID = 'siem'; +export const MINIMUM_SS_MIGRATION_VERSION = '8.3.0'; +export const LEGACY_LAST_MODIFIED_VERSION = 'pre-7.10.0'; +export const FILEBEAT_7X_INDICATOR_PATH = 'threatintel.indicator'; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/index.test.ts similarity index 99% rename from x-pack/plugins/alerting/server/saved_objects/migrations.test.ts rename to x-pack/plugins/alerting/server/saved_objects/migrations/index.test.ts index be9bee7634990..09f466a8e9a37 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/index.test.ts @@ -7,12 +7,13 @@ import sinon from 'sinon'; import uuid from 'uuid'; -import { getMigrations, isAnyActionSupportIncidents } from './migrations'; -import { RawRule } from '../types'; +import { getMigrations } from '.'; +import { RawRule } from '../../types'; import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { migrationMocks } from '@kbn/core/server/mocks'; import { RuleType, ruleTypeMappings } from '@kbn/securitysolution-rules'; +import { isAnyActionSupportIncidents } from './7.11'; const migrationContext = migrationMocks.createContext(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); @@ -24,6 +25,7 @@ describe('successful migrations', () => { jest.resetAllMocks(); encryptedSavedObjectsSetup.createMigration.mockImplementation(({ migration }) => migration); }); + describe('7.10.0', () => { test('marks alerts as legacy', () => { const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/index.ts new file mode 100644 index 0000000000000..a3a06614ec4c5 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/index.ts @@ -0,0 +1,155 @@ +/* + * 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 { gte } from 'semver'; +import { + SavedObjectMigrationMap, + SavedObjectUnsanitizedDoc, + SavedObjectMigrationFn, + SavedObjectMigrationContext, +} from '@kbn/core/server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { MigrateFunctionsObject, MigrateFunction } from '@kbn/kibana-utils-plugin/common'; +import { mergeSavedObjectMigrationMaps } from '@kbn/core/server'; +import { isSerializedSearchSource, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { RawRule } from '../../types'; +import { getMigrations7100 } from './7.10'; +import { getMigrations7110, getMigrations7112 } from './7.11'; +import { getMigrations7130 } from './7.13'; +import { getMigrations7140 } from './7.14'; +import { getMigrations7150 } from './7.15'; +import { getMigrations7160 } from './7.16'; +import { getMigrations800, getMigrations801 } from './8.0'; +import { getMigrations820 } from './8.2'; +import { getMigrations830 } from './8.3'; +import { getMigrations841 } from './8.4'; +import { getMigrations850 } from './8.5'; +import { AlertLogMeta, AlertMigration } from './types'; +import { MINIMUM_SS_MIGRATION_VERSION } from './constants'; +import { createEsoMigration, isEsQueryRuleType, pipeMigrations } from './utils'; + +export { FILEBEAT_7X_INDICATOR_PATH } from './constants'; + +export function getMigrations( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + searchSourceMigrations: MigrateFunctionsObject, + isPreconfigured: (connectorId: string) => boolean +): SavedObjectMigrationMap { + return mergeSavedObjectMigrationMaps( + { + '7.10.0': executeMigrationWithErrorHandling( + getMigrations7100(encryptedSavedObjects), + '7.10.0' + ), + '7.11.0': executeMigrationWithErrorHandling( + getMigrations7110(encryptedSavedObjects), + '7.11.0' + ), + '7.11.2': executeMigrationWithErrorHandling( + getMigrations7112(encryptedSavedObjects), + '7.11.2' + ), + '7.13.0': executeMigrationWithErrorHandling( + getMigrations7130(encryptedSavedObjects), + '7.13.0' + ), + '7.14.1': executeMigrationWithErrorHandling( + getMigrations7140(encryptedSavedObjects), + '7.14.1' + ), + '7.15.0': executeMigrationWithErrorHandling( + getMigrations7150(encryptedSavedObjects), + '7.15.0' + ), + '7.16.0': executeMigrationWithErrorHandling( + getMigrations7160(encryptedSavedObjects, isPreconfigured), + '7.16.0' + ), + '8.0.0': executeMigrationWithErrorHandling(getMigrations800(encryptedSavedObjects), '8.0.0'), + '8.0.1': executeMigrationWithErrorHandling(getMigrations801(encryptedSavedObjects), '8.0.1'), + '8.2.0': executeMigrationWithErrorHandling(getMigrations820(encryptedSavedObjects), '8.2.0'), + '8.3.0': executeMigrationWithErrorHandling(getMigrations830(encryptedSavedObjects), '8.3.0'), + '8.4.1': executeMigrationWithErrorHandling(getMigrations841(encryptedSavedObjects), '8.4.1'), + '8.5.0': executeMigrationWithErrorHandling(getMigrations850(encryptedSavedObjects), '8.5.0'), + }, + getSearchSourceMigrations(encryptedSavedObjects, searchSourceMigrations) + ); +} + +function executeMigrationWithErrorHandling( + migrationFunc: SavedObjectMigrationFn, + version: string +) { + return (doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) => { + try { + return migrationFunc(doc, context); + } catch (ex) { + context.log.error( + `encryptedSavedObject ${version} migration failed for alert ${doc.id} with error: ${ex.message}`, + { + migrations: { + alertDocument: doc, + }, + } + ); + throw ex; + } + }; +} + +function mapSearchSourceMigrationFunc( + migrateSerializedSearchSourceFields: MigrateFunction +): MigrateFunction { + return (doc) => { + const _doc = doc as { attributes: RawRule }; + + const serializedSearchSource = _doc.attributes.params.searchConfiguration; + + if (isSerializedSearchSource(serializedSearchSource)) { + return { + ..._doc, + attributes: { + ..._doc.attributes, + params: { + ..._doc.attributes.params, + searchConfiguration: migrateSerializedSearchSourceFields(serializedSearchSource), + }, + }, + }; + } + return _doc; + }; +} + +/** + * This creates a migration map that applies search source migrations to legacy es query rules. + * It doesn't modify existing migrations. The following migrations will occur at minimum version of 8.3+. + */ +function getSearchSourceMigrations( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + searchSourceMigrations: MigrateFunctionsObject +) { + const filteredMigrations: SavedObjectMigrationMap = {}; + for (const versionKey in searchSourceMigrations) { + if (gte(versionKey, MINIMUM_SS_MIGRATION_VERSION)) { + const migrateSearchSource = mapSearchSourceMigrationFunc( + searchSourceMigrations[versionKey] + ) as unknown as AlertMigration; + + filteredMigrations[versionKey] = executeMigrationWithErrorHandling( + createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => + isEsQueryRuleType(doc), + pipeMigrations(migrateSearchSource) + ), + versionKey + ); + } + } + return filteredMigrations; +} diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/types.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/types.ts new file mode 100644 index 0000000000000..6d657e168187e --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogMeta, SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; +import { RawRule } from '../../types'; + +export interface AlertLogMeta extends LogMeta { + migrations: { alertDocument: SavedObjectUnsanitizedDoc }; +} + +export type AlertMigration = ( + doc: SavedObjectUnsanitizedDoc, + context: SavedObjectMigrationContext +) => SavedObjectUnsanitizedDoc; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/utils.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/utils.ts new file mode 100644 index 0000000000000..c96d0cfa5d7fc --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/utils.ts @@ -0,0 +1,59 @@ +/* + * 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 { ruleTypeMappings } from '@kbn/securitysolution-rules'; +import { + SavedObjectMigrationContext, + SavedObjectUnsanitizedDoc, +} from '@kbn/core-saved-objects-server'; +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import type { IsMigrationNeededPredicate } from '@kbn/encrypted-saved-objects-plugin/server'; + +import { RawRule } from '../../types'; + +type AlertMigration = ( + doc: SavedObjectUnsanitizedDoc, + context: SavedObjectMigrationContext +) => SavedObjectUnsanitizedDoc; + +export function createEsoMigration( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + isMigrationNeededPredicate: IsMigrationNeededPredicate, + migrationFunc: AlertMigration +) { + return encryptedSavedObjects.createMigration({ + isMigrationNeededPredicate, + migration: migrationFunc, + shouldMigrateIfDecryptionFails: true, // shouldMigrateIfDecryptionFails flag that applies the migration to undecrypted document if decryption fails + }); +} + +export function pipeMigrations(...migrations: AlertMigration[]): AlertMigration { + return (doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) => + migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc, context), doc); +} + +// Deprecated in 8.0 +export const isSiemSignalsRuleType = (doc: SavedObjectUnsanitizedDoc): boolean => + doc.attributes.alertTypeId === 'siem.signals'; + +export const isEsQueryRuleType = (doc: SavedObjectUnsanitizedDoc) => + doc.attributes.alertTypeId === '.es-query'; + +export const isDetectionEngineAADRuleType = (doc: SavedObjectUnsanitizedDoc): boolean => + (Object.values(ruleTypeMappings) as string[]).includes(doc.attributes.alertTypeId); + +/** + * Returns true if the alert type is that of "siem.notifications" which is a legacy notification system that was deprecated in 7.16.0 + * in favor of using the newer alerting notifications system. + * @param doc The saved object alert type document + * @returns true if this is a legacy "siem.notifications" rule, otherwise false + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const isSecuritySolutionLegacyNotification = ( + doc: SavedObjectUnsanitizedDoc +): boolean => doc.attributes.alertTypeId === 'siem.notifications'; diff --git a/x-pack/plugins/apm/dev_docs/apm_queries.md b/x-pack/plugins/apm/dev_docs/apm_queries.md index 4dd9a807eb249..952d38b42691a 100644 --- a/x-pack/plugins/apm/dev_docs/apm_queries.md +++ b/x-pack/plugins/apm/dev_docs/apm_queries.md @@ -1,17 +1,20 @@ ### Table of Contents - - [Transactions](#transactions) - - [System metrics](#system-metrics) - - [Transaction breakdown metrics](#transaction-breakdown-metrics) - - [Span breakdown metrics](#span-breakdown-metrics) - - [Service destination metrics](#service-destination-metrics) - - [Common filters](#common-filters) + +- [Transactions](#transactions) +- [System metrics](#system-metrics) +- [Transaction breakdown metrics](#transaction-breakdown-metrics) +- [Span breakdown metrics](#span-breakdown-metrics) +- [Service destination metrics](#service-destination-metrics) +- [Common filters](#common-filters) --- ### Data model + Elastic APM agents capture different types of information from within their instrumented applications. These are known as events, and can be spans, transactions, errors, or metrics. You can find more information [here](https://www.elastic.co/guide/en/apm/get-started/current/apm-data-model.html). ### Running examples + You can run the example queries on the [edge cluster](https://edge-oblt.elastic.dev/) or any another cluster that contains APM data. # Transactions @@ -34,7 +37,8 @@ A single transaction event where `transaction.duration.us` is the latency. or #### Aggregated (metric) document -A pre-aggregated document where `_doc_count` is the number of transaction events, and `transaction.duration.histogram` is the latency distribution. + +A pre-aggregated document where `_doc_count` is the number of transaction events, and `transaction.duration.histogram` is the latency distribution. ```json { @@ -52,7 +56,7 @@ A pre-aggregated document where `_doc_count` is the number of transaction events You can find all the APM transaction fields [here](https://www.elastic.co/guide/en/apm/server/current/exported-fields-apm-transaction.html). -The decision to use aggregated transactions or not is determined in [`getSearchAggregatedTransactions`](https://github.com/elastic/kibana/blob/a2ac439f56313b7a3fc4708f54a4deebf2615136/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts#L53-L79) and then used to specify [the transaction index](https://github.com/elastic/kibana/blob/a2ac439f56313b7a3fc4708f54a4deebf2615136/x-pack/plugins/apm/server/lib/suggestions/get_suggestions.ts#L30-L32) and [the latency field](https://github.com/elastic/kibana/blob/a2ac439f56313b7a3fc4708f54a4deebf2615136/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts#L62-L65) +The decision to use aggregated transactions or not is determined in [`getSearchTransactionsEvents`](https://github.com/elastic/kibana/blob/a2ac439f56313b7a3fc4708f54a4deebf2615136/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts#L53-L79) and then used to specify [the transaction index](https://github.com/elastic/kibana/blob/a2ac439f56313b7a3fc4708f54a4deebf2615136/x-pack/plugins/apm/server/lib/suggestions/get_suggestions.ts#L30-L32) and [the latency field](https://github.com/elastic/kibana/blob/a2ac439f56313b7a3fc4708f54a4deebf2615136/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts#L62-L65) ### Latency @@ -134,7 +138,6 @@ GET apm-*-transaction-*,traces-apm*/_search?terminate_after=1000 } ``` - #### Metric-based throughput ```json @@ -174,23 +177,23 @@ Noteworthy fields: `event.outcome` #### Transaction-based failed transaction rate - ```json +```json GET apm-*-transaction-*,traces-apm*/_search?terminate_after=1000 { - "size": 0, - "query": { - "bool": { - "filter": [{ "terms": { "processor.event": ["transaction"] } }] - } - }, - "aggs": { - "outcomes": { - "terms": { - "field": "event.outcome", - "include": ["failure", "success"] - } - } - } + "size": 0, + "query": { + "bool": { + "filter": [{ "terms": { "processor.event": ["transaction"] } }] + } + }, + "aggs": { + "outcomes": { + "terms": { + "field": "event.outcome", + "include": ["failure", "success"] + } + } + } } ``` @@ -219,6 +222,57 @@ GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 } ``` +# Transactions in service inventory page + +Service metrics is an aggregated metric document that holds latency and throughput metrics pivoted by `service.name + service.environment + transaction.type` + +The decision to use service metrics aggregation or not is determined in [getServiceInventorySearchSource](https://github.com/elastic/kibana/blob/5d585ea375be551a169a0bea49b011819b9ac669/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts#L12) and [getSearchServiceMetrics](https://github.com/elastic/kibana/blob/5d585ea375be551a169a0bea49b011819b9ac669/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts#L38) + +A pre-aggregated document where `_doc_count` is the number of transaction events + +``` +{ + "_doc_count": 627, + "@timestamp": "2021-09-01T10:00:00.000Z", + "processor.event": "metric", + "metricset.name": "service", + "service": { + "environment": "production", + "name": "web-go" + }, + "transaction": { + "duration.summary": { + "sum": 376492831, + "value_count": 627 + }, + "success_count": 476, + "failure_count": 151, + "type": "request" + } +} +``` + +- `_doc_count` is the number of bucket counts +- `transaction.duration.summary` is an [aggregate_metric_double](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/aggregate-metric-double.html) field and holds an aggregated transaction duration summary, for service metrics +- `failure_count` holds an aggregated count of transactions with the outcome "failure" +- `success_count` holds an aggregated count of transactions with the outcome "success" + +### Latency + +``` +{ + "size": 0, + "query": { + "bool": { + "filter": [{ "term": { "metricset.name": "service" } }] + } + }, + "aggs": { + "latency": { "avg": { "field": "transaction.duration.summary" }} + } +} +``` + # System metrics System metrics are captured periodically (every 60 seconds by default). You can find all the System Metrics fields [here](https://www.elastic.co/guide/en/apm/server/current/exported-fields-system.html). @@ -317,7 +371,7 @@ The above example is overly simplified. In reality [we do a bit more](https://gi # Span breakdown metrics -A pre-aggregations of span documents where `span.self_time.count` is the number of original spans. Measures the "self-time" for a span type, and optional subtype, within a transaction group. +A pre-aggregations of span documents where `span.self_time.count` is the number of original spans. Measures the "self-time" for a span type, and optional subtype, within a transaction group. Span breakdown metrics are used to power the "Time spent by span type" graph. Agents collect summarized metrics about the timings of spans, broken down by `span.type`. @@ -436,7 +490,6 @@ GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 Captures the number of requests made from a service to an (external) endpoint - #### Query ```json diff --git a/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts b/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts index 459eddfbdcc1f..c378e00c39253 100644 --- a/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts +++ b/x-pack/plugins/apm/server/lib/helpers/get_service_inventory_search_source.ts @@ -5,8 +5,8 @@ * 2.0. */ import { APMEventClient } from './create_es_client/create_apm_event_client'; -import { getSearchAggregatedTransactions } from './transactions'; -import { getSearchAggregatedServiceMetrics } from './service_metrics'; +import { getSearchTransactionsEvents } from './transactions'; +import { getSearchServiceMetrics } from './service_metrics'; import { APMConfig } from '../..'; export async function getServiceInventorySearchSource({ @@ -35,8 +35,8 @@ export async function getServiceInventorySearchSource({ }; const [searchAggregatedTransactions, searchAggregatedServiceMetrics] = await Promise.all([ - getSearchAggregatedTransactions({ ...commonProps, config }), - getSearchAggregatedServiceMetrics({ + getSearchTransactionsEvents({ ...commonProps, config }), + getSearchServiceMetrics({ ...commonProps, serviceMetricsEnabled, }), diff --git a/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts b/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts index d0ba695c2240e..48936b644c2af 100644 --- a/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts @@ -10,7 +10,7 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { METRICSET_NAME } from '../../../../common/elasticsearch_fieldnames'; import { APMEventClient } from '../create_es_client/create_apm_event_client'; -export async function getSearchAggregatedServiceMetrics({ +export async function getSearchServiceMetrics({ serviceMetricsEnabled, start, end, @@ -24,7 +24,7 @@ export async function getSearchAggregatedServiceMetrics({ kuery: string; }): Promise { if (serviceMetricsEnabled) { - return getHasAggregatedServicesMetrics({ + return getHasServicesMetrics({ start, end, apmEventClient, @@ -35,7 +35,7 @@ export async function getSearchAggregatedServiceMetrics({ return false; } -export async function getHasAggregatedServicesMetrics({ +export async function getHasServicesMetrics({ start, end, apmEventClient, diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts index f64b7f08e9077..409010d3c695d 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts @@ -7,7 +7,7 @@ import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { getSearchAggregatedTransactions } from '.'; +import { getSearchTransactionsEvents } from '.'; import { Setup } from '../setup_request'; import { APMEventClient } from '../create_es_client/create_apm_event_client'; import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions'; @@ -23,7 +23,7 @@ export async function getIsUsingTransactionEvents({ start?: number; end?: number; }): Promise { - const searchesAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchesAggregatedTransactions = await getSearchTransactionsEvents({ config, start, end, diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts index 6a52b2c39f59a..b6f7dfa4083e1 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts @@ -17,7 +17,7 @@ import { import { APMConfig } from '../../..'; import { APMEventClient } from '../create_es_client/create_apm_event_client'; -export async function getHasAggregatedTransactions({ +export async function getHasTransactionsEvents({ start, end, apmEventClient, @@ -54,7 +54,7 @@ export async function getHasAggregatedTransactions({ return response.hits.total.value > 0; } -export async function getSearchAggregatedTransactions({ +export async function getSearchTransactionsEvents({ config, start, end, @@ -70,11 +70,11 @@ export async function getSearchAggregatedTransactions({ switch (config.searchAggregatedTransactions) { case SearchAggregatedTransactionSetting.always: return kuery - ? getHasAggregatedTransactions({ start, end, apmEventClient, kuery }) + ? getHasTransactionsEvents({ start, end, apmEventClient, kuery }) : true; case SearchAggregatedTransactionSetting.auto: - return getHasAggregatedTransactions({ + return getHasTransactionsEvents({ start, end, apmEventClient, diff --git a/x-pack/plugins/apm/server/routes/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/routes/alerts/chart_preview/get_transaction_duration.ts index 9636098fbbee5..06e5c29cdca18 100644 --- a/x-pack/plugins/apm/server/routes/alerts/chart_preview/get_transaction_duration.ts +++ b/x-pack/plugins/apm/server/routes/alerts/chart_preview/get_transaction_duration.ts @@ -14,7 +14,7 @@ import { import { environmentQuery } from '../../../../common/utils/environment_query'; import { AlertParams } from '../route'; import { - getSearchAggregatedTransactions, + getSearchTransactionsEvents, getDocumentTypeFilterForTransactions, getDurationFieldForTransactions, getProcessorEventForTransactions, @@ -38,7 +38,7 @@ export async function getTransactionDurationChartPreview({ start, end, } = alertParams; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery: '', }); diff --git a/x-pack/plugins/apm/server/routes/alerts/chart_preview/get_transaction_error_rate.ts b/x-pack/plugins/apm/server/routes/alerts/chart_preview/get_transaction_error_rate.ts index 16ee19e3bce8b..48d04a856cb55 100644 --- a/x-pack/plugins/apm/server/routes/alerts/chart_preview/get_transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/routes/alerts/chart_preview/get_transaction_error_rate.ts @@ -13,7 +13,7 @@ import { import { environmentQuery } from '../../../../common/utils/environment_query'; import { AlertParams } from '../route'; import { - getSearchAggregatedTransactions, + getSearchTransactionsEvents, getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../../lib/helpers/transactions'; @@ -34,7 +34,7 @@ export async function getTransactionErrorRateChartPreview({ const { serviceName, environment, transactionType, interval, start, end } = alertParams; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery: '', start, diff --git a/x-pack/plugins/apm/server/routes/environments/route.ts b/x-pack/plugins/apm/server/routes/environments/route.ts index 03fb07fd51cb8..4a7755230754c 100644 --- a/x-pack/plugins/apm/server/routes/environments/route.ts +++ b/x-pack/plugins/apm/server/routes/environments/route.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { maxSuggestions } from '@kbn/observability-plugin/common'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { setupRequest } from '../../lib/helpers/setup_request'; import { getEnvironments } from './get_environments'; import { rangeRt } from '../default_api_types'; @@ -39,7 +39,7 @@ const environmentsRoute = createApmServerRoute({ const setup = await setupRequest(resources); const { context, params } = resources; const { serviceName, start, end } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, start, diff --git a/x-pack/plugins/apm/server/routes/latency_distribution/route.ts b/x-pack/plugins/apm/server/routes/latency_distribution/route.ts index 7eaaa6e641030..ac0e65abe3157 100644 --- a/x-pack/plugins/apm/server/routes/latency_distribution/route.ts +++ b/x-pack/plugins/apm/server/routes/latency_distribution/route.ts @@ -11,7 +11,7 @@ import { termQuery } from '@kbn/observability-plugin/server'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { getOverallLatencyDistribution } from './get_overall_latency_distribution'; import { setupRequest } from '../../lib/helpers/setup_request'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { @@ -74,7 +74,7 @@ const latencyOverallTransactionDistributionRoute = createApmServerRoute({ // only the transaction latency distribution chart can use metrics data const searchAggregatedTransactions = chartType === LatencyDistributionChartType.transactionLatency - ? await getSearchAggregatedTransactions({ + ? await getSearchTransactionsEvents({ ...setup, kuery, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts index 9200e9d07e1ce..5635dd5c0e50b 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts @@ -13,7 +13,7 @@ import { getMemoryChartData } from '../shared/memory'; import { getComputeUsage } from './compute_usage'; import { getActiveInstances } from './active_instances'; import { getColdStartCount } from './cold_start_count'; -import { getSearchAggregatedTransactions } from '../../../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../../../lib/helpers/transactions'; export function getServerlessAgentMetricCharts({ environment, @@ -31,7 +31,7 @@ export function getServerlessAgentMetricCharts({ end: number; }) { return withApmSpan('get_serverless_agent_metric_charts', async () => { - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, diff --git a/x-pack/plugins/apm/server/routes/observability_overview/route.ts b/x-pack/plugins/apm/server/routes/observability_overview/route.ts index e32c04b849664..4ed5332801ef3 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview/route.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview/route.ts @@ -12,7 +12,7 @@ import { getServiceCount } from './get_service_count'; import { getTransactionsPerMinute } from './get_transactions_per_minute'; import { getHasData } from './has_data'; import { rangeRt } from '../default_api_types'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { withApmSpan } from '../../utils/with_apm_span'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; @@ -50,7 +50,7 @@ const observabilityOverviewRoute = createApmServerRoute({ const setup = await setupRequest(resources); const { bucketSize, intervalString, start, end } = resources.params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, start, diff --git a/x-pack/plugins/apm/server/routes/service_map/route.ts b/x-pack/plugins/apm/server/routes/service_map/route.ts index 5c07b68fb0bd2..c2de9bf956a85 100644 --- a/x-pack/plugins/apm/server/routes/service_map/route.ts +++ b/x-pack/plugins/apm/server/routes/service_map/route.ts @@ -12,7 +12,7 @@ import { apmServiceGroupMaxNumberOfServices } from '@kbn/observability-plugin/co import { isActivePlatinumLicense } from '../../../common/license_check'; import { invalidLicenseMessage } from '../../../common/service_map'; import { notifyFeatureUsage } from '../../feature'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { setupRequest } from '../../lib/helpers/setup_request'; import { getServiceMap } from './get_service_map'; import { getServiceMapDependencyNodeInfo } from './get_service_map_dependency_node_info'; @@ -128,7 +128,7 @@ const serviceMapRoute = createApmServerRoute({ const serviceNames = compact([serviceName]); - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, start, @@ -183,7 +183,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ query: { environment, start, end, offset }, } = params; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, start, diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index 79ff09101aeca..1f1cac260331f 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -19,7 +19,7 @@ import { ScopedAnnotationsClient } from '@kbn/observability-plugin/server'; import { Annotation } from '@kbn/observability-plugin/common/annotations'; import { apmServiceGroupMaxNumberOfServices } from '@kbn/observability-plugin/common'; import { latencyAggregationTypeRt } from '../../../common/latency_aggregation_types'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { getServiceInventorySearchSource } from '../../lib/helpers/get_service_inventory_search_source'; import { setupRequest } from '../../lib/helpers/setup_request'; import { getServiceAnnotations } from './annotations'; @@ -280,7 +280,7 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ const { serviceName } = params.path; const { start, end } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, start, @@ -326,7 +326,7 @@ const serviceMetadataIconsRoute = createApmServerRoute({ const { serviceName } = params.path; const { start, end } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, start, @@ -391,7 +391,7 @@ const serviceTransactionTypesRoute = createApmServerRoute({ return getServiceTransactionTypes({ serviceName, setup, - searchAggregatedTransactions: await getSearchAggregatedTransactions({ + searchAggregatedTransactions: await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, start, @@ -465,7 +465,7 @@ const serviceAnnotationsRoute = createApmServerRoute({ observability.setup.getScopedAnnotationsClient(context, request) ) : undefined, - getSearchAggregatedTransactions({ + getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, start, @@ -598,7 +598,7 @@ const serviceThroughputRoute = createApmServerRoute({ start, end, } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, @@ -693,7 +693,7 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ end, } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, @@ -815,7 +815,7 @@ const serviceInstancesDetailedStatisticsRoute = createApmServerRoute({ end, } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts index 5c247d469bfda..eb04de4430381 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts @@ -23,7 +23,7 @@ import { serviceRt, agentConfigurationIntakeRt, } from '../../../../common/agent_configuration/runtime_types/agent_configuration_intake_rt'; -import { getSearchAggregatedTransactions } from '../../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../../lib/helpers/transactions'; import { syncAgentConfigsToApmPackagePolicies } from '../../fleet/sync_agent_configs_to_apm_package_policies'; // get list of configurations @@ -274,7 +274,7 @@ const listAgentConfigurationEnvironmentsRoute = createApmServerRoute({ const coreContext = await context.core; const { serviceName, start, end } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, kuery: '', diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts index fa15ef06ff16a..e30f669b9cd7b 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts @@ -15,7 +15,7 @@ import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; import { createAnomalyDetectionJobs } from '../../../lib/anomaly_detection/create_anomaly_detection_jobs'; import { setupRequest } from '../../../lib/helpers/setup_request'; import { getAllEnvironments } from '../../environments/get_all_environments'; -import { getSearchAggregatedTransactions } from '../../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../../lib/helpers/transactions'; import { notifyFeatureUsage } from '../../../feature'; import { updateToV3 } from './update_to_v3'; import { environmentStringRt } from '../../../../common/environment_rt'; @@ -97,7 +97,7 @@ const anomalyDetectionEnvironmentsRoute = createApmServerRoute({ const setup = await setupRequest(resources); const coreContext = await resources.context.core; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, kuery: '', diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/route.ts b/x-pack/plugins/apm/server/routes/storage_explorer/route.ts index 7ee36fae5493c..9d817efbf34f5 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/route.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/route.ts @@ -10,7 +10,7 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { setupRequest } from '../../lib/helpers/setup_request'; import { indexLifecyclePhaseRt } from '../../../common/storage_explorer_types'; import { getServiceStatistics } from './get_service_statistics'; @@ -76,7 +76,7 @@ const storageExplorerRoute = createApmServerRoute({ getRandomSampler({ security, request, probability }), ]); - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, kuery, @@ -211,7 +211,7 @@ const storageChartRoute = createApmServerRoute({ getRandomSampler({ security, request, probability }), ]); - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, kuery, @@ -300,7 +300,7 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ getRandomSampler({ security, request, probability }), ]); - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, kuery, diff --git a/x-pack/plugins/apm/server/routes/suggestions/route.ts b/x-pack/plugins/apm/server/routes/suggestions/route.ts index 68f94634a5ded..92a64da42eca3 100644 --- a/x-pack/plugins/apm/server/routes/suggestions/route.ts +++ b/x-pack/plugins/apm/server/routes/suggestions/route.ts @@ -9,7 +9,7 @@ import * as t from 'io-ts'; import { maxSuggestions } from '@kbn/observability-plugin/common'; import { getSuggestions } from './get_suggestions'; import { getSuggestionsWithTermsAggregation } from './get_suggestions_with_terms_aggregation'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { rangeRt } from '../default_api_types'; @@ -31,7 +31,7 @@ const suggestionsRoute = createApmServerRoute({ const setup = await setupRequest(resources); const { context, params } = resources; const { fieldName, fieldValue, serviceName, start, end } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ apmEventClient: setup.apmEventClient, config: setup.config, kuery: '', diff --git a/x-pack/plugins/apm/server/routes/traces/route.ts b/x-pack/plugins/apm/server/routes/traces/route.ts index 9884aafaf25f3..4036495a18076 100644 --- a/x-pack/plugins/apm/server/routes/traces/route.ts +++ b/x-pack/plugins/apm/server/routes/traces/route.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { TraceSearchType } from '../../../common/trace_explorer'; import { setupRequest } from '../../lib/helpers/setup_request'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, @@ -56,7 +56,7 @@ const tracesRoute = createApmServerRoute({ getRandomSampler({ security, request, probability }), ]); - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, diff --git a/x-pack/plugins/apm/server/routes/transactions/route.ts b/x-pack/plugins/apm/server/routes/transactions/route.ts index a96e4416e49e8..3bc4dbfe7aae7 100644 --- a/x-pack/plugins/apm/server/routes/transactions/route.ts +++ b/x-pack/plugins/apm/server/routes/transactions/route.ts @@ -11,7 +11,7 @@ import { LatencyAggregationType, latencyAggregationTypeRt, } from '../../../common/latency_aggregation_types'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; +import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { setupRequest } from '../../lib/helpers/setup_request'; import { getServiceTransactionGroups } from '../services/get_service_transaction_groups'; import { getServiceTransactionGroupDetailedStatisticsPeriods } from '../services/get_service_transaction_group_detailed_statistics'; @@ -70,7 +70,7 @@ const transactionGroupsMainStatisticsRoute = createApmServerRoute({ }, } = params; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, @@ -157,7 +157,7 @@ const transactionGroupsDetailedStatisticsRoute = createApmServerRoute({ }, } = params; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, @@ -236,7 +236,7 @@ const transactionLatencyChartsRoute = createApmServerRoute({ offset, } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, @@ -430,7 +430,7 @@ const transactionChartsErrorRateRoute = createApmServerRoute({ offset, } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, @@ -497,7 +497,7 @@ const transactionChartsColdstartRateRoute = createApmServerRoute({ const { environment, kuery, transactionType, start, end, offset } = params.query; - const searchAggregatedTransactions = await getSearchAggregatedTransactions({ + const searchAggregatedTransactions = await getSearchTransactionsEvents({ ...setup, kuery, start, @@ -571,13 +571,12 @@ const transactionChartsColdstartRateByTransactionNameRoute = offset, } = params.query; - const searchAggregatedTransactions = - await getSearchAggregatedTransactions({ - ...setup, - kuery, - start, - end, - }); + const searchAggregatedTransactions = await getSearchTransactionsEvents({ + ...setup, + kuery, + start, + end, + }); return getColdstartRatePeriods({ environment, diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index e7b7f565ad599..3c8f422d4eb7a 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { CASE_VIEW_PAGE_TABS } from './types'; import { CasesFeaturesAllRequired } from './ui/types'; export const DEFAULT_DATE_FORMAT = 'dateFormat' as const; @@ -17,6 +18,14 @@ export const APP_ID = 'cases' as const; export const FEATURE_ID = 'generalCases' as const; export const APP_OWNER = 'cases' as const; export const APP_PATH = '/app/management/insightsAndAlerting/cases' as const; +export const CASES_CREATE_PATH = '/create' as const; +export const CASES_CONFIGURE_PATH = '/configure' as const; +export const CASE_VIEW_PATH = '/:detailName' as const; +export const CASE_VIEW_COMMENT_PATH = `${CASE_VIEW_PATH}/:commentId` as const; +export const CASE_VIEW_ALERT_TABLE_PATH = + `${CASE_VIEW_PATH}/?tabId=${CASE_VIEW_PAGE_TABS.ALERTS}` as const; +export const CASE_VIEW_TAB_PATH = `${CASE_VIEW_PATH}/?tabId=:tabId` as const; + /** * The main Cases application is in the stack management under the * Alerts and Insights section. To do that, Cases registers to the management @@ -106,16 +115,19 @@ export const OWNER_INFO = { appId: 'securitySolutionUI', label: 'Security', iconType: 'logoSecurity', + appRoute: '/app/security', }, [OBSERVABILITY_OWNER]: { appId: 'observability-overview', label: 'Observability', iconType: 'logoObservability', + appRoute: '/app/observability', }, [GENERAL_CASES_OWNER]: { appId: 'management', label: 'Stack', iconType: 'casesApp', + appRoute: '/app/management/insightsAndAlerting', }, } as const; diff --git a/x-pack/plugins/cases/common/types.ts b/x-pack/plugins/cases/common/types.ts index b6fb90d2923fd..95525bd980927 100644 --- a/x-pack/plugins/cases/common/types.ts +++ b/x-pack/plugins/cases/common/types.ts @@ -14,3 +14,8 @@ export type SnakeToCamelCase = T extends Record [K in keyof T as SnakeToCamelCaseString]: SnakeToCamelCase; } : T; + +export enum CASE_VIEW_PAGE_TABS { + ALERTS = 'alerts', + ACTIVITY = 'activity', +} diff --git a/x-pack/plugins/cases/common/utils/owner.test.ts b/x-pack/plugins/cases/common/utils/owner.test.ts new file mode 100644 index 0000000000000..09016880c0a95 --- /dev/null +++ b/x-pack/plugins/cases/common/utils/owner.test.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { OWNER_INFO } from '../constants'; +import { isValidOwner } from './owner'; + +describe('isValidOwner', () => { + const owners = Object.keys(OWNER_INFO) as Array; + + it.each(owners)('returns true for valid owner: %s', (owner) => { + expect(isValidOwner(owner)).toBe(true); + }); + + it('return false for invalid owner', () => { + expect(isValidOwner('not-valid')).toBe(false); + }); +}); diff --git a/x-pack/plugins/cases/common/utils/owner.ts b/x-pack/plugins/cases/common/utils/owner.ts new file mode 100644 index 0000000000000..44068f36f0d3f --- /dev/null +++ b/x-pack/plugins/cases/common/utils/owner.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { OWNER_INFO } from '../constants'; + +export const isValidOwner = (owner: string): owner is keyof typeof OWNER_INFO => + Object.keys(OWNER_INFO).includes(owner); diff --git a/x-pack/plugins/cases/public/common/navigation/hooks.ts b/x-pack/plugins/cases/public/common/navigation/hooks.ts index 1197ef0c5cf11..31048e33d50eb 100644 --- a/x-pack/plugins/cases/public/common/navigation/hooks.ts +++ b/x-pack/plugins/cases/public/common/navigation/hooks.ts @@ -9,17 +9,11 @@ import { useCallback, useEffect, useState } from 'react'; import { useLocation, useParams } from 'react-router-dom'; import { parse, stringify } from 'query-string'; -import { APP_ID } from '../../../common/constants'; +import { APP_ID, CASES_CONFIGURE_PATH, CASES_CREATE_PATH } from '../../../common/constants'; import { useNavigation } from '../lib/kibana'; import { useCasesContext } from '../../components/cases_context/use_cases_context'; import { ICasesDeepLinkId } from './deep_links'; -import { - CASES_CONFIGURE_PATH, - CASES_CREATE_PATH, - CaseViewPathParams, - CaseViewPathSearchParams, - generateCaseViewPath, -} from './paths'; +import { CaseViewPathParams, CaseViewPathSearchParams, generateCaseViewPath } from './paths'; export const useCaseViewParams = () => useParams(); diff --git a/x-pack/plugins/cases/public/common/navigation/paths.ts b/x-pack/plugins/cases/public/common/navigation/paths.ts index 857f832f7aed3..883ef44567e10 100644 --- a/x-pack/plugins/cases/public/common/navigation/paths.ts +++ b/x-pack/plugins/cases/public/common/navigation/paths.ts @@ -6,7 +6,14 @@ */ import { generatePath } from 'react-router-dom'; -import { CASE_VIEW_PAGE_TABS } from '../../components/case_view/types'; +import { + CASES_CREATE_PATH, + CASES_CONFIGURE_PATH, + CASE_VIEW_PATH, + CASE_VIEW_COMMENT_PATH, + CASE_VIEW_TAB_PATH, +} from '../../../common/constants'; +import { CASE_VIEW_PAGE_TABS } from '../../../common/types'; export const DEFAULT_BASE_PATH = '/cases'; @@ -19,14 +26,6 @@ export type CaseViewPathParams = { commentId?: string; } & CaseViewPathSearchParams; -export const CASES_CREATE_PATH = '/create' as const; -export const CASES_CONFIGURE_PATH = '/configure' as const; -export const CASE_VIEW_PATH = '/:detailName' as const; -export const CASE_VIEW_COMMENT_PATH = `${CASE_VIEW_PATH}/:commentId` as const; -export const CASE_VIEW_ALERT_TABLE_PATH = - `${CASE_VIEW_PATH}/?tabId=${CASE_VIEW_PAGE_TABS.ALERTS}` as const; -export const CASE_VIEW_TAB_PATH = `${CASE_VIEW_PATH}/?tabId=:tabId` as const; - const normalizePath = (path: string): string => path.replaceAll('//', '/'); export const getCreateCasePath = (casesBasePath: string) => diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.tsx index 42a358897bc62..18732c5240976 100644 --- a/x-pack/plugins/cases/public/common/use_cases_toast.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_toast.tsx @@ -10,6 +10,7 @@ import { EuiButtonEmpty, EuiText } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { isValidOwner } from '../../common/utils/owner'; import { Case, CommentType } from '../../common'; import { useKibana, useToasts } from './lib/kibana'; import { generateCaseViewPath } from './navigation'; @@ -96,9 +97,6 @@ function getToastContent({ return undefined; } -const isValidOwner = (owner: string): owner is keyof typeof OWNER_INFO => - Object.keys(OWNER_INFO).includes(owner); - const isServerError = (error: Error | ServerError): error is ServerError => Object.hasOwn(error, 'body'); diff --git a/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx b/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx index a06ae9e772c11..5fb3eba7555a5 100644 --- a/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx @@ -31,9 +31,10 @@ import { defaultUpdateCaseState, defaultUseGetCaseUserActions, } from './mocks'; -import { CaseViewPageProps, CASE_VIEW_PAGE_TABS } from './types'; +import { CaseViewPageProps } from './types'; import { userProfiles } from '../../containers/user_profiles/api.mock'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; +import { CASE_VIEW_PAGE_TABS } from '../../../common/types'; jest.mock('../../containers/use_get_action_license'); jest.mock('../../containers/use_update_case'); diff --git a/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx b/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx index 0075fb985f113..f01deea5c0c91 100644 --- a/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx +++ b/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx @@ -8,6 +8,7 @@ import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import styled from 'styled-components'; +import { CASE_VIEW_PAGE_TABS } from '../../../common/types'; import { useCaseViewNavigation, useUrlParams } from '../../common/navigation'; import { useCasesContext } from '../cases_context/use_cases_context'; import { CaseActionBar } from '../case_action_bar'; @@ -21,7 +22,7 @@ import { CaseViewActivity } from './components/case_view_activity'; import { CaseViewAlerts } from './components/case_view_alerts'; import { CaseViewMetrics } from './metrics'; import { ACTIVITY_TAB, ALERTS_TAB } from './translations'; -import { CaseViewPageProps, CASE_VIEW_PAGE_TABS } from './types'; +import { CaseViewPageProps } from './types'; import { useRefreshCaseViewPage } from './use_on_refresh_case_view_page'; import { useOnUpdateField } from './use_on_update_field'; diff --git a/x-pack/plugins/cases/public/components/case_view/types.ts b/x-pack/plugins/cases/public/components/case_view/types.ts index 3f6e058013993..671eadc9442b2 100644 --- a/x-pack/plugins/cases/public/components/case_view/types.ts +++ b/x-pack/plugins/cases/public/components/case_view/types.ts @@ -39,8 +39,3 @@ export interface OnUpdateFields { onSuccess?: () => void; onError?: () => void; } - -export enum CASE_VIEW_PAGE_TABS { - ALERTS = 'alerts', - ACTIVITY = 'activity', -} diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/show_alert_table_link.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/show_alert_table_link.tsx index 3ec52e83e5dda..257a639e4e5cd 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/show_alert_table_link.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/show_alert_table_link.tsx @@ -7,8 +7,8 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { useCallback } from 'react'; +import { CASE_VIEW_PAGE_TABS } from '../../../../common/types'; import { useCaseViewNavigation, useCaseViewParams } from '../../../common/navigation'; -import { CASE_VIEW_PAGE_TABS } from '../../case_view/types'; import { SHOW_ALERT_TABLE_TOOLTIP } from '../translations'; export const ShowAlertTableLink = () => { diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 53b3b57d4e2ef..73c8866ca71a3 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -67,7 +67,7 @@ export class CasesUiPlugin plugins.management.sections.section.insightsAndAlerting.registerApp({ id: APP_ID, title: APP_TITLE, - order: 0, + order: 1, async mount(params: ManagementAppMountParams) { const [coreStart, pluginsStart] = (await core.getStartServices()) as [ CoreStart, diff --git a/x-pack/plugins/cases/server/client/cases/mock.ts b/x-pack/plugins/cases/server/client/cases/mock.ts index 3f912cda3eb3c..a7ffe2a3c8bd3 100644 --- a/x-pack/plugins/cases/server/client/cases/mock.ts +++ b/x-pack/plugins/cases/server/client/cases/mock.ts @@ -8,16 +8,15 @@ import { CommentResponse, CommentType, - ConnectorMappingsAttributes, CaseUserActionsResponse, CommentResponseAlertsType, ConnectorTypes, Actions, + ConnectorMappingsAttributes, + ExternalReferenceStorageType, } from '../../../common/api'; import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; -import { BasicParams } from './types'; - export const updateUser = { updated_at: '2020-03-13T08:34:53.450Z', updated_by: { full_name: 'Another User', username: 'another', email: 'elastic@elastic.co' }, @@ -31,7 +30,7 @@ const entity = { }; export const comment: CommentResponse = { - id: 'mock-comment-1', + id: 'comment-user-1', comment: 'Wow, good luck catching that bad meanie!', type: CommentType.user as const, created_at: '2019-11-25T21:55:00.177Z', @@ -84,7 +83,7 @@ export const isolateCommentActions: CommentResponse = { }; export const releaseCommentActions: CommentResponse = { - id: 'mock-action-comment-1', + id: 'mock-action-comment-2', comment: 'Releasing this for investigation', type: CommentType.actions as const, created_at: '2019-11-25T21:55:00.177Z', @@ -115,7 +114,7 @@ export const releaseCommentActions: CommentResponse = { }; export const isolateCommentActionsMultipleTargets: CommentResponse = { - id: 'mock-action-comment-1', + id: 'mock-action-comment-3', comment: 'Isolating this for investigation', type: CommentType.actions as const, created_at: '2019-11-25T21:55:00.177Z', @@ -150,7 +149,7 @@ export const isolateCommentActionsMultipleTargets: CommentResponse = { }; export const commentAlert: CommentResponse = { - id: 'mock-comment-1', + id: 'comment-alert-1', alertId: 'alert-id-1', index: 'alert-index-1', rule: { @@ -178,15 +177,64 @@ export const commentAlert: CommentResponse = { export const commentAlertMultipleIds: CommentResponseAlertsType = { ...commentAlert, - id: 'mock-comment-2', + id: 'comment-alert-2', alertId: ['alert-id-1', 'alert-id-2'], index: 'alert-index-1', type: CommentType.alert as const, owner: SECURITY_SOLUTION_OWNER, }; -export const defaultPipes = ['informationCreated']; -export const basicParams: BasicParams = { +export const commentExternalReference: CommentResponse = { + id: 'comment-external-reference-1', + type: CommentType.externalReference as const, + externalReferenceId: 'my-id', + externalReferenceStorage: { + type: ExternalReferenceStorageType.elasticSearchDoc as const, + }, + externalReferenceAttachmentTypeId: '.test', + externalReferenceMetadata: null, + created_at: '2019-11-25T21:55:00.177Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + owner: SECURITY_SOLUTION_OWNER, + pushed_at: null, + pushed_by: null, + updated_at: '2019-11-25T21:55:00.177Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + version: 'WzEsMV0=', +}; + +export const commentPersistableState: CommentResponse = { + id: 'comment-persistable-state-1', + type: CommentType.persistableState, + persistableStateAttachmentTypeId: '.test', + persistableStateAttachmentState: { foo: 'foo', injectedId: 'testRef' }, + created_at: '2019-11-25T21:55:00.177Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + owner: SECURITY_SOLUTION_OWNER, + pushed_at: null, + pushed_by: null, + updated_at: '2019-11-25T21:55:00.177Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + version: 'WzEsMV0=', +}; + +export const basicParams = { description: 'a description', title: 'a title', ...entity, @@ -286,16 +334,16 @@ export const userActions: CaseUserActionsResponse = [ }, payload: { comment: { - type: CommentType.alert, - alertId: 'alert-id-1', - index: '.siem-signals-default-000008', - rule: { id: '123', name: 'rule name' }, - owner: SECURITY_SOLUTION_OWNER, + type: commentAlert.type, + alertId: commentAlert.alertId, + index: commentAlert.index, + rule: commentAlert.rule, + owner: commentAlert.owner, }, }, action_id: '7373eb60-6647-11eb-a291-51bf6b175a53', case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', - comment_id: 'comment-alert-1', + comment_id: commentAlert.id, owner: SECURITY_SOLUTION_OWNER, }, { @@ -309,16 +357,129 @@ export const userActions: CaseUserActionsResponse = [ }, payload: { comment: { - type: CommentType.alert, - alertId: 'alert-id-2', - index: '.siem-signals-default-000008', - rule: { id: '123', name: 'rule name' }, - owner: SECURITY_SOLUTION_OWNER, + type: commentAlertMultipleIds.type, + alertId: commentAlertMultipleIds.alertId, + index: commentAlertMultipleIds.index, + rule: commentAlertMultipleIds.rule, + owner: commentAlertMultipleIds.owner, }, }, action_id: '7abc6410-6647-11eb-a291-51bf6b175a53', case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', - comment_id: 'comment-alert-2', + comment_id: commentAlertMultipleIds.id, + owner: SECURITY_SOLUTION_OWNER, + }, + { + type: 'comment', + action: Actions.create, + created_at: '2021-02-03T17:48:30.616Z', + created_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }, + payload: { + comment: { + type: isolateCommentActions.type, + owner: isolateCommentActions.owner, + comment: isolateCommentActions.comment, + actions: isolateCommentActions.actions, + }, + }, + action_id: '0818e5e0-6648-11eb-a291-51bf6b175a53', + case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', + comment_id: isolateCommentActions.id, + owner: SECURITY_SOLUTION_OWNER, + }, + { + type: 'comment', + action: Actions.create, + created_at: '2021-02-03T17:48:30.616Z', + created_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }, + payload: { + comment: { + type: releaseCommentActions.type, + owner: releaseCommentActions.owner, + comment: releaseCommentActions.comment, + actions: releaseCommentActions.actions, + }, + }, + action_id: '0818e5e0-6648-11eb-a291-51bf6b175a53', + case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', + comment_id: releaseCommentActions.id, + owner: SECURITY_SOLUTION_OWNER, + }, + { + type: 'comment', + action: Actions.create, + created_at: '2021-02-03T17:48:30.616Z', + created_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }, + payload: { + comment: { + type: isolateCommentActionsMultipleTargets.type, + owner: isolateCommentActionsMultipleTargets.owner, + comment: isolateCommentActionsMultipleTargets.comment, + actions: isolateCommentActionsMultipleTargets.actions, + }, + }, + action_id: '0818e5e0-6648-11eb-a291-51bf6b175a53', + case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', + comment_id: isolateCommentActionsMultipleTargets.id, + owner: SECURITY_SOLUTION_OWNER, + }, + { + type: 'comment', + action: Actions.create, + created_at: '2021-02-03T17:48:30.616Z', + created_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }, + payload: { + comment: { + type: commentExternalReference.type, + owner: commentExternalReference.owner, + externalReferenceId: commentExternalReference.externalReferenceId, + externalReferenceAttachmentTypeId: + commentExternalReference.externalReferenceAttachmentTypeId, + externalReferenceMetadata: commentExternalReference.externalReferenceMetadata, + externalReferenceStorage: commentExternalReference.externalReferenceStorage, + }, + }, + action_id: '0818e5e0-6648-11eb-a291-51bf6b175a53', + case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', + comment_id: commentExternalReference.id, + owner: SECURITY_SOLUTION_OWNER, + }, + { + type: 'comment', + action: Actions.create, + created_at: '2021-02-03T17:48:30.616Z', + created_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }, + payload: { + comment: { + type: commentPersistableState.type, + owner: commentPersistableState.owner, + persistableStateAttachmentState: commentPersistableState.persistableStateAttachmentState, + persistableStateAttachmentTypeId: commentPersistableState.persistableStateAttachmentTypeId, + }, + }, + action_id: '0818e5e0-6648-11eb-a291-51bf6b175a53', + case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', + comment_id: commentPersistableState.id, owner: SECURITY_SOLUTION_OWNER, }, { @@ -357,11 +518,11 @@ export const userActions: CaseUserActionsResponse = [ username: 'elastic', }, payload: { - comment: { comment: 'a comment!', type: CommentType.user, owner: SECURITY_SOLUTION_OWNER }, + comment: { comment: comment.comment, type: comment.type, owner: comment.owner }, }, action_id: '0818e5e0-6648-11eb-a291-51bf6b175a53', case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', - comment_id: 'comment-user-1', + comment_id: comment.id, owner: SECURITY_SOLUTION_OWNER, }, ]; diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 6ddea5dd01bc9..6cca689057680 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -22,10 +22,11 @@ import { OWNER_FIELD, CommentType, CommentRequestAlertType, + CommentAttributes, } from '../../../common/api'; import { CASE_COMMENT_SAVED_OBJECT } from '../../../common/constants'; -import { createIncident, getCommentContextFromAttributes, getDurationInSeconds } from './utils'; +import { createIncident, getDurationInSeconds } from './utils'; import { createCaseError } from '../../common/error'; import { createAlertUpdateRequest, @@ -114,6 +115,7 @@ export const push = async ( logger, authorization, securityStartPlugin, + publicBaseUrl, } = clientArgs; try { @@ -139,32 +141,17 @@ export const push = async ( } const alertsInfo = getAlertInfoFromComments(theCase?.comments); - const alerts = await getAlerts(alertsInfo, clientArgs); - - const getMappingsResponse = await casesClientInternal.configuration.getMappings({ - connector: theCase.connector, - }); - - const mappings = - getMappingsResponse.length === 0 - ? await casesClientInternal.configuration.createMappings({ - connector: theCase.connector, - owner: theCase.owner, - }) - : getMappingsResponse[0].attributes.mappings; - const profiles = await getProfiles(theCase, securityStartPlugin); const externalServiceIncident = await createIncident({ - actionsClient, theCase, userActions, connector: connector as ActionConnector, - mappings, alerts, casesConnectors, userProfiles: profiles, + publicBaseUrl, }); const pushRes = await actionsClient.execute({ @@ -308,8 +295,7 @@ export const push = async ( attributes: { ...origComment.attributes, ...updatedComment?.attributes, - ...getCommentContextFromAttributes(origComment.attributes), - }, + } as CommentAttributes, version: updatedComment?.version ?? origComment.version, references: origComment?.references ?? [], }; diff --git a/x-pack/plugins/cases/server/client/cases/translations.ts b/x-pack/plugins/cases/server/client/cases/translations.ts new file mode 100644 index 0000000000000..819f5fd37ac14 --- /dev/null +++ b/x-pack/plugins/cases/server/client/cases/translations.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const ADDED_BY = (user: string) => + i18n.translate('xpack.cases.server.addedBy', { + defaultMessage: 'Added by {user}', + values: { user }, + }); + +export const VIEW_IN_KIBANA = i18n.translate('xpack.cases.server.viewCaseInKibana', { + defaultMessage: 'For more details, view this case in Kibana', +}); + +export const VIEW_ALERTS_IN_KIBANA = i18n.translate('xpack.cases.server.viewAlertsInKibana', { + defaultMessage: 'For more details, view the alerts in Kibana', +}); + +export const CASE_URL = (url: string) => + i18n.translate('xpack.cases.server.caseUrl', { + defaultMessage: 'Case URL: {url}', + values: { url }, + }); + +export const ALERTS_URL = (url: string) => + i18n.translate('xpack.cases.server.alertsUrl', { + defaultMessage: 'Alerts URL: {url}', + values: { url }, + }); + +export const UNKNOWN = i18n.translate('xpack.cases.server.unknown', { + defaultMessage: 'Unknown', +}); diff --git a/x-pack/plugins/cases/server/client/cases/types.ts b/x-pack/plugins/cases/server/client/cases/types.ts index 6d56fa28dca59..0232d001dadb9 100644 --- a/x-pack/plugins/cases/server/client/cases/types.ts +++ b/x-pack/plugins/cases/server/client/cases/types.ts @@ -5,78 +5,14 @@ * 2.0. */ -import { - PushToServiceApiParams as JiraPushToServiceApiParams, - Incident as JiraIncident, -} from '@kbn/stack-connectors-plugin/server/connector_types/cases/jira/types'; -import { - PushToServiceApiParams as ResilientPushToServiceApiParams, - Incident as ResilientIncident, -} from '@kbn/stack-connectors-plugin/server/connector_types/cases/resilient/types'; -import { - PushToServiceApiParamsITSM as ServiceNowITSMPushToServiceApiParams, - PushToServiceApiParamsSIR as ServiceNowSIRPushToServiceApiParams, - ServiceNowITSMIncident, -} from '@kbn/stack-connectors-plugin/server/connector_types/lib/servicenow/types'; -import { UserProfile } from '@kbn/security-plugin/common'; -import { CaseResponse, ConnectorMappingsAttributes } from '../../../common/api'; - -export type Incident = JiraIncident | ResilientIncident | ServiceNowITSMIncident; -export type PushToServiceApiParams = - | JiraPushToServiceApiParams - | ResilientPushToServiceApiParams - | ServiceNowITSMPushToServiceApiParams - | ServiceNowSIRPushToServiceApiParams; - export type ExternalServiceParams = Record; -export interface BasicParams { - title: CaseResponse['title']; - description: CaseResponse['description']; - createdAt: CaseResponse['created_at']; - createdBy: CaseResponse['created_by']; - updatedAt: CaseResponse['updated_at']; - updatedBy: CaseResponse['updated_by']; -} - -export interface PipedField { - actionType: string; - key: string; - pipes: string[]; - value: string; -} -export interface PrepareFieldsForTransformArgs { - defaultPipes: string[]; - mappings: ConnectorMappingsAttributes[]; - params: { title: string; description: string }; -} -export interface EntityInformation { - createdAt: CaseResponse['created_at']; - createdBy: CaseResponse['created_by']; - updatedAt: CaseResponse['updated_at']; - updatedBy: CaseResponse['updated_by']; -} -export interface TransformerArgs { - date?: string; - previousValue?: string; - user?: string; - value: string; -} - -export type Transformer = (args: TransformerArgs) => TransformerArgs; -export interface TransformFieldsArgs { - currentIncident?: S; - fields: PipedField[]; - params: P; - userProfiles?: Map; -} - export interface ExternalServiceComment { comment: string; commentId: string; } -export interface MapIncident { +export interface ExternalServiceIncident { incident: ExternalServiceParams; comments: ExternalServiceComment[]; } diff --git a/x-pack/plugins/cases/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts index 3b00d4e284c21..2db72682224de 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.test.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts @@ -5,21 +5,19 @@ * 2.0. */ -import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client.mock'; import { mockCases } from '../../routes/api/__fixtures__'; -import { BasicParams, ExternalServiceParams, Incident } from './types'; import { comment as commentObj, - mappings, - defaultPipes, - basicParams, userActions, commentAlert, commentAlertMultipleIds, + mappings, isolateCommentActions, releaseCommentActions, isolateCommentActionsMultipleTargets, + commentExternalReference, + commentPersistableState, } from './mock'; import { @@ -29,10 +27,9 @@ import { getDurationForUpdate, getEntity, getLatestPushInfo, - prepareFieldsForTransformation, - transformComments, - transformers, - transformFields, + mapCaseFieldsToExternalSystemFields, + formatComments, + addKibanaInformationToDescription, } from './utils'; import { Actions, CaseStatuses } from '../../../common/api'; import { flattenCaseSavedObject } from '../../common/utils'; @@ -40,58 +37,16 @@ import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { casesConnectors } from '../../connectors'; import { userProfiles, userProfilesMap } from '../user_profiles.mock'; -const formatComment = { - commentId: commentObj.id, - comment: 'Wow, good luck catching that bad meanie!', -}; - -const formatIsolateActionComment = { - commentId: isolateCommentActions.id, - comment: 'Isolating this for investigation', - actions: { - targets: [ - { - hostname: 'windows-host-1', - endpointId: '123', - }, - ], - type: 'isolate', - }, -}; - -const formatReleaseActionComment = { - commentId: releaseCommentActions.id, - comment: 'Releasing this for investigation', - actions: { - targets: [ - { - hostname: 'windows-host-1', - endpointId: '123', - }, - ], - type: 'unisolate', - }, -}; - -const formatIsolateCommentActionsMultipleTargets = { - commentId: isolateCommentActionsMultipleTargets.id, - comment: 'Isolating this for investigation', - actions: { - targets: [ - { - hostname: 'windows-host-1', - endpointId: '123', - }, - { - hostname: 'windows-host-2', - endpointId: '456', - }, - ], - type: 'isolate', - }, -}; - -const params = { ...basicParams }; +const allComments = [ + commentObj, + commentAlert, + commentAlertMultipleIds, + isolateCommentActions, + releaseCommentActions, + isolateCommentActionsMultipleTargets, + commentExternalReference, + commentPersistableState, +]; describe('utils', () => { describe('dedupAssignees', () => { @@ -114,420 +69,7 @@ describe('utils', () => { }); }); - describe('prepareFieldsForTransformation', () => { - test('prepare fields with defaults', () => { - const res = prepareFieldsForTransformation({ - defaultPipes, - params, - mappings, - }); - - expect(res).toEqual([ - { - actionType: 'overwrite', - key: 'short_description', - pipes: [], - value: 'a title', - }, - { - actionType: 'append', - key: 'description', - pipes: ['informationCreated', 'append'], - value: 'a description', - }, - ]); - }); - - test('prepare fields with default pipes', () => { - const res = prepareFieldsForTransformation({ - defaultPipes: ['myTestPipe'], - mappings, - params, - }); - - expect(res).toEqual([ - { - actionType: 'overwrite', - key: 'short_description', - pipes: [], - value: 'a title', - }, - { - actionType: 'append', - key: 'description', - pipes: ['myTestPipe', 'append'], - value: 'a description', - }, - ]); - }); - }); - - describe('transformFields', () => { - test('transform fields for creation correctly', () => { - const fields = prepareFieldsForTransformation({ - defaultPipes, - mappings, - params, - }); - - const res = transformFields({ - params, - fields, - }); - - expect(res).toEqual({ - short_description: 'a title', - description: 'a description (created at 2020-03-13T08:34:53.450Z by Elastic User)', - }); - }); - - test('transform fields for update correctly', () => { - const fields = prepareFieldsForTransformation({ - params, - mappings, - defaultPipes: ['informationUpdated'], - }); - - const res = transformFields({ - params: { - ...params, - updatedAt: '2020-03-15T08:34:53.450Z', - updatedBy: { - username: 'anotherUser', - full_name: 'Another User', - email: 'elastic@elastic.co', - }, - }, - fields, - currentIncident: { - short_description: 'first title', - description: 'first description (created at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - - expect(res).toEqual({ - short_description: 'a title', - description: - 'first description (created at 2020-03-13T08:34:53.450Z by Elastic User) \r\na description (updated at 2020-03-15T08:34:53.450Z by Another User)', - }); - }); - - test('add newline character to description', () => { - const fields = prepareFieldsForTransformation({ - params, - mappings, - defaultPipes: ['informationUpdated'], - }); - - const res = transformFields({ - params, - fields, - currentIncident: { - short_description: 'first title', - description: 'first description', - }, - }); - expect(res.description?.includes('\r\n')).toBe(true); - }); - - test('append username if fullname is undefined when create', () => { - const fields = prepareFieldsForTransformation({ - defaultPipes, - mappings, - params, - }); - - const res = transformFields({ - params: { - ...params, - createdBy: { full_name: '', username: 'elastic', email: 'elastic@elastic.co' }, - }, - fields, - }); - - expect(res).toEqual({ - short_description: 'a title', - description: 'a description (created at 2020-03-13T08:34:53.450Z by elastic)', - }); - }); - - test('append username if fullname is undefined when update', () => { - const fields = prepareFieldsForTransformation({ - defaultPipes: ['informationUpdated'], - mappings, - params, - }); - - const res = transformFields({ - params: { - ...params, - updatedAt: '2020-03-15T08:34:53.450Z', - updatedBy: { username: 'anotherUser', full_name: '', email: 'elastic@elastic.co' }, - }, - fields, - }); - - expect(res).toEqual({ - short_description: 'a title', - description: 'a description (updated at 2020-03-15T08:34:53.450Z by anotherUser)', - }); - }); - }); - - describe('transformComments', () => { - test('transform creation comments', () => { - const comments = [commentObj]; - const res = transformComments(comments, ['informationCreated'], new Map()); - expect(res).toEqual([ - { - ...formatComment, - comment: `${formatComment.comment} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`, - }, - ]); - }); - - test('transform update comments', () => { - const comments = [ - { - ...commentObj, - updated_at: '2020-03-13T08:34:53.450Z', - updated_by: { - full_name: 'Another User', - username: 'another', - email: 'elastic@elastic.co', - }, - }, - ]; - const res = transformComments(comments, ['informationUpdated'], new Map()); - expect(res).toEqual([ - { - ...formatComment, - comment: `${formatComment.comment} (updated at ${comments[0].updated_at} by ${comments[0].updated_by.full_name})`, - }, - ]); - }); - - test('transform added comments', () => { - const comments = [commentObj]; - const res = transformComments(comments, ['informationAdded'], new Map()); - expect(res).toEqual([ - { - ...formatComment, - comment: `${formatComment.comment} (added at ${comments[0].created_at} by ${comments[0].created_by.full_name})`, - }, - ]); - }); - - test('transform comments without fullname', () => { - const comments = [{ ...commentObj, createdBy: { username: commentObj.created_by.username } }]; - // @ts-ignore testing no full_name - const res = transformComments(comments, ['informationAdded']); - expect(res).toEqual([ - { - ...formatComment, - comment: `${formatComment.comment} (added at ${comments[0].created_at} by ${comments[0].created_by.username})`, - }, - ]); - }); - - test('adds update user correctly', () => { - const comments = [ - { - ...commentObj, - updated_at: '2020-04-13T08:34:53.450Z', - updated_by: { full_name: 'Elastic2', username: 'elastic', email: 'elastic@elastic.co' }, - }, - ]; - const res = transformComments(comments, ['informationAdded'], new Map()); - expect(res).toEqual([ - { - ...formatComment, - comment: `${formatComment.comment} (added at ${comments[0].updated_at} by ${comments[0].updated_by.full_name})`, - }, - ]); - }); - - test('adds update user with empty fullname correctly', () => { - const comments = [ - { - ...commentObj, - updated_at: '2020-04-13T08:34:53.450Z', - updated_by: { full_name: '', username: 'elastic2', email: 'elastic@elastic.co' }, - }, - ]; - const res = transformComments(comments, ['informationAdded'], new Map()); - expect(res).toEqual([ - { - ...formatComment, - comment: `${formatComment.comment} (added at ${comments[0].updated_at} by ${comments[0].updated_by.username})`, - }, - ]); - }); - - test('transform isolate action comment', () => { - const comments = [isolateCommentActions]; - const res = transformComments(comments, ['informationCreated'], new Map()); - const actionText = `Isolated host ${formatIsolateActionComment.actions.targets[0].hostname} with comment: ${formatIsolateActionComment.comment}`; - expect(res).toEqual([ - { - commentId: formatIsolateActionComment.commentId, - comment: `${actionText} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`, - }, - ]); - }); - - test('transform release action comment', () => { - const comments = [releaseCommentActions]; - const res = transformComments(comments, ['informationCreated'], new Map()); - const actionText = `Released host ${formatReleaseActionComment.actions.targets[0].hostname} with comment: ${formatReleaseActionComment.comment}`; - expect(res).toEqual([ - { - commentId: formatReleaseActionComment.commentId, - comment: `${actionText} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`, - }, - ]); - }); - - test('transform isolate action comment with multiple hosts', () => { - const comments = [isolateCommentActionsMultipleTargets]; - const res = transformComments(comments, ['informationCreated'], new Map()); - const actionText = `Isolated host ${formatIsolateCommentActionsMultipleTargets.actions.targets[0].hostname} and 1 more with comment: ${formatIsolateCommentActionsMultipleTargets.comment}`; - expect(res).toEqual([ - { - commentId: formatIsolateCommentActionsMultipleTargets.commentId, - comment: `${actionText} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`, - }, - ]); - }); - }); - - describe('transformers', () => { - const { informationCreated, informationUpdated, informationAdded, append } = transformers; - describe('informationCreated', () => { - test('transforms correctly', () => { - const res = informationCreated({ - value: 'a value', - date: '2020-04-15T08:19:27.400Z', - user: 'elastic', - }); - expect(res).toEqual({ value: 'a value (created at 2020-04-15T08:19:27.400Z by elastic)' }); - }); - - test('transforms correctly without optional fields', () => { - const res = informationCreated({ - value: 'a value', - }); - expect(res).toEqual({ value: 'a value (created at by )' }); - }); - - test('returns correctly rest fields', () => { - const res = informationCreated({ - value: 'a value', - date: '2020-04-15T08:19:27.400Z', - user: 'elastic', - previousValue: 'previous value', - }); - expect(res).toEqual({ - value: 'a value (created at 2020-04-15T08:19:27.400Z by elastic)', - previousValue: 'previous value', - }); - }); - }); - - describe('informationUpdated', () => { - test('transforms correctly', () => { - const res = informationUpdated({ - value: 'a value', - date: '2020-04-15T08:19:27.400Z', - user: 'elastic', - }); - expect(res).toEqual({ value: 'a value (updated at 2020-04-15T08:19:27.400Z by elastic)' }); - }); - - test('transforms correctly without optional fields', () => { - const res = informationUpdated({ - value: 'a value', - }); - expect(res).toEqual({ value: 'a value (updated at by )' }); - }); - - test('returns correctly rest fields', () => { - const res = informationUpdated({ - value: 'a value', - date: '2020-04-15T08:19:27.400Z', - user: 'elastic', - previousValue: 'previous value', - }); - expect(res).toEqual({ - value: 'a value (updated at 2020-04-15T08:19:27.400Z by elastic)', - previousValue: 'previous value', - }); - }); - }); - - describe('informationAdded', () => { - test('transforms correctly', () => { - const res = informationAdded({ - value: 'a value', - date: '2020-04-15T08:19:27.400Z', - user: 'elastic', - }); - expect(res).toEqual({ value: 'a value (added at 2020-04-15T08:19:27.400Z by elastic)' }); - }); - - test('transforms correctly without optional fields', () => { - const res = informationAdded({ - value: 'a value', - }); - expect(res).toEqual({ value: 'a value (added at by )' }); - }); - - test('returns correctly rest fields', () => { - const res = informationAdded({ - value: 'a value', - date: '2020-04-15T08:19:27.400Z', - user: 'elastic', - previousValue: 'previous value', - }); - expect(res).toEqual({ - value: 'a value (added at 2020-04-15T08:19:27.400Z by elastic)', - previousValue: 'previous value', - }); - }); - }); - - describe('append', () => { - test('transforms correctly', () => { - const res = append({ - value: 'a value', - previousValue: 'previous value', - }); - expect(res).toEqual({ value: 'previous value \r\na value' }); - }); - - test('transforms correctly without optional fields', () => { - const res = append({ - value: 'a value', - }); - expect(res).toEqual({ value: 'a value' }); - }); - - test('returns correctly rest fields', () => { - const res = append({ - value: 'a value', - user: 'elastic', - previousValue: 'previous value', - }); - expect(res).toEqual({ - value: 'previous value \r\na value', - user: 'elastic', - }); - }); - }); - }); - describe('createIncident', () => { - let actionsMock = actionsClientMock.create(); const theCase = { ...flattenCaseSavedObject({ savedObject: mockCases[0], @@ -549,11 +91,9 @@ describe('utils', () => { it('creates an external incident', async () => { const res = await createIncident({ - actionsClient: actionsMock, theCase, userActions: [], connector, - mappings, alerts: [], casesConnectors, }); @@ -564,94 +104,81 @@ describe('utils', () => { labels: ['defacement'], issueType: null, parent: null, - short_description: 'Super Bad Security Issue', + summary: 'Super Bad Security Issue', description: - 'This is a brand new case of a bad meanie defacing data (created at 2019-11-25T21:54:48.952Z by elastic)', + 'This is a brand new case of a bad meanie defacing data\n\nAdded by elastic.', externalId: null, }, comments: [], }); }); - it('it creates comments correctly', async () => { + it('formats the connector fields correctly', async () => { + const caseWithConnector = { + ...flattenCaseSavedObject({ + savedObject: mockCases[2], + }), + comments: [], + totalComments: 0, + }; + const res = await createIncident({ - actionsClient: actionsMock, - theCase: { - ...theCase, - comments: [{ ...commentObj, id: 'comment-user-1' }], - }, - userActions, + theCase: caseWithConnector, + userActions: [], connector, - mappings, alerts: [], casesConnectors, }); - expect(res.comments).toEqual([ - { - comment: - 'Wow, good luck catching that bad meanie! (added at 2019-11-25T21:55:00.177Z by elastic)', - commentId: 'comment-user-1', + expect(res).toEqual({ + incident: { + priority: 'High', + labels: ['LOLBins'], + issueType: 'Task', + parent: null, + summary: 'Another bad one', + description: 'Oh no, a bad meanie going LOLBins all over the place!\n\nAdded by elastic.', + externalId: null, }, - ]); + comments: [], + }); }); - it('it does NOT creates comments when mapping is nothing', async () => { + it('creates comments correctly', async () => { const res = await createIncident({ - actionsClient: actionsMock, theCase: { ...theCase, - comments: [{ ...commentObj, id: 'comment-user-1' }], + comments: [commentObj], }, userActions, connector, - mappings: [ - mappings[0], - mappings[1], - { - source: 'comments', - target: 'comments', - action_type: 'nothing', - }, - ], alerts: [], casesConnectors, }); - expect(res.comments).toEqual([]); + expect(res.comments).toEqual([ + { + comment: 'Wow, good luck catching that bad meanie!\n\nAdded by elastic.', + commentId: 'comment-user-1', + }, + ]); }); - it('it adds the total alert comments correctly', async () => { + it('adds the total alert comments correctly', async () => { const res = await createIncident({ - actionsClient: actionsMock, theCase: { ...theCase, - comments: [ - { ...commentObj, id: 'comment-user-1' }, - { ...commentAlert, id: 'comment-alert-1' }, - { - ...commentAlertMultipleIds, - }, - ], + comments: [commentObj, commentAlert, commentAlertMultipleIds], }, userActions, connector, - mappings: [ - ...mappings, - { - source: 'comments', - target: 'comments', - action_type: 'nothing', - }, - ], alerts: [], casesConnectors, }); expect(res.comments).toEqual([ { - comment: - 'Wow, good luck catching that bad meanie! (added at 2019-11-25T21:55:00.177Z by elastic)', + comment: 'Wow, good luck catching that bad meanie!\n\nAdded by elastic.', commentId: 'comment-user-1', }, { @@ -661,24 +188,21 @@ describe('utils', () => { ]); }); - it('it filters out the alerts from the comments correctly', async () => { + it('filters out the alerts from the comments correctly', async () => { const res = await createIncident({ - actionsClient: actionsMock, theCase: { ...theCase, comments: [{ ...commentObj, id: 'comment-user-1' }, commentAlertMultipleIds], }, userActions, connector, - mappings, alerts: [], casesConnectors, }); expect(res.comments).toEqual([ { - comment: - 'Wow, good luck catching that bad meanie! (added at 2019-11-25T21:55:00.177Z by elastic)', + comment: 'Wow, good luck catching that bad meanie!\n\nAdded by elastic.', commentId: 'comment-user-1', }, { @@ -690,7 +214,6 @@ describe('utils', () => { it('does not add the alerts count comment if all alerts have been pushed', async () => { const res = await createIncident({ - actionsClient: actionsMock, theCase: { ...theCase, comments: [ @@ -700,40 +223,26 @@ describe('utils', () => { }, userActions, connector, - mappings, alerts: [], casesConnectors, }); expect(res.comments).toEqual([ { - comment: - 'Wow, good luck catching that bad meanie! (added at 2019-11-25T21:55:00.177Z by elastic)', + comment: 'Wow, good luck catching that bad meanie!\n\nAdded by elastic.', commentId: 'comment-user-1', }, ]); }); - it('updates an existing incident', async () => { - const existingIncidentData = { - priority: null, - issueType: null, - parent: null, - short_description: 'fun title', - description: 'fun description', - }; - - const execute = jest.fn().mockReturnValue(existingIncidentData); - actionsMock = { ...actionsMock, execute }; - + it('adds the backlink to cases correctly', async () => { const res = await createIncident({ - actionsClient: actionsMock, theCase, - userActions, + userActions: [], connector, - mappings, alerts: [], casesConnectors, + publicBaseUrl: 'https://example.com', }); expect(res).toEqual({ @@ -742,238 +251,599 @@ describe('utils', () => { labels: ['defacement'], issueType: null, parent: null, + summary: 'Super Bad Security Issue', description: - 'fun description \r\nThis is a brand new case of a bad meanie defacing data (updated at 2019-11-25T21:54:48.952Z by elastic)', - externalId: 'external-id', - short_description: 'Super Bad Security Issue', + 'This is a brand new case of a bad meanie defacing data\n\nAdded by elastic.\nFor more details, view this case in Kibana.\nCase URL: https://example.com/app/security/cases/mock-id-1', + externalId: null, }, comments: [], }); }); - it('throws error when existing incident throws', async () => { - expect.assertions(2); - const execute = jest.fn().mockImplementation(() => { - throw new Error('exception'); + it('adds the user profile correctly to description', async () => { + const res = await createIncident({ + theCase: { + ...theCase, + created_by: { ...theCase.created_by, profile_uid: userProfiles[0].uid }, + updated_by: null, + }, + userActions: [], + connector, + alerts: [], + casesConnectors, + userProfiles: userProfilesMap, }); - actionsMock = { ...actionsMock, execute }; - createIncident({ - actionsClient: actionsMock, - theCase, + expect(res).toEqual({ + incident: { + priority: null, + labels: ['defacement'], + issueType: null, + parent: null, + summary: 'Super Bad Security Issue', + description: + 'This is a brand new case of a bad meanie defacing data\n\nAdded by Damaged Raccoon.', + externalId: null, + }, + comments: [], + }); + }); + + it('adds the user profile correctly to comments', async () => { + const res = await createIncident({ + theCase: { + ...theCase, + created_by: { ...theCase.created_by, profile_uid: userProfiles[0].uid }, + updated_by: null, + comments: [ + { + ...commentObj, + created_by: { ...theCase.created_by, profile_uid: userProfiles[0].uid }, + updated_by: null, + }, + ], + totalComment: 1, + }, userActions, connector, - mappings, alerts: [], casesConnectors, - }).catch((e) => { - expect(e).not.toBeNull(); - expect(e).toEqual( - new Error( - `Retrieving Incident by id external-id from .jira failed with exception: Error: exception` - ) - ); + userProfiles: userProfilesMap, }); - }); - describe('getLatestPushInfo', () => { - it('it returns the latest push information correctly', async () => { - const res = getLatestPushInfo('456', userActions); - expect(res).toEqual({ - index: 4, - pushedInfo: { - connector_id: '456', - connector_name: 'ServiceNow SN', - external_id: 'external-id', - external_title: 'SIR0010037', - external_url: - 'https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id', - pushed_at: '2021-02-03T17:45:29.400Z', - pushed_by: { - email: 'elastic@elastic.co', - full_name: 'Elastic', - username: 'elastic', - }, + expect(res).toEqual({ + incident: { + priority: null, + labels: ['defacement'], + issueType: null, + parent: null, + summary: 'Super Bad Security Issue', + description: + 'This is a brand new case of a bad meanie defacing data\n\nAdded by Damaged Raccoon.', + externalId: 'external-id', + }, + comments: [ + { + comment: 'Wow, good luck catching that bad meanie!\n\nAdded by Damaged Raccoon.', + commentId: 'comment-user-1', }, - }); + ], }); + }); - it('it returns null when there are not actions', async () => { - const res = getLatestPushInfo('456', []); - expect(res).toBe(null); + it('does not map if the connector is not registered', async () => { + const res = await createIncident({ + theCase, + userActions: [], + // @ts-expect-error: not existing connector + connector: { actionTypeId: '.not-exist' }, + alerts: [], + casesConnectors, }); - it('it returns null when there are no push user action', async () => { - const res = getLatestPushInfo('456', [userActions[0]]); - expect(res).toBe(null); + expect(res).toEqual({ + incident: { + externalId: null, + }, + comments: [], }); + }); + + it('adds a backlink to the total alert comments correctly', async () => { + const res = await createIncident({ + theCase: { + ...theCase, + comments: [commentObj, commentAlert, commentAlertMultipleIds], + }, + userActions, + connector, + alerts: [], + casesConnectors, + publicBaseUrl: 'https://example.com', + }); + + expect(res.comments).toEqual([ + { + comment: 'Wow, good luck catching that bad meanie!\n\nAdded by elastic.', + commentId: 'comment-user-1', + }, + { + comment: + 'Elastic Alerts attached to the case: 3\n\nFor more details, view the alerts in Kibana\nAlerts URL: https://example.com/app/security/cases/mock-id-1/?tabId=alerts', + commentId: 'mock-id-1-total-alerts', + }, + ]); + }); + }); - it('it returns the correct push information when with multiple push on different connectors', async () => { - const res = getLatestPushInfo('456', [ - ...userActions.slice(0, 3), + describe('mapCaseFieldsToExternalSystemFields', () => { + const caseFields = { title: 'My title', description: 'my desc' }; + + it('maps correctly', () => { + expect(mapCaseFieldsToExternalSystemFields(caseFields, mappings)).toEqual({ + description: 'my desc', + short_description: 'My title', + }); + }); + + it('does not map unknown case fields', () => { + // @ts-expect-error + expect(mapCaseFieldsToExternalSystemFields({ notCaseField: 'test' }, mappings)).toEqual({}); + }); + + it('does not map unknown source', () => { + expect( + mapCaseFieldsToExternalSystemFields(caseFields, [ { - type: 'pushed', - action: Actions.push_to_service, - created_at: '2021-02-03T17:45:29.400Z', - created_by: { - email: 'elastic@elastic.co', - full_name: 'Elastic', - username: 'elastic', - }, - payload: { - externalService: { - pushed_at: '2021-02-03T17:45:29.400Z', - pushed_by: { - username: 'elastic', - full_name: 'Elastic', - email: 'elastic@elastic.co', - }, - connector_id: '123', - connector_name: 'ServiceNow SN', - external_id: 'external-id', - external_title: 'SIR0010037', - external_url: - 'https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id', - }, - }, - action_id: '9b91d8f0-6647-11eb-a291-51bf6b175a53', - case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', - comment_id: null, - owner: SECURITY_SOLUTION_OWNER, + // @ts-expect-error + source: 'not-a-case-field', + target: 'short_description', + action_type: 'overwrite', }, - ]); - - expect(res).toEqual({ - index: 1, - pushedInfo: { - connector_id: '456', - connector_name: 'ServiceNow SN', - external_id: 'external-id', - external_title: 'SIR0010037', - external_url: - 'https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id', - pushed_at: '2021-02-03T17:41:26.108Z', - pushed_by: { - email: 'elastic@elastic.co', - full_name: 'Elastic', - username: 'elastic', - }, + ]) + ).toEqual({}); + }); + + it('does not map if target=not_mapped', () => { + expect( + mapCaseFieldsToExternalSystemFields(caseFields, [ + { + source: 'title', + target: 'not_mapped', + action_type: 'overwrite', }, - }); - }); + ]) + ).toEqual({}); }); + }); + + describe('formatComments', () => { + it('formats comments correctly', () => { + const theCase = { + ...flattenCaseSavedObject({ + savedObject: mockCases[0], + }), + comments: allComments, + totalComments: allComments.length, + }; - describe('getClosedInfoForUpdate', () => { - const date = '2021-02-03T17:41:26.108Z'; - const user = { full_name: 'Elastic', username: 'elastic', email: 'elastic@elastic.co' }; + const latestPushInfo = getLatestPushInfo('not-exists', userActions); - it('returns the correct closed info when the case closes', async () => { - expect( - getClosedInfoForUpdate({ status: CaseStatuses.closed, closedDate: date, user }) - ).toEqual({ - closed_at: date, - closed_by: user, - }); - }); + expect( + formatComments({ userActions, theCase, latestPushInfo, userProfiles: userProfilesMap }) + ).toEqual([ + { + comment: 'Wow, good luck catching that bad meanie!\n\nAdded by elastic.', + commentId: 'comment-user-1', + }, + { + comment: + 'Isolated host windows-host-1 with comment: Isolating this for investigation\n\nAdded by elastic.', + commentId: 'mock-action-comment-1', + }, + { + comment: + 'Released host windows-host-1 with comment: Releasing this for investigation\n\nAdded by elastic.', + commentId: 'mock-action-comment-2', + }, + { + comment: + 'Isolated host windows-host-1 and 1 more with comment: Isolating this for investigation\n\nAdded by elastic.', + commentId: 'mock-action-comment-3', + }, + { + comment: 'Elastic Alerts attached to the case: 3', + commentId: 'mock-id-1-total-alerts', + }, + ]); + }); + + it('filters unsupported comments and adds the user profile information correctly', () => { + const theCase = { + ...flattenCaseSavedObject({ + savedObject: mockCases[0], + }), + comments: allComments.map((theComment) => ({ + ...theComment, + created_by: { ...theComment.created_by, profile_uid: userProfiles[0].uid }, + updated_by: null, + })), + totalComments: allComments.length, + }; + + const latestPushInfo = getLatestPushInfo('not-exists', userActions); + + expect( + formatComments({ userActions, theCase, latestPushInfo, userProfiles: userProfilesMap }) + ).toEqual([ + { + comment: 'Wow, good luck catching that bad meanie!\n\nAdded by Damaged Raccoon.', + commentId: 'comment-user-1', + }, + { + comment: + 'Isolated host windows-host-1 with comment: Isolating this for investigation\n' + + '\n' + + 'Added by Damaged Raccoon.', + commentId: 'mock-action-comment-1', + }, + { + comment: + 'Released host windows-host-1 with comment: Releasing this for investigation\n' + + '\n' + + 'Added by Damaged Raccoon.', + commentId: 'mock-action-comment-2', + }, + { + comment: + 'Isolated host windows-host-1 and 1 more with comment: Isolating this for investigation\n' + + '\n' + + 'Added by Damaged Raccoon.', + commentId: 'mock-action-comment-3', + }, + { + comment: 'Elastic Alerts attached to the case: 3', + commentId: 'mock-id-1-total-alerts', + }, + ]); + }); + + it('formats only comments that have not been pushed', () => { + const theCase = { + ...flattenCaseSavedObject({ + savedObject: mockCases[0], + }), + comments: allComments, + totalComments: allComments.length, + }; + + const latestPushInfo = getLatestPushInfo('456', userActions); + expect( + formatComments({ userActions, theCase, latestPushInfo, userProfiles: userProfilesMap }) + ).toEqual([ + { + comment: 'Wow, good luck catching that bad meanie!\n\nAdded by elastic.', + commentId: 'comment-user-1', + }, + { + comment: 'Elastic Alerts attached to the case: 3', + commentId: 'mock-id-1-total-alerts', + }, + ]); + }); + + it('filters out alert comments correctly and appends the total alerts on the end', () => { + const theCase = { + ...flattenCaseSavedObject({ + savedObject: mockCases[0], + }), + comments: [commentAlert], + totalComments: 1, + }; - it.each([[CaseStatuses.open], [CaseStatuses['in-progress']]])( - 'returns the correct closed info when the case %s', - async (status) => { - expect(getClosedInfoForUpdate({ status, closedDate: date, user })).toEqual({ - closed_at: null, - closed_by: null, - }); - } + const latestPushInfo = getLatestPushInfo('not-exists', userActions); + + expect( + formatComments({ userActions, theCase, latestPushInfo, userProfiles: userProfilesMap }) + ).toEqual([ + { + comment: 'Elastic Alerts attached to the case: 1', + commentId: 'mock-id-1-total-alerts', + }, + ]); + }); + + it('returns an empty array when there are no comments', () => { + const theCase = { + ...flattenCaseSavedObject({ + savedObject: mockCases[0], + }), + comments: [], + totalComments: 0, + }; + + const latestPushInfo = getLatestPushInfo('456', userActions); + + expect( + formatComments({ userActions, theCase, latestPushInfo, userProfiles: userProfilesMap }) + ).toEqual([]); + }); + + it('returns an empty array when there the comment has been pushed', () => { + const theCase = { + ...flattenCaseSavedObject({ + savedObject: mockCases[0], + }), + comments: [isolateCommentActions], + totalComments: 1, + }; + + const latestPushInfo = getLatestPushInfo('456', userActions); + + expect( + formatComments({ userActions, theCase, latestPushInfo, userProfiles: userProfilesMap }) + ).toEqual([]); + }); + + it('adds a backlink to the total alerts comment', () => { + const theCase = { + ...flattenCaseSavedObject({ + savedObject: mockCases[0], + }), + comments: [commentAlert], + totalComments: 1, + }; + + const latestPushInfo = getLatestPushInfo('not-exists', userActions); + + expect( + formatComments({ + userActions, + theCase, + latestPushInfo, + userProfiles: userProfilesMap, + publicBaseUrl: 'https://example.com', + }) + ).toEqual([ + { + comment: + 'Elastic Alerts attached to the case: 1\n\nFor more details, view the alerts in Kibana\nAlerts URL: https://example.com/app/security/cases/mock-id-1/?tabId=alerts', + commentId: 'mock-id-1-total-alerts', + }, + ]); + }); + }); + + describe('addKibanaInformationToDescription', () => { + const theCase = { + ...flattenCaseSavedObject({ + savedObject: mockCases[0], + }), + comments: [], + totalComments: 0, + }; + const publicBaseUrl = 'https://example.com'; + + it('adds the kibana information to description correctly', () => { + expect( + addKibanaInformationToDescription( + { + ...theCase, + created_by: { ...theCase.created_by, profile_uid: userProfiles[0].uid }, + updated_by: null, + }, + userProfilesMap, + publicBaseUrl + ) + ).toBe( + 'This is a brand new case of a bad meanie defacing data\n\nAdded by Damaged Raccoon.\nFor more details, view this case in Kibana.\nCase URL: https://example.com/app/security/cases/mock-id-1' ); + }); + + it('adds the kibana information to description correctly without publicBaseUrl and userProfilesMap', () => { + expect(addKibanaInformationToDescription(theCase)).toBe( + 'This is a brand new case of a bad meanie defacing data\n\nAdded by elastic.' + ); + }); + }); - it('returns undefined if the status is not provided', async () => { - expect(getClosedInfoForUpdate({ closedDate: date, user })).toBe(undefined); + describe('getLatestPushInfo', () => { + it('it returns the latest push information correctly', async () => { + const res = getLatestPushInfo('456', userActions); + expect(res).toEqual({ + index: 9, + pushedInfo: { + connector_id: '456', + connector_name: 'ServiceNow SN', + external_id: 'external-id', + external_title: 'SIR0010037', + external_url: + 'https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id', + pushed_at: '2021-02-03T17:45:29.400Z', + pushed_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }, + }, }); }); - describe('getDurationForUpdate', () => { - const createdAt = '2021-11-23T19:00:00Z'; - const closedAt = '2021-11-23T19:02:00Z'; + it('it returns null when there are not actions', async () => { + const res = getLatestPushInfo('456', []); + expect(res).toBe(null); + }); - it('returns the correct duration when the case closes', () => { - expect(getDurationForUpdate({ status: CaseStatuses.closed, closedAt, createdAt })).toEqual({ - duration: 120, - }); + it('it returns null when there are no push user action', async () => { + const res = getLatestPushInfo('456', [userActions[0]]); + expect(res).toBe(null); + }); + + it('it returns the correct push information when with multiple push on different connectors', async () => { + const res = getLatestPushInfo('456', [ + ...userActions.slice(0, 3), + { + type: 'pushed', + action: Actions.push_to_service, + created_at: '2021-02-03T17:45:29.400Z', + created_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }, + payload: { + externalService: { + pushed_at: '2021-02-03T17:45:29.400Z', + pushed_by: { + username: 'elastic', + full_name: 'Elastic', + email: 'elastic@elastic.co', + }, + connector_id: '123', + connector_name: 'ServiceNow SN', + external_id: 'external-id', + external_title: 'SIR0010037', + external_url: + 'https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id', + }, + }, + action_id: '9b91d8f0-6647-11eb-a291-51bf6b175a53', + case_id: 'fcdedd20-6646-11eb-a291-51bf6b175a53', + comment_id: null, + owner: SECURITY_SOLUTION_OWNER, + }, + ]); + + expect(res).toEqual({ + index: 1, + pushedInfo: { + connector_id: '456', + connector_name: 'ServiceNow SN', + external_id: 'external-id', + external_title: 'SIR0010037', + external_url: + 'https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id', + pushed_at: '2021-02-03T17:41:26.108Z', + pushed_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic', + username: 'elastic', + }, + }, }); + }); + }); - it.each([[CaseStatuses.open], [CaseStatuses['in-progress']]])( - 'returns the correct duration when the case %s', - (status) => { - expect(getDurationForUpdate({ status, closedAt, createdAt })).toEqual({ - duration: null, - }); - } - ); + describe('getClosedInfoForUpdate', () => { + const date = '2021-02-03T17:41:26.108Z'; + const user = { full_name: 'Elastic', username: 'elastic', email: 'elastic@elastic.co' }; - it('returns undefined if the status is not provided', async () => { - expect(getDurationForUpdate({ closedAt, createdAt })).toBe(undefined); + it('returns the correct closed info when the case closes', async () => { + expect( + getClosedInfoForUpdate({ status: CaseStatuses.closed, closedDate: date, user }) + ).toEqual({ + closed_at: date, + closed_by: user, }); + }); - it.each([['invalid'], [null]])( - 'returns undefined if the createdAt date is %s', - (createdAtInvalid) => { - expect( - getDurationForUpdate({ - status: CaseStatuses.closed, - closedAt, - // @ts-expect-error - createdAt: createdAtInvalid, - }) - ).toBe(undefined); - } - ); + it.each([[CaseStatuses.open], [CaseStatuses['in-progress']]])( + 'returns the correct closed info when the case %s', + async (status) => { + expect(getClosedInfoForUpdate({ status, closedDate: date, user })).toEqual({ + closed_at: null, + closed_by: null, + }); + } + ); - it.each([['invalid'], [null]])( - 'returns undefined if the closedAt date is %s', - (closedAtInvalid) => { - expect( - getDurationForUpdate({ - status: CaseStatuses.closed, - // @ts-expect-error - closedAt: closedAtInvalid, - createdAt, - }) - ).toBe(undefined); - } - ); + it('returns undefined if the status is not provided', async () => { + expect(getClosedInfoForUpdate({ closedDate: date, user })).toBe(undefined); + }); + }); + + describe('getDurationForUpdate', () => { + const createdAt = '2021-11-23T19:00:00Z'; + const closedAt = '2021-11-23T19:02:00Z'; + + it('returns the correct duration when the case closes', () => { + expect(getDurationForUpdate({ status: CaseStatuses.closed, closedAt, createdAt })).toEqual({ + duration: 120, + }); + }); + + it.each([[CaseStatuses.open], [CaseStatuses['in-progress']]])( + 'returns the correct duration when the case %s', + (status) => { + expect(getDurationForUpdate({ status, closedAt, createdAt })).toEqual({ + duration: null, + }); + } + ); - it('returns undefined if created_at > closed_at', async () => { + it('returns undefined if the status is not provided', async () => { + expect(getDurationForUpdate({ closedAt, createdAt })).toBe(undefined); + }); + + it.each([['invalid'], [null]])( + 'returns undefined if the createdAt date is %s', + (createdAtInvalid) => { expect( getDurationForUpdate({ status: CaseStatuses.closed, - closedAt: '2021-11-23T19:00:00Z', - createdAt: '2021-11-23T19:05:00Z', + closedAt, + // @ts-expect-error + createdAt: createdAtInvalid, }) ).toBe(undefined); - }); + } + ); - it('rounds the seconds correctly', () => { + it.each([['invalid'], [null]])( + 'returns undefined if the closedAt date is %s', + (closedAtInvalid) => { expect( getDurationForUpdate({ status: CaseStatuses.closed, - createdAt: '2022-04-11T15:56:00.087Z', - closedAt: '2022-04-11T15:58:56.187Z', + // @ts-expect-error + closedAt: closedAtInvalid, + createdAt, }) - ).toEqual({ - duration: 176, - }); + ).toBe(undefined); + } + ); + + it('returns undefined if created_at > closed_at', async () => { + expect( + getDurationForUpdate({ + status: CaseStatuses.closed, + closedAt: '2021-11-23T19:00:00Z', + createdAt: '2021-11-23T19:05:00Z', + }) + ).toBe(undefined); + }); + + it('rounds the seconds correctly', () => { + expect( + getDurationForUpdate({ + status: CaseStatuses.closed, + createdAt: '2022-04-11T15:56:00.087Z', + closedAt: '2022-04-11T15:58:56.187Z', + }) + ).toEqual({ + duration: 176, }); + }); - it('rounds the zero correctly', () => { - expect( - getDurationForUpdate({ - status: CaseStatuses.closed, - createdAt: '2022-04-11T15:56:00.087Z', - closedAt: '2022-04-11T15:56:00.187Z', - }) - ).toEqual({ - duration: 0, - }); + it('rounds the zero correctly', () => { + expect( + getDurationForUpdate({ + status: CaseStatuses.closed, + createdAt: '2022-04-11T15:56:00.087Z', + closedAt: '2022-04-11T15:56:00.187Z', + }) + ).toEqual({ + duration: 0, }); }); }); @@ -989,9 +859,7 @@ describe('utils', () => { it('returns the username when full name is empty for updatedBy', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: null, username: null }, - updatedAt: '', updatedBy: { email: null, full_name: '', username: 'updatedBy_username' }, }) ).toEqual('updatedBy_username'); @@ -1000,43 +868,35 @@ describe('utils', () => { it('returns the username when full name is empty for createdBy', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: '', username: 'createdBy_username' }, - updatedAt: '', updatedBy: null, }) ).toEqual('createdBy_username'); }); - it('returns an empty string with neither updatedBy or createdBy are defined', () => { + it('returns Unknown with neither updatedBy or createdBy are defined', () => { expect( getEntity({ - createdAt: '', - // @ts-expect-error createdBy should be defined but for testing purposes we want to make sure the function handles null + // @ts-expect-error createdBy: null, - updatedAt: '', updatedBy: null, }) - ).toEqual(''); + ).toEqual('Unknown'); }); - it('returns an empty string when createdBy fields are all null', () => { + it('returns Unknown when createdBy fields are all null', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: null, username: null }, - updatedAt: '', updatedBy: null, }) - ).toEqual(''); + ).toEqual('Unknown'); }); it('returns the full name of updatedBy when available', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: 'createdBy_full_name', username: null }, - updatedAt: '', updatedBy: { full_name: 'updatedBy_full_name', email: null, username: null }, }) ).toEqual('updatedBy_full_name'); @@ -1045,35 +905,29 @@ describe('utils', () => { it('returns the username of updatedBy when available', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: null, username: 'createdBy_username' }, - updatedAt: '', updatedBy: { full_name: null, email: null, username: 'updatedBy_username' }, }) ).toEqual('updatedBy_username'); }); - it('returns an empty string when updatedBy username is null', () => { + it('returns Unknown when updatedBy username is null', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: null, username: 'createdBy_username' }, - updatedAt: '', updatedBy: { full_name: null, email: null, username: null }, }) - ).toEqual(''); + ).toEqual('Unknown'); }); it('returns the full name of createdBy when available', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: 'createdBy_full_name', username: 'createdBy_username', }, - updatedAt: '', updatedBy: null, }) ).toEqual('createdBy_full_name'); @@ -1082,9 +936,7 @@ describe('utils', () => { it('returns the username of createdBy when available', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: null, username: 'createdBy_username' }, - updatedAt: '', updatedBy: null, }) ).toEqual('createdBy_username'); @@ -1093,9 +945,7 @@ describe('utils', () => { it('returns updatedBy full name when the profile uid is not found', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: null, username: 'createdBy_username' }, - updatedAt: '', updatedBy: { email: null, full_name: 'updatedBy_full_name', @@ -1109,14 +959,12 @@ describe('utils', () => { it('returns createdBy full name when the profile uid is not found', () => { expect( getEntity({ - createdAt: '', createdBy: { email: null, full_name: 'createdBy_full_name', username: 'createdBy_username', profile_uid: '123', }, - updatedAt: '', updatedBy: null, }) ).toEqual('createdBy_full_name'); @@ -1126,13 +974,11 @@ describe('utils', () => { expect( getEntity( { - createdAt: '', createdBy: { email: null, full_name: null, username: null, }, - updatedAt: '', updatedBy: { email: null, full_name: null, @@ -1149,13 +995,11 @@ describe('utils', () => { expect( getEntity( { - createdAt: '', createdBy: { email: null, full_name: null, username: null, }, - updatedAt: '', updatedBy: { email: null, full_name: null, @@ -1172,14 +1016,12 @@ describe('utils', () => { expect( getEntity( { - createdAt: '', createdBy: { email: null, full_name: null, username: null, profile_uid: userProfiles[0].uid, }, - updatedAt: '', updatedBy: null, }, userProfilesMap @@ -1191,9 +1033,7 @@ describe('utils', () => { expect( getEntity( { - updatedAt: '', updatedBy: null, - createdAt: '', createdBy: { email: null, full_name: null, diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts index d1721c0709cda..92bba3fa7e9e1 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; -import { flow, uniqBy, isEmpty } from 'lodash'; -import { ActionsClient } from '@kbn/actions-plugin/server'; +import { uniqBy, isEmpty } from 'lodash'; import { UserProfile } from '@kbn/security-plugin/common'; +import { IBasePath } from '@kbn/core-http-browser'; +import { CASE_VIEW_PAGE_TABS } from '../../../common/types'; import { isPushedUserAction } from '../../../common/utils/user_actions'; import { ActionConnector, @@ -16,46 +16,31 @@ import { CaseResponse, CaseUserActionsResponse, CommentResponse, - CommentResponseAlertsType, CommentType, - ConnectorMappingsAttributes, - CommentAttributes, - CommentRequestUserType, - CommentRequestAlertType, - CommentRequestActionsType, ActionTypes, CaseStatuses, User, CaseAttributes, CaseAssignees, + ConnectorMappingsAttributes, + CaseField, + ThirdPartyField, } from '../../../common/api'; import { CasesClientGetAlertsResponse } from '../alerts/types'; -import { - BasicParams, - EntityInformation, - ExternalServiceComment, - ExternalServiceParams, - Incident, - MapIncident, - PipedField, - PrepareFieldsForTransformArgs, - PushToServiceApiParams, - Transformer, - TransformerArgs, - TransformFieldsArgs, -} from './types'; +import { ExternalServiceComment, ExternalServiceIncident } from './types'; import { getAlertIds } from '../utils'; import { CasesConnectorsMap } from '../../connectors'; +import { getCaseViewPath } from '../../common/utils'; +import * as i18n from './translations'; interface CreateIncidentArgs { - actionsClient: ActionsClient; theCase: CaseResponse; userActions: CaseUserActionsResponse; connector: ActionConnector; - mappings: ConnectorMappingsAttributes[]; alerts: CasesClientGetAlertsResponse; casesConnectors: CasesConnectorsMap; userProfiles?: Map; + publicBaseUrl?: IBasePath['publicBaseUrl']; } export const dedupAssignees = (assignees?: CaseAssignees): CaseAssignees | undefined => { @@ -66,10 +51,12 @@ export const dedupAssignees = (assignees?: CaseAssignees): CaseAssignees | undef return uniqBy(assignees, 'uid'); }; +type LatestPushInfo = { index: number; pushedInfo: CaseFullExternalService } | null; + export const getLatestPushInfo = ( connectorId: string, userActions: CaseUserActionsResponse -): { index: number; pushedInfo: CaseFullExternalService } | null => { +): LatestPushInfo => { for (const [index, action] of [...userActions].reverse().entries()) { if (isPushedUserAction(action) && connectorId === action.payload.externalService.connector_id) { try { @@ -141,18 +128,32 @@ const getAlertsInfo = ( }; const addAlertMessage = ( - caseId: string, + theCase: CaseResponse, caseComments: CaseResponse['comments'], - comments: ExternalServiceComment[] + comments: ExternalServiceComment[], + publicBaseUrl?: IBasePath['publicBaseUrl'] ): ExternalServiceComment[] => { const { totalAlerts, hasUnpushedAlertComments } = getAlertsInfo(caseComments); const newComments = [...comments]; if (hasUnpushedAlertComments) { + let comment = `Elastic Alerts attached to the case: ${totalAlerts}`; + + if (publicBaseUrl) { + const alertsTableUrl = getCaseViewPath({ + publicBaseUrl, + caseId: theCase.id, + owner: theCase.owner, + tabId: CASE_VIEW_PAGE_TABS.ALERTS, + }); + + comment = `${comment}\n\n${i18n.VIEW_ALERTS_IN_KIBANA}\n${i18n.ALERTS_URL(alertsTableUrl)}`; + } + newComments.push({ - comment: `Elastic Alerts attached to the case: ${totalAlerts}`, - commentId: `${caseId}-total-alerts`, + comment, + commentId: `${theCase.id}-total-alerts`, }); } @@ -160,67 +161,80 @@ const addAlertMessage = ( }; export const createIncident = async ({ - actionsClient, theCase, userActions, connector, - mappings, alerts, casesConnectors, userProfiles, -}: CreateIncidentArgs): Promise => { - const { - comments: caseComments, - title, - description, - created_at: createdAt, - created_by: createdBy, - updated_at: updatedAt, - updated_by: updatedBy, - } = theCase; - - const params = { title, description, createdAt, createdBy, updatedAt, updatedBy }; + publicBaseUrl, +}: CreateIncidentArgs): Promise => { const latestPushInfo = getLatestPushInfo(connector.id, userActions); const externalId = latestPushInfo?.pushedInfo?.external_id ?? null; - const defaultPipes = externalId ? ['informationUpdated'] : ['informationCreated']; - let currentIncident: ExternalServiceParams | undefined; const externalServiceFields = casesConnectors.get(connector.actionTypeId)?.format(theCase, alerts) ?? {}; - let incident: Partial = { ...externalServiceFields }; - - if (externalId) { - try { - currentIncident = (await actionsClient.execute({ - actionId: connector.id, - params: { - subAction: 'getIncident', - subActionParams: { externalId }, - }, - })) as unknown as ExternalServiceParams | undefined; - } catch (ex) { - throw new Error( - `Retrieving Incident by id ${externalId} from ${connector.actionTypeId} failed with exception: ${ex}` - ); - } - } - - const fields = prepareFieldsForTransformation({ - defaultPipes, - mappings, - params, - }); + const connectorMappings = casesConnectors.get(connector.actionTypeId)?.getMapping() ?? []; + const descriptionWithKibanaInformation = addKibanaInformationToDescription( + theCase, + userProfiles, + publicBaseUrl + ); - const transformedFields = transformFields({ - params, - fields, - currentIncident, + const comments = formatComments({ + userActions, + latestPushInfo, + theCase, userProfiles, + publicBaseUrl, }); - incident = { ...incident, ...transformedFields, externalId }; + const mappedIncident = mapCaseFieldsToExternalSystemFields( + { title: theCase.title, description: descriptionWithKibanaInformation }, + connectorMappings + ); + + const incident = { + ...mappedIncident, + ...externalServiceFields, + externalId, + }; + return { incident, comments }; +}; + +export const mapCaseFieldsToExternalSystemFields = ( + caseFields: Record, unknown>, + mapping: ConnectorMappingsAttributes[] +): Record => { + const mappedCaseFields: Record = {}; + + for (const caseFieldKey of Object.keys(caseFields) as Array>) { + const mapDefinition = mapping.find( + (mappingEntry) => mappingEntry.source === caseFieldKey && mappingEntry.target !== 'not_mapped' + ); + + if (mapDefinition) { + mappedCaseFields[mapDefinition.target] = caseFields[caseFieldKey]; + } + } + return mappedCaseFields; +}; + +export const formatComments = ({ + userActions, + latestPushInfo, + theCase, + userProfiles, + publicBaseUrl, +}: { + theCase: CaseResponse; + latestPushInfo: LatestPushInfo; + userActions: CaseUserActionsResponse; + userProfiles?: Map; + publicBaseUrl?: IBasePath['publicBaseUrl']; +}): ExternalServiceComment[] => { const commentsIdsToBeUpdated = new Set( userActions .slice(latestPushInfo?.index ?? 0) @@ -228,7 +242,7 @@ export const createIncident = async ({ .map((action) => action.comment_id) ); - const commentsToBeUpdated = caseComments?.filter( + const commentsToBeUpdated = theCase.comments?.filter( (comment) => // We push only user's comments (comment.type === CommentType.user || comment.type === CommentType.actions) && @@ -238,25 +252,68 @@ export const createIncident = async ({ let comments: ExternalServiceComment[] = []; if (commentsToBeUpdated && Array.isArray(commentsToBeUpdated) && commentsToBeUpdated.length > 0) { - const commentsMapping = mappings.find((m) => m.source === 'comments'); - if (commentsMapping?.action_type !== 'nothing') { - comments = transformComments(commentsToBeUpdated, ['informationAdded'], userProfiles); - } + comments = addKibanaInformationToComments(commentsToBeUpdated, userProfiles); } - comments = addAlertMessage(theCase.id, caseComments, comments); + comments = addAlertMessage(theCase, theCase.comments, comments, publicBaseUrl); + return comments; +}; - return { incident, comments }; +export const addKibanaInformationToDescription = ( + theCase: CaseResponse, + userProfiles?: Map, + publicBaseUrl?: IBasePath['publicBaseUrl'] +) => { + const addedBy = i18n.ADDED_BY( + getEntity( + { + createdBy: theCase.created_by, + updatedBy: theCase.updated_by, + }, + userProfiles + ) + ); + + const descriptionWithKibanaInformation = `${theCase.description}\n\n${addedBy}.`; + + if (!publicBaseUrl) { + return descriptionWithKibanaInformation; + } + + const caseUrl = getCaseViewPath({ publicBaseUrl, caseId: theCase.id, owner: theCase.owner }); + + return `${descriptionWithKibanaInformation}\n${i18n.VIEW_IN_KIBANA}.\n${i18n.CASE_URL(caseUrl)}`; }; +const addKibanaInformationToComments = ( + comments: CaseResponse['comments'] = [], + userProfiles?: Map +): ExternalServiceComment[] => + comments.map((theComment) => { + const addedBy = i18n.ADDED_BY( + getEntity( + { + createdBy: theComment.created_by, + updatedBy: theComment.updated_by, + }, + userProfiles + ) + ); + + return { + comment: `${getCommentContent(theComment)}\n\n${addedBy}.`, + commentId: theComment.id, + }; + }); + export const getEntity = ( - entity: EntityInformation, + entity: { createdBy: CaseResponse['created_by']; updatedBy: CaseResponse['updated_by'] }, userProfiles?: Map ): string => { return ( getDisplayName(entity.updatedBy, userProfiles) ?? getDisplayName(entity.createdBy, userProfiles) ?? - '' + i18n.UNKNOWN ); }; @@ -279,7 +336,7 @@ const getDisplayName = ( } } - return validOrUndefined(user.full_name) ?? validOrUndefined(user.username) ?? ''; + return validOrUndefined(user.full_name) ?? validOrUndefined(user.username) ?? i18n.UNKNOWN; }; const validOrUndefined = (value: string | undefined | null): string | undefined => { @@ -290,173 +347,6 @@ const validOrUndefined = (value: string | undefined | null): string | undefined return value; }; -export const FIELD_INFORMATION = ( - mode: string, - date: string | undefined, - user: string | undefined -) => { - switch (mode) { - case 'create': - return i18n.translate('xpack.cases.connectors.cases.externalIncidentCreated', { - values: { date, user }, - defaultMessage: '(created at {date} by {user})', - }); - case 'update': - return i18n.translate('xpack.cases.connectors.cases.externalIncidentUpdated', { - values: { date, user }, - defaultMessage: '(updated at {date} by {user})', - }); - case 'add': - return i18n.translate('xpack.cases.connectors.cases.externalIncidentAdded', { - values: { date, user }, - defaultMessage: '(added at {date} by {user})', - }); - default: - return i18n.translate('xpack.cases.connectors.cases.externalIncidentDefault', { - values: { date, user }, - defaultMessage: '(created at {date} by {user})', - }); - } -}; - -export const transformers: Record = { - informationCreated: ({ value, date, user, ...rest }: TransformerArgs): TransformerArgs => ({ - value: `${value} ${FIELD_INFORMATION('create', date, user)}`, - ...rest, - }), - informationUpdated: ({ value, date, user, ...rest }: TransformerArgs): TransformerArgs => ({ - value: `${value} ${FIELD_INFORMATION('update', date, user)}`, - ...rest, - }), - informationAdded: ({ value, date, user, ...rest }: TransformerArgs): TransformerArgs => ({ - value: `${value} ${FIELD_INFORMATION('add', date, user)}`, - ...rest, - }), - append: ({ value, previousValue, ...rest }: TransformerArgs): TransformerArgs => ({ - value: previousValue ? `${previousValue} \r\n${value}` : `${value}`, - ...rest, - }), -}; - -export const prepareFieldsForTransformation = ({ - defaultPipes, - mappings, - params, -}: PrepareFieldsForTransformArgs): PipedField[] => - mappings.reduce( - (acc: PipedField[], mapping) => - mapping != null && - mapping.target != null && - mapping.target !== 'not_mapped' && - mapping.action_type !== 'nothing' && - mapping.source !== 'comments' - ? [ - ...acc, - { - key: mapping.target, - value: params[mapping.source] ?? '', - actionType: mapping.action_type, - pipes: - // Do not transform titles - mapping.source !== 'title' - ? mapping.action_type === 'append' - ? [...defaultPipes, 'append'] - : defaultPipes - : [], - }, - ] - : acc, - [] - ); - -export const transformFields = < - P extends EntityInformation, - S extends Record, - R extends {} ->({ - params, - fields, - currentIncident, - userProfiles, -}: TransformFieldsArgs): R => { - return fields.reduce((prev, cur) => { - const transform = flow(...cur.pipes.map((p) => transformers[p])); - return { - ...prev, - [cur.key]: transform({ - value: cur.value, - date: params.updatedAt ?? params.createdAt, - user: getEntity(params, userProfiles), - previousValue: currentIncident ? currentIncident[cur.key] : '', - }).value, - }; - }, {} as R); -}; - -export const transformComments = ( - comments: CaseResponse['comments'] = [], - pipes: string[], - userProfiles?: Map -): ExternalServiceComment[] => - comments.map((c) => ({ - comment: flow(...pipes.map((p) => transformers[p]))({ - value: getCommentContent(c), - date: c.updated_at ?? c.created_at, - user: getEntity( - { - createdAt: c.created_at, - createdBy: c.created_by, - updatedAt: c.updated_at, - updatedBy: c.updated_by, - }, - userProfiles - ), - }).value, - commentId: c.id, - })); - -export const isCommentAlertType = ( - comment: CommentResponse -): comment is CommentResponseAlertsType => comment.type === CommentType.alert; - -export const getCommentContextFromAttributes = ( - attributes: CommentAttributes -): CommentRequestUserType | CommentRequestAlertType | CommentRequestActionsType => { - const owner = attributes.owner; - switch (attributes.type) { - case CommentType.user: - return { - type: CommentType.user, - comment: attributes.comment, - owner, - }; - case CommentType.alert: - return { - type: attributes.type, - alertId: attributes.alertId, - index: attributes.index, - rule: attributes.rule, - owner, - }; - case CommentType.actions: - return { - type: attributes.type, - comment: attributes.comment, - actions: { - targets: attributes.actions.targets, - type: attributes.actions.type, - }, - owner, - }; - default: - return { - type: CommentType.user, - comment: '', - owner, - }; - } -}; - export const getClosedInfoForUpdate = ({ user, status, diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index 30d2e24a144a1..960c55e2882e3 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -11,6 +11,7 @@ import { Logger, ElasticsearchClient, SavedObjectsClientContract, + IBasePath, } from '@kbn/core/server'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; import { PluginStartContract as FeaturesPluginStart } from '@kbn/features-plugin/server'; @@ -46,6 +47,7 @@ interface CasesClientFactoryArgs { lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory']; persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry; externalReferenceAttachmentTypeRegistry: ExternalReferenceAttachmentTypeRegistry; + publicBaseUrl?: IBasePath['publicBaseUrl']; } /** @@ -126,6 +128,7 @@ export class CasesClientFactory { persistableStateAttachmentTypeRegistry: this.options.persistableStateAttachmentTypeRegistry, externalReferenceAttachmentTypeRegistry: this.options.externalReferenceAttachmentTypeRegistry, securityStartPlugin: this.options.securityPluginStart, + publicBaseUrl: this.options.publicBaseUrl, }); } diff --git a/x-pack/plugins/cases/server/client/types.ts b/x-pack/plugins/cases/server/client/types.ts index d1d9e60e45caa..81d17420b4c88 100644 --- a/x-pack/plugins/cases/server/client/types.ts +++ b/x-pack/plugins/cases/server/client/types.ts @@ -11,6 +11,7 @@ import { ActionsClient } from '@kbn/actions-plugin/server'; import { LensServerPluginSetup } from '@kbn/lens-plugin/server'; import { KueryNode } from '@kbn/es-query'; import { SecurityPluginStart } from '@kbn/security-plugin/server'; +import { IBasePath } from '@kbn/core-http-browser'; import { CaseSeverity, CaseStatuses, User } from '../../common/api'; import { Authorization } from '../authorization/authorization'; import { @@ -49,6 +50,7 @@ export interface CasesClientArgs { readonly persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry; readonly externalReferenceAttachmentTypeRegistry: ExternalReferenceAttachmentTypeRegistry; readonly securityStartPlugin: SecurityPluginStart; + readonly publicBaseUrl?: IBasePath['publicBaseUrl']; } export interface ConstructQueryParams { diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts index e10211cca8fe0..432fb0f92c4e6 100644 --- a/x-pack/plugins/cases/server/common/utils.test.ts +++ b/x-pack/plugins/cases/server/common/utils.test.ts @@ -7,7 +7,7 @@ import { SavedObject, SavedObjectsFindResponse } from '@kbn/core/server'; import { makeLensEmbeddableFactory } from '@kbn/lens-plugin/server/embeddable/make_lens_embeddable_factory'; -import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; +import { OWNER_INFO, SECURITY_SOLUTION_OWNER } from '../../common/constants'; import { CaseConnector, CaseResponse, @@ -33,8 +33,11 @@ import { getOrUpdateLensReferences, asArray, transformNewCase, + getApplicationRoute, + getCaseViewPath, } from './utils'; import { newCase } from '../routes/api/__mocks__/request_responses'; +import { CASE_VIEW_PAGE_TABS } from '../../common/types'; interface CommentReference { ids: string[]; @@ -1184,4 +1187,98 @@ describe('common utils', () => { expect(asArray(100)).toEqual([100]); }); }); + + describe('getApplicationRoute', () => { + const owners = Object.keys(OWNER_INFO) as Array; + + it.each(owners)('returns the correct appRoute for owner: %s', (owner) => { + expect(getApplicationRoute(OWNER_INFO, owner)).toEqual(OWNER_INFO[owner].appRoute); + }); + + it('return the stack management app route if the owner info is not valid', () => { + // @ts-expect-error + expect(getApplicationRoute({ test: { appRoute: 'no-slash' } }, 'test')).toEqual( + '/app/management/insightsAndAlerting' + ); + }); + + it('return the stack management app route if the owner is not valid', () => { + expect(getApplicationRoute(OWNER_INFO, 'not-valid')).toEqual( + '/app/management/insightsAndAlerting' + ); + }); + }); + + describe('getCaseViewPath', () => { + const publicBaseUrl = 'https://example.com'; + const caseId = 'my-case-id'; + const commentId = 'my-comment-id'; + + it('returns the case view path correctly', () => { + expect(getCaseViewPath({ publicBaseUrl, caseId, owner: SECURITY_SOLUTION_OWNER })).toBe( + 'https://example.com/app/security/cases/my-case-id' + ); + }); + + it('removes the ending slash from the publicBaseUrl correctly', () => { + expect( + getCaseViewPath({ + publicBaseUrl: 'https://example.com/', + caseId, + owner: SECURITY_SOLUTION_OWNER, + }) + ).toBe('https://example.com/app/security/cases/my-case-id'); + }); + + it('remove the extra trailing slashes from case view path correctly', () => { + expect( + getCaseViewPath({ publicBaseUrl, caseId: '/my-case-id', owner: SECURITY_SOLUTION_OWNER }) + ).toBe('https://example.com/app/security/cases/my-case-id'); + }); + + it('returns the case view path correctly with invalid owner', () => { + expect(getCaseViewPath({ publicBaseUrl, caseId, owner: 'invalid' })).toBe( + 'https://example.com/app/management/insightsAndAlerting/cases/my-case-id' + ); + }); + + it('returns the case comment path correctly', () => { + expect( + getCaseViewPath({ publicBaseUrl, caseId, owner: SECURITY_SOLUTION_OWNER, commentId }) + ).toBe('https://example.com/app/security/cases/my-case-id/my-comment-id'); + }); + + it('remove the extra trailing slashes from case comment path correctly', () => { + expect( + getCaseViewPath({ + publicBaseUrl, + caseId: '/my-case-id', + owner: SECURITY_SOLUTION_OWNER, + commentId: '/my-comment-id', + }) + ).toBe('https://example.com/app/security/cases/my-case-id/my-comment-id'); + }); + + it('returns the case tab path correctly', () => { + expect( + getCaseViewPath({ + publicBaseUrl, + caseId, + owner: SECURITY_SOLUTION_OWNER, + tabId: CASE_VIEW_PAGE_TABS.ALERTS, + }) + ).toBe('https://example.com/app/security/cases/my-case-id/?tabId=alerts'); + }); + + it('remove the extra trailing slashes from case tab path correctly', () => { + expect( + getCaseViewPath({ + publicBaseUrl, + caseId: '/my-case-id', + owner: SECURITY_SOLUTION_OWNER, + tabId: CASE_VIEW_PAGE_TABS.ALERTS, + }) + ).toBe('https://example.com/app/security/cases/my-case-id/?tabId=alerts'); + }); + }); }); diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 9ddb5feb5042f..1c41ea53105c9 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -10,9 +10,19 @@ import { SavedObjectsFindResponse, SavedObject, SavedObjectReference, + IBasePath, } from '@kbn/core/server'; import { flatMap, uniqWith, xorWith } from 'lodash'; import { LensServerPluginSetup } from '@kbn/lens-plugin/server'; +import { isValidOwner } from '../../common/utils/owner'; +import { + CASE_VIEW_COMMENT_PATH, + CASE_VIEW_PATH, + CASE_VIEW_TAB_PATH, + GENERAL_CASES_OWNER, + OWNER_INFO, +} from '../../common/constants'; +import { CASE_VIEW_PAGE_TABS } from '../../common/types'; import { AlertInfo } from './types'; import { @@ -394,3 +404,50 @@ export const asArray = (field?: T | T[] | null): T[] => { export const assertUnreachable = (x: never): never => { throw new Error('You should not reach this part of code'); }; + +export const getApplicationRoute = ( + appRouteInfo: { [K in keyof typeof OWNER_INFO]: { appRoute: string } }, + owner: string +): string => { + const appRoute = isValidOwner(owner) + ? appRouteInfo[owner].appRoute + : OWNER_INFO[GENERAL_CASES_OWNER].appRoute; + + return appRoute.startsWith('/') ? appRoute : `/${appRoute}`; +}; + +export const getCaseViewPath = (params: { + publicBaseUrl: NonNullable; + caseId: string; + owner: string; + commentId?: string; + tabId?: CASE_VIEW_PAGE_TABS; +}): string => { + const normalizePath = (path: string): string => path.replaceAll('//', '/'); + const removeEndingSlash = (path: string): string => + path.endsWith('/') ? path.slice(0, -1) : path; + + const { publicBaseUrl, caseId, owner, commentId, tabId } = params; + + const publicBaseUrlWithoutEndingSlash = removeEndingSlash(publicBaseUrl); + const appRoute = getApplicationRoute(OWNER_INFO, owner); + const basePath = `${publicBaseUrlWithoutEndingSlash}${appRoute}/cases`; + + if (commentId) { + const commentPath = normalizePath( + CASE_VIEW_COMMENT_PATH.replace(':detailName', caseId).replace(':commentId', commentId) + ); + + return `${basePath}${commentPath}`; + } + + if (tabId) { + const tabPath = normalizePath( + CASE_VIEW_TAB_PATH.replace(':detailName', caseId).replace(':tabId', tabId) + ); + + return `${basePath}${tabPath}`; + } + + return `${basePath}${normalizePath(CASE_VIEW_PATH.replace(':detailName', caseId))}`; +}; diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index 1f16db77ca137..2ca0c140f0202 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -198,6 +198,7 @@ export class CasePlugin { lensEmbeddableFactory: this.lensEmbeddableFactory!, persistableStateAttachmentTypeRegistry: this.persistableStateAttachmentTypeRegistry, externalReferenceAttachmentTypeRegistry: this.externalReferenceAttachmentTypeRegistry, + publicBaseUrl: core.http.basePath.publicBaseUrl, }); const client = core.elasticsearch.client; diff --git a/x-pack/plugins/files/public/files_client/files_client.ts b/x-pack/plugins/files/public/files_client/files_client.ts index 8db201dbbc891..a17929a4a100b 100644 --- a/x-pack/plugins/files/public/files_client/files_client.ts +++ b/x-pack/plugins/files/public/files_client/files_client.ts @@ -102,9 +102,11 @@ export function createFilesClient({ getById: ({ kind, ...args }) => { return http.get(apiRoutes.getByIdRoute(scopedFileKind ?? kind, args.id)); }, - list({ kind, page, perPage } = { kind: '' }) { - return http.get(apiRoutes.getListRoute(scopedFileKind ?? kind), { + list: ({ kind, page, perPage, ...body } = { kind: '' }) => { + return http.post(apiRoutes.getListRoute(scopedFileKind ?? kind), { + headers: commonBodyHeaders, query: { page, perPage }, + body: JSON.stringify(body), }); }, update: ({ kind, id, ...body }) => { diff --git a/x-pack/plugins/files/server/file_client/file_client.ts b/x-pack/plugins/files/server/file_client/file_client.ts index ca806b6644f42..6954ac9c635d7 100644 --- a/x-pack/plugins/files/server/file_client/file_client.ts +++ b/x-pack/plugins/files/server/file_client/file_client.ts @@ -154,12 +154,16 @@ export class FileClientImpl implements FileClient { await this.internalUpdate(id, payload); } - public async find(arg: P1): Promise>> { - return this.metadataClient - .find(arg) - .then((r) => - r.map(({ id, metadata }) => this.instantiateFile(id, metadata as FileMetadata)) - ); + public async find( + arg: P1 + ): Promise<{ files: File[]; total: number }> { + const result = await this.metadataClient.find(arg); + return { + total: result.total, + files: result.files.map(({ id, metadata }) => + this.instantiateFile(id, metadata as FileMetadata) + ), + }; } public async delete({ id, hasContent = true }: DeleteArgs) { @@ -192,12 +196,6 @@ export class FileClientImpl implements FileClient { return this.blobStorageClient.delete(arg); }; - public async list(arg?: P1): Promise { - return this.metadataClient - .list(arg) - .then((r) => r.map(({ id, metadata }) => this.instantiateFile(id, metadata))); - } - /** * Upload a blob * @param id - The ID of the file content is associated with diff --git a/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/es_index.ts b/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/es_index.ts index 5b41dca5e158e..dcfe39e112075 100644 --- a/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/es_index.ts +++ b/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/es_index.ts @@ -10,7 +10,7 @@ import { pipe } from 'lodash/fp'; import { Logger } from '@kbn/core/server'; import { toElasticsearchQuery } from '@kbn/es-query'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { MappingProperty } from '@elastic/elasticsearch/lib/api/types'; +import { MappingProperty, SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; import type { FilesMetrics, FileMetadata, Pagination } from '../../../../common'; import type { FindFileArgs } from '../../../file_service'; import type { @@ -19,10 +19,9 @@ import type { FileMetadataClient, GetArg, GetUsageMetricsArgs, - ListArg, UpdateArgs, } from '../file_metadata_client'; -import { filterArgsToKuery, filterDeletedFiles } from './query_filters'; +import { filterArgsToKuery } from './query_filters'; import { fileObjectType } from '../../../saved_objects/file'; const filterArgsToESQuery = pipe(filterArgsToKuery, toElasticsearchQuery); @@ -114,25 +113,12 @@ export class EsIndexFilesMetadataClient implements FileMetadataClie private attrPrefix: keyof FileDocument = 'file'; - async list({ page, perPage }: ListArg = {}): Promise>> { - const result = await this.esClient.search>({ - index: this.index, - expand_wildcards: 'hidden', - query: toElasticsearchQuery(filterDeletedFiles({ attrPrefix: this.attrPrefix })), - ...this.paginationToES({ page, perPage }), - sort: 'file.created', - }); - - return result.hits.hits.map((hit) => { - return { - id: hit._id, - metadata: hit._source?.file!, - }; - }); - } - - async find({ page, perPage, ...filterArgs }: FindFileArgs): Promise>> { + async find({ page, perPage, ...filterArgs }: FindFileArgs = {}): Promise<{ + total: number; + files: Array>; + }> { const result = await this.esClient.search>({ + track_total_hits: true, index: this.index, expand_wildcards: 'hidden', query: filterArgsToESQuery({ ...filterArgs, attrPrefix: this.attrPrefix }), @@ -140,7 +126,10 @@ export class EsIndexFilesMetadataClient implements FileMetadataClie sort: 'file.created', }); - return result.hits.hits.map((r) => ({ id: r._id, metadata: r._source?.file! })); + return { + total: (result.hits.total as SearchTotalHits).value, + files: result.hits.hits.map((r) => ({ id: r._id, metadata: r._source?.file! })), + }; } async getUsageMetrics(arg: GetUsageMetricsArgs): Promise { diff --git a/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts b/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts index 4b33c60f320a4..e5c49b8f12b9d 100644 --- a/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts +++ b/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts @@ -12,12 +12,10 @@ import { getFlattenedObject } from '@kbn/std'; import { FileMetadata, FileStatus } from '../../../../common/types'; import { FindFileArgs } from '../../../file_service'; -const { buildNode } = nodeTypes.function; - const deletedStatus: FileStatus = 'DELETED'; export function filterDeletedFiles({ attrPrefix }: { attrPrefix: string }): KueryNode { - return buildNode('not', nodeBuilder.is(`${attrPrefix}.Status`, deletedStatus)); + return nodeTypes.function.buildNode('not', nodeBuilder.is(`${attrPrefix}.Status`, deletedStatus)); } export function filterArgsToKuery({ @@ -30,16 +28,25 @@ export function filterArgsToKuery({ }: Omit & { attrPrefix?: string }): KueryNode { const kueryExpressions: KueryNode[] = [filterDeletedFiles({ attrPrefix })]; - const addFilters = (fieldName: keyof FileMetadata, values: string[] = []): void => { + const addFilters = ( + fieldName: keyof FileMetadata, + values: string[] = [], + isWildcard = false + ): void => { if (values.length) { const orExpressions = values .filter(Boolean) - .map((value) => nodeBuilder.is(`${attrPrefix}.${fieldName}`, escapeKuery(value))); + .map((value) => + nodeBuilder.is( + `${attrPrefix}.${fieldName}`, + isWildcard ? nodeTypes.wildcard.buildNode(value) : escapeKuery(value) + ) + ); kueryExpressions.push(nodeBuilder.or(orExpressions)); } }; - addFilters('name', name); + addFilters('name', name, true); addFilters('FileKind', kind); addFilters('Status', status); addFilters('extension', extension); @@ -51,7 +58,8 @@ export function filterArgsToKuery({ forEach(([fieldName, value]) => { addFilters( `Meta.${fieldName}` as keyof FileMetadata, - Array.isArray(value) ? value : [value] + Array.isArray(value) ? value : [value], + true ); }) ); diff --git a/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/saved_objects.ts b/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/saved_objects.ts index 9afd12689acbf..b1a43a05bfb7b 100644 --- a/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/saved_objects.ts +++ b/x-pack/plugins/files/server/file_client/file_metadata_client/adapters/saved_objects.ts @@ -13,11 +13,10 @@ import type { SavedObjectsOpenPointInTimeResponse, } from '@kbn/core-saved-objects-api-server'; import { AggregationsSumAggregate } from '@elastic/elasticsearch/lib/api/types'; -import { escapeKuery } from '@kbn/es-query'; import { FindFileArgs } from '../../../file_service/file_action_types'; import { ES_FIXED_SIZE_INDEX_BLOB_STORE } from '../../../../common/constants'; -import type { FileMetadata, FilesMetrics, FileStatus, Pagination } from '../../../../common/types'; +import type { FileMetadata, FilesMetrics, FileStatus } from '../../../../common/types'; import type { FileMetadataClient, UpdateArgs, @@ -61,26 +60,11 @@ export class SavedObjectsFileMetadataClient implements FileMetadataClient { metadata: result.attributes as FileDescriptor['metadata'], }; } - async list({ fileKind, page, perPage }: { fileKind?: string } & Pagination = {}): Promise< - FileDescriptor[] - > { - let filter = `NOT ${this.soType}.attributes.Status: DELETED`; - if (fileKind) { - filter = `${this.soType}.attributes.FileKind: ${escapeKuery(fileKind)} AND ${filter}`; - } - const result = await this.soClient.find({ - type: this.soType, - filter, - page, - perPage, - }); - return result.saved_objects.map((file) => ({ - id: file.id, - metadata: file.attributes as FileDescriptor['metadata'], - })); - } - async find({ page, perPage, ...filterArgs }: FindFileArgs): Promise { + async find({ page, perPage, ...filterArgs }: FindFileArgs = {}): Promise<{ + total: number; + files: Array>; + }> { const result = await this.soClient.find({ type: this.soType, filter: filterArgsToKuery({ ...filterArgs, attrPrefix: `${this.soType}.attributes` }), @@ -89,10 +73,13 @@ export class SavedObjectsFileMetadataClient implements FileMetadataClient { sortOrder: 'desc', sortField: 'created', }); - return result.saved_objects.map((so) => ({ - id: so.id, - metadata: so.attributes as FileMetadata, - })); + return { + files: result.saved_objects.map((so) => ({ + id: so.id, + metadata: so.attributes as FileMetadata, + })), + total: result.total, + }; } async delete({ id }: { id: string }): Promise { diff --git a/x-pack/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts b/x-pack/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts index 5934ac1f422b8..5136fc9744d51 100644 --- a/x-pack/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts +++ b/x-pack/plugins/files/server/file_client/file_metadata_client/file_metadata_client.ts @@ -72,13 +72,6 @@ export interface DeleteArg { id: string; } -export interface ListArg extends Pagination { - /** - * The file kind to scope this query to - */ - fileKind?: string; -} - export interface FindArg extends Pagination { /** * The file kind to scope this query to @@ -116,18 +109,12 @@ export interface FileMetadataClient { * @param arg - Arguments to delete file metadata */ delete(arg: DeleteArg): Promise; - /** - * List all instances of metadata for a file kind. - * - * @param arg - Arguments to list file metadata - */ - list(arg?: ListArg): Promise; /** * Search for a set of file kind instances that match the filters. * * @param arg - Filters and other settings to match against */ - find(arg: FindFileArgs): Promise; + find(arg?: FindFileArgs): Promise<{ total: number; files: FileDescriptor[] }>; /** * Prepare a set of metrics based on the file metadata. * diff --git a/x-pack/plugins/files/server/file_client/file_metadata_client/index.ts b/x-pack/plugins/files/server/file_client/file_metadata_client/index.ts index e4b84a1c6cb04..690a6b472b00f 100644 --- a/x-pack/plugins/files/server/file_client/file_metadata_client/index.ts +++ b/x-pack/plugins/files/server/file_client/file_metadata_client/index.ts @@ -12,7 +12,6 @@ export type { FindArg as FindMetadataArg, GetArg as GetMetadataArg, GetUsageMetricsArgs, - ListArg as ListMetadataArg, UpdateArgs as UpdateMetadataArg, } from './file_metadata_client'; export { SavedObjectsFileMetadataClient, EsIndexFilesMetadataClient } from './adapters'; diff --git a/x-pack/plugins/files/server/file_client/index.ts b/x-pack/plugins/files/server/file_client/index.ts index cb9c2187bc996..46eec400b77bc 100644 --- a/x-pack/plugins/files/server/file_client/index.ts +++ b/x-pack/plugins/files/server/file_client/index.ts @@ -13,7 +13,6 @@ export type { FindMetadataArg, GetMetadataArg, GetUsageMetricsArgs, - ListMetadataArg, UpdateMetadataArg, } from './file_metadata_client'; export { FileClientImpl } from './file_client'; diff --git a/x-pack/plugins/files/server/file_client/integration_tests/es_file_client.test.ts b/x-pack/plugins/files/server/file_client/integration_tests/es_file_client.test.ts index 58bf5cec29c41..0ae1b9c7d29f8 100644 --- a/x-pack/plugins/files/server/file_client/integration_tests/es_file_client.test.ts +++ b/x-pack/plugins/files/server/file_client/integration_tests/es_file_client.test.ts @@ -106,7 +106,7 @@ describe('ES-index-backed file client', () => { await file3.uploadContent(Readable.from(['test'])); { - const results = await fileClient.find({ + const { files: results } = await fileClient.find({ status: ['READY'], meta: { test: '3' }, }); @@ -121,7 +121,7 @@ describe('ES-index-backed file client', () => { } { - const results = await fileClient.find({ + const { files: results } = await fileClient.find({ status: ['READY', 'AWAITING_UPLOAD'], }); @@ -178,10 +178,10 @@ describe('ES-index-backed file client', () => { }, }); - const list = await fileClient.list(); + const { files } = await fileClient.find(); - expect(list).toHaveLength(1); - expect(list[0].toJSON()).toEqual( + expect(files).toHaveLength(1); + expect(files[0].toJSON()).toEqual( expect.objectContaining({ id: '123', fileKind: 'none', diff --git a/x-pack/plugins/files/server/file_client/types.ts b/x-pack/plugins/files/server/file_client/types.ts index bbab0066cf3f0..19a50f7249fa2 100644 --- a/x-pack/plugins/files/server/file_client/types.ts +++ b/x-pack/plugins/files/server/file_client/types.ts @@ -93,19 +93,12 @@ export interface FileClient { */ delete(arg: DeleteArgs): Promise; - /** - * See {@link FileMetadataClient.list} - * - * @param arg - Argument to list files - */ - list(arg?: P1): Promise; - /** * See {@link FileMetadataClient.find}. * * @param arg - Argument to find files */ - find: (arg: P1) => Promise; + find: (arg?: P1) => Promise<{ files: File[]; total: number }>; /** * Create a file share instance for this file. diff --git a/x-pack/plugins/files/server/file_service/file_action_types.ts b/x-pack/plugins/files/server/file_service/file_action_types.ts index c3d3a67ec6c66..f0242cb523d40 100644 --- a/x-pack/plugins/files/server/file_service/file_action_types.ts +++ b/x-pack/plugins/files/server/file_service/file_action_types.ts @@ -65,16 +65,6 @@ export interface DeleteFileArgs { fileKind: string; } -/** - * Arguments list files. - */ -export interface ListFilesArgs extends Pagination { - /** - * File kind, must correspond to a registered {@link FileKind}. - */ - fileKind: string; -} - /** * Arguments to get a file by ID. */ diff --git a/x-pack/plugins/files/server/file_service/file_service.ts b/x-pack/plugins/files/server/file_service/file_service.ts index f8d8534b7a635..e48b0a6ea38ca 100644 --- a/x-pack/plugins/files/server/file_service/file_service.ts +++ b/x-pack/plugins/files/server/file_service/file_service.ts @@ -5,14 +5,13 @@ * 2.0. */ -import type { FileJSON, FilesMetrics, File } from '../../common'; +import type { FilesMetrics, File, FileJSON } from '../../common'; import type { FileShareServiceStart } from '../file_share_service/types'; import type { CreateFileArgs, UpdateFileArgs, DeleteFileArgs, GetByIdArgs, - ListFilesArgs, FindFileArgs, } from './file_action_types'; @@ -55,14 +54,7 @@ export interface FileServiceStart { * * @param args - find files args */ - find(args: FindFileArgs): Promise>>; - - /** - * List all files of specific file kind. - * - * @param args - list files args - */ - list(args: ListFilesArgs): Promise>>; + find(args: FindFileArgs): Promise<{ files: Array>; total: number }>; /** * Get an instance of a share object diff --git a/x-pack/plugins/files/server/file_service/file_service_factory.ts b/x-pack/plugins/files/server/file_service/file_service_factory.ts index cff416e184356..393777c9a9abf 100644 --- a/x-pack/plugins/files/server/file_service/file_service_factory.ts +++ b/x-pack/plugins/files/server/file_service/file_service_factory.ts @@ -19,13 +19,7 @@ import { fileObjectType, fileShareObjectType, hiddenTypes } from '../saved_objec import { BlobStorageService } from '../blob_storage_service'; import { FileClientImpl } from '../file_client/file_client'; import { InternalFileShareService } from '../file_share_service'; -import { - CreateFileArgs, - FindFileArgs, - GetByIdArgs, - ListFilesArgs, - UpdateFileArgs, -} from './file_action_types'; +import { CreateFileArgs, FindFileArgs, GetByIdArgs, UpdateFileArgs } from './file_action_types'; import { InternalFileService } from './internal_file_service'; import { FileServiceStart } from './file_service'; import { FileKindsRegistry } from '../../common/file_kinds_registry'; @@ -105,10 +99,10 @@ export class FileServiceFactoryImpl implements FileServiceFactory { return internalFileService.getById(args) as Promise>; }, async find(args: FindFileArgs) { - return internalFileService.findFilesJSON(args) as Promise>>; - }, - async list(args: ListFilesArgs) { - return internalFileService.list(args) as Promise>>; + return internalFileService.findFilesJSON(args) as Promise<{ + files: Array>; + total: number; + }>; }, async getUsageMetrics() { return internalFileService.getUsageMetrics(); diff --git a/x-pack/plugins/files/server/file_service/index.ts b/x-pack/plugins/files/server/file_service/index.ts index c094c9f2d056b..457e5f3d4dfb8 100644 --- a/x-pack/plugins/files/server/file_service/index.ts +++ b/x-pack/plugins/files/server/file_service/index.ts @@ -11,7 +11,6 @@ export type { DeleteFileArgs, FindFileArgs, GetByIdArgs, - ListFilesArgs, UpdateFileArgs, } from './file_action_types'; export type { FileServiceStart } from './file_service'; diff --git a/x-pack/plugins/files/server/file_service/internal_file_service.ts b/x-pack/plugins/files/server/file_service/internal_file_service.ts index 4e676fbc26653..7768237e9fd50 100644 --- a/x-pack/plugins/files/server/file_service/internal_file_service.ts +++ b/x-pack/plugins/files/server/file_service/internal_file_service.ts @@ -21,7 +21,6 @@ import type { DeleteFileArgs, FindFileArgs, GetByIdArgs, - ListFilesArgs, } from './file_action_types'; import { createFileClient, FileClientImpl } from '../file_client/file_client'; /** @@ -88,28 +87,16 @@ export class InternalFileService { return file; } - public async list({ - fileKind: fileKindId, - page = 1, - perPage = 100, - }: ListFilesArgs): Promise { - const fileKind = this.getFileKind(fileKindId); - const result = await this.metadataClient.list({ - fileKind: fileKind.id, - page, - perPage, - }); - const fileClient = this.createFileClient(fileKind.id); - return result.map((file) => this.toFile(file.id, file.metadata, fileKind.id, fileClient)); - } - public getFileKind(id: string): FileKind { return this.fileKindRegistry.get(id); } - public async findFilesJSON(args: FindFileArgs): Promise { - const result = await this.metadataClient.find(args); - return result.map((r) => toJSON(r.id, r.metadata)); + public async findFilesJSON(args: FindFileArgs): Promise<{ files: FileJSON[]; total: number }> { + const { total, files } = await this.metadataClient.find(args); + return { + total, + files: files.map(({ id, metadata }) => toJSON(id, metadata)), + }; } public async getUsageMetrics(): Promise { diff --git a/x-pack/plugins/files/server/index.ts b/x-pack/plugins/files/server/index.ts index 7b1ecc5ac89ce..fe2bd3e69eec0 100755 --- a/x-pack/plugins/files/server/index.ts +++ b/x-pack/plugins/files/server/index.ts @@ -13,7 +13,6 @@ export type { FileDescriptor, GetMetadataArg, FindMetadataArg, - ListMetadataArg, UpdateMetadataArg, DeleteMetedataArg, FileMetadataClient, @@ -35,7 +34,6 @@ export type { export type { GetByIdArgs, FindFileArgs, - ListFilesArgs, CreateFileArgs, DeleteFileArgs, UpdateFileArgs, diff --git a/x-pack/plugins/files/server/integration_tests/file_service.test.ts b/x-pack/plugins/files/server/integration_tests/file_service.test.ts index 6277be74e1409..9ea208ca29855 100644 --- a/x-pack/plugins/files/server/integration_tests/file_service.test.ts +++ b/x-pack/plugins/files/server/integration_tests/file_service.test.ts @@ -99,8 +99,8 @@ describe('FileService', () => { } afterEach(async () => { await Promise.all(disposables.map((file) => file.delete())); - const results = await fileService.list({ fileKind }); - expect(results.length).toBe(0); + const { files } = await fileService.find({ kind: [fileKind] }); + expect(files.length).toBe(0); disposables = []; }); @@ -146,16 +146,46 @@ describe('FileService', () => { createDisposableFile({ fileKind, name: 'test-3' }), createDisposableFile({ fileKind, name: 'test-3' /* Also test file with same name */ }), ]); - const result = await fileService.list({ fileKind }); - expect(result.length).toBe(4); + const result = await fileService.find({ kind: [fileKind] }); + expect(result.files.length).toBe(4); + }); + + it('lists files and filters', async () => { + await Promise.all([ + createDisposableFile({ fileKind, name: 'foo-1' }), + createDisposableFile({ fileKind, name: 'foo-2' }), + createDisposableFile({ fileKind, name: 'foo-3' }), + createDisposableFile({ fileKind, name: 'test-3' }), + ]); + { + const { files, total } = await fileService.find({ + kind: [fileKind], + name: ['foo*'], + perPage: 2, + page: 1, + }); + expect(files.length).toBe(2); + expect(total).toBe(3); + } + + { + const { files, total } = await fileService.find({ + kind: [fileKind], + name: ['foo*'], + perPage: 2, + page: 2, + }); + expect(files.length).toBe(1); + expect(total).toBe(3); + } }); it('deletes files', async () => { const file = await fileService.create({ fileKind, name: 'test' }); - const files = await fileService.list({ fileKind }); - expect(files.length).toBe(1); + const result = await fileService.find({ kind: [fileKind] }); + expect(result.files.length).toBe(1); await file.delete(); - expect(await fileService.list({ fileKind })).toEqual([]); + expect(await fileService.find({ kind: [fileKind] })).toEqual({ files: [], total: 0 }); }); interface CustomMeta { diff --git a/x-pack/plugins/files/server/mocks.ts b/x-pack/plugins/files/server/mocks.ts index c5b5afa5d842e..033e94c3bfd7f 100644 --- a/x-pack/plugins/files/server/mocks.ts +++ b/x-pack/plugins/files/server/mocks.ts @@ -17,7 +17,6 @@ export const createFileServiceMock = (): DeeplyMockedKeys => ( getByToken: jest.fn(), getShareObject: jest.fn(), getUsageMetrics: jest.fn(), - list: jest.fn(), listShareObjects: jest.fn(), update: jest.fn(), updateShareObject: jest.fn(), diff --git a/x-pack/plugins/files/server/routes/file_kind/integration_tests/file_kind_http.test.ts b/x-pack/plugins/files/server/routes/file_kind/integration_tests/file_kind_http.test.ts index e4d4bbdf0b297..6acd1e3317344 100644 --- a/x-pack/plugins/files/server/routes/file_kind/integration_tests/file_kind_http.test.ts +++ b/x-pack/plugins/files/server/routes/file_kind/integration_tests/file_kind_http.test.ts @@ -110,14 +110,17 @@ describe('File kind HTTP API', () => { const { body: { files }, - } = await request.get(root, `/api/files/files/${fileKind}/list`).expect(200); + } = await request.post(root, `/api/files/files/${fileKind}/list`).send({}).expect(200); expect(files).toHaveLength(nrOfFiles); expect(files[0]).toEqual(expect.objectContaining({ name: 'test' })); const { body: { files: files2 }, - } = await request.get(root, `/api/files/files/${fileKind}/list?page=1&perPage=5`).expect(200); + } = await request + .post(root, `/api/files/files/${fileKind}/list?page=1&perPage=5`) + .send({}) + .expect(200); expect(files2).toHaveLength(5); }); diff --git a/x-pack/plugins/files/server/routes/file_kind/list.ts b/x-pack/plugins/files/server/routes/file_kind/list.ts index b6a869117b37f..b9b389f41b7a9 100644 --- a/x-pack/plugins/files/server/routes/file_kind/list.ts +++ b/x-pack/plugins/files/server/routes/file_kind/list.ts @@ -8,27 +8,47 @@ import { schema } from '@kbn/config-schema'; import type { FileJSON, FileKind } from '../../../common/types'; import { CreateRouteDefinition, FILES_API_ROUTES } from '../api_routes'; import type { CreateHandler, FileKindRouter } from './types'; +import { + stringOrArrayOfStrings, + nameStringOrArrayOfNameStrings, + toArrayOrUndefined, +} from '../find'; -export const method = 'get' as const; +export const method = 'post' as const; const rt = { + body: schema.object({ + status: schema.maybe(stringOrArrayOfStrings), + extension: schema.maybe(stringOrArrayOfStrings), + name: schema.maybe(nameStringOrArrayOfNameStrings), + meta: schema.maybe(schema.object({}, { unknowns: 'allow' })), + }), query: schema.object({ - page: schema.maybe(schema.number({ defaultValue: 1 })), + page: schema.maybe(schema.number()), perPage: schema.maybe(schema.number({ defaultValue: 100 })), }), }; -export type Endpoint = CreateRouteDefinition> }>; +export type Endpoint = CreateRouteDefinition< + typeof rt, + { files: Array>; total: number } +>; export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { + body: { name, status, extension, meta }, query: { page, perPage }, } = req; const { fileService } = await files; - const response = await fileService.asCurrentUser().list({ fileKind, page, perPage }); - const body: Endpoint['output'] = { - files: response.map((result) => result.toJSON()), - }; + const body: Endpoint['output'] = await fileService.asCurrentUser().find({ + kind: [fileKind], + name: toArrayOrUndefined(name), + status: toArrayOrUndefined(status), + extension: toArrayOrUndefined(extension), + page, + perPage, + meta, + }); return res.ok({ body }); }; diff --git a/x-pack/plugins/files/server/routes/find.ts b/x-pack/plugins/files/server/routes/find.ts index 43348491b77b5..4f5a6da46b455 100644 --- a/x-pack/plugins/files/server/routes/find.ts +++ b/x-pack/plugins/files/server/routes/find.ts @@ -14,8 +14,13 @@ const method = 'post' as const; const string64 = schema.string({ maxLength: 64 }); const string256 = schema.string({ maxLength: 256 }); -const stringOrArrayOfStrings = schema.oneOf([string64, schema.arrayOf(string64)]); -const nameStringOrArrayOfNameStrings = schema.oneOf([string256, schema.arrayOf(string256)]); +export const stringOrArrayOfStrings = schema.oneOf([string64, schema.arrayOf(string64)]); +export const nameStringOrArrayOfNameStrings = schema.oneOf([string256, schema.arrayOf(string256)]); + +export function toArrayOrUndefined(val?: string | string[]): undefined | string[] { + if (val == null) return undefined; + return Array.isArray(val) ? val : [val]; +} const rt = { body: schema.object({ @@ -31,11 +36,7 @@ const rt = { }), }; -export type Endpoint = CreateRouteDefinition; - -function toArray(val: string | string[]) { - return Array.isArray(val) ? val : [val]; -} +export type Endpoint = CreateRouteDefinition; const handler: CreateHandler = async ({ files }, req, res) => { const { fileService } = await files; @@ -44,15 +45,18 @@ const handler: CreateHandler = async ({ files }, req, res) => { query, } = req; + const { files: results, total } = await fileService.asCurrentUser().find({ + kind: toArrayOrUndefined(kind), + name: toArrayOrUndefined(name), + status: toArrayOrUndefined(status), + extension: toArrayOrUndefined(extension), + meta, + ...query, + }); + const body: Endpoint['output'] = { - files: await fileService.asCurrentUser().find({ - kind: kind ? toArray(kind) : undefined, - name: name ? toArray(name) : undefined, - status: status ? toArray(status) : undefined, - extension: extension ? toArray(extension) : undefined, - meta, - ...query, - }), + total, + files: results, }; return res.ok({ body, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/add_first_integration_splash.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/add_first_integration_splash.tsx index 1e0aeb3236492..e1e53efa16985 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/add_first_integration_splash.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/add_first_integration_splash.tsx @@ -230,7 +230,7 @@ export const AddFirstIntegrationSplashScreen: React.FC<{ error?: RequestError | null; packageInfo?: PackageInfo; isLoading: boolean; - cancelClickHandler: React.ReactEventHandler; + cancelClickHandler?: React.ReactEventHandler; cancelUrl: string; onNext: () => void; }> = ({ @@ -276,6 +276,12 @@ export const AddFirstIntegrationSplashScreen: React.FC<{ + } cancelClickHandler={cancelClickHandler} isLoading={isLoading} onNext={onNext} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx index 228a89fa4e495..61875a063207d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx @@ -29,8 +29,9 @@ export const NotObscuredByBottomBar = styled('div')` export const CreatePackagePolicyBottomBar: React.FC<{ isLoading?: boolean; isDisabled?: boolean; - cancelClickHandler: React.ReactEventHandler; + cancelClickHandler?: React.ReactEventHandler; cancelUrl?: string; + cancelMessage?: React.ReactElement; actionMessage: React.ReactElement; onNext: () => void; noAnimation?: boolean; @@ -42,6 +43,7 @@ export const CreatePackagePolicyBottomBar: React.FC<{ cancelClickHandler, cancelUrl, actionMessage, + cancelMessage, isDisabled = false, noAnimation = false, }) => { @@ -53,10 +55,12 @@ export const CreatePackagePolicyBottomBar: React.FC<{ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - + {cancelMessage || ( + + )} @@ -85,7 +89,7 @@ export const CreatePackagePolicyBottomBar: React.FC<{ }; export const AgentStandaloneBottomBar: React.FC<{ - cancelClickHandler: React.ReactEventHandler; + cancelClickHandler?: React.ReactEventHandler; cancelUrl?: string; onNext: () => void; noAnimation?: boolean; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx index c14d4bcd67752..6ce311b36aa6a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx @@ -10,10 +10,9 @@ import { i18n } from '@kbn/i18n'; import { splitPkgKey } from '../../../../../../../common/services'; -import { useGetPackageInfoByKey, useGetSettings } from '../../../../hooks'; +import { useGetPackageInfoByKey, useGetSettings, useLink } from '../../../../hooks'; import type { AddToPolicyParams, CreatePackagePolicyParams } from '../types'; -import { useCancelAddPackagePolicy } from '../hooks'; import { useGetAgentPolicyOrDefault } from './hooks'; @@ -55,11 +54,12 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ queryParamsPolicyId, }) => { const { params } = useRouteMatch(); - - const { pkgName, pkgVersion } = splitPkgKey(params.pkgkey); + const { pkgkey, policyId, integration } = params; + const { pkgName, pkgVersion } = splitPkgKey(pkgkey); const [onSplash, setOnSplash] = useState(true); const [currentStep, setCurrentStep] = useState(0); const [isManaged, setIsManaged] = useState(true); + const { getHref } = useLink(); const [enrolledAgentIds, setEnrolledAgentIds] = useState([]); const toggleIsManaged = (newIsManaged: boolean) => { setIsManaged(newIsManaged); @@ -85,19 +85,21 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ const settings = useMemo(() => settingsData?.item, [settingsData]); const integrationInfo = useMemo(() => { - if (!params.integration) return; + if (!integration) return; return packageInfo?.policy_templates?.find( - (policyTemplate) => policyTemplate.name === params.integration + (policyTemplate) => policyTemplate.name === integration ); - }, [packageInfo?.policy_templates, params]); + }, [packageInfo?.policy_templates, integration]); const splashScreenNext = () => { setOnSplash(false); }; - const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({ - from, - pkgkey: params.pkgkey, + const cancelUrl = getHref('add_integration_to_policy', { + pkgkey, + useMultiPageLayout: false, + ...(integration ? { integration } : {}), + ...(policyId ? { agentPolicyId: policyId } : {}), }); if (onSplash || !packageInfo) { @@ -108,7 +110,6 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ integrationInfo={integrationInfo} packageInfo={packageInfo} cancelUrl={cancelUrl} - cancelClickHandler={cancelClickHandler} onNext={splashScreenNext} /> ); @@ -125,7 +126,6 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ const stepsBack = () => { if (currentStep === 0) { - cancelClickHandler(null); return; } @@ -142,7 +142,6 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ packageInfo={packageInfo} integrationInfo={integrationInfo} cancelUrl={cancelUrl} - cancelClickHandler={cancelClickHandler} onNext={stepsNext} onBack={stepsBack} isManaged={isManaged} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/types.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/types.ts index 327be01e0b3e9..d3a195e0513d4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/types.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/types.ts @@ -27,7 +27,7 @@ export interface MultiPageStepLayoutProps { enrollmentAPIKey?: EnrollmentAPIKey; packageInfo: PackageInfo; integrationInfo?: RegistryPolicyTemplate; - cancelClickHandler: React.ReactEventHandler; + cancelClickHandler?: React.ReactEventHandler; onBack: React.ReactEventHandler; cancelUrl: string; steps: MultiPageStep[]; diff --git a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts index d761f8b5e7f30..274ab22999ff8 100644 --- a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts @@ -59,8 +59,16 @@ export const getListHandler: RequestHandler = async (context, request, response) getPackageSavedObjects(savedObjects.client), ]); - const dataStreamsInfoByName = keyBy(dataStreamsInfo, 'name'); - const dataStreamsStatsByName = keyBy(dataStreamStats, 'data_stream'); + const filteredDataStreamsInfo = dataStreamsInfo.filter( + (ds) => ds?._meta?.managed_by === 'fleet' + ); + + const dataStreamsInfoByName = keyBy(filteredDataStreamsInfo, 'name'); + + const filteredDataStreamsStats = dataStreamStats.filter( + (dss) => !!dataStreamsInfoByName[dss.data_stream] + ); + const dataStreamsStatsByName = keyBy(filteredDataStreamsStats, 'data_stream'); // Combine data stream info const dataStreams = merge(dataStreamsInfoByName, dataStreamsStatsByName); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx index 4464b54fb8a32..fa293a57903fc 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx @@ -41,6 +41,8 @@ import { getTokenInfo, offsetToRowColumn, monacoPositionToOffset, + createEditOperation, + MARKER, } from './math_completion'; import { LANGUAGE_ID } from './math_tokenization'; import { MemoizedFormulaHelp } from './formula_help'; @@ -85,6 +87,8 @@ export const WrappedFormulaEditor = ({ const MemoizedFormulaEditor = React.memo(FormulaEditor); +const namedArgumentsTypes = new Set(['kql', 'lucene', 'shift', 'reducedTimeRange']); + export function FormulaEditor({ layer, paramEditorUpdater, @@ -533,47 +537,44 @@ export function FormulaEditor({ const isSingleQuoteCase = /'LENS_MATH_MARKER/; // Make sure that we are only adding kql='' or lucene='', and also // check that the = sign isn't inside the KQL expression like kql='=' - if ( - !tokenInfo || - typeof tokenInfo.ast === 'number' || - tokenInfo.ast.type !== 'namedArgument' || - (tokenInfo.ast.name !== 'kql' && - tokenInfo.ast.name !== 'lucene' && - tokenInfo.ast.name !== 'shift' && - tokenInfo.ast.name !== 'reducedTimeRange') || - (tokenInfo.ast.value !== 'LENS_MATH_MARKER' && - !isSingleQuoteCase.test(tokenInfo.ast.value)) - ) { - return; + if (tokenInfo) { + if ( + typeof tokenInfo.ast === 'number' || + tokenInfo.ast.type !== 'namedArgument' || + !namedArgumentsTypes.has(tokenInfo.ast.name) || + (tokenInfo.ast.value !== MARKER && !isSingleQuoteCase.test(tokenInfo.ast.value)) + ) { + return; + } } let editOperation: monaco.editor.IIdentifiedSingleEditOperation | null = null; + const cursorOffset = 2; if (char === '=') { - editOperation = { - range: { - ...currentPosition, - // Insert after the current char - startColumn: currentPosition.startColumn + 1, - endColumn: currentPosition.startColumn + 1, - }, - text: `''`, - }; + // check also the previous char whether it was already a = + // to avoid infinite loops + if (!tokenInfo && currentText.charAt(offset - 1) !== '=') { + editOperation = createEditOperation('=', currentPosition, 1); + } + if (tokenInfo) { + editOperation = createEditOperation(`''`, currentPosition, 1); + } } + + if (!tokenInfo && !editOperation) { + return; + } + if ( char === "'" && + tokenInfo?.ast && + typeof tokenInfo.ast !== 'number' && + 'name' in tokenInfo.ast && tokenInfo.ast.name !== 'shift' && tokenInfo.ast.name !== 'reducedTimeRange' ) { - editOperation = { - range: { - ...currentPosition, - // Insert after the current char - startColumn: currentPosition.startColumn, - endColumn: currentPosition.startColumn + 1, - }, - text: `\\'`, - }; + editOperation = createEditOperation(`\\'`, currentPosition); } if (editOperation) { diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_help.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_help.tsx index 8f728bc5d15a2..d0b4b3b0bb173 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_help.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_help.tsx @@ -543,27 +543,30 @@ export function getFunctionSignatureLabel( } if (operationDefinitionMap[name]) { const def = operationDefinitionMap[name]; - let extraArgs = ''; + const extraArgs: string[] = []; if (def.filterable) { - extraArgs += ','; - extraArgs += i18n.translate('xpack.lens.formula.kqlExtraArguments', { - defaultMessage: '[kql]?: string, [lucene]?: string', - }); - } - if (def.filterable && def.shiftable) { - extraArgs += ', '; + extraArgs.push( + i18n.translate('xpack.lens.formula.kqlExtraArguments', { + defaultMessage: '[kql]?: string, [lucene]?: string', + }) + ); } if (def.shiftable) { - extraArgs += i18n.translate('xpack.lens.formula.shiftExtraArguments', { - defaultMessage: '[shift]?: string', - }); + extraArgs.push( + i18n.translate('xpack.lens.formula.shiftExtraArguments', { + defaultMessage: '[shift]?: string', + }) + ); } if (def.canReduceTimeRange) { - extraArgs += i18n.translate('xpack.lens.formula.reducedTimeRangeExtraArguments', { - defaultMessage: '[reducedTimeRange]?: string', - }); + extraArgs.push( + i18n.translate('xpack.lens.formula.reducedTimeRangeExtraArguments', { + defaultMessage: '[reducedTimeRange]?: string', + }) + ); } - return `${name}(${def.documentation?.signature}${extraArgs})`; + const extraComma = extraArgs.length ? ', ' : ''; + return `${name}(${def.documentation?.signature}${extraComma}${extraArgs.join(', ')})`; } return ''; } diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts index 0dba9dc993151..8d0c9fd4d6b6b 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts @@ -61,7 +61,7 @@ function inLocation(cursorPosition: number, location: TinymathLocation) { return cursorPosition >= location.min && cursorPosition < location.max; } -const MARKER = 'LENS_MATH_MARKER'; +export const MARKER = 'LENS_MATH_MARKER'; export function getInfoAtZeroIndexedPosition( ast: TinymathAST, @@ -94,6 +94,23 @@ export function getInfoAtZeroIndexedPosition( }; } +export function createEditOperation( + textToInject: string, + currentPosition: monaco.IRange, + startOffset: number = 0, + endOffset: number = 1 +) { + return { + range: { + ...currentPosition, + // Insert after the current char + startColumn: currentPosition.startColumn + startOffset, + endColumn: currentPosition.startColumn + endOffset, + }, + text: textToInject, + }; +} + export function offsetToRowColumn(expression: string, offset: number): monaco.Position { const lines = expression.split(/\n/); let remainingChars = offset; diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.test.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.test.ts index 60fc7d5b72632..c96bff33b5267 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.test.ts @@ -348,5 +348,90 @@ describe('math operation', () => { }, ]); }); + + it('should work for comparison operations as well', () => { + const tinymathAst = { + type: 'function', + name: 'ifelse', + args: [ + { + type: 'function', + name: 'eq', + args: ['columnX0', 0], + }, + { + type: 'function', + name: 'ifelse', + args: [ + { + type: 'function', + name: 'lt', + args: ['columnX1', 0], + }, + { + type: 'function', + name: 'ifelse', + args: [ + { + type: 'function', + name: 'lte', + args: ['columnX2', 0], + }, + 'columnX3', + 'columnX4', + ], + }, + 'columnX5', + ], + }, + { + type: 'function', + name: 'ifelse', + args: [ + { + type: 'function', + name: 'gt', + args: ['columnX6', 0], + }, + { + type: 'function', + name: 'ifelse', + args: [ + { + type: 'function', + name: 'gte', + args: ['columnX7', 0], + }, + 'columnX8', + 'columnX9', + ], + }, + 'columnX10', + ], + }, + ], + } as unknown as TinymathAST; + + const expression = mathOperation.toExpression( + createLayerWithMathColumn(tinymathAst), + 'myColumnId', + {} as IndexPattern + ); + + expect(expression).toEqual([ + { + type: 'function', + function: 'mathColumn', + arguments: { + id: ['myColumnId'], + name: ['Math'], + expression: [ + 'ifelse(("columnX0" == 0),ifelse(("columnX1" < 0),ifelse(("columnX2" <= 0),"columnX3","columnX4"),"columnX5"),ifelse(("columnX6" > 0),ifelse(("columnX7" >= 0),"columnX8","columnX9"),"columnX10"))', + ], + onError: ['null'], + }, + }, + ]); + }); }); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.tsx index de197c2de8189..4215d727a4c42 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.tsx @@ -77,6 +77,11 @@ const optimizableFnsMap: Record = { subtract: '-', multiply: '*', divide: '/', + lt: '<', + gt: '>', + eq: '==', + lte: '<=', + gte: '>=', }; function astToString(ast: TinymathAST | string): string | number { diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx index 8a7da27386b9d..8aa5648ab2d1b 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx @@ -14,6 +14,7 @@ import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { AggregateQuery } from '@kbn/es-query'; import type { SavedObjectReference } from '@kbn/core/public'; import { EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; +import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import type { ExpressionsStart, DatatableColumnType } from '@kbn/expressions-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; @@ -349,15 +350,17 @@ export function getTextBasedDatasource({ renderDataPanel(domElement: Element, props: DatasourceDataPanelProps) { const layerFields = TextBasedDatasource?.getSelectedFields?.(props.state); render( - - - , + + + + + , domElement ); }, @@ -404,60 +407,65 @@ export function getTextBasedDatasource({ (column) => column.columnId === props.columnId ); render( - - { - const meta = fields.find((f) => f.name === choice.field)?.meta; - const newColumn = { - columnId: props.columnId, - fieldName: choice.field, - meta, - }; - return props.setState( - !selectedField - ? { - ...props.state, - layers: { - ...props.state.layers, - [props.layerId]: { - ...props.state.layers[props.layerId], - columns: [...props.state.layers[props.layerId].columns, newColumn], - allColumns: [...props.state.layers[props.layerId].allColumns, newColumn], + + + { + const meta = fields.find((f) => f.name === choice.field)?.meta; + const newColumn = { + columnId: props.columnId, + fieldName: choice.field, + meta, + }; + return props.setState( + !selectedField + ? { + ...props.state, + layers: { + ...props.state.layers, + [props.layerId]: { + ...props.state.layers[props.layerId], + columns: [...props.state.layers[props.layerId].columns, newColumn], + allColumns: [ + ...props.state.layers[props.layerId].allColumns, + newColumn, + ], + }, }, - }, - } - : { - ...props.state, - layers: { - ...props.state.layers, - [props.layerId]: { - ...props.state.layers[props.layerId], - columns: props.state.layers[props.layerId].columns.map((col) => - col.columnId !== props.columnId - ? col - : { ...col, fieldName: choice.field } - ), - allColumns: props.state.layers[props.layerId].allColumns.map((col) => - col.columnId !== props.columnId - ? col - : { ...col, fieldName: choice.field } - ), + } + : { + ...props.state, + layers: { + ...props.state.layers, + [props.layerId]: { + ...props.state.layers[props.layerId], + columns: props.state.layers[props.layerId].columns.map((col) => + col.columnId !== props.columnId + ? col + : { ...col, fieldName: choice.field } + ), + allColumns: props.state.layers[props.layerId].allColumns.map((col) => + col.columnId !== props.columnId + ? col + : { ...col, fieldName: choice.field } + ), + }, }, - }, - } - ); - }} - /> - , + } + ); + }} + /> + + , domElement ); }, @@ -467,9 +475,11 @@ export function getTextBasedDatasource({ props: DatasourceLayerPanelProps ) => { render( - - - , + + + + + , domElement ); }, diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/columns.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/columns.tsx index 2ea1cfd059e40..faf2e9d5d826d 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/columns.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/columns.tsx @@ -48,7 +48,8 @@ export const createGridColumns = ( alignments: Record, headerRowHeight: 'auto' | 'single' | 'custom', headerRowLines: number, - closeCellPopover?: Function + closeCellPopover?: Function, + columnFilterable?: boolean[] ) => { const columnsReverseLookup = table.columns.reduce< Record @@ -57,8 +58,6 @@ export const createGridColumns = ( return memo; }, {}); - const bucketLookup = new Set(bucketColumns); - const getContentData = ({ rowIndex, columnId, @@ -73,8 +72,8 @@ export const createGridColumns = ( }; return visibleColumns.map((field) => { - const filterable = bucketLookup.has(field); const { name, index: colIndex } = columnsReverseLookup[field]; + const filterable = columnFilterable?.[colIndex] || false; const columnArgs = columnConfig.columns.find(({ columnId }) => columnId === field); diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx index 3583225584ec7..5d5c16f9e8be3 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx @@ -201,6 +201,7 @@ describe('DatatableComponent', () => { uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} interactive renderComplete={renderComplete} + columnFilterable={[true, true, true]} /> ); @@ -241,6 +242,7 @@ describe('DatatableComponent', () => { uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} interactive renderComplete={renderComplete} + columnFilterable={[true, true, true]} /> ); @@ -316,6 +318,7 @@ describe('DatatableComponent', () => { uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} interactive renderComplete={renderComplete} + columnFilterable={[true, true, true]} /> ); @@ -356,6 +359,7 @@ describe('DatatableComponent', () => { uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} interactive={false} renderComplete={renderComplete} + columnFilterable={[true, true, true]} /> ); diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx index bbf5eddef879e..4b8f8067e974e 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx @@ -310,7 +310,8 @@ export const DatatableComponent = (props: DatatableRenderProps) => { alignments, headerRowHeight, headerRowLines, - dataGridRef.current?.closeCellPopover + dataGridRef.current?.closeCellPopover, + props.columnFilterable ), [ bucketColumns, @@ -326,6 +327,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { alignments, headerRowHeight, headerRowLines, + props.columnFilterable, ] ); diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/types.ts b/x-pack/plugins/lens/public/visualizations/datatable/components/types.ts index 96a1e3caf56ce..ec2b687690dd9 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/types.ts +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/types.ts @@ -58,6 +58,7 @@ export type DatatableRenderProps = DatatableProps & { * ROW_CLICK_TRIGGER actions attached to it, otherwise false. */ rowHasRowClickTriggerActions?: boolean[]; + columnFilterable?: boolean[]; }; export interface DataContextType { diff --git a/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx b/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx index da7fbceee5b23..32fb293647620 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx @@ -12,7 +12,11 @@ import { I18nProvider } from '@kbn/i18n-react'; import type { PaletteRegistry } from '@kbn/coloring'; import type { IAggType } from '@kbn/data-plugin/public'; import { IUiSettingsClient, ThemeServiceStart } from '@kbn/core/public'; -import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; +import type { + Datatable, + ExpressionRenderDefinition, + IInterpreterRenderHandlers, +} from '@kbn/expressions-plugin/common'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { trackUiCounterEvents } from '../../lens_ui_telemetry'; import { DatatableComponent } from './components/table_basic'; @@ -21,6 +25,30 @@ import type { ILensInterpreterRenderHandlers } from '../../types'; import type { FormatFactory } from '../../../common'; import type { DatatableProps } from '../../../common/expressions'; +async function columnsFilterable(table: Datatable, handlers: IInterpreterRenderHandlers) { + if (!table.rows.length) { + return; + } + return Promise.all( + table.columns.map(async (column, colIndex) => { + return Boolean( + await handlers.hasCompatibleActions?.({ + name: 'filter', + data: { + data: [ + { + table, + column: colIndex, + row: 0, + }, + ], + }, + }) + ); + }) + ); +} + export const getDatatableRenderer = (dependencies: { formatFactory: FormatFactory; getType: Promise<(name: string) => IAggType>; @@ -87,6 +115,7 @@ export const getDatatableRenderer = (dependencies: { paletteService={dependencies.paletteService} getType={resolvedGetType} rowHasRowClickTriggerActions={rowHasRowClickTriggerActions} + columnFilterable={await columnsFilterable(config.data, handlers)} interactive={isInteractive()} uiSettings={dependencies.uiSettings} renderComplete={renderComplete} diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 96318890a2d94..94ae72a050e21 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -53,6 +53,7 @@ export enum LAYER_TYPE { HEATMAP = 'HEATMAP', BLENDED_VECTOR = 'BLENDED_VECTOR', MVT_VECTOR = 'MVT_VECTOR', + LAYER_GROUP = 'LAYER_GROUP', } export enum SOURCE_TYPES { diff --git a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts index 160a34f8bcc0d..a547ef9c6d93a 100644 --- a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts @@ -71,6 +71,7 @@ export type LayerDescriptor = { style?: StyleDescriptor | null; query?: Query; includeInFitToBounds?: boolean; + parent?: string; }; export type VectorLayerDescriptor = LayerDescriptor & { @@ -89,3 +90,10 @@ export type EMSVectorTileLayerDescriptor = LayerDescriptor & { type: LAYER_TYPE.EMS_VECTOR_TILE; style: EMSVectorTileStyleDescriptor; }; + +export type LayerGroupDescriptor = LayerDescriptor & { + type: LAYER_TYPE.LAYER_GROUP; + label: string; + sourceDescriptor: null; + visible: boolean; +}; diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index b5ce42ebefc09..5e27b488065ab 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -9,9 +9,7 @@ import { AnyAction, Dispatch } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; -import bbox from '@turf/bbox'; import uuid from 'uuid/v4'; -import { multiPoint } from '@turf/helpers'; import { FeatureCollection } from 'geojson'; import { Adapters } from '@kbn/inspector-plugin/common/adapters'; import { MapStoreState } from '../reducers/store'; @@ -49,7 +47,9 @@ import { ILayer } from '../classes/layers/layer'; import { IVectorLayer } from '../classes/layers/vector_layer'; import { DataRequestMeta, MapExtent, DataFilters } from '../../common/descriptor_types'; import { DataRequestAbortError } from '../classes/util/data_request'; -import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_util'; +import { scaleBounds } from '../../common/elasticsearch_util'; +import { getLayersExtent } from './get_layers_extent'; +import { isLayerGroup } from '../classes/layers/layer_group'; const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1; @@ -101,7 +101,7 @@ export function cancelAllInFlightRequests() { export function updateStyleMeta(layerId: string | null) { return async (dispatch: Dispatch, getState: () => MapStoreState) => { const layer = getLayerById(layerId, getState()); - if (!layer) { + if (!layer || isLayerGroup(layer)) { return; } @@ -378,8 +378,8 @@ export function fitToLayerExtent(layerId: string) { if (targetLayer) { try { - const bounds = await targetLayer.getBounds( - getDataRequestContext(dispatch, getState, layerId, false, false) + const bounds = await targetLayer.getBounds((boundsLayerId) => + getDataRequestContext(dispatch, getState, boundsLayerId, false, false) ); if (bounds) { await dispatch(setGotoWithBounds(scaleBounds(bounds, FIT_TO_BOUNDS_SCALE_FACTOR))); @@ -401,65 +401,22 @@ export function fitToLayerExtent(layerId: string) { export function fitToDataBounds(onNoBounds?: () => void) { return async (dispatch: Dispatch, getState: () => MapStoreState) => { - const layerList = getLayerList(getState()); - - if (!layerList.length) { - return; - } - - const boundsPromises = layerList.map(async (layer: ILayer) => { - if (!(await layer.isFittable())) { - return null; - } - return layer.getBounds( - getDataRequestContext(dispatch, getState, layer.getId(), false, false) - ); + const rootLayers = getLayerList(getState()).filter((layer) => { + return layer.getParent() === undefined; }); - let bounds; - try { - bounds = await Promise.all(boundsPromises); - } catch (error) { - if (!(error instanceof DataRequestAbortError)) { - // eslint-disable-next-line no-console - console.warn( - 'Unhandled getBounds error for layer. Only DataRequestAbortError should be surfaced', - error - ); - } - // new fitToDataBounds request has superseded this thread of execution. Results no longer needed. - return; - } - - const corners = []; - for (let i = 0; i < bounds.length; i++) { - const b = bounds[i]; - - // filter out undefined bounds (uses Infinity due to turf responses) - if ( - b === null || - b.minLon === Infinity || - b.maxLon === Infinity || - b.minLat === -Infinity || - b.maxLat === -Infinity - ) { - continue; - } - - corners.push([b.minLon, b.minLat]); - corners.push([b.maxLon, b.maxLat]); - } + const extent = await getLayersExtent(rootLayers, (boundsLayerId) => + getDataRequestContext(dispatch, getState, boundsLayerId, false, false) + ); - if (!corners.length) { + if (extent === null) { if (onNoBounds) { onNoBounds(); } return; } - const dataBounds = turfBboxToBounds(bbox(multiPoint(corners))); - - dispatch(setGotoWithBounds(scaleBounds(dataBounds, FIT_TO_BOUNDS_SCALE_FACTOR))); + dispatch(setGotoWithBounds(scaleBounds(extent, FIT_TO_BOUNDS_SCALE_FACTOR))); }; } diff --git a/x-pack/plugins/maps/public/actions/get_layers_extent.tsx b/x-pack/plugins/maps/public/actions/get_layers_extent.tsx new file mode 100644 index 0000000000000..81d8367bd2803 --- /dev/null +++ b/x-pack/plugins/maps/public/actions/get_layers_extent.tsx @@ -0,0 +1,66 @@ +/* + * 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 bbox from '@turf/bbox'; +import { multiPoint } from '@turf/helpers'; +import { MapExtent } from '../../common/descriptor_types'; +import { turfBboxToBounds } from '../../common/elasticsearch_util'; +import { ILayer } from '../classes/layers/layer'; +import type { DataRequestContext } from './data_request_actions'; +import { DataRequestAbortError } from '../classes/util/data_request'; + +export async function getLayersExtent( + layers: ILayer[], + getDataRequestContext: (layerId: string) => DataRequestContext +): Promise { + if (!layers.length) { + return null; + } + + const boundsPromises = layers.map(async (layer: ILayer) => { + if (!(await layer.isFittable())) { + return null; + } + return layer.getBounds(getDataRequestContext); + }); + + let bounds; + try { + bounds = await Promise.all(boundsPromises); + } catch (error) { + if (!(error instanceof DataRequestAbortError)) { + // eslint-disable-next-line no-console + console.warn( + 'Unhandled getBounds error for layer. Only DataRequestAbortError should be surfaced', + error + ); + } + // new fitToDataBounds request has superseded this thread of execution. Results no longer needed. + return null; + } + + const corners = []; + for (let i = 0; i < bounds.length; i++) { + const b = bounds[i]; + + // filter out undefined bounds (uses Infinity due to turf responses) + if ( + b === null || + b.minLon === Infinity || + b.maxLon === Infinity || + b.minLat === -Infinity || + b.maxLat === -Infinity + ) { + continue; + } + + corners.push([b.minLon, b.minLat]); + corners.push([b.maxLon, b.maxLat]); + } + + return corners.length ? turfBboxToBounds(bbox(multiPoint(corners))) : null; +} diff --git a/x-pack/plugins/maps/public/actions/index.ts b/x-pack/plugins/maps/public/actions/index.ts index 96db1cebe7d39..235f8d141411e 100644 --- a/x-pack/plugins/maps/public/actions/index.ts +++ b/x-pack/plugins/maps/public/actions/index.ts @@ -24,3 +24,4 @@ export { openOnHoverTooltip, updateOpenTooltips, } from './tooltip_actions'; +export { getLayersExtent } from './get_layers_extent'; diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index 317f6e09053e5..5ba9edaee58d0 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -75,6 +75,7 @@ import { IESAggField } from '../classes/fields/agg'; import { IField } from '../classes/fields/field'; import type { IESSource } from '../classes/sources/es_source'; import { getDrawMode, getOpenTOCDetails } from '../selectors/ui_selectors'; +import { isLayerGroup, LayerGroup } from '../classes/layers/layer_group'; export function trackCurrentLayerState(layerId: string) { return { @@ -160,8 +161,12 @@ export function cloneLayer(layerId: string) { return; } - const clonedDescriptor = await layer.cloneDescriptor(); - dispatch(addLayer(clonedDescriptor)); + (await layer.cloneDescriptor()).forEach((layerDescriptor) => { + dispatch(addLayer(layerDescriptor)); + if (layer.getParent()) { + dispatch(moveLayerToLeftOfTarget(layerDescriptor.id, layerId)); + } + }); }; } @@ -249,12 +254,19 @@ export function setLayerVisibility(layerId: string, makeVisible: boolean) { dispatch: ThunkDispatch, getState: () => MapStoreState ) => { - // if the current-state is invisible, we also want to sync data - // e.g. if a layer was invisible at start-up, it won't have any data loaded const layer = getLayerById(layerId, getState()); + if (!layer) { + return; + } + + if (isLayerGroup(layer)) { + (layer as LayerGroup).getChildren().forEach((childLayer) => { + dispatch(setLayerVisibility(childLayer.getId(), makeVisible)); + }); + } // If the layer visibility is already what we want it to be, do nothing - if (!layer || layer.isVisible() === makeVisible) { + if (layer.isVisible() === makeVisible) { return; } @@ -263,6 +275,9 @@ export function setLayerVisibility(layerId: string, makeVisible: boolean) { layerId, visibility: makeVisible, }); + + // if the current-state is invisible, we also want to sync data + // e.g. if a layer was invisible at start-up, it won't have any data loaded if (makeVisible) { dispatch(syncDataForLayerId(layerId, false)); } @@ -290,7 +305,7 @@ export function hideAllLayers() { getState: () => MapStoreState ) => { getLayerList(getState()).forEach((layer: ILayer, index: number) => { - if (layer.isVisible() && !layer.isBasemap(index)) { + if (!layer.isBasemap(index)) { dispatch(setLayerVisibility(layer.getId(), false)); } }); @@ -303,9 +318,7 @@ export function showAllLayers() { getState: () => MapStoreState ) => { getLayerList(getState()).forEach((layer: ILayer, index: number) => { - if (!layer.isVisible()) { - dispatch(setLayerVisibility(layer.getId(), true)); - } + dispatch(setLayerVisibility(layer.getId(), true)); }); }; } @@ -316,23 +329,20 @@ export function showThisLayerOnly(layerId: string) { getState: () => MapStoreState ) => { getLayerList(getState()).forEach((layer: ILayer, index: number) => { - if (layer.isBasemap(index)) { - return; - } - - // show target layer - if (layer.getId() === layerId) { - if (!layer.isVisible()) { - dispatch(setLayerVisibility(layerId, true)); - } + if (layer.isBasemap(index) || layer.getId() === layerId) { return; } // hide all other layers - if (layer.isVisible()) { - dispatch(setLayerVisibility(layer.getId(), false)); - } + dispatch(setLayerVisibility(layer.getId(), false)); }); + + // show target layer after hiding all other layers + // since hiding layer group will hide its children + const targetLayer = getLayerById(layerId, getState()); + if (targetLayer) { + dispatch(setLayerVisibility(layerId, true)); + } }; } @@ -602,6 +612,15 @@ export function setLayerQuery(id: string, query: Query) { }; } +export function setLayerParent(id: string, parent: string | undefined) { + return { + type: UPDATE_LAYER_PROP, + id, + propName: 'parent', + newValue: parent, + }; +} + export function removeSelectedLayer() { return ( dispatch: ThunkDispatch, @@ -657,6 +676,12 @@ function removeLayerFromLayerList(layerId: string) { if (openTOCDetails.includes(layerId)) { dispatch(hideTOCDetails(layerId)); } + + if (isLayerGroup(layerGettingRemoved)) { + (layerGettingRemoved as LayerGroup).getChildren().forEach((childLayer) => { + dispatch(removeLayerFromLayerList(childLayer.getId())); + }); + } }; } @@ -786,7 +811,7 @@ export function updateMetaFromTiles(layerId: string, mbMetaFeatures: TileMetaFea } function clearInspectorAdapters(layer: ILayer, adapters: Adapters) { - if (!layer.getSource().isESSource()) { + if (isLayerGroup(layer) || !layer.getSource().isESSource()) { return; } @@ -811,3 +836,93 @@ function hasByValueStyling(styleDescriptor: StyleDescriptor) { }) ); } + +export function createLayerGroup(draggedLayerId: string, combineLayerId: string) { + return ( + dispatch: ThunkDispatch, + getState: () => MapStoreState + ) => { + const group = LayerGroup.createDescriptor({}); + const combineLayerDescriptor = getLayerDescriptor(getState(), combineLayerId); + if (combineLayerDescriptor?.parent) { + group.parent = combineLayerDescriptor.parent; + } + dispatch({ + type: ADD_LAYER, + layer: group, + }); + // Move group to left of combine-layer + dispatch(moveLayerToLeftOfTarget(group.id, combineLayerId)); + + dispatch(showTOCDetails(group.id)); + dispatch(setLayerParent(draggedLayerId, group.id)); + dispatch(setLayerParent(combineLayerId, group.id)); + + // Move dragged-layer to left of combine-layer + dispatch(moveLayerToLeftOfTarget(draggedLayerId, combineLayerId)); + }; +} + +export function moveLayerToLeftOfTarget(moveLayerId: string, targetLayerId: string) { + return ( + dispatch: ThunkDispatch, + getState: () => MapStoreState + ) => { + const layers = getLayerList(getState()); + const moveLayerIndex = layers.findIndex((layer) => layer.getId() === moveLayerId); + const targetLayerIndex = layers.findIndex((layer) => layer.getId() === targetLayerId); + if (moveLayerIndex === -1 || targetLayerIndex === -1) { + return; + } + const moveLayer = layers[moveLayerIndex]; + + const newIndex = + moveLayerIndex > targetLayerIndex + ? // When layer is moved to the right, new left sibling index is to the left of destination + targetLayerIndex + 1 + : // When layer is moved to the left, new left sibling index is the destination index + targetLayerIndex; + const newOrder = []; + for (let i = 0; i < layers.length; i++) { + newOrder.push(i); + } + newOrder.splice(moveLayerIndex, 1); + newOrder.splice(newIndex, 0, moveLayerIndex); + dispatch(updateLayerOrder(newOrder)); + + if (isLayerGroup(moveLayer)) { + (moveLayer as LayerGroup).getChildren().forEach((childLayer) => { + dispatch(moveLayerToLeftOfTarget(childLayer.getId(), targetLayerId)); + }); + } + }; +} + +export function moveLayerToBottom(moveLayerId: string) { + return ( + dispatch: ThunkDispatch, + getState: () => MapStoreState + ) => { + const layers = getLayerList(getState()); + const moveLayerIndex = layers.findIndex((layer) => layer.getId() === moveLayerId); + if (moveLayerIndex === -1) { + return; + } + const moveLayer = layers[moveLayerIndex]; + + const newIndex = 0; + const newOrder = []; + for (let i = 0; i < layers.length; i++) { + newOrder.push(i); + } + newOrder.splice(moveLayerIndex, 1); + newOrder.splice(newIndex, 0, moveLayerIndex); + dispatch(updateLayerOrder(newOrder)); + + if (isLayerGroup(moveLayer)) { + (moveLayer as LayerGroup).getChildren().forEach((childLayer) => { + dispatch(moveLayerToBottom(childLayer.getId())); + }); + } + }; +} diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts index ec9cec3a914ba..bc013cb958a4f 100644 --- a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts @@ -200,10 +200,10 @@ export class HeatmapLayer extends AbstractLayer { return this.getCurrentStyle().renderLegendDetails(metricFields[0]); } - async getBounds(syncContext: DataRequestContext) { + async getBounds(getDataRequestContext: (layerId: string) => DataRequestContext) { return await syncBoundsData({ layerId: this.getId(), - syncContext, + syncContext: getDataRequestContext(this.getId()), source: this.getSource(), sourceQuery: this.getQuery(), }); diff --git a/x-pack/plugins/maps/public/classes/layers/layer.test.ts b/x-pack/plugins/maps/public/classes/layers/layer.test.ts index 194b41680872c..908c38a2eef27 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/layer.test.ts @@ -18,7 +18,7 @@ class MockSource { this._fitToBounds = fitToBounds; } cloneDescriptor() { - return {}; + return [{}]; } async supportsFitToBounds() { diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index b01f2b9b8ca04..ef1a72649bbf0 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -20,7 +20,6 @@ import { MAX_ZOOM, MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, MIN_ZOOM, - SOURCE_BOUNDS_DATA_REQUEST_ID, SOURCE_DATA_REQUEST_ID, } from '../../../common/constants'; import { copyPersistentState } from '../../reducers/copy_persistent_state'; @@ -41,7 +40,9 @@ import { LICENSED_FEATURES } from '../../licensed_features'; import { IESSource } from '../sources/es_source'; export interface ILayer { - getBounds(dataRequestContext: DataRequestContext): Promise; + getBounds( + getDataRequestContext: (layerId: string) => DataRequestContext + ): Promise; getDataRequest(id: string): DataRequest | undefined; getDisplayName(source?: ISource): Promise; getId(): string; @@ -68,7 +69,6 @@ export interface ILayer { getImmutableSourceProperties(): Promise; renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null; isLayerLoading(): boolean; - isLoadingBounds(): boolean; isFilteredByGlobalTime(): Promise; hasErrors(): boolean; getErrors(): string; @@ -92,7 +92,7 @@ export interface ILayer { getQueryableIndexPatternIds(): string[]; getType(): LAYER_TYPE; isVisible(): boolean; - cloneDescriptor(): Promise; + cloneDescriptor(): Promise; renderStyleEditor( onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void, onCustomIconsChange: (customIcons: CustomIcon[]) => void @@ -117,6 +117,7 @@ export interface ILayer { getGeoFieldNames(): string[]; getStyleMetaDescriptorFromLocalFeatures(): Promise; isBasemap(order: number): boolean; + getParent(): string | undefined; } export type LayerIcon = { @@ -174,14 +175,14 @@ export class AbstractLayer implements ILayer { return this._descriptor; } - async cloneDescriptor(): Promise { + async cloneDescriptor(): Promise { const clonedDescriptor = copyPersistentState(this._descriptor); // layer id is uuid used to track styles/layers in mapbox clonedDescriptor.id = uuid(); const displayName = await this.getDisplayName(); clonedDescriptor.label = `Clone of ${displayName}`; clonedDescriptor.sourceDescriptor = this.getSource().cloneDescriptor(); - return clonedDescriptor; + return [clonedDescriptor]; } makeMbLayerId(layerNameSuffix: string): string { @@ -383,11 +384,6 @@ export class AbstractLayer implements ILayer { return areTilesLoading || this._dataRequests.some((dataRequest) => dataRequest.isLoading()); } - isLoadingBounds() { - const boundsDataRequest = this.getDataRequest(SOURCE_BOUNDS_DATA_REQUEST_ID); - return !!boundsDataRequest && boundsDataRequest.isLoading(); - } - hasErrors(): boolean { return _.get(this._descriptor, '__isInErrorState', false); } @@ -427,7 +423,9 @@ export class AbstractLayer implements ILayer { return sourceDataRequest ? sourceDataRequest.hasData() : false; } - async getBounds(dataRequestContext: DataRequestContext): Promise { + async getBounds( + getDataRequestContext: (layerId: string) => DataRequestContext + ): Promise { return null; } @@ -488,6 +486,10 @@ export class AbstractLayer implements ILayer { return false; } + getParent(): string | undefined { + return this._descriptor.parent; + } + _getMetaFromTiles(): TileMetaFeature[] { return this._descriptor.__metaFromTiles || []; } diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts b/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts new file mode 100644 index 0000000000000..3b2848d03f5ff --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { isLayerGroup, LayerGroup } from './layer_group'; diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx new file mode 100644 index 0000000000000..c0e3c4ee56402 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx @@ -0,0 +1,385 @@ +/* + * 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 _ from 'lodash'; +import { i18n } from '@kbn/i18n'; +import type { Map as MbMap } from '@kbn/mapbox-gl'; +import type { Query } from '@kbn/es-query'; +import { asyncMap } from '@kbn/std'; +import React, { ReactElement } from 'react'; +import { EuiIcon } from '@elastic/eui'; +import uuid from 'uuid/v4'; +import { LAYER_TYPE, MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants'; +import { DataRequest } from '../../util/data_request'; +import { copyPersistentState } from '../../../reducers/copy_persistent_state'; +import { + Attribution, + CustomIcon, + LayerDescriptor, + LayerGroupDescriptor, + MapExtent, + StyleDescriptor, + StyleMetaDescriptor, +} from '../../../../common/descriptor_types'; +import { ImmutableSourceProperty, ISource, SourceEditorArgs } from '../../sources/source'; +import { type DataRequestContext } from '../../../actions'; +import { getLayersExtent } from '../../../actions/get_layers_extent'; +import { ILayer, LayerIcon } from '../layer'; +import { IStyle } from '../../styles/style'; +import { LICENSED_FEATURES } from '../../../licensed_features'; + +export function isLayerGroup(layer: ILayer) { + return layer instanceof LayerGroup; +} + +export class LayerGroup implements ILayer { + protected readonly _descriptor: LayerGroupDescriptor; + private _children: ILayer[] = []; + + static createDescriptor(options: Partial): LayerGroupDescriptor { + return { + ...options, + type: LAYER_TYPE.LAYER_GROUP, + id: typeof options.id === 'string' && options.id.length ? options.id : uuid(), + label: + typeof options.label === 'string' && options.label.length + ? options.label + : i18n.translate('xpack.maps.layerGroup.defaultName', { + defaultMessage: 'Layer group', + }), + sourceDescriptor: null, + visible: typeof options.visible === 'boolean' ? options.visible : true, + }; + } + + constructor({ layerDescriptor }: { layerDescriptor: LayerGroupDescriptor }) { + this._descriptor = LayerGroup.createDescriptor(layerDescriptor); + } + + setChildren(children: ILayer[]) { + this._children = children; + } + + getChildren(): ILayer[] { + return [...this._children]; + } + + async _asyncSomeChildren(methodName: string) { + const promises = this.getChildren().map(async (child) => { + // @ts-ignore + return (child[methodName] as () => Promise)(); + }); + return ((await Promise.all(promises)) as boolean[]).some((result) => { + return result; + }); + } + + getDescriptor(): LayerGroupDescriptor { + return this._descriptor; + } + + async cloneDescriptor(): Promise { + const clonedDescriptor = copyPersistentState(this._descriptor); + clonedDescriptor.id = uuid(); + const displayName = await this.getDisplayName(); + clonedDescriptor.label = `Clone of ${displayName}`; + + const childrenDescriptors = await asyncMap(this.getChildren(), async (childLayer) => { + return (await childLayer.cloneDescriptor()).map((childLayerDescriptor) => { + if (childLayerDescriptor.parent === this.getId()) { + childLayerDescriptor.parent = clonedDescriptor.id; + } + return childLayerDescriptor; + }); + }); + + return [..._.flatten(childrenDescriptors), clonedDescriptor]; + } + + makeMbLayerId(layerNameSuffix: string): string { + throw new Error( + 'makeMbLayerId should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + isPreviewLayer(): boolean { + return !!this._descriptor.__isPreviewLayer; + } + + supportsElasticsearchFilters(): boolean { + return this.getChildren().some((child) => { + return child.supportsElasticsearchFilters(); + }); + } + + async supportsFitToBounds(): Promise { + return this._asyncSomeChildren('supportsFitToBounds'); + } + + async isFittable(): Promise { + return this._asyncSomeChildren('isFittable'); + } + + isIncludeInFitToBounds(): boolean { + return this.getChildren().some((child) => { + return child.isIncludeInFitToBounds(); + }); + } + + async isFilteredByGlobalTime(): Promise { + return this._asyncSomeChildren('isFilteredByGlobalTime'); + } + + async getDisplayName(source?: ISource): Promise { + return this.getLabel(); + } + + async getAttributions(): Promise { + return []; + } + + getStyleForEditing(): IStyle { + throw new Error( + 'getStyleForEditing should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + getStyle(): IStyle { + throw new Error( + 'getStyle should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + getCurrentStyle(): IStyle { + throw new Error( + 'getCurrentStyle should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + getLabel(): string { + return this._descriptor.label ? this._descriptor.label : ''; + } + + getLocale(): string | null { + return null; + } + + getLayerIcon(isTocIcon: boolean): LayerIcon { + return { + icon: , + tooltipContent: '', + }; + } + + async hasLegendDetails(): Promise { + return this._children.length > 0; + } + + renderLegendDetails(): ReactElement | null { + return null; + } + + getId(): string { + return this._descriptor.id; + } + + getSource(): ISource { + throw new Error( + 'getSource should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + getSourceForEditing(): ISource { + throw new Error( + 'getSourceForEditing should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + isVisible(): boolean { + return !!this._descriptor.visible; + } + + showAtZoomLevel(zoom: number): boolean { + return zoom >= this.getMinZoom() && zoom <= this.getMaxZoom(); + } + + getMinZoom(): number { + let min = MIN_ZOOM; + this._children.forEach((child) => { + min = Math.max(min, child.getMinZoom()); + }); + return min; + } + + getMaxZoom(): number { + let max = MAX_ZOOM; + this._children.forEach((child) => { + max = Math.min(max, child.getMaxZoom()); + }); + return max; + } + + getMinSourceZoom(): number { + let min = MIN_ZOOM; + this._children.forEach((child) => { + min = Math.max(min, child.getMinSourceZoom()); + }); + return min; + } + + getMbSourceId(): string { + throw new Error( + 'getMbSourceId should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + getAlpha(): number { + throw new Error( + 'getAlpha should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + getQuery(): Query | null { + return null; + } + + async getImmutableSourceProperties(): Promise { + return []; + } + + renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs) { + return null; + } + + getPrevRequestToken(dataId: string): symbol | undefined { + return undefined; + } + + getInFlightRequestTokens(): symbol[] { + return []; + } + + getSourceDataRequest(): DataRequest | undefined { + return undefined; + } + + getDataRequest(id: string): DataRequest | undefined { + return undefined; + } + + isLayerLoading(): boolean { + return this._children.some((child) => { + return child.isLayerLoading(); + }); + } + + hasErrors(): boolean { + return this._children.some((child) => { + return child.hasErrors(); + }); + } + + getErrors(): string { + const firstChildWithError = this._children.find((child) => { + return child.hasErrors(); + }); + return firstChildWithError ? firstChildWithError.getErrors() : ''; + } + + async syncData(syncContext: DataRequestContext) { + // layer group does not render to map so there is never sync data request + } + + getMbLayerIds(): string[] { + return []; + } + + ownsMbLayerId(layerId: string): boolean { + return false; + } + + ownsMbSourceId(mbSourceId: string): boolean { + return false; + } + + syncLayerWithMB(mbMap: MbMap) { + // layer group does not render to map so there is never sync data request + } + + getLayerTypeIconName(): string { + return 'layers'; + } + + isInitialDataLoadComplete(): boolean { + return true; + } + + async getBounds( + getDataRequestContext: (layerId: string) => DataRequestContext + ): Promise { + return getLayersExtent(this.getChildren(), getDataRequestContext); + } + + renderStyleEditor( + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void, + onCustomIconsChange: (customIcons: CustomIcon[]) => void + ): ReactElement | null { + return null; + } + + getIndexPatternIds(): string[] { + return []; + } + + getQueryableIndexPatternIds(): string[] { + return []; + } + + syncVisibilityWithMb(mbMap: unknown, mbLayerId: string) { + throw new Error( + 'syncVisibilityWithMb should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + getType(): LAYER_TYPE { + return LAYER_TYPE.LAYER_GROUP; + } + + areLabelsOnTop(): boolean { + return false; + } + + supportsLabelsOnTop(): boolean { + return false; + } + + supportsLabelLocales(): boolean { + return false; + } + + async getLicensedFeatures(): Promise { + return []; + } + + getGeoFieldNames(): string[] { + return []; + } + + async getStyleMetaDescriptorFromLocalFeatures(): Promise { + throw new Error( + 'getStyleMetaDescriptorFromLocalFeatures should not be called on LayerGroup, LayerGroup does not render to map' + ); + } + + isBasemap(order: number): boolean { + return false; + } + + getParent(): string | undefined { + return this._descriptor.parent; + } +} diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.test.tsx index f2ef7ca9588be..03da177cddbd9 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.test.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.test.tsx @@ -141,7 +141,9 @@ describe('cloneDescriptor', () => { customIcons, }); - const clonedLayerDescriptor = await blendedVectorLayer.cloneDescriptor(); + const clones = await blendedVectorLayer.cloneDescriptor(); + expect(clones.length).toBe(1); + const clonedLayerDescriptor = clones[0]; expect(clonedLayerDescriptor.sourceDescriptor!.type).toBe(SOURCE_TYPES.ES_SEARCH); expect(clonedLayerDescriptor.label).toBe('Clone of myIndexPattern'); }); @@ -161,7 +163,9 @@ describe('cloneDescriptor', () => { customIcons, }); - const clonedLayerDescriptor = await blendedVectorLayer.cloneDescriptor(); + const clones = await blendedVectorLayer.cloneDescriptor(); + expect(clones.length).toBe(1); + const clonedLayerDescriptor = clones[0]; expect(clonedLayerDescriptor.sourceDescriptor!.type).toBe(SOURCE_TYPES.ES_SEARCH); expect(clonedLayerDescriptor.label).toBe('Clone of myIndexPattern'); }); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts index a4b06fe043ff2..ee9fdaf410abb 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts @@ -250,8 +250,12 @@ export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLay return false; } - async cloneDescriptor(): Promise { - const clonedDescriptor = await super.cloneDescriptor(); + async cloneDescriptor(): Promise { + const clones = await super.cloneDescriptor(); + if (clones.length === 0) { + return []; + } + const clonedDescriptor = clones[0]; // Use super getDisplayName instead of instance getDisplayName to avoid getting 'Clustered Clone of Clustered' const displayName = await super.getDisplayName(); @@ -260,7 +264,7 @@ export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLay // sourceDescriptor must be document source descriptor clonedDescriptor.sourceDescriptor = this._documentSource.cloneDescriptor(); - return clonedDescriptor; + return [clonedDescriptor]; } getSource(): IVectorSource { diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx index bc7ba78c84d98..14a5092606b4d 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx @@ -15,6 +15,7 @@ import { EMPTY_FEATURE_COLLECTION, FEATURE_VISIBLE_PROPERTY_NAME, LAYER_TYPE, + SOURCE_BOUNDS_DATA_REQUEST_ID, } from '../../../../../common/constants'; import { StyleMetaDescriptor, @@ -59,11 +60,11 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer { return layerDescriptor; } - async getBounds(syncContext: DataRequestContext) { + async getBounds(getDataRequestContext: (layerId: string) => DataRequestContext) { const isStaticLayer = !this.getSource().isBoundsAware(); return isStaticLayer || this.hasJoins() ? getFeatureCollectionBounds(this._getSourceFeatureCollection(), this.hasJoins()) - : super.getBounds(syncContext); + : super.getBounds(getDataRequestContext); } getLayerIcon(isTocIcon: boolean): LayerIcon { @@ -211,6 +212,11 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer { await this._syncData(syncContext, this.getSource(), this.getCurrentStyle()); } + _isLoadingBounds() { + const boundsDataRequest = this.getDataRequest(SOURCE_BOUNDS_DATA_REQUEST_ID); + return !!boundsDataRequest && boundsDataRequest.isLoading(); + } + // TLDR: Do not call getSource or getCurrentStyle in syncData flow. Use 'source' and 'style' arguments instead. // // 1) State is contained in the redux store. Layer instance state is readonly. @@ -222,7 +228,7 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer { // Given 2 above, which source/style to use can not be pulled from data request state. // Therefore, source and style are provided as arugments and must be used instead of calling getSource or getCurrentStyle. async _syncData(syncContext: DataRequestContext, source: IVectorSource, style: IVectorStyle) { - if (this.isLoadingBounds()) { + if (this._isLoadingBounds()) { return; } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx index 7eaec94eac0a2..a16093af20426 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx @@ -45,6 +45,7 @@ import { getAggsMeta, getHitsMeta, } from '../../../util/tile_meta_feature_utils'; +import { syncBoundsData } from '../bounds_data'; const MAX_RESULT_WINDOW_DATA_REQUEST_ID = 'maxResultWindow'; @@ -77,7 +78,7 @@ export class MvtVectorLayer extends AbstractVectorLayer { : super.isInitialDataLoadComplete(); } - async getBounds(syncContext: DataRequestContext) { + async getBounds(getDataRequestContext: (layerId: string) => DataRequestContext) { // Add filter to narrow bounds to features with matching join keys let joinKeyFilter; if (this.getSource().isESSource()) { @@ -93,12 +94,18 @@ export class MvtVectorLayer extends AbstractVectorLayer { } } - return super.getBounds({ - ...syncContext, - dataFilters: { - ...syncContext.dataFilters, - joinKeyFilter, + const syncContext = getDataRequestContext(this.getId()); + return syncBoundsData({ + layerId: this.getId(), + syncContext: { + ...syncContext, + dataFilters: { + ...syncContext.dataFilters, + joinKeyFilter, + }, }, + source: this.getSource(), + sourceQuery: this.getQuery(), }); } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx index b71fef484de01..d450f92467e46 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx @@ -87,7 +87,9 @@ describe('cloneDescriptor', () => { source: new MockSource() as unknown as IVectorSource, customIcons: [], }); - const clonedDescriptor = await layer.cloneDescriptor(); + const clones = await layer.cloneDescriptor(); + expect(clones.length).toBe(1); + const clonedDescriptor = clones[0]; const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; // Should update style field belonging to join // @ts-expect-error @@ -124,7 +126,9 @@ describe('cloneDescriptor', () => { source: new MockSource() as unknown as IVectorSource, customIcons: [], }); - const clonedDescriptor = await layer.cloneDescriptor(); + const clones = await layer.cloneDescriptor(); + expect(clones.length).toBe(1); + const clonedDescriptor = clones[0]; const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; // Should update style field belonging to join // @ts-expect-error diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 35a5caa7ff9b8..27768dc717bd7 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -162,8 +162,13 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { ); } - async cloneDescriptor(): Promise { - const clonedDescriptor = (await super.cloneDescriptor()) as VectorLayerDescriptor; + async cloneDescriptor(): Promise { + const clones = await super.cloneDescriptor(); + if (clones.length === 0) { + return []; + } + + const clonedDescriptor = clones[0] as VectorLayerDescriptor; if (clonedDescriptor.joins) { clonedDescriptor.joins.forEach((joinDescriptor: JoinDescriptor) => { if (joinDescriptor.right && joinDescriptor.right.type === SOURCE_TYPES.TABLE_SOURCE) { @@ -215,7 +220,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { } }); } - return clonedDescriptor; + return [clonedDescriptor]; } getSource(): IVectorSource { @@ -295,10 +300,10 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { return this.getCurrentStyle().renderLegendDetails(); } - async getBounds(syncContext: DataRequestContext) { + async getBounds(getDataRequestContext: (layerId: string) => DataRequestContext) { return syncBoundsData({ layerId: this.getId(), - syncContext, + syncContext: getDataRequestContext(this.getId()), source: this.getSource(), sourceQuery: this.getQuery(), }); diff --git a/x-pack/plugins/maps/public/components/remove_layer_confirm_modal.tsx b/x-pack/plugins/maps/public/components/remove_layer_confirm_modal.tsx new file mode 100644 index 0000000000000..8c35750265cd6 --- /dev/null +++ b/x-pack/plugins/maps/public/components/remove_layer_confirm_modal.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiConfirmModal, EuiText } from '@elastic/eui'; +import { ILayer } from '../classes/layers/layer'; +import { isLayerGroup, LayerGroup } from '../classes/layers/layer_group'; + +export interface Props { + layer: ILayer; + onCancel: () => void; + onConfirm: () => void; +} + +export function RemoveLayerConfirmModal(props: Props) { + function getChildrenCount(layerGroup: LayerGroup) { + let count = 0; + layerGroup.getChildren().forEach((childLayer) => { + count++; + if (isLayerGroup(childLayer)) { + count = count + getChildrenCount(childLayer as LayerGroup); + } + }); + return count; + } + + function renderMultiLayerWarning() { + if (!isLayerGroup(props.layer)) { + return null; + } + + const numChildren = getChildrenCount(props.layer as LayerGroup); + return numChildren > 0 ? ( +

+ {i18n.translate('xpack.maps.deleteLayerConfirmModal.multiLayerWarning', { + defaultMessage: `Removing this layer also removes {numChildren} nested {numChildren, plural, one {layer} other {layers}}.`, + values: { numChildren }, + })} +

+ ) : null; + } + + return ( + + + {renderMultiLayerWarning()} +

+ {i18n.translate('xpack.maps.deleteLayerConfirmModal.unrecoverableWarning', { + defaultMessage: `You can't recover removed layers.`, + })} +

+
+
+ ); +} diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx index 906947562f940..8ef8319a82798 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx @@ -35,6 +35,7 @@ import { ILayer } from '../../classes/layers/layer'; import { isVectorLayer, IVectorLayer } from '../../classes/layers/vector_layer'; import { ImmutableSourceProperty, OnSourceChangeArgs } from '../../classes/sources/source'; import { IField } from '../../classes/fields/field'; +import { isLayerGroup } from '../../classes/layers/layer_group'; const localStorage = new Storage(window.localStorage); @@ -95,7 +96,7 @@ export class EditLayerPanel extends Component { }; _loadImmutableSourceProperties = async () => { - if (!this.props.selectedLayer) { + if (!this.props.selectedLayer || isLayerGroup(this.props.selectedLayer)) { return; } @@ -160,7 +161,11 @@ export class EditLayerPanel extends Component { } _renderFilterSection() { - if (!this.props.selectedLayer || !this.props.selectedLayer.supportsElasticsearchFilters()) { + if ( + !this.props.selectedLayer || + isLayerGroup(this.props.selectedLayer) || + !this.props.selectedLayer.supportsElasticsearchFilters() + ) { return null; } @@ -197,35 +202,70 @@ export class EditLayerPanel extends Component { ); } - _renderSourceProperties() { - return this.state.immutableSourceProps.map( - ({ label, value, link }: ImmutableSourceProperty) => { - function renderValue() { - if (link) { - return ( - - {value} - - ); - } - return {value}; - } - return ( -

- {label} {renderValue()} -

- ); - } + _renderSourceDetails() { + return !this.props.selectedLayer || isLayerGroup(this.props.selectedLayer) ? null : ( +
+ + + + {this.state.immutableSourceProps.map( + ({ label, value, link }: ImmutableSourceProperty) => { + function renderValue() { + if (link) { + return ( + + {value} + + ); + } + return {value}; + } + return ( +

+ {label} {renderValue()} +

+ ); + } + )} +
+
+
); } - render() { + _renderSourceEditor() { if (!this.props.selectedLayer) { return null; } const descriptor = this.props.selectedLayer.getDescriptor() as VectorLayerDescriptor; const numberOfJoins = descriptor.joins ? descriptor.joins.length : 0; + return isLayerGroup(this.props.selectedLayer) + ? null + : this.props.selectedLayer.renderSourceSettingsEditor({ + currentLayerType: this.props.selectedLayer.getType(), + numberOfJoins, + onChange: this._onSourceChange, + onStyleDescriptorChange: this.props.updateStyleDescriptor, + style: this.props.selectedLayer.getStyleForEditing(), + }); + } + + _renderStyleEditor() { + return !this.props.selectedLayer || isLayerGroup(this.props.selectedLayer) ? null : ( + + ); + } + + render() { + if (!this.props.selectedLayer) { + return null; + } return ( { -
- - - - {this._renderSourceProperties()} - - -
+ {this._renderSourceDetails()}
@@ -273,19 +301,13 @@ export class EditLayerPanel extends Component { supportsFitToBounds={this.state.supportsFitToBounds} /> - {this.props.selectedLayer.renderSourceSettingsEditor({ - currentLayerType: this.props.selectedLayer.getType(), - numberOfJoins, - onChange: this._onSourceChange, - onStyleDescriptorChange: this.props.updateStyleDescriptor, - style: this.props.selectedLayer.getStyleForEditing(), - })} + {this._renderSourceEditor()} {this._renderFilterSection()} {this._renderJoinSection()} - + {this._renderStyleEditor()}
diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx index b9761f5d48430..614fbfcebe4e1 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx @@ -5,69 +5,102 @@ * 2.0. */ -import React from 'react'; +import React, { Component } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { ILayer } from '../../../classes/layers/layer'; +import { RemoveLayerConfirmModal } from '../../../components/remove_layer_confirm_modal'; export interface Props { + selectedLayer?: ILayer; cancelLayerPanel: () => void; saveLayerEdits: () => void; removeLayer: () => void; hasStateChanged: boolean; } -export const FlyoutFooter = ({ - cancelLayerPanel, - saveLayerEdits, - removeLayer, - hasStateChanged, -}: Props) => { - const removeBtn = ( - - - - - - ); +interface State { + showRemoveModal: boolean; +} + +export class FlyoutFooter extends Component { + state: State = { + showRemoveModal: false, + }; + + _showRemoveModal = () => { + this.setState({ showRemoveModal: true }); + }; - const cancelButtonLabel = hasStateChanged ? ( - - ) : ( - - ); + render() { + const cancelButtonLabel = this.props.hasStateChanged ? ( + + ) : ( + + ); - return ( - - - - {cancelButtonLabel} - - - - - - {removeBtn} - - - - - - - ); -}; + const removeModal = + this.props.selectedLayer && this.state.showRemoveModal ? ( + { + this.setState({ showRemoveModal: false }); + }} + onConfirm={() => { + this.setState({ showRemoveModal: false }); + this.props.removeLayer(); + }} + /> + ) : null; + + return ( + <> + {removeModal} + + + + {cancelButtonLabel} + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/index.ts b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/index.ts index 8546b8088d40a..093f0524b271b 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/index.ts +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/index.ts @@ -11,7 +11,7 @@ import { connect } from 'react-redux'; import { FlyoutFooter } from './flyout_footer'; import { FLYOUT_STATE } from '../../../reducers/ui'; -import { hasDirtyState } from '../../../selectors/map_selectors'; +import { getSelectedLayer, hasDirtyState } from '../../../selectors/map_selectors'; import { setSelectedLayer, removeSelectedLayer, @@ -23,6 +23,7 @@ import { MapStoreState } from '../../../reducers/store'; function mapStateToProps(state: MapStoreState) { return { hasStateChanged: hasDirtyState(state), + selectedLayer: getSelectedLayer(state), }; } diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx index 794064e09d3c6..6d63fc08ef85e 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx @@ -26,6 +26,7 @@ import { AlphaSlider } from '../../../components/alpha_slider'; import { ILayer } from '../../../classes/layers/layer'; import { isVectorLayer, IVectorLayer } from '../../../classes/layers/vector_layer'; import { AttributionFormRow } from './attribution_form_row'; +import { isLayerGroup } from '../../../classes/layers/layer_group'; export interface Props { layer: ILayer; @@ -87,7 +88,7 @@ export function LayerSettings(props: Props) { }; const renderIncludeInFitToBounds = () => { - if (!props.supportsFitToBounds) { + if (!props.supportsFitToBounds || isLayerGroup(props.layer)) { return null; } return ( @@ -113,7 +114,7 @@ export function LayerSettings(props: Props) { }; const renderZoomSliders = () => { - return ( + return isLayerGroup(props.layer) ? null : ( {renderLabel()} {renderZoomSliders()} - + {isLayerGroup(props.layer) ? null : ( + + )} {renderShowLabelsOnTop()} {renderShowLocaleSelector()} - + {isLayerGroup(props.layer) ? null : ( + + )} {renderIncludeInFitToBounds()} {renderDisableTooltips()} diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_index.scss b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_index.scss index 9a3e3a45d6c4e..9ca24d055432b 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_index.scss +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_index.scss @@ -1,2 +1,3 @@ @import 'layer_control'; +@import 'layer_toc/layer_toc'; @import 'layer_toc/toc_entry/toc_entry'; diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap index fbd83ed145a08..7b0741e4bc74a 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap @@ -6,9 +6,12 @@ exports[`LayerTOC is rendered 1`] = ` > @@ -22,19 +25,23 @@ exports[`LayerTOC props isReadOnly 1`] = ` data-test-subj="mapLayerTOC" > ) { return { - updateLayerOrder: (newOrder: number[]) => dispatch(updateLayerOrder(newOrder)), + createLayerGroup: (draggedLayerId: string, combineWithLayerId: string) => + dispatch(createLayerGroup(draggedLayerId, combineWithLayerId)), + moveLayerToBottom: (moveLayerId: string) => dispatch(moveLayerToBottom(moveLayerId)), + moveLayerToLeftOfTarget: (moveLayerId: string, targetLayerId: string) => + dispatch(moveLayerToLeftOfTarget(moveLayerId, targetLayerId)), + setLayerParent: (layerId: string, parent: string | undefined) => + dispatch(setLayerParent(layerId, parent)), }; } diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.test.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.test.tsx index 359794f1468f6..b7ee829b67368 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.test.tsx @@ -22,6 +22,9 @@ const mockLayers = [ getId: () => { return '1'; }, + getParent: () => { + return undefined; + }, supportsFitToBounds: () => { return true; }, @@ -30,6 +33,9 @@ const mockLayers = [ getId: () => { return '2'; }, + getParent: () => { + return undefined; + }, supportsFitToBounds: () => { return false; }, @@ -39,7 +45,11 @@ const mockLayers = [ const defaultProps = { layerList: mockLayers, isReadOnly: false, - updateLayerOrder: () => {}, + openTOCDetails: [], + moveLayerToBottom: () => {}, + moveLayerToLeftOfTarget: () => {}, + setLayerParent: () => {}, + createLayerGroup: () => {}, }; describe('LayerTOC', () => { diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx index 1800f2dc33618..f152d1686b3bd 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx @@ -9,15 +9,38 @@ import _ from 'lodash'; import React, { Component } from 'react'; import { DropResult, EuiDragDropContext, EuiDroppable, EuiDraggable } from '@elastic/eui'; import { TOCEntry } from './toc_entry'; +import { isLayerGroup } from '../../../../classes/layers/layer_group'; import { ILayer } from '../../../../classes/layers/layer'; export interface Props { isReadOnly: boolean; layerList: ILayer[]; - updateLayerOrder: (newOrder: number[]) => void; + openTOCDetails: string[]; + createLayerGroup: (draggedLayerId: string, combineWithLayerId: string) => void; + setLayerParent: (layerId: string, parent: string | undefined) => void; + moveLayerToBottom: (moveLayerId: string) => void; + moveLayerToLeftOfTarget: (moveLayerId: string, targetLayerId: string) => void; } +interface State { + combineLayer: ILayer | null; + isOwnAncestor: boolean; + newRightSiblingLayer: ILayer | null; + sourceLayer: ILayer | null; +} + +const CLEAR_DND_STATE = { + combineLayer: null, + isOwnAncestor: false, + newRightSiblingLayer: null, + sourceLayer: null, +}; + export class LayerTOC extends Component { + state: State = { + ...CLEAR_DND_STATE, + }; + componentWillUnmount() { this._updateDebounced.cancel(); } @@ -29,60 +52,201 @@ export class LayerTOC extends Component { _updateDebounced = _.debounce(this.forceUpdate, 100); - _onDragEnd = ({ source, destination }: DropResult) => { - // Dragging item out of EuiDroppable results in destination of null - if (!destination) { + _reverseIndex(index: number) { + return this.props.layerList.length - index - 1; + } + + _getForebearers(layer: ILayer): string[] { + const parentId = layer.getParent(); + if (!parentId) { + return []; + } + + const parentLayer = this.props.layerList.find((findLayer) => { + return findLayer.getId() === parentId; + }); + if (!parentLayer) { + return []; + } + + return [...this._getForebearers(parentLayer), parentId]; + } + + _onDragStart = ({ source }: DropResult) => { + const sourceIndex = this._reverseIndex(source.index); + const sourceLayer = this.props.layerList[sourceIndex]; + this.setState({ ...CLEAR_DND_STATE, sourceLayer }); + }; + + _onDragUpdate = ({ combine, destination, source }: DropResult) => { + const sourceIndex = this._reverseIndex(source.index); + const sourceLayer = this.props.layerList[sourceIndex]; + + if (combine) { + const combineIndex = this.props.layerList.findIndex((findLayer) => { + return findLayer.getId() === combine.draggableId; + }); + const combineLayer = combineIndex !== -1 ? this.props.layerList[combineIndex] : null; + + const newRightSiblingIndex = combineIndex - 1; + const newRightSiblingLayer = + newRightSiblingIndex < 0 ? null : this.props.layerList[newRightSiblingIndex]; + + const forebearers = combineLayer ? this._getForebearers(combineLayer) : []; + + this.setState({ + combineLayer, + newRightSiblingLayer, + sourceLayer, + isOwnAncestor: forebearers.includes(sourceLayer.getId()), + }); + return; + } + + if (!destination || source.index === destination.index) { + this.setState({ ...CLEAR_DND_STATE }); return; } - // Layer list is displayed in reverse order so index needs to reversed to get back to original reference. - const reverseIndex = (index: number) => { - return this.props.layerList.length - index - 1; - }; + const destinationIndex = this._reverseIndex(destination.index); + const newRightSiblingIndex = + sourceIndex > destinationIndex + ? // When layer is moved to the right, new right sibling is layer to the right of destination + destinationIndex - 1 + : // When layer is moved to the left, new right sibling is the destination + destinationIndex; + const newRightSiblingLayer = + newRightSiblingIndex < 0 ? null : this.props.layerList[newRightSiblingIndex]; + + const forebearers = newRightSiblingLayer ? this._getForebearers(newRightSiblingLayer) : []; - const prevIndex = reverseIndex(source.index); - const newIndex = reverseIndex(destination.index); - const newOrder = []; - for (let i = 0; i < this.props.layerList.length; i++) { - newOrder.push(i); + this.setState({ + combineLayer: null, + newRightSiblingLayer, + sourceLayer, + isOwnAncestor: forebearers.includes(sourceLayer.getId()), + }); + }; + + _onDragEnd = () => { + const { combineLayer, isOwnAncestor, sourceLayer, newRightSiblingLayer } = this.state; + this.setState({ ...CLEAR_DND_STATE }); + + if (isOwnAncestor || !sourceLayer) { + return; + } + + if (combineLayer) { + // add source to layer group when combine is layer group + if (isLayerGroup(combineLayer) && newRightSiblingLayer) { + this.props.setLayerParent(sourceLayer.getId(), combineLayer.getId()); + this.props.moveLayerToLeftOfTarget(sourceLayer.getId(), newRightSiblingLayer.getId()); + return; + } + + // creage layer group that contains source and combine + this.props.createLayerGroup(sourceLayer.getId(), combineLayer.getId()); + return; } - newOrder.splice(prevIndex, 1); - newOrder.splice(newIndex, 0, prevIndex); - this.props.updateLayerOrder(newOrder); + + if (newRightSiblingLayer) { + this.props.setLayerParent(sourceLayer.getId(), newRightSiblingLayer.getParent()); + this.props.moveLayerToLeftOfTarget(sourceLayer.getId(), newRightSiblingLayer.getId()); + return; + } + + this.props.moveLayerToBottom(sourceLayer.getId()); }; + _getDepth(layer: ILayer, depth: number): { depth: number; showInTOC: boolean } { + if (layer.getParent() === undefined) { + return { depth, showInTOC: true }; + } + + const parent = this.props.layerList.find((nextLayer) => { + return layer.getParent() === nextLayer.getId(); + }); + if (!parent) { + return { depth, showInTOC: false }; + } + + return this.props.openTOCDetails.includes(parent.getId()) + ? this._getDepth(parent, depth + 1) + : { depth, showInTOC: false }; + } + + _getDroppableClass() { + if (!this.state.sourceLayer) { + // nothing is dragged + return ''; + } + + if (this.state.isOwnAncestor) { + return 'mapLayerToc-droppable-dropNotAllowed'; + } + + if (this.state.combineLayer) { + return 'mapLayerToc-droppable-isCombining'; + } + + return 'mapLayerToc-droppable-isDragging'; + } + _renderLayers() { - // Reverse layer list so first layer drawn on map is at the bottom and - // last layer drawn on map is at the top. - const reverseLayerList = [...this.props.layerList].reverse(); + const tocEntryList = this.props.layerList + .map((layer, index) => { + return { + ...this._getDepth(layer, 0), + draggableIndex: this._reverseIndex(index), + layer, + }; + }) + .filter(({ showInTOC }) => { + return showInTOC; + }) + // Reverse layer list so first layer drawn on map is at the bottom and + // last layer drawn on map is at the top. + .reverse(); if (this.props.isReadOnly) { - return reverseLayerList.map((layer) => { - return ; + return tocEntryList.map(({ depth, layer }) => { + return ; }); } return ( - - - {(droppableProvided, snapshot) => { - const tocEntries = reverseLayerList.map((layer, idx: number) => ( + + + {(droppableProvided, droppableSnapshot) => { + const tocEntries = tocEntryList.map(({ draggableIndex, depth, layer }) => ( - {(provided, state) => ( - - )} + {(draggableProvided, draggableSnapshot) => { + return ( + + ); + }} )); return
{tocEntries}
; diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap index cec85cb0e1cd6..0973bd4f24459 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap @@ -5,6 +5,94 @@ exports[`TOCEntry is rendered 1`] = ` className="mapTocEntry" data-layerid="1" id="1" + style={Object {}} +> +
+ +
+ + + + +
+
+ + + + +`; + +exports[`TOCEntry props Should indent child layer 1`] = ` +
{}, @@ -93,6 +94,17 @@ describe('TOCEntry', () => { expect(component).toMatchSnapshot(); }); + test('Should indent child layer', async () => { + const component = shallow(); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + test('should display layer details when isLegendDetailsOpen is true', async () => { const component = shallow(); diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx index 65431432d8c6d..72eb38f07257e 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx @@ -44,6 +44,7 @@ export interface ReduxDispatchProps { } export interface OwnProps { + depth: number; layer: ILayer; dragHandleProps?: DraggableProvidedDragHandleProps; isDragging?: boolean; @@ -226,7 +227,7 @@ export class TOCEntry extends Component { } _renderDetailsToggle() { - if (!this.state.hasLegendDetails) { + if (this.props.isDragging || !this.state.hasLegendDetails) { return null; } @@ -319,8 +320,12 @@ export class TOCEntry extends Component { 'mapTocEntry-isInEditingMode': this.props.isFeatureEditorOpenForLayer, }); + const depthStyle = + this.props.depth > 0 ? { paddingLeft: `${8 + this.props.depth * 24}px` } : {}; + return (
+ + } + className="mapLayTocActions" + closePopover={[Function]} + display="inline-block" + hasArrow={true} + id="testLayer" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > + , + "name": "Fit to data", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerVisibilityToggleButton", + "icon": , + "name": "Hide layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerSettingsButton", + "disabled": false, + "icon": , + "name": "Edit layer settings", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "cloneLayerButton", + "icon": , + "name": "Clone layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "removeLayerButton", + "icon": , + "name": "Remove layer", + "onClick": [Function], + "toolTipContent": null, + }, + ], + "title": "Layer actions", + }, + ] } - onClick={[Function]} + size="m" /> - } - className="mapLayTocActions" - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="testLayer" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" -> - , - "name": "Fit to data", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerVisibilityToggleButton", - "icon": , - "name": "Hide layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerSettingsButton", - "disabled": false, - "icon": , - "name": "Edit layer settings", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "cloneLayerButton", - "icon": , - "name": "Clone layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "removeLayerButton", - "icon": , - "name": "Remove layer", - "onClick": [Function], - "toolTipContent": null, - }, - ], - "title": "Layer actions", - }, - ] - } - size="m" - /> - + + `; exports[`TOCEntryActionsPopover should disable Edit features when edit mode active for layer 1`] = ` - + + } + className="mapLayTocActions" + closePopover={[Function]} + display="inline-block" + hasArrow={true} + id="testLayer" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > + , + "name": "Fit to data", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerVisibilityToggleButton", + "icon": , + "name": "Hide layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerSettingsButton", + "disabled": false, + "icon": , + "name": "Edit layer settings", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "cloneLayerButton", + "icon": , + "name": "Clone layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "removeLayerButton", + "icon": , + "name": "Remove layer", + "onClick": [Function], + "toolTipContent": null, + }, + ], + "title": "Layer actions", + }, + ] } - onClick={[Function]} + size="m" /> - } - className="mapLayTocActions" - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="testLayer" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" -> - , - "name": "Fit to data", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerVisibilityToggleButton", - "icon": , - "name": "Hide layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerSettingsButton", - "disabled": false, - "icon": , - "name": "Edit layer settings", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "cloneLayerButton", - "icon": , - "name": "Clone layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "removeLayerButton", - "icon": , - "name": "Remove layer", - "onClick": [Function], - "toolTipContent": null, - }, - ], - "title": "Layer actions", - }, - ] - } - size="m" - /> - + + `; exports[`TOCEntryActionsPopover should disable fit to data when supportsFitToBounds is false 1`] = ` - + + } + className="mapLayTocActions" + closePopover={[Function]} + display="inline-block" + hasArrow={true} + id="testLayer" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > + , + "name": "Fit to data", + "onClick": [Function], + "toolTipContent": "Layer does not support fit to data", + }, + Object { + "data-test-subj": "layerVisibilityToggleButton", + "icon": , + "name": "Hide layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerSettingsButton", + "disabled": false, + "icon": , + "name": "Edit layer settings", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "cloneLayerButton", + "icon": , + "name": "Clone layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "removeLayerButton", + "icon": , + "name": "Remove layer", + "onClick": [Function], + "toolTipContent": null, + }, + ], + "title": "Layer actions", + }, + ] } - onClick={[Function]} + size="m" /> - } - className="mapLayTocActions" - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="testLayer" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" -> - , - "name": "Fit to data", - "onClick": [Function], - "toolTipContent": "Layer does not support fit to data", - }, - Object { - "data-test-subj": "layerVisibilityToggleButton", - "icon": , - "name": "Hide layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerSettingsButton", - "disabled": false, - "icon": , - "name": "Edit layer settings", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "cloneLayerButton", - "icon": , - "name": "Clone layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "removeLayerButton", - "icon": , - "name": "Remove layer", - "onClick": [Function], - "toolTipContent": null, - }, - ], - "title": "Layer actions", - }, - ] - } - size="m" - /> - + + `; exports[`TOCEntryActionsPopover should have "show layer" action when layer is not visible 1`] = ` - + + } + className="mapLayTocActions" + closePopover={[Function]} + display="inline-block" + hasArrow={true} + id="testLayer" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > + , + "name": "Fit to data", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerVisibilityToggleButton", + "icon": , + "name": "Show layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerSettingsButton", + "disabled": false, + "icon": , + "name": "Edit layer settings", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "cloneLayerButton", + "icon": , + "name": "Clone layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "removeLayerButton", + "icon": , + "name": "Remove layer", + "onClick": [Function], + "toolTipContent": null, + }, + ], + "title": "Layer actions", + }, + ] } - onClick={[Function]} + size="m" /> - } - className="mapLayTocActions" - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="testLayer" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" -> - , - "name": "Fit to data", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerVisibilityToggleButton", - "icon": , - "name": "Show layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerSettingsButton", - "disabled": false, - "icon": , - "name": "Edit layer settings", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "cloneLayerButton", - "icon": , - "name": "Clone layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "removeLayerButton", - "icon": , - "name": "Remove layer", - "onClick": [Function], - "toolTipContent": null, - }, - ], - "title": "Layer actions", - }, - ] - } - size="m" - /> - + + `; exports[`TOCEntryActionsPopover should not show edit actions in read only mode 1`] = ` - + + } + className="mapLayTocActions" + closePopover={[Function]} + display="inline-block" + hasArrow={true} + id="testLayer" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > + , + "name": "Fit to data", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerVisibilityToggleButton", + "icon": , + "name": "Hide layer", + "onClick": [Function], + "toolTipContent": null, + }, + ], + "title": "Layer actions", + }, + ] } - onClick={[Function]} + size="m" /> - } - className="mapLayTocActions" - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="testLayer" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" -> - , - "name": "Fit to data", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerVisibilityToggleButton", - "icon": , - "name": "Hide layer", - "onClick": [Function], - "toolTipContent": null, - }, - ], - "title": "Layer actions", - }, - ] - } - size="m" - /> - + + `; exports[`TOCEntryActionsPopover should show "show this layer only" action when there are more then 2 layers 1`] = ` - + + } + className="mapLayTocActions" + closePopover={[Function]} + display="inline-block" + hasArrow={true} + id="testLayer" + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > + , + "name": "Fit to data", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerVisibilityToggleButton", + "icon": , + "name": "Hide layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "showThisLayerOnlyButton", + "icon": , + "name": "Show this layer only", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "layerSettingsButton", + "disabled": false, + "icon": , + "name": "Edit layer settings", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "cloneLayerButton", + "icon": , + "name": "Clone layer", + "onClick": [Function], + "toolTipContent": null, + }, + Object { + "data-test-subj": "removeLayerButton", + "icon": , + "name": "Remove layer", + "onClick": [Function], + "toolTipContent": null, + }, + ], + "title": "Layer actions", + }, + ] } - onClick={[Function]} + size="m" /> - } - className="mapLayTocActions" - closePopover={[Function]} - display="inline-block" - hasArrow={true} - id="testLayer" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" -> - , - "name": "Fit to data", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerVisibilityToggleButton", - "icon": , - "name": "Hide layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "showThisLayerOnlyButton", - "icon": , - "name": "Show this layer only", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "layerSettingsButton", - "disabled": false, - "icon": , - "name": "Edit layer settings", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "cloneLayerButton", - "icon": , - "name": "Clone layer", - "onClick": [Function], - "toolTipContent": null, - }, - Object { - "data-test-subj": "removeLayerButton", - "icon": , - "name": "Remove layer", - "onClick": [Function], - "toolTipContent": null, - }, - ], - "title": "Layer actions", - }, - ] - } - size="m" - /> - + + `; diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index 5e33931a8943e..a67c12d2928a4 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -20,6 +20,7 @@ import { import { ESSearchSource } from '../../../../../../classes/sources/es_search_source'; import { isVectorLayer, IVectorLayer } from '../../../../../../classes/layers/vector_layer'; import { SCALING_TYPES, VECTOR_SHAPE_TYPE } from '../../../../../../../common/constants'; +import { RemoveLayerConfirmModal } from '../../../../../../components/remove_layer_confirm_modal'; export interface Props { cloneLayer: (layerId: string) => void; @@ -41,6 +42,7 @@ export interface Props { interface State { isPopoverOpen: boolean; + showRemoveModal: boolean; supportsFeatureEditing: boolean; isFeatureEditingEnabled: boolean; } @@ -48,6 +50,7 @@ interface State { export class TOCEntryActionsPopover extends Component { state: State = { isPopoverOpen: false, + showRemoveModal: false, supportsFeatureEditing: false, isFeatureEditingEnabled: false, }; @@ -119,10 +122,6 @@ export class TOCEntryActionsPopover extends Component { this.props.fitToBounds(this.props.layer.getId()); } - _removeLayer() { - this.props.removeLayer(this.props.layer.getId()); - } - _toggleVisible() { this.props.toggleVisible(this.props.layer.getId()); } @@ -230,8 +229,7 @@ export class TOCEntryActionsPopover extends Component { toolTipContent: null, 'data-test-subj': 'removeLayerButton', onClick: () => { - this._closePopover(); - this._removeLayer(); + this.setState({ showRemoveModal: true }); }, }); } @@ -246,30 +244,46 @@ export class TOCEntryActionsPopover extends Component { } render() { + const removeModal = this.state.showRemoveModal ? ( + { + this.setState({ showRemoveModal: false }); + }} + onConfirm={() => { + this.setState({ showRemoveModal: false }); + this._closePopover(); + this.props.removeLayer(this.props.layer.getId()); + }} + /> + ) : null; return ( - + {removeModal} + + } + isOpen={this.state.isPopoverOpen} + closePopover={this._closePopover} + panelPaddingSize="none" + anchorPosition="leftUp" + anchorClassName="mapLayTocActions__popoverAnchor" + > + - } - isOpen={this.state.isPopoverOpen} - closePopover={this._closePopover} - panelPaddingSize="none" - anchorPosition="leftUp" - anchorClassName="mapLayTocActions__popoverAnchor" - > - - + + ); } } diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx index c55821c522d14..7e35447d45e41 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx @@ -11,6 +11,7 @@ import { EuiButtonEmpty, EuiIcon, EuiToolTip, EuiLoadingSpinner } from '@elastic import { i18n } from '@kbn/i18n'; import { ILayer } from '../../../../../../classes/layers/layer'; import { IVectorSource } from '../../../../../../classes/sources/vector_source'; +import { isLayerGroup } from '../../../../../../classes/layers/layer_group'; interface Footnote { icon: ReactNode; @@ -69,72 +70,88 @@ export class TOCEntryButton extends Component { } getIconAndTooltipContent(): IconAndTooltipContent { - let icon; - let tooltipContent = null; - const footnotes = []; if (this.props.layer.hasErrors()) { - icon = ( - - ); - tooltipContent = this.props.layer.getErrors(); - } else if (!this.props.layer.isVisible()) { - icon = ; - tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', { - defaultMessage: `Layer is hidden.`, - }); - } else if (this.props.layer.isLayerLoading()) { - icon = ; - } else if (!this.props.layer.showAtZoomLevel(this.props.zoom)) { + return { + icon: ( + + ), + tooltipContent: this.props.layer.getErrors(), + footnotes: [], + }; + } + + if (!this.props.layer.isVisible()) { + return { + icon: , + tooltipContent: i18n.translate('xpack.maps.layer.layerHiddenTooltip', { + defaultMessage: `Layer is hidden.`, + }), + footnotes: [], + }; + } + + if (this.props.layer.isLayerLoading()) { + return { + icon: , + tooltipContent: '', + footnotes: [], + }; + } + + if (!this.props.layer.showAtZoomLevel(this.props.zoom)) { const minZoom = this.props.layer.getMinZoom(); const maxZoom = this.props.layer.getMaxZoom(); - icon = ; - tooltipContent = i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', { - defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`, - values: { minZoom, maxZoom }, + return { + icon: , + tooltipContent: i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', { + defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`, + values: { minZoom, maxZoom }, + }), + footnotes: [], + }; + } + + const { icon, tooltipContent } = this.props.layer.getLayerIcon(true); + + if (isLayerGroup(this.props.layer)) { + return { icon, tooltipContent, footnotes: [] }; + } + + const footnotes = []; + if (this.props.isUsingSearch && this.props.layer.getQueryableIndexPatternIds().length) { + footnotes.push({ + icon: , + message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { + defaultMessage: 'Results narrowed by global search', + }), + }); + } + if (this.state.isFilteredByGlobalTime) { + footnotes.push({ + icon: , + message: i18n.translate('xpack.maps.layer.isUsingTimeFilter', { + defaultMessage: 'Results narrowed by global time', + }), + }); + } + const source = this.props.layer.getSource(); + if ( + typeof source.isFilterByMapBounds === 'function' && + (source as IVectorSource).isFilterByMapBounds() + ) { + footnotes.push({ + icon: , + message: i18n.translate('xpack.maps.layer.isUsingBoundsFilter', { + defaultMessage: 'Results narrowed by visible map area', + }), }); - } else { - const { icon: layerIcon, tooltipContent: layerTooltipContent } = - this.props.layer.getLayerIcon(true); - icon = layerIcon; - if (layerTooltipContent) { - tooltipContent = layerTooltipContent; - } - - if (this.props.isUsingSearch && this.props.layer.getQueryableIndexPatternIds().length) { - footnotes.push({ - icon: , - message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { - defaultMessage: 'Results narrowed by global search', - }), - }); - } - if (this.state.isFilteredByGlobalTime) { - footnotes.push({ - icon: , - message: i18n.translate('xpack.maps.layer.isUsingTimeFilter', { - defaultMessage: 'Results narrowed by global time', - }), - }); - } - const source = this.props.layer.getSource(); - if ( - typeof source.isFilterByMapBounds === 'function' && - (source as IVectorSource).isFilterByMapBounds() - ) { - footnotes.push({ - icon: , - message: i18n.translate('xpack.maps.layer.isUsingBoundsFilter', { - defaultMessage: 'Results narrowed by visible map area', - }), - }); - } } return { diff --git a/x-pack/plugins/maps/public/reducers/map/layer_utils.ts b/x-pack/plugins/maps/public/reducers/map/layer_utils.ts index 206cc4a740192..bfe7b39fe2868 100644 --- a/x-pack/plugins/maps/public/reducers/map/layer_utils.ts +++ b/x-pack/plugins/maps/public/reducers/map/layer_utils.ts @@ -62,12 +62,7 @@ export function updateLayerInList( const updatedLayer = { ...layerList[layerIdx], - // Update layer w/ new value. If no value provided, toggle boolean value - // allow empty strings, 0-value - [attribute]: - newValue || newValue === '' || newValue === 0 - ? newValue - : !(layerList[layerIdx][attribute] as boolean), + [attribute]: newValue, }; const updatedList = [ ...layerList.slice(0, layerIdx), diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index 6ee55bd72e49d..1d46ef3015046 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -20,6 +20,7 @@ import { GeoJsonVectorLayer, } from '../classes/layers/vector_layer'; import { VectorStyle } from '../classes/styles/vector/vector_style'; +import { isLayerGroup, LayerGroup } from '../classes/layers/layer_group'; import { HeatmapLayer } from '../classes/layers/heatmap_layer'; import { getTimeFilter } from '../kibana_services'; import { getChartsPaletteServiceGetColor } from '../reducers/non_serializable_instances'; @@ -47,6 +48,7 @@ import { Goto, HeatmapLayerDescriptor, LayerDescriptor, + LayerGroupDescriptor, MapCenter, MapExtent, MapSettings, @@ -74,8 +76,11 @@ export function createLayerInstance( customIcons: CustomIcon[], chartsPaletteServiceGetColor?: (value: string) => string | null ): ILayer { - const source: ISource = createSourceInstance(layerDescriptor.sourceDescriptor); + if (layerDescriptor.type === LAYER_TYPE.LAYER_GROUP) { + return new LayerGroup({ layerDescriptor: layerDescriptor as LayerGroupDescriptor }); + } + const source: ISource = createSourceInstance(layerDescriptor.sourceDescriptor); switch (layerDescriptor.type) { case LAYER_TYPE.RASTER_TILE: return new RasterTileLayer({ layerDescriptor, source: source as IRasterSource }); @@ -324,9 +329,32 @@ export const getLayerList = createSelector( getChartsPaletteServiceGetColor, getCustomIcons, (layerDescriptorList, chartsPaletteServiceGetColor, customIcons) => { - return layerDescriptorList.map((layerDescriptor) => + const layers = layerDescriptorList.map((layerDescriptor) => createLayerInstance(layerDescriptor, customIcons, chartsPaletteServiceGetColor) ); + + const childrenMap = new Map(); + layers.forEach((layer) => { + const parent = layer.getParent(); + if (!parent) { + return; + } + + const children = childrenMap.has(parent) ? childrenMap.get(parent)! : []; + childrenMap.set(parent, [...children, layer]); + }); + + childrenMap.forEach((children, parent) => { + const parentLayer = layers.find((layer) => { + return layer.getId() === parent; + }); + if (!parentLayer || !isLayerGroup(parentLayer)) { + return; + } + (parentLayer as LayerGroup).setChildren(children); + }); + + return layers; } ); diff --git a/x-pack/plugins/ml/public/application/management/index.ts b/x-pack/plugins/ml/public/application/management/index.ts index 86f7fa0ff92ab..f64d7cbb5bb64 100644 --- a/x-pack/plugins/ml/public/application/management/index.ts +++ b/x-pack/plugins/ml/public/application/management/index.ts @@ -23,7 +23,7 @@ export function registerManagementSection( title: i18n.translate('xpack.ml.management.jobsListTitle', { defaultMessage: 'Machine Learning', }), - order: 2, + order: 4, async mount(params: ManagementAppMountParams) { const { mountApp } = await import('./jobs_list'); return mountApp(core, params, deps); diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index df47c6f28a6e3..d8f4132a59541 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -172,7 +172,7 @@ export class ReportingPublicPlugin management.sections.section.insightsAndAlerting.registerApp({ id: 'reporting', title: this.title, - order: 1, + order: 3, mount: async (params) => { params.setBreadcrumbs([{ text: this.breadcrumbText }]); const [[start, startDeps], { mountManagementSection }] = await Promise.all([ diff --git a/x-pack/plugins/security/public/analytics/analytics_service.test.ts b/x-pack/plugins/security/public/analytics/analytics_service.test.ts index 28a272c12f9ec..be13fa25c1c8d 100644 --- a/x-pack/plugins/security/public/analytics/analytics_service.test.ts +++ b/x-pack/plugins/security/public/analytics/analytics_service.test.ts @@ -34,9 +34,12 @@ describe('AnalyticsService', () => { const authc = authenticationMock.createSetup(); authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + const { analytics, http } = coreMock.createSetup(); + analyticsService.setup({ authc, - analytics: coreMock.createSetup().analytics, + analytics, + http, securityLicense: licenseMock.create({ allowLogin: true }), }); analyticsService.start({ http: mockCore.http }); @@ -63,9 +66,12 @@ describe('AnalyticsService', () => { const authc = authenticationMock.createSetup(); authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + const { analytics, http } = coreMock.createSetup(); + analyticsService.setup({ authc, - analytics: coreMock.createSetup().analytics, + analytics, + http, securityLicense: licenseMock.create(licenseFeatures$.asObservable()), }); analyticsService.start({ http: mockCore.http }); @@ -116,9 +122,12 @@ describe('AnalyticsService', () => { const authc = authenticationMock.createSetup(); authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + const { analytics, http } = coreMock.createSetup(); + analyticsService.setup({ authc, - analytics: coreMock.createSetup().analytics, + analytics, + http, securityLicense: licenseMock.create({ allowLogin: true }), }); analyticsService.start({ http: mockCore.http }); @@ -141,9 +150,12 @@ describe('AnalyticsService', () => { const authc = authenticationMock.createSetup(); authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + const { analytics, http } = coreMock.createSetup(); + analyticsService.setup({ authc, - analytics: coreMock.createSetup().analytics, + analytics, + http, securityLicense: licenseMock.create({ allowLogin: false }), }); analyticsService.start({ http: mockCore.http }); @@ -167,9 +179,12 @@ describe('AnalyticsService', () => { const authc = authenticationMock.createSetup(); authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + const { analytics, http } = coreMock.createSetup(); + analyticsService.setup({ authc, - analytics: coreMock.createSetup().analytics, + analytics, + http, securityLicense: licenseMock.create({ allowLogin: true }), }); analyticsService.start({ http: mockCore.http }); @@ -185,4 +200,42 @@ describe('AnalyticsService', () => { mockCurrentAuthTypeInfo ); }); + + it('does not register the analytics context provider if the page is anonymous', () => { + const authc = authenticationMock.createSetup(); + const { analytics, http } = coreMock.createSetup(); + + http.anonymousPaths.isAnonymous.mockReturnValue(true); + + analyticsService.setup({ + authc, + analytics, + http, + securityLicense: licenseMock.create({ allowLogin: false }), + }); + + expect(analytics.registerContextProvider).not.toHaveBeenCalled(); + }); + + it('registers the user_id analytics context provider if the page is not anonymous', () => { + const authc = authenticationMock.createSetup(); + authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + + const { analytics, http } = coreMock.createSetup(); + + http.anonymousPaths.isAnonymous.mockReturnValue(false); + + analyticsService.setup({ + authc, + analytics, + http, + securityLicense: licenseMock.create({ allowLogin: false }), + }); + + expect(analytics.registerContextProvider).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'user_id', + }) + ); + }); }); diff --git a/x-pack/plugins/security/public/analytics/analytics_service.ts b/x-pack/plugins/security/public/analytics/analytics_service.ts index 87c402e9983a0..1c87db674eb40 100644 --- a/x-pack/plugins/security/public/analytics/analytics_service.ts +++ b/x-pack/plugins/security/public/analytics/analytics_service.ts @@ -11,6 +11,7 @@ import { throttleTime } from 'rxjs/operators'; import type { AnalyticsServiceSetup as CoreAnalyticsServiceSetup, + HttpSetup, HttpStart, } from '@kbn/core/public'; @@ -22,6 +23,7 @@ interface AnalyticsServiceSetupParams { securityLicense: SecurityLicense; analytics: CoreAnalyticsServiceSetup; authc: AuthenticationServiceSetup; + http: HttpSetup; cloudId?: string; } @@ -43,9 +45,11 @@ export class AnalyticsService { private securityLicense!: SecurityLicense; private securityFeaturesSubscription?: Subscription; - public setup({ analytics, authc, cloudId, securityLicense }: AnalyticsServiceSetupParams) { + public setup({ analytics, authc, cloudId, http, securityLicense }: AnalyticsServiceSetupParams) { this.securityLicense = securityLicense; - registerUserContext(analytics, authc, cloudId); + if (http.anonymousPaths.isAnonymous(window.location.pathname) === false) { + registerUserContext(analytics, authc, cloudId); + } } public start({ http }: AnalyticsServiceStartParams) { diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index 2a91479824062..c56c40f63b4d0 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -113,6 +113,7 @@ export class SecurityPlugin analytics: core.analytics, authc: this.authc, cloudId: cloud?.cloudId, + http: core.http, securityLicense: license, }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx rename to x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx index 57611fcdf2484..1b1debe616844 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx @@ -6,18 +6,17 @@ */ import React from 'react'; -import type { AppContextTestRender } from '../../../../../common/mock/endpoint'; -import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint'; -import { endpointPageHttpMock } from '../../mocks'; +import type { AppContextTestRender } from '../../../../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint'; +import { endpointPageHttpMock } from '../../../mocks'; import { act, waitFor, cleanup } from '@testing-library/react'; -import { getEndpointListPath } from '../../../../common/routing'; -import { AdminSearchBar } from './search_bar'; +import { getEndpointListPath } from '../../../../../common/routing'; +import { AdminSearchBar } from '../search_bar'; import { fireEvent } from '@testing-library/dom'; -import { uiQueryParams } from '../../store/selectors'; -import type { EndpointIndexUIQueryParams } from '../../types'; +import { uiQueryParams } from '../../../store/selectors'; +import type { EndpointIndexUIQueryParams } from '../../../types'; -// FLAKY: https://github.com/elastic/kibana/issues/140618 -describe.skip('when rendering the endpoint list `AdminSearchBar`', () => { +describe('when rendering the endpoint list `AdminSearchBar`', () => { let render: ( urlParams?: EndpointIndexUIQueryParams ) => Promise>; @@ -85,8 +84,7 @@ describe.skip('when rendering the endpoint list `AdminSearchBar`', () => { expect(getQueryParamsFromStore().admin_query).toBe("(language:kuery,query:'host.name: foo')"); }); - // FLAKY: https://github.com/elastic/kibana/issues/132398 - it.skip.each([ + it.each([ ['nothing', ''], ['spaces', ' '], ])( diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx similarity index 79% rename from x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.test.tsx rename to x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx index 0ef525418f385..75b35eaea2be7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx @@ -8,13 +8,13 @@ import { act, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { EVENT_FILTERS_PATH } from '../../../../../common/constants'; -import type { AppContextTestRender } from '../../../../common/mock/endpoint'; -import { createAppRootMockRenderer } from '../../../../common/mock/endpoint'; -import { EventFiltersList } from './event_filters_list'; -import { exceptionsListAllHttpMocks } from '../../../mocks/exceptions_list_http_mocks'; -import { SEARCHABLE_FIELDS } from '../constants'; -import { parseQueryFilterToKQL } from '../../../common/utils'; +import { EVENT_FILTERS_PATH } from '../../../../../../common/constants'; +import type { AppContextTestRender } from '../../../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint'; +import { EventFiltersList } from '../event_filters_list'; +import { exceptionsListAllHttpMocks } from '../../../../mocks/exceptions_list_http_mocks'; +import { SEARCHABLE_FIELDS } from '../../constants'; +import { parseQueryFilterToKQL } from '../../../../common/utils'; describe('When on the Event Filters list page', () => { let render: () => ReturnType; diff --git a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx similarity index 68% rename from x-pack/plugins/security_solution/public/management/pages/index.test.tsx rename to x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx index 1df471633c3c2..0fcc8aa1f0ca5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; -import { ManagementContainer } from '.'; -import '../../common/mock/match_media'; -import type { AppContextTestRender } from '../../common/mock/endpoint'; -import { createAppRootMockRenderer } from '../../common/mock/endpoint'; -import { useUserPrivileges } from '../../common/components/user_privileges'; -import { endpointPageHttpMock } from './endpoint_hosts/mocks'; +import { ManagementContainer } from '..'; +import '../../../common/mock/match_media'; +import type { AppContextTestRender } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import { useUserPrivileges } from '../../../common/components/user_privileges'; +import { endpointPageHttpMock } from '../endpoint_hosts/mocks'; -jest.mock('../../common/components/user_privileges'); +jest.mock('../../../common/components/user_privileges'); describe('when in the Administration tab', () => { let render: () => ReturnType; @@ -34,8 +34,7 @@ describe('when in the Administration tab', () => { expect(await render().findByTestId('noIngestPermissions')).not.toBeNull(); }); - // FLAKY: https://github.com/elastic/kibana/issues/135166 - it.skip('should display the Management view if user has privileges', async () => { + it('should display the Management view if user has privileges', async () => { (useUserPrivileges as jest.Mock).mockReturnValue({ endpointPrivileges: { loading: false, canAccessEndpointManagement: true }, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts index abe65089143aa..d959add145663 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts @@ -67,10 +67,10 @@ export function registerEndpointRoutes( { path: HOST_METADATA_GET_ROUTE, validate: GetMetadataRequestSchema, - options: { authRequired: true, tags: ['access:securitySolution'] }, + options: { authRequired: true }, }, withEndpointAuthz( - { all: ['canReadSecuritySolution'] }, + { any: ['canReadSecuritySolution', 'canAccessFleet'] }, logger, getMetadataRequestHandler(endpointAppContext, logger) ) diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 58cda4c9737c9..86b57fcb85022 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -674,7 +674,6 @@ describe('test endpoint routes', () => { expect(esSearchMock).toHaveBeenCalledTimes(1); expect(routeConfig.options).toEqual({ authRequired: true, - tags: ['access:securitySolution'], }); expect(mockResponse.notFound).toBeCalled(); const message = mockResponse.notFound.mock.calls[0][0]?.body; @@ -706,7 +705,6 @@ describe('test endpoint routes', () => { expect(esSearchMock).toHaveBeenCalledTimes(1); expect(routeConfig.options).toEqual({ authRequired: true, - tags: ['access:securitySolution'], }); expect(mockResponse.ok).toBeCalled(); const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; @@ -741,7 +739,6 @@ describe('test endpoint routes', () => { expect(esSearchMock).toHaveBeenCalledTimes(1); expect(routeConfig.options).toEqual({ authRequired: true, - tags: ['access:securitySolution'], }); expect(mockResponse.ok).toBeCalled(); const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; @@ -778,7 +775,6 @@ describe('test endpoint routes', () => { expect(esSearchMock).toHaveBeenCalledTimes(1); expect(routeConfig.options).toEqual({ authRequired: true, - tags: ['access:securitySolution'], }); expect(mockResponse.ok).toBeCalled(); const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; @@ -814,7 +810,37 @@ describe('test endpoint routes', () => { expect(mockResponse.badRequest).toBeCalled(); }); - it('should get forbidden if no security solution access', async () => { + it('should work if no security solution access but has fleet access', async () => { + const response = legacyMetadataSearchResponseMock( + new EndpointDocGenerator().generateHostMetadata() + ); + const mockRequest = httpServerMock.createKibanaRequest({ + params: { id: response.hits.hits[0]._id }, + }); + const esSearchMock = mockScopedClient.asInternalUser.search; + + mockAgentClient.getAgent.mockResolvedValue(agentGenerator.generate({ status: 'online' })); + esSearchMock.mockResponseOnce(response); + + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith(HOST_METADATA_GET_ROUTE) + )!; + + const contextOverrides = { + endpointAuthz: getEndpointAuthzInitialStateMock({ + canReadSecuritySolution: false, + }), + }; + await routeHandler( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient, contextOverrides), + mockRequest, + mockResponse + ); + + expect(mockResponse.ok).toBeCalled(); + }); + + it('should get forbidden if no security solution or fleet access', async () => { const mockRequest = httpServerMock.createKibanaRequest(); [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => @@ -822,7 +848,10 @@ describe('test endpoint routes', () => { )!; const contextOverrides = { - endpointAuthz: getEndpointAuthzInitialStateMock({ canReadSecuritySolution: false }), + endpointAuthz: getEndpointAuthzInitialStateMock({ + canAccessFleet: false, + canReadSecuritySolution: false, + }), }; await routeHandler( createRouteHandlerContext(mockScopedClient, mockSavedObjectClient, contextOverrides), diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts index a9a7c7c4837e3..e92f316f7c125 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts @@ -55,7 +55,7 @@ describe('test policy response handler', () => { const response = createSearchResponse(new EndpointDocGenerator().generatePolicyResponse()); const hostPolicyResponseHandler = getHostPolicyResponseHandler(); - mockScopedClient.asCurrentUser.search.mockResponseOnce(response); + mockScopedClient.asInternalUser.search.mockResponseOnce(response); const mockRequest = httpServerMock.createKibanaRequest({ params: { agentId: 'id' }, }); @@ -78,7 +78,7 @@ describe('test policy response handler', () => { it('should return not found when there is no response policy for host', async () => { const hostPolicyResponseHandler = getHostPolicyResponseHandler(); - mockScopedClient.asCurrentUser.search.mockResponseOnce(createSearchResponse()); + mockScopedClient.asInternalUser.search.mockResponseOnce(createSearchResponse()); const mockRequest = httpServerMock.createKibanaRequest({ params: { agentId: 'id' }, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts index 18eb1c961ee76..568a61d2691d8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts @@ -30,7 +30,7 @@ export function registerPolicyRoutes(router: IRouter, endpointAppContext: Endpoi options: { authRequired: true }, }, withEndpointAuthz( - { all: ['canAccessEndpointManagement'] }, + { any: ['canReadSecuritySolution', 'canAccessFleet'] }, logger, getHostPolicyResponseHandler() ) diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts index c0639bcbcb848..1afafa2b61245 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts @@ -51,7 +51,7 @@ export async function getPolicyResponseByAgentId( dataClient: IScopedClusterClient ): Promise { const query = getESQueryPolicyResponseByAgentID(agentID, index); - const response = await dataClient.asCurrentUser.search(query); + const response = await dataClient.asInternalUser.search(query); if (response.hits.hits.length > 0 && response.hits.hits[0]._source != null) { return { diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.test.tsx index 6fb83aad7287d..4b79a68f2125c 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.test.tsx @@ -121,6 +121,7 @@ describe('IndexThresholdAlertTypeExpression', () => { expect(wrapper.find('[data-test-subj="forLastExpression"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="visualizationPlaceholder"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="thresholdVisualization"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="filterKuery"]').exists()).toBeTruthy(); }); test(`should render IndexThresholdAlertTypeExpression with expected components when aggType does require field`, async () => { @@ -133,6 +134,7 @@ describe('IndexThresholdAlertTypeExpression', () => { expect(wrapper.find('[data-test-subj="forLastExpression"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="visualizationPlaceholder"]').exists()).toBeTruthy(); expect(wrapper.find('[data-test-subj="thresholdVisualization"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="filterKuery"]').exists()).toBeTruthy(); }); test(`should render IndexThresholdAlertTypeExpression with visualization when there are no expression errors`, async () => { @@ -175,6 +177,7 @@ describe('IndexThresholdAlertTypeExpression', () => { expect( wrapper.find('EuiEmptyPrompt[data-test-subj="visualizationPlaceholder"]').text() ).toEqual(`Complete the expression to generate a preview.`); + expect(wrapper.find('input[data-test-subj="filterKuery"]').text()).toEqual(''); }); test(`should use alert params when params are defined`, async () => { @@ -186,6 +189,7 @@ describe('IndexThresholdAlertTypeExpression', () => { const groupBy = 'top'; const termSize = '27'; const termField = 'host.name'; + const wrapper = await setup( getAlertParams({ aggType, diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx index de8b1ec484afc..37ad715aae18b 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx @@ -5,10 +5,18 @@ * 2.0. */ -import React, { useState, Fragment, useEffect } from 'react'; +import React, { useState, Fragment, useEffect, useCallback, ChangeEvent } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiSpacer, EuiCallOut, EuiEmptyPrompt, EuiText, EuiTitle } from '@elastic/eui'; +import { + EuiSpacer, + EuiCallOut, + EuiEmptyPrompt, + EuiText, + EuiTitle, + EuiFieldSearch, + EuiFormRow, +} from '@elastic/eui'; import { HttpSetup } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { @@ -78,6 +86,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent< threshold, timeWindowSize, timeWindowUnit, + filterKuery, } = ruleParams; const indexArray = indexParamToArray(index); @@ -133,6 +142,13 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent< setEsFields(currentEsFields); }; + const handleFilterChange = useCallback( + (e: ChangeEvent) => { + setRuleParams('filterKuery', e.target.value || undefined); + }, + [setRuleParams] + ); + useEffect(() => { setDefaultExpressionValues(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -261,6 +277,33 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent< } /> + +
+ +
+
+ + 0} + error={errors.filterKuery} + > + 0} + /> + +
{cannotShowVisualization ? ( diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts index d1e5613c659f7..9dfcfa0bffc31 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts @@ -36,6 +36,7 @@ export async function getThresholdAlertVisualizationData({ termSize: model.termSize, timeWindowSize: model.timeWindowSize, timeWindowUnit: model.timeWindowUnit, + filterKuery: model.filterKuery, dateStart: new Date(visualizeOptions.rangeFrom).toISOString(), dateEnd: new Date(visualizeOptions.rangeTo).toISOString(), interval: visualizeOptions.interval, diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts index ed6758e6d7326..20a4bd5c6a2c0 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts @@ -39,4 +39,5 @@ export interface IndexThresholdAlertParams extends RuleTypeParams { threshold: number[]; timeWindowSize: number; timeWindowUnit: string; + filterKuery?: string; } diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.test.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.test.ts index 459949f18528e..d02afc87ad4f4 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.test.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.test.ts @@ -94,4 +94,21 @@ describe('expression params validation', () => { expect(validateExpression(initialParams).errors.threshold1.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.threshold1[0]).toBe('Threshold1 is required.'); }); + + test('if filterKuery is invalid should return proper error message', () => { + const initialParams: IndexThresholdAlertParams = { + index: ['test'], + aggType: 'count', + groupBy: 'top', + threshold: [1], + timeWindowSize: 1, + timeWindowUnit: 's', + thresholdComparator: 'between', + filterKuery: 'group:', + }; + expect(validateExpression(initialParams).errors.filterKuery.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.filterKuery[0]).toBe( + 'Filter query is invalid.' + ); + }); }); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.ts index a7f695094ce31..3b92fe8ae2dbf 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { ValidationResult, @@ -26,6 +27,7 @@ export const validateExpression = (alertParams: IndexThresholdAlertParams): Vali threshold, timeWindowSize, thresholdComparator, + filterKuery, } = alertParams; const validationResult = { errors: {} }; const errors = { @@ -37,8 +39,22 @@ export const validateExpression = (alertParams: IndexThresholdAlertParams): Vali threshold1: new Array(), index: new Array(), timeField: new Array(), + filterKuery: new Array(), }; validationResult.errors = errors; + + if (!!filterKuery) { + try { + toElasticsearchQuery(fromKueryExpression(filterKuery as string)); + } catch (e) { + errors.filterKuery.push( + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.invalidKql', { + defaultMessage: 'Filter query is invalid.', + }) + ); + } + } + if (!index || index.length === 0) { errors.index.push( i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.requiredIndexText', { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.test.ts index 656a1e5f275e5..adb722ef094d2 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.test.ts @@ -104,6 +104,10 @@ describe('ruleType', () => { "description": "termField", "name": "termField", }, + Object { + "description": "filterKuery", + "name": "filterKuery", + }, Object { "description": "termSize", "name": "termSize", diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.ts index 3b3480407fcfc..32f30c2db437d 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.ts @@ -169,6 +169,7 @@ export function getRuleType( timeWindowSize: params.timeWindowSize, timeWindowUnit: params.timeWindowUnit, interval: undefined, + filterKuery: params.filterKuery, }; // console.log(`index_threshold: query: ${JSON.stringify(queryParams, null, 4)}`); const result = await ( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx index 3a9cfcf72812a..bc05d8fcc7a51 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx @@ -31,7 +31,7 @@ export const AvailabilitySparklines = () => { attributes={[ { seriesType: 'area', - time: { from: 'now-15m/m', to: 'now' }, + time: { from: 'now-30d/d', to: 'now' }, name: 'Monitor availability', dataType: 'synthetics', selectedMetricField: 'monitor_availability', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx new file mode 100644 index 0000000000000..f388decd14ddb --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ReportTypes, useTheme } from '@kbn/observability-plugin/public'; +import { useParams } from 'react-router-dom'; +import { ClientPluginsStart } from '../../../../../plugin'; + +export const DurationSparklines = () => { + const { + services: { + observability: { ExploratoryViewEmbeddable }, + }, + } = useKibana(); + const { monitorId } = useParams<{ monitorId: string }>(); + + const theme = useTheme(); + + return ( + <> + + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx index efba55158c5b8..6d4dd6018acaf 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx @@ -17,6 +17,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { DurationSparklines } from './duration_sparklines'; import { MonitorDurationTrend } from './duration_trend'; import { StepDurationPanel } from './step_duration_panel'; import { AvailabilityPanel } from './availability_panel'; @@ -56,7 +57,9 @@ export const MonitorSummary = () => { - {/* TODO: Add duration metric sparkline*/} + + + diff --git a/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts b/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts index 82ce2ffdc5cce..0a2c3f599ff62 100644 --- a/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts @@ -56,7 +56,7 @@ export const testNowMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ const testRunId = uuidv4(); - const errors = await syntheticsService.triggerConfigs(request, [ + const errors = await syntheticsService.runOnceConfigs([ formatHeartbeatRequest({ // making it enabled, even if it's disabled in the UI monitor: { ...normalizedMonitor.attributes, enabled: true }, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts index 5c935aba57dd1..472d9792a37bf 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts @@ -157,23 +157,6 @@ describe('SyntheticsService', () => { }); describe('pushConfigs', () => { - it('does not include the isEdit flag on normal push requests', async () => { - const { service, locations } = getMockedService(); - - (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); - - const payload = getFakePayload([locations[0]]); - - await service.pushConfigs([payload] as HeartbeatConfig[]); - - expect(axios).toHaveBeenCalledTimes(1); - expect(axios).toHaveBeenCalledWith( - expect.objectContaining({ - data: expect.objectContaining({ is_edit: false }), - }) - ); - }); - it('includes the isEdit flag on edit requests', async () => { const { service, locations } = getMockedService(); @@ -181,7 +164,7 @@ describe('SyntheticsService', () => { const payload = getFakePayload([locations[0]]); - await service.pushConfigs([payload] as HeartbeatConfig[], true); + await service.editConfig([payload] as HeartbeatConfig[]); expect(axios).toHaveBeenCalledTimes(1); expect(axios).toHaveBeenCalledWith( diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index c0f977758f6da..457d4e79e418e 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -8,13 +8,14 @@ /* eslint-disable max-classes-per-file */ import { SavedObject } from '@kbn/core/server'; -import { KibanaRequest, Logger } from '@kbn/core/server'; +import { Logger } from '@kbn/core/server'; import { ConcreteTaskInstance, TaskManagerSetupContract, TaskManagerStartContract, TaskInstance, } from '@kbn/task-manager-plugin/server'; +import { Subject } from 'rxjs'; import { sendErrorTelemetryEvents } from '../routes/telemetry/monitor_upgrade_sender'; import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; import { installSyntheticsIndexTemplates } from '../routes/synthetics_service/install_index_templates'; @@ -170,7 +171,7 @@ export class SyntheticsService { if (service.isAllowed) { service.setupIndexTemplates(); - service.syncErrors = await service.pushConfigs(); + await service.pushConfigs(); } } catch (e) { sendErrorTelemetryEvents(service.logger, service.server.telemetry, { @@ -306,65 +307,44 @@ export class SyntheticsService { } } - async pushConfigs(configs?: HeartbeatConfig[], isEdit?: boolean) { - const monitorConfigs = configs ?? (await this.getMonitorConfigs()); - const monitors = this.formatConfigs(monitorConfigs); - - if (monitors.length === 0) { - this.logger.debug('No monitor found which can be pushed to service.'); - return null; - } - - this.apiKey = await this.getApiKey(); - - if (!this.apiKey) { - return null; - } + async pushConfigs() { + const service = this; + const subject = new Subject(); - const data = { - monitors, - output: await this.getOutput(this.apiKey), - isEdit: !!isEdit, - }; + subject.subscribe(async (monitorConfigs) => { + const monitors = this.formatConfigs(monitorConfigs); - this.logger.debug(`${monitors.length} monitors will be pushed to synthetics service.`); + if (monitors.length === 0) { + this.logger.debug('No monitor found which can be pushed to service.'); + return null; + } - try { - this.syncErrors = await this.apiClient.put(data); - return this.syncErrors; - } catch (e) { - this.logger.error(e); - throw e; - } - } + this.apiKey = await this.getApiKey(); - async runOnceConfigs(configs?: HeartbeatConfig[]) { - const monitors = this.formatConfigs(configs || (await this.getMonitorConfigs())); - if (monitors.length === 0) { - return; - } + if (!this.apiKey) { + return null; + } - this.apiKey = await this.getApiKey(); + const data = { + monitors, + output: await this.getOutput(this.apiKey), + }; - if (!this.apiKey) { - return null; - } + this.logger.debug(`${monitors.length} monitors will be pushed to synthetics service.`); - const data = { - monitors, - output: await this.getOutput(this.apiKey), - }; + try { + service.syncErrors = await this.apiClient.put(data); + } catch (e) { + this.logger.error(e); + throw e; + } + }); - try { - return await this.apiClient.runOnce(data); - } catch (e) { - this.logger.error(e); - throw e; - } + await this.getMonitorConfigs(subject); } - async triggerConfigs(request?: KibanaRequest, configs?: HeartbeatConfig[]) { - const monitors = this.formatConfigs(configs || (await this.getMonitorConfigs())); + async runOnceConfigs(configs: HeartbeatConfig[]) { + const monitors = this.formatConfigs(configs); if (monitors.length === 0) { return; } @@ -401,81 +381,92 @@ export class SyntheticsService { }; const result = await this.apiClient.delete(data); if (this.syncErrors && this.syncErrors?.length > 0) { - this.syncErrors = await this.pushConfigs(); + await this.pushConfigs(); } return result; } async deleteAllConfigs() { - const configs = await this.getMonitorConfigs(); - return await this.deleteConfigs(configs); + const subject = new Subject(); + + subject.subscribe(async (monitors) => { + await this.deleteConfigs(monitors); + }); + + await this.getMonitorConfigs(subject); } - async getMonitorConfigs() { - const savedObjectsClient = this.server.savedObjectsClient; + async getMonitorConfigs(subject: Subject) { + const soClient = this.server.savedObjectsClient; const encryptedClient = this.server.encryptedSavedObjects.getClient(); - if (!savedObjectsClient?.find) { + if (!soClient?.find) { return [] as SyntheticsMonitorWithId[]; } - const { saved_objects: encryptedMonitors } = await savedObjectsClient.find({ + const finder = soClient.createPointInTimeFinder({ type: syntheticsMonitorType, + perPage: 500, namespaces: ['*'], - perPage: 10000, }); const start = performance.now(); - const monitors: Array> = ( - await Promise.all( - encryptedMonitors.map( - (monitor) => - new Promise((resolve) => { - encryptedClient - .getDecryptedAsInternalUser( - syntheticsMonitor.name, - monitor.id, - { - namespace: monitor.namespaces?.[0], - } - ) - .then((decryptedMonitor) => resolve(decryptedMonitor)) - .catch((e) => { - this.logger.error(e); - sendErrorTelemetryEvents(this.logger, this.server.telemetry, { - reason: 'Failed to decrypt monitor', - message: e?.message, - type: 'runTaskError', - code: e?.code, - status: e.status, - kibanaVersion: this.server.kibanaVersion, + for await (const result of finder.find()) { + const encryptedMonitors = result.saved_objects; + + const monitors: Array> = ( + await Promise.all( + encryptedMonitors.map( + (monitor) => + new Promise((resolve) => { + encryptedClient + .getDecryptedAsInternalUser( + syntheticsMonitor.name, + monitor.id, + { + namespace: monitor.namespaces?.[0], + } + ) + .then((decryptedMonitor) => resolve(decryptedMonitor)) + .catch((e) => { + this.logger.error(e); + sendErrorTelemetryEvents(this.logger, this.server.telemetry, { + reason: 'Failed to decrypt monitor', + message: e?.message, + type: 'runTaskError', + code: e?.code, + status: e.status, + kibanaVersion: this.server.kibanaVersion, + }); + resolve(null); }); - resolve(null); - }); - }) + }) + ) ) - ) - ).filter((monitor) => monitor !== null) as Array>; - - const end = performance.now(); - const duration = end - start; + ).filter((monitor) => monitor !== null) as Array>; - this.logger.debug(`Decrypted ${monitors.length} monitors. Took ${duration} milliseconds`, { - event: { - duration, - }, - monitors: monitors.length, - }); + const end = performance.now(); + const duration = end - start; - return (monitors ?? []).map((monitor) => { - const attributes = monitor.attributes as unknown as MonitorFields; - return formatHeartbeatRequest({ - monitor: normalizeSecrets(monitor).attributes, - monitorId: monitor.id, - customHeartbeatId: attributes[ConfigKey.CUSTOM_HEARTBEAT_ID], + this.logger.debug(`Decrypted ${monitors.length} monitors. Took ${duration} milliseconds`, { + event: { + duration, + }, + monitors: monitors.length, }); - }); + + subject.next( + (monitors ?? []).map((monitor) => { + const attributes = monitor.attributes as unknown as MonitorFields; + return formatHeartbeatRequest({ + monitor: normalizeSecrets(monitor).attributes, + monitorId: monitor.id, + customHeartbeatId: attributes[ConfigKey.CUSTOM_HEARTBEAT_ID], + }); + }) + ); + } } formatConfigs(configs: SyntheticsMonitorWithId[]) { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 3750a4af42538..eba4c64dc818d 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -9279,10 +9279,6 @@ "xpack.cases.connecors.get.missingCaseConnectorErrorMessage": "Le type d'objet \"{id}\" n'est pas enregistré.", "xpack.cases.connecors.register.duplicateCaseConnectorErrorMessage": "Le type d'objet \"{id}\" est déjà enregistré.", "xpack.cases.connectors.card.createCommentWarningDesc": "Configurez les champs Create Comment URL et Create Comment Objects pour que le connecteur {connectorName} puisse partager les commentaires.", - "xpack.cases.connectors.cases.externalIncidentAdded": "(ajouté le {date} par {user})", - "xpack.cases.connectors.cases.externalIncidentCreated": "(créé le {date} par {user})", - "xpack.cases.connectors.cases.externalIncidentDefault": "(créé le {date} par {user})", - "xpack.cases.connectors.cases.externalIncidentUpdated": "(mis à jour le {date} par {user})", "xpack.cases.connectors.jira.unableToGetIssueMessage": "Impossible d'obtenir le problème ayant l'ID {id}", "xpack.cases.containers.pushToExternalService": "Envoyé avec succès à { serviceName }", "xpack.cases.containers.syncCase": "Les alertes de \"{caseTitle}\" ont été synchronisées", @@ -32297,7 +32293,6 @@ "xpack.triggersActionsUI.fieldBrowser.viewSelected": "sélectionné", "xpack.triggersActionsUI.home.appTitle": "Règles et connecteurs", "xpack.triggersActionsUI.home.breadcrumbTitle": "Règles et connecteurs", - "xpack.triggersActionsUI.home.connectorsTabTitle": "Connecteurs", "xpack.triggersActionsUI.home.docsLinkText": "Documentation", "xpack.triggersActionsUI.home.rulesTabTitle": "Règles", "xpack.triggersActionsUI.home.sectionDescription": "Détecter les conditions à l'aide des règles, et entreprendre des actions à l'aide des connecteurs.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f0d7d45fec244..f5ef512297e2e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9266,10 +9266,6 @@ "xpack.cases.connecors.get.missingCaseConnectorErrorMessage": "オブジェクトタイプ「{id}」は登録されていません。", "xpack.cases.connecors.register.duplicateCaseConnectorErrorMessage": "オブジェクトタイプ「{id}」はすでに登録されています。", "xpack.cases.connectors.card.createCommentWarningDesc": "コメントを外部で共有するには、{connectorName}コネクターの[コメントURLを作成]および[コメントオブジェクトを作成]フィールドを構成します。", - "xpack.cases.connectors.cases.externalIncidentAdded": "({date}に{user}が追加)", - "xpack.cases.connectors.cases.externalIncidentCreated": "({date}に{user}が作成)", - "xpack.cases.connectors.cases.externalIncidentDefault": "({date}に{user}が作成)", - "xpack.cases.connectors.cases.externalIncidentUpdated": "({date}に{user}が更新)", "xpack.cases.connectors.jira.unableToGetIssueMessage": "ID {id}の問題を取得できません", "xpack.cases.containers.pushToExternalService": "{ serviceName }への送信が正常に完了しました", "xpack.cases.containers.syncCase": "\"{caseTitle}\"のアラートが同期されました", @@ -32271,7 +32267,6 @@ "xpack.triggersActionsUI.fieldBrowser.viewSelected": "選択済み", "xpack.triggersActionsUI.home.appTitle": "ルールとコネクター", "xpack.triggersActionsUI.home.breadcrumbTitle": "ルールとコネクター", - "xpack.triggersActionsUI.home.connectorsTabTitle": "コネクター", "xpack.triggersActionsUI.home.docsLinkText": "ドキュメント", "xpack.triggersActionsUI.home.rulesTabTitle": "ルール", "xpack.triggersActionsUI.home.sectionDescription": "ルールを使用して条件を検出し、コネクターを使用してアクションを実行します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 482a967318153..e4f966a0a485f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9284,10 +9284,6 @@ "xpack.cases.connecors.get.missingCaseConnectorErrorMessage": "对象类型“{id}”未注册。", "xpack.cases.connecors.register.duplicateCaseConnectorErrorMessage": "已注册对象类型“{id}”。", "xpack.cases.connectors.card.createCommentWarningDesc": "为 {connectorName} 连接器配置“创建注释 URL”和“创建注释对象”字段以在外部共享注释。", - "xpack.cases.connectors.cases.externalIncidentAdded": "(由 {user} 于 {date}添加)", - "xpack.cases.connectors.cases.externalIncidentCreated": "(由 {user} 于 {date}创建)", - "xpack.cases.connectors.cases.externalIncidentDefault": "(由 {user} 于 {date}创建)", - "xpack.cases.connectors.cases.externalIncidentUpdated": "(由 {user} 于 {date}更新)", "xpack.cases.connectors.jira.unableToGetIssueMessage": "无法获取 ID 为 {id} 的问题", "xpack.cases.containers.pushToExternalService": "已成功发送到 { serviceName }", "xpack.cases.containers.syncCase": "“{caseTitle}”中的告警已同步", @@ -32308,7 +32304,6 @@ "xpack.triggersActionsUI.fieldBrowser.viewSelected": "已选定", "xpack.triggersActionsUI.home.appTitle": "规则和连接器", "xpack.triggersActionsUI.home.breadcrumbTitle": "规则和连接器", - "xpack.triggersActionsUI.home.connectorsTabTitle": "连接器", "xpack.triggersActionsUI.home.docsLinkText": "文档", "xpack.triggersActionsUI.home.rulesTabTitle": "规则", "xpack.triggersActionsUI.home.sectionDescription": "使规则检测条件,并使用连接器采取操作。", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index 5f2dcfcb5ff62..d0c61c884e528 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -31,11 +31,17 @@ import { AlertsTableConfigurationRegistryContract, RuleTypeRegistryContract, } from '../types'; -import { Section, routeToRuleDetails, legacyRouteToRuleDetails } from './constants'; +import { + Section, + routeToRuleDetails, + legacyRouteToRuleDetails, + routeToConnectors, +} from './constants'; import { setDataViewsService } from '../common/lib/data_apis'; import { KibanaContextProvider, useKibana } from '../common/lib/kibana'; import { ConnectorProvider } from './context/connector_context'; +import { CONNECTORS_PLUGIN_ID } from '../common/constants'; const TriggersActionsUIHome = lazy(() => import('./home')); const RuleDetailsRoute = lazy( @@ -73,7 +79,7 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => { export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => { const { dataViews, uiSettings, theme$ } = deps; - const sections: Section[] = ['rules', 'connectors', 'logs', 'alerts']; + const sections: Section[] = ['rules', 'logs', 'alerts']; const isDarkMode = useObservable(uiSettings.get$('theme:darkMode')); const sectionsRegex = sections.join('|'); @@ -96,6 +102,7 @@ export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => { export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => { const { actions: { validateEmailAddresses }, + application: { navigateToApp }, } = useKibana().services; return ( @@ -114,6 +121,15 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) = path={legacyRouteToRuleDetails} render={({ match }) => } /> + { + navigateToApp(`management/insightsAndAlerting/${CONNECTORS_PLUGIN_ID}`); + return null; + }} + /> + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx index 4637b6aa1d801..f6b149328db97 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx @@ -7,11 +7,25 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; -import { EuiButton, EuiEmptyPrompt, EuiIcon, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiPageTemplate, + EuiIcon, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { DocLinksStart } from '@kbn/core-doc-links-browser'; import './empty_connectors_prompt.scss'; -export const EmptyConnectorsPrompt = ({ onCTAClicked }: { onCTAClicked: () => void }) => ( - void; + docLinks: DocLinksStart; +}) => ( + @@ -33,24 +47,39 @@ export const EmptyConnectorsPrompt = ({ onCTAClicked }: { onCTAClicked: () => vo

} actions={ - - - + <> + + + +
+ + + + } /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx new file mode 100644 index 0000000000000..78b2559cc3dec --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx @@ -0,0 +1,104 @@ +/* + * 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, { lazy } from 'react'; +import { Switch, Route, Router } from 'react-router-dom'; +import { ChromeBreadcrumb, CoreStart, CoreTheme, ScopedHistory } from '@kbn/core/public'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nProvider } from '@kbn/i18n-react'; +import useObservable from 'react-use/lib/useObservable'; +import { Observable } from 'rxjs'; +import { KibanaFeature } from '@kbn/features-plugin/common'; +import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; + +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; +import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; +import { + ActionTypeRegistryContract, + AlertsTableConfigurationRegistryContract, + RuleTypeRegistryContract, +} from '../types'; + +import { setDataViewsService } from '../common/lib/data_apis'; +import { KibanaContextProvider, useKibana } from '../common/lib/kibana'; +import { ConnectorProvider } from './context/connector_context'; + +const ActionsConnectorsList = lazy( + () => import('./sections/actions_connectors_list/components/actions_connectors_list') +); + +export interface TriggersAndActionsUiServices extends CoreStart { + actions: ActionsPublicPluginSetup; + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; + dataViewEditor: DataViewEditorStart; + charts: ChartsPluginStart; + alerting?: AlertingStart; + spaces?: SpacesPluginStart; + storage?: Storage; + isCloud: boolean; + setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; + actionTypeRegistry: ActionTypeRegistryContract; + ruleTypeRegistry: RuleTypeRegistryContract; + alertsTableConfigurationRegistry: AlertsTableConfigurationRegistryContract; + history: ScopedHistory; + kibanaFeatures: KibanaFeature[]; + element: HTMLElement; + theme$: Observable; + unifiedSearch: UnifiedSearchPublicPluginStart; +} + +export const renderApp = (deps: TriggersAndActionsUiServices) => { + const { element } = deps; + render(, element); + return () => { + unmountComponentAtNode(element); + }; +}; + +export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => { + const { dataViews, uiSettings, theme$ } = deps; + const isDarkMode = useObservable(uiSettings.get$('theme:darkMode')); + + setDataViewsService(dataViews); + return ( + + + + + + + + + + + + ); +}; + +export const AppWithoutRouter = () => { + const { + actions: { validateEmailAddresses }, + } = useKibana().services; + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/plugin.ts index bf5cf6d58c69c..93ba3dd88b709 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/plugin.ts @@ -9,7 +9,7 @@ export const PLUGIN = { ID: 'triggersActionsUi', getI18nName: (i18n: any): string => { return i18n.translate('xpack.triggersActionsUI.appName', { - defaultMessage: 'Rules and Connectors', + defaultMessage: 'Rules', }); }, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx index 0024fef8ac125..83c856663f8f8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx @@ -69,8 +69,8 @@ describe('home', () => { let home = mountWithIntl(); - // Just rules/connectors - expect(home.find('.euiTab__content').length).toBe(3); + // Just rules/logs + expect(home.find('.euiTab__content').length).toBe(2); (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation((feature: string) => { if (feature === 'internalAlertsTable') { @@ -81,6 +81,6 @@ describe('home', () => { home = mountWithIntl(); // alerts now too! - expect(home.find('.euiTab__content').length).toBe(4); + expect(home.find('.euiTab__content').length).toBe(3); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index 8963e63eb44fc..894db7ce5a59e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -11,25 +11,15 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, EuiButtonEmpty, EuiPageHeader } from '@elastic/eui'; import { getIsExperimentalFeatureEnabled } from '../common/get_experimental_features'; -import { - Section, - routeToConnectors, - routeToRules, - routeToInternalAlerts, - routeToLogs, -} from './constants'; +import { Section, routeToRules, routeToInternalAlerts, routeToLogs } from './constants'; import { getAlertingSectionBreadcrumb } from './lib/breadcrumb'; import { getCurrentDocTitle } from './lib/doc_title'; -import { hasShowActionsCapability } from './lib/capabilities'; import { HealthCheck } from './components/health_check'; import { HealthContextProvider } from './context/health_context'; import { useKibana } from '../common/lib/kibana'; import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; -const ActionsConnectorsList = lazy( - () => import('./sections/actions_connectors_list/components/actions_connectors_list') -); const RulesList = lazy(() => import('./sections/rules_list/components/rules_list')); const LogsList = lazy(() => import('./sections/logs_list/components/logs_list')); const AlertsPage = lazy(() => import('./sections/alerts_table/alerts_page')); @@ -44,16 +34,9 @@ export const TriggersActionsUIHome: React.FunctionComponent { - const { - chrome, - application: { capabilities }, - - setBreadcrumbs, - docLinks, - } = useKibana().services; + const { chrome, setBreadcrumbs, docLinks } = useKibana().services; const isInternalAlertsTableEnabled = getIsExperimentalFeatureEnabled('internalAlertsTable'); - const canShowActions = hasShowActionsCapability(capabilities); const tabs: Array<{ id: Section; name: React.ReactNode; @@ -66,18 +49,6 @@ export const TriggersActionsUIHome: React.FunctionComponent - ), - }); - } - tabs.push({ id: 'logs', name: , @@ -111,10 +82,7 @@ export const TriggersActionsUIHome: React.FunctionComponent - + } rightSideItems={[ @@ -133,7 +101,7 @@ export const TriggersActionsUIHome: React.FunctionComponent } tabs={tabs.map((tab) => ({ @@ -155,13 +123,6 @@ export const TriggersActionsUIHome: React.FunctionComponent - {canShowActions && ( - - )} { }); expect(getAlertingSectionBreadcrumb('home', true)).toMatchObject({ text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { - defaultMessage: 'Rules and Connectors', + defaultMessage: 'Rules', }), href: `${routeToHome}`, }); @@ -44,7 +44,7 @@ describe('getAlertingSectionBreadcrumb', () => { }); expect(getAlertingSectionBreadcrumb('home', false)).toMatchObject({ text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { - defaultMessage: 'Rules and Connectors', + defaultMessage: 'Rules', }), }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts index 46a15b12bb733..c0b9551968870 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { routeToHome, routeToConnectors, routeToRules } from '../constants'; +import { routeToHome, routeToConnectors, routeToRules, routeToLogs } from '../constants'; export const getAlertingSectionBreadcrumb = ( type: string, @@ -14,6 +14,17 @@ export const getAlertingSectionBreadcrumb = ( ): { text: string; href?: string } => { // Home and sections switch (type) { + case 'logs': + return { + text: i18n.translate('xpack.triggersActionsUI.logs.breadcrumbTitle', { + defaultMessage: 'Logs', + }), + ...(returnHref + ? { + href: `${routeToLogs}`, + } + : {}), + }; case 'connectors': return { text: i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', { @@ -39,7 +50,7 @@ export const getAlertingSectionBreadcrumb = ( default: return { text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { - defaultMessage: 'Rules and Connectors', + defaultMessage: 'Rules', }), ...(returnHref ? { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.test.ts index af352de0cc6e7..8d4996ceb8259 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.test.ts @@ -9,7 +9,7 @@ import { getCurrentDocTitle } from './doc_title'; describe('getCurrentDocTitle', () => { test('if change calls return the proper doc title ', async () => { - expect(getCurrentDocTitle('home') === 'Rules and Connectors').toBeTruthy(); + expect(getCurrentDocTitle('home') === 'Rules').toBeTruthy(); expect(getCurrentDocTitle('connectors') === 'Connectors').toBeTruthy(); expect(getCurrentDocTitle('rules') === 'Rules').toBeTruthy(); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.ts index fab9e19c8acee..16699e750d223 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.ts @@ -11,6 +11,11 @@ export const getCurrentDocTitle = (page: string): string => { let updatedTitle: string; switch (page) { + case 'logs': + updatedTitle = i18n.translate('xpack.triggersActionsUI.logs.breadcrumbTitle', { + defaultMessage: 'Logs', + }); + break; case 'connectors': updatedTitle = i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', { defaultMessage: 'Connectors', @@ -23,7 +28,7 @@ export const getCurrentDocTitle = (page: string): string => { break; default: updatedTitle = i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { - defaultMessage: 'Rules and Connectors', + defaultMessage: 'Rules', }); } return updatedTitle; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index e973fce282dcb..55885aa3c14c3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -22,6 +22,7 @@ import { Criteria, EuiButtonEmpty, EuiBadge, + EuiPageTemplate, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { omit } from 'lodash'; @@ -52,6 +53,8 @@ import { } from '../../../../common/connectors_selection'; import { CreateConnectorFlyout } from '../../action_connector_form/create_connector_flyout'; import { EditConnectorFlyout } from '../../action_connector_form/edit_connector_flyout'; +import { getAlertingSectionBreadcrumb } from '../../../lib/breadcrumb'; +import { getCurrentDocTitle } from '../../../lib/doc_title'; const ConnectorIconTipWithSpacing = withTheme(({ theme }: { theme: EuiTheme }) => { return ( @@ -83,6 +86,9 @@ const ActionsConnectorsList: React.FunctionComponent = () => { notifications: { toasts }, application: { capabilities }, actionTypeRegistry, + setBreadcrumbs, + chrome, + docLinks, } = useKibana().services; const canDelete = hasDeleteActionsCapability(capabilities); const canExecute = hasExecuteActionsCapability(capabilities); @@ -107,6 +113,12 @@ const ActionsConnectorsList: React.FunctionComponent = () => { }, []); const [showWarningText, setShowWarningText] = useState(false); + // Set breadcrumb and page title + useEffect(() => { + setBreadcrumbs([getAlertingSectionBreadcrumb('connectors')]); + chrome.docTitle.change(getCurrentDocTitle('connectors')); + }, [chrome, setBreadcrumbs]); + useEffect(() => { (async () => { try { @@ -428,110 +440,144 @@ const ActionsConnectorsList: React.FunctionComponent = () => { /> , ], - toolsRight: canSave - ? [ - setAddFlyoutVisibility(true)} - > - - , - ] - : [], }} /> ); return ( -
- { - if (selectedItems.length === 0 || selectedItems.length === deleted.length) { - const updatedActions = actions.filter( - (action) => action.id && !connectorsToDelete.includes(action.id) - ); - setActions(updatedActions); - setSelectedItems([]); - } - setConnectorsToDelete([]); - }} - onErrors={async () => { - // Refresh the actions from the server, some actions may have beend deleted - await loadActions(); - setConnectorsToDelete([]); - }} - onCancel={async () => { - setConnectorsToDelete([]); - }} - apiDeleteCall={deleteActions} - idsToDelete={connectorsToDelete} - singleTitle={i18n.translate( - 'xpack.triggersActionsUI.sections.actionsConnectorsList.singleTitle', - { defaultMessage: 'connector' } - )} - multipleTitle={i18n.translate( - 'xpack.triggersActionsUI.sections.actionsConnectorsList.multipleTitle', - { defaultMessage: 'connectors' } - )} - showWarningText={showWarningText} - warningText={i18n.translate( - 'xpack.triggersActionsUI.sections.actionsConnectorsList.warningText', - { - defaultMessage: - '{connectors, plural, one {This connector is} other {Some connectors are}} currently in use.', - values: { - connectors: connectorsToDelete.length, - }, - } - )} - setIsLoadingState={(isLoading: boolean) => setIsLoadingActionTypes(isLoading)} - /> - - - {/* Render the view based on if there's data or if they can save */} - {(isLoadingActions || isLoadingActionTypes) && } - {actionConnectorTableItems.length !== 0 && table} - {actionConnectorTableItems.length === 0 && - canSave && - !isLoadingActions && - !isLoadingActionTypes && ( - setAddFlyoutVisibility(true)} /> - )} - {actionConnectorTableItems.length === 0 && !canSave && } - {addFlyoutVisible ? ( - { - setAddFlyoutVisibility(false); - }} - onTestConnector={(connector) => editItem(connector, EditConnectorTabs.Test)} - onConnectorCreated={loadActions} - actionTypeRegistry={actionTypeRegistry} + <> + {actionConnectorTableItems.length !== 0 && ( + setAddFlyoutVisibility(true)} + iconType="plusInCircle" + > + + , + ] + : [] + ).concat([ + + + , + ])} /> - ) : null} - {editConnectorProps.initialConnector ? ( - { - setEditConnectorProps(omit(editConnectorProps, 'initialConnector')); + )} + + { + if (selectedItems.length === 0 || selectedItems.length === deleted.length) { + const updatedActions = actions.filter( + (action) => action.id && !connectorsToDelete.includes(action.id) + ); + setActions(updatedActions); + setSelectedItems([]); + } + setConnectorsToDelete([]); }} - onConnectorUpdated={(connector) => { - setEditConnectorProps({ ...editConnectorProps, initialConnector: connector }); - loadActions(); + onErrors={async () => { + // Refresh the actions from the server, some actions may have beend deleted + await loadActions(); + setConnectorsToDelete([]); }} - actionTypeRegistry={actionTypeRegistry} + onCancel={async () => { + setConnectorsToDelete([]); + }} + apiDeleteCall={deleteActions} + idsToDelete={connectorsToDelete} + singleTitle={i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.singleTitle', + { defaultMessage: 'connector' } + )} + multipleTitle={i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.multipleTitle', + { defaultMessage: 'connectors' } + )} + showWarningText={showWarningText} + warningText={i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.warningText', + { + defaultMessage: + '{connectors, plural, one {This connector is} other {Some connectors are}} currently in use.', + values: { + connectors: connectorsToDelete.length, + }, + } + )} + setIsLoadingState={(isLoading: boolean) => setIsLoadingActionTypes(isLoading)} /> - ) : null} -
+ + + {/* Render the view based on if there's data or if they can save */} + {(isLoadingActions || isLoadingActionTypes) && } + {actionConnectorTableItems.length !== 0 && table} + {actionConnectorTableItems.length === 0 && + canSave && + !isLoadingActions && + !isLoadingActionTypes && ( + setAddFlyoutVisibility(true)} + docLinks={docLinks} + /> + )} + {actionConnectorTableItems.length === 0 && !canSave && } + {addFlyoutVisible ? ( + { + setAddFlyoutVisibility(false); + }} + onTestConnector={(connector) => editItem(connector, EditConnectorTabs.Test)} + onConnectorCreated={loadActions} + actionTypeRegistry={actionTypeRegistry} + /> + ) : null} + {editConnectorProps.initialConnector ? ( + { + setEditConnectorProps(omit(editConnectorProps, 'initialConnector')); + }} + onConnectorUpdated={(connector) => { + setEditConnectorProps({ ...editConnectorProps, initialConnector: connector }); + loadActions(); + }} + actionTypeRegistry={actionTypeRegistry} + /> + ) : null} + + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts index 794a7e90bf53f..1db1551ecfd38 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts @@ -12,3 +12,4 @@ export { builtInGroupByTypes } from './group_by_types'; export const VIEW_LICENSE_OPTIONS_LINK = 'https://www.elastic.co/subscriptions'; export const PLUGIN_ID = 'triggersActions'; +export const CONNECTORS_PLUGIN_ID = 'triggersActionsConnectors'; diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 1374f10355e16..9e4645b164f21 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -69,7 +69,7 @@ import type { } from './types'; import { TriggersActionsUiConfigType } from '../common/types'; import { registerAlertsTableConfiguration } from './application/sections/alerts_table/alerts_page/register_alerts_table_configuration'; -import { PLUGIN_ID } from './common/constants'; +import { PLUGIN_ID, CONNECTORS_PLUGIN_ID } from './common/constants'; import type { AlertsTableStateProps } from './application/sections/alerts_table/alerts_table_state'; import { getAlertsTableStateLazy } from './common/get_alerts_table_state'; import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form'; @@ -182,12 +182,24 @@ export class Plugin ExperimentalFeaturesService.init({ experimentalFeatures: this.experimentalFeatures }); const featureTitle = i18n.translate('xpack.triggersActionsUI.managementSection.displayName', { - defaultMessage: 'Rules and Connectors', + defaultMessage: 'Rules', }); const featureDescription = i18n.translate( 'xpack.triggersActionsUI.managementSection.displayDescription', { - defaultMessage: 'Detect conditions using rules, and take actions using connectors.', + defaultMessage: 'Detect conditions using rules.', + } + ); + const connectorsFeatureTitle = i18n.translate( + 'xpack.triggersActionsUI.managementSection.connectors.displayName', + { + defaultMessage: 'Connectors', + } + ); + const connectorsFeatureDescription = i18n.translate( + 'xpack.triggersActionsUI.managementSection.connectors.displayDescription', + { + defaultMessage: 'Connect third-party software with your alerting data.', } ); @@ -201,6 +213,15 @@ export class Plugin showOnHomePage: false, category: 'admin', }); + plugins.home.featureCatalogue.register({ + id: CONNECTORS_PLUGIN_ID, + title: connectorsFeatureTitle, + description: connectorsFeatureDescription, + icon: 'watchesApp', + path: '/app/management/insightsAndAlerting/triggersActions', + showOnHomePage: false, + category: 'admin', + }); } plugins.management.sections.section.insightsAndAlerting.registerApp({ @@ -250,6 +271,53 @@ export class Plugin }, }); + plugins.management.sections.section.insightsAndAlerting.registerApp({ + id: CONNECTORS_PLUGIN_ID, + title: connectorsFeatureTitle, + order: 2, + async mount(params: ManagementAppMountParams) { + const [coreStart, pluginsStart] = (await core.getStartServices()) as [ + CoreStart, + PluginsStart, + unknown + ]; + + const { renderApp } = await import('./application/connectors_app'); + + // The `/api/features` endpoint requires the "Global All" Kibana privilege. Users with a + // subset of this privilege are not authorized to access this endpoint and will receive a 404 + // error that causes the Alerting view to fail to load. + let kibanaFeatures: KibanaFeature[]; + try { + kibanaFeatures = await pluginsStart.features.getFeatures(); + } catch (err) { + kibanaFeatures = []; + } + + return renderApp({ + ...coreStart, + actions: plugins.actions, + data: pluginsStart.data, + dataViews: pluginsStart.dataViews, + dataViewEditor: pluginsStart.dataViewEditor, + charts: pluginsStart.charts, + alerting: pluginsStart.alerting, + spaces: pluginsStart.spaces, + unifiedSearch: pluginsStart.unifiedSearch, + isCloud: Boolean(plugins.cloud?.isCloudEnabled), + element: params.element, + theme$: params.theme$, + storage: new Storage(window.localStorage), + setBreadcrumbs: params.setBreadcrumbs, + history: params.history, + actionTypeRegistry, + ruleTypeRegistry, + alertsTableConfigurationRegistry, + kibanaFeatures, + }); + }, + }); + if (this.experimentalFeatures.internalAlertsTable) { registerAlertsTableConfiguration({ alertsTableConfigurationRegistry: this.alertsTableConfigurationRegistry, diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.test.ts index 9909297da1063..6ffed5accf9cd 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.test.ts @@ -19,6 +19,7 @@ const DefaultParams: Writable> = { groupBy: 'all', timeWindowSize: 5, timeWindowUnit: 'm', + filterKuery: 'event.provider: alerting', }; export function runTests(schema: ObjectType, defaultTypeParams: Record): void { @@ -183,6 +184,13 @@ export function runTests(schema: ObjectType, defaultTypeParams: Record { + params.filterKuery = 'event:'; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + '"[filterKuery]: Filter query is invalid."' + ); + }); }); function onValidate(): () => void { diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts index 605a06ab7ab5e..06e7c75d9795b 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; +import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; import { MAX_GROUPS } from '..'; export const CoreQueryParamsSchemaProperties = { @@ -28,6 +29,8 @@ export const CoreQueryParamsSchemaProperties = { groupBy: schema.string({ validate: validateGroupBy }), // field to group on (for groupBy: top) termField: schema.maybe(schema.string({ minLength: 1 })), + // filter field + filterKuery: schema.maybe(schema.string({ validate: validateKuery })), // limit on number of groups returned termSize: schema.maybe(schema.number({ min: 1 })), // size of time window for date range aggregations @@ -135,3 +138,16 @@ export function validateTimeWindowUnits(timeWindowUnit: string): string | undefi } ); } + +export function validateKuery(query: string): string | undefined { + try { + toElasticsearchQuery(fromKueryExpression(query)); + } catch (e) { + return i18n.translate( + 'xpack.triggersActionsUI.data.coreQueryParams.invalidKQLQueryErrorMessage', + { + defaultMessage: 'Filter query is invalid.', + } + ); + } +} diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts index 5e2550e15f35d..514601612db21 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts @@ -54,6 +54,26 @@ describe('timeSeriesQuery', () => { ).rejects.toThrowErrorMatchingInlineSnapshot(`"invalid date format for dateStart: \\"x\\""`); }); + it('filters the results when filter param is passed', async () => { + await timeSeriesQuery({ + ...params, + query: { ...params.query, filterKuery: 'event.provider: alerting' }, + }); + // @ts-ignore + expect(esClient.search.mock.calls[0]![0].body.query.bool.filter[1]).toEqual({ + bool: { + minimum_should_match: 1, + should: [ + { + match: { + 'event.provider': 'alerting', + }, + }, + ], + }, + }); + }); + it('should create correct query when aggType=count and termField is undefined (count over all) and selector params are undefined', async () => { await timeSeriesQuery(params); expect(esClient.search).toHaveBeenCalledWith( @@ -80,15 +100,17 @@ describe('timeSeriesQuery', () => { }, query: { bool: { - filter: { - range: { - 'time-field': { - format: 'strict_date_time', - gte: '2021-04-22T15:14:31.000Z', - lt: '2021-04-22T15:20:31.000Z', + filter: [ + { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, }, }, - }, + ], }, }, size: 0, @@ -132,15 +154,17 @@ describe('timeSeriesQuery', () => { }, query: { bool: { - filter: { - range: { - 'time-field': { - format: 'strict_date_time', - gte: '2021-04-22T15:14:31.000Z', - lt: '2021-04-22T15:20:31.000Z', + filter: [ + { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, }, }, - }, + ], }, }, size: 0, @@ -193,15 +217,17 @@ describe('timeSeriesQuery', () => { }, query: { bool: { - filter: { - range: { - 'time-field': { - format: 'strict_date_time', - gte: '2021-04-22T15:14:31.000Z', - lt: '2021-04-22T15:20:31.000Z', + filter: [ + { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, }, }, - }, + ], }, }, size: 0, @@ -271,15 +297,17 @@ describe('timeSeriesQuery', () => { }, query: { bool: { - filter: { - range: { - 'time-field': { - format: 'strict_date_time', - gte: '2021-04-22T15:14:31.000Z', - lt: '2021-04-22T15:20:31.000Z', + filter: [ + { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, }, }, - }, + ], }, }, size: 0, @@ -336,15 +364,17 @@ describe('timeSeriesQuery', () => { }, query: { bool: { - filter: { - range: { - 'time-field': { - format: 'strict_date_time', - gte: '2021-04-22T15:14:31.000Z', - lt: '2021-04-22T15:20:31.000Z', + filter: [ + { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, }, }, - }, + ], }, }, size: 0, @@ -405,15 +435,17 @@ describe('timeSeriesQuery', () => { }, query: { bool: { - filter: { - range: { - 'time-field': { - format: 'strict_date_time', - gte: '2021-04-22T15:14:31.000Z', - lt: '2021-04-22T15:20:31.000Z', + filter: [ + { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, }, }, - }, + ], }, }, size: 0, @@ -483,15 +515,17 @@ describe('timeSeriesQuery', () => { }, query: { bool: { - filter: { - range: { - 'time-field': { - format: 'strict_date_time', - gte: '2021-04-22T15:14:31.000Z', - lt: '2021-04-22T15:20:31.000Z', + filter: [ + { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, }, }, - }, + ], }, }, size: 0, @@ -578,15 +612,17 @@ describe('timeSeriesQuery', () => { }, query: { bool: { - filter: { - range: { - 'time-field': { - format: 'strict_date_time', - gte: '2021-04-22T15:14:31.000Z', - lt: '2021-04-22T15:20:31.000Z', + filter: [ + { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, }, }, - }, + ], }, }, size: 0, @@ -656,15 +692,17 @@ describe('timeSeriesQuery', () => { }, query: { bool: { - filter: { - range: { - 'time-field': { - format: 'strict_date_time', - gte: '2021-04-22T15:14:31.000Z', - lt: '2021-04-22T15:20:31.000Z', + filter: [ + { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, }, }, - }, + ], }, }, size: 0, diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts index 885be0bf59f5b..ccf4ae631bf8e 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts @@ -9,6 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { Logger } from '@kbn/core/server'; import type { ElasticsearchClient } from '@kbn/core/server'; import { getEsErrorMessage } from '@kbn/alerting-plugin/server'; +import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; import { DEFAULT_GROUPS } from '..'; import { getDateRangeInfo } from './date_range_info'; @@ -34,8 +35,16 @@ export async function timeSeriesQuery( params: TimeSeriesQueryParameters ): Promise { const { logger, esClient, query: queryParams, condition: conditionParams } = params; - const { index, timeWindowSize, timeWindowUnit, interval, timeField, dateStart, dateEnd } = - queryParams; + const { + index, + timeWindowSize, + timeWindowUnit, + interval, + timeField, + dateStart, + dateEnd, + filterKuery, + } = queryParams; const window = `${timeWindowSize}${timeWindowUnit}`; const dateRangeInfo = getDateRangeInfo({ dateStart, dateEnd, window, interval }); @@ -49,15 +58,18 @@ export async function timeSeriesQuery( size: 0, query: { bool: { - filter: { - range: { - [timeField]: { - gte: dateRangeInfo.dateStart, - lt: dateRangeInfo.dateEnd, - format: 'strict_date_time', + filter: [ + { + range: { + [timeField]: { + gte: dateRangeInfo.dateStart, + lt: dateRangeInfo.dateEnd, + format: 'strict_date_time', + }, }, }, - }, + ...(!!filterKuery ? [toElasticsearchQuery(fromKueryExpression(filterKuery))] : []), + ], }, }, // aggs: {...}, filled in below diff --git a/x-pack/plugins/watcher/public/plugin.ts b/x-pack/plugins/watcher/public/plugin.ts index 8f3f549654130..9a6564e6e286c 100644 --- a/x-pack/plugins/watcher/public/plugin.ts +++ b/x-pack/plugins/watcher/public/plugin.ts @@ -41,7 +41,7 @@ export class WatcherUIPlugin implements Plugin { const watcherESApp = esSection.registerApp({ id: 'watcher', title: pluginName, - order: 3, + order: 5, mount: async ({ element, setBreadcrumbs, history, theme$ }) => { const [coreStart] = await getStartServices(); const { diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/data_handler.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/data_handler.ts index d12681626ee8d..126a485b1d5ea 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/data_handler.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/data_handler.ts @@ -7,8 +7,8 @@ import http from 'http'; -export const getDataFromPostRequest = async (request: http.IncomingMessage) => { - if (request.method !== 'POST') { +export const getDataFromRequest = async (request: http.IncomingMessage) => { + if (request.method !== 'POST' && request.method !== 'PATCH') { return {}; } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/servicenow_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/servicenow_simulation.ts index 4d4b0ce43df24..5e5d05da833f1 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/servicenow_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/servicenow_simulation.ts @@ -6,7 +6,8 @@ */ import http from 'http'; -import { getDataFromPostRequest } from './data_handler'; +import { isEmpty } from 'lodash'; +import { getDataFromRequest } from './data_handler'; /** * This server records the data from a create incident request. It only saves the most recent request. When building tests, @@ -15,6 +16,7 @@ import { getDataFromPostRequest } from './data_handler'; export class RecordingServiceNowSimulator { private _incident: Record | undefined; private _server: http.Server | undefined; + private _allRequestData: Array> = []; private constructor() {} @@ -29,13 +31,17 @@ export class RecordingServiceNowSimulator { } private handler = async (request: http.IncomingMessage, response: http.ServerResponse) => { - const data = await getDataFromPostRequest(request); + const data = await getDataFromRequest(request); const pathName = request.url!; if (isCreateRequest(pathName)) { this._incident = data; } + if (!isEmpty(data)) { + this._allRequestData.push(data); + } + return handleSendingResponse(request, response, data); }; @@ -46,6 +52,10 @@ export class RecordingServiceNowSimulator { public get server() { return this._server!; } + + public get allRequestData() { + return this._allRequestData!; + } } const isCreateRequest = (pathName: string) => { @@ -63,7 +73,7 @@ const sendResponse = (response: http.ServerResponse, data: any) => { }; const requestHandler = async (request: http.IncomingMessage, response: http.ServerResponse) => { - const data: Record = await getDataFromPostRequest(request); + const data: Record = await getDataFromRequest(request); return handleSendingResponse(request, response, data); }; diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/simulator.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/simulator.ts index 2a6bda8c35bd1..d226859e25659 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/simulator.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/simulator.ts @@ -9,7 +9,7 @@ import getPort from 'get-port'; import http from 'http'; import httpProxy from 'http-proxy'; import { getProxyPort } from '../../../../lib/get_proxy_server'; -import { getDataFromPostRequest } from './data_handler'; +import { getDataFromRequest } from './data_handler'; export interface ProxyArgs { config: string; @@ -53,7 +53,7 @@ export abstract class Simulator { }; private baseHandler = async (request: http.IncomingMessage, response: http.ServerResponse) => { - const data = await getDataFromPostRequest(request); + const data = await getDataFromRequest(request); this._requestData = data; this._requestUrl = new URL(request.url ?? '', `http://${request.headers.host}`).toString(); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index c06eb41759d63..4dae8cdccd1cb 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -55,9 +55,6 @@ export default function ruleTests({ getService }: FtrProviderContext) { const endDateMillis = Date.now() + (RULE_INTERVALS_TO_WRITE - 1) * RULE_INTERVAL_MILLIS; endDate = new Date(endDateMillis).toISOString(); - // write documents from now to the future end date in 3 groups - await createEsDocumentsInGroups(3); - await createDataStream(es, ES_TEST_DATA_STREAM_NAME); }); @@ -72,6 +69,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { // never fire; the tests ensure the ones that should fire, do fire, and // those that shouldn't fire, do not fire. it('runs correctly: count all < >', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); + await createRule({ name: 'never fire', aggType: 'count', @@ -106,6 +106,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); it('runs correctly: count grouped <= =>', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); + await createRule({ name: 'never fire', aggType: 'count', @@ -150,6 +153,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); it('runs correctly: sum all between', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); + await createRule({ name: 'never fire', aggType: 'sum', @@ -184,6 +190,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); it('runs correctly: avg all', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); + // this never fires because of bad fields error await createRule({ name: 'never fire', @@ -220,6 +229,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); it('runs correctly: max grouped', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); + await createRule({ name: 'never fire', aggType: 'max', @@ -266,6 +278,8 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); it('runs correctly: max grouped on float', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); await createRule({ name: 'never fire', aggType: 'max', @@ -305,6 +319,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); it('runs correctly: max grouped on unsigned long', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); + await createRule({ name: 'never fire', aggType: 'max', @@ -344,6 +361,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); it('runs correctly: min grouped', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); + await createRule({ name: 'never fire', aggType: 'min', @@ -390,6 +410,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); it('runs correctly and populates recovery context', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); + // This rule should be active initially when the number of documents is below the threshold // and then recover when we add more documents. await createRule({ @@ -438,6 +461,9 @@ export default function ruleTests({ getService }: FtrProviderContext) { }); it('runs correctly over a data stream: count all < >', async () => { + // write documents from now to the future end date in 3 groups + await createEsDocumentsInGroups(3); + await createRule({ name: 'never fire', aggType: 'count', @@ -477,6 +503,35 @@ export default function ruleTests({ getService }: FtrProviderContext) { } }); + it('runs correctly: filters by Kuery', async () => { + // add documents that have timestamp as "now" + await createEsDocumentsWithGroups({ + es, + esTestIndexTool, + endDate: new Date().toISOString(), + intervals: 5, + intervalMillis: 5000, + groups: 3, + indexName: ES_TEST_INDEX_NAME, + }); + + await createRule({ + name: 'always fire', + aggType: 'count', + groupBy: 'all', + thresholdComparator: '>', + timeWindowSize: 3000, + threshold: [-1], + filterKuery: 'group: group-0', + }); + + const docs = await waitForDocs(1); + const doc = docs[0]; + const { message } = doc._source.params; + + expect(message).to.contain('Value: 5'); + }); + async function createEsDocumentsInGroups( groups: number, indexName: string = ES_TEST_INDEX_NAME @@ -513,6 +568,7 @@ export default function ruleTests({ getService }: FtrProviderContext) { threshold: number[]; notifyWhen?: string; indexName?: string; + filterKuery?: string; } async function createRule(params: CreateRuleParams): Promise { @@ -584,6 +640,7 @@ export default function ruleTests({ getService }: FtrProviderContext) { timeWindowUnit: 's', thresholdComparator: params.thresholdComparator, threshold: params.threshold, + filterKuery: params.filterKuery, }, }); diff --git a/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts b/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts index a2e1f158a73e2..2804fc3fc3e0e 100644 --- a/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts +++ b/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts @@ -75,13 +75,21 @@ export default ({ getService }: FtrProviderContext) => { await esArchiver.unload('x-pack/test/functional/es_archives/ml/ecommerce'); }); - it('should return full data without streaming', async () => { + async function requestWithoutStreaming(body: ApiExplainLogRateSpikes['body']) { const resp = await supertest .post(`/internal/aiops/explain_log_rate_spikes`) .set('kbn-xsrf', 'kibana') - .send(requestBody) + .send(body) .expect(200); + // compression is on by default so if the request body is undefined + // the response header should include "gzip" and otherwise be "undefined" + if (body.compressResponse === undefined) { + expect(resp.header['content-encoding']).to.be('gzip'); + } else if (body.compressResponse === false) { + expect(resp.header['content-encoding']).to.be(undefined); + } + expect(Buffer.isBuffer(resp.body)).to.be(true); const chunks: string[] = resp.body.toString().split('\n'); @@ -131,34 +139,64 @@ export default ({ getService }: FtrProviderContext) => { histograms.forEach((h, index) => { expect(h.histogram.length).to.be(20); }); + } + + it('should return full data without streaming with compression with flushFix', async () => { + await requestWithoutStreaming(requestBody); + }); + + it('should return full data without streaming with compression without flushFix', async () => { + await requestWithoutStreaming({ ...requestBody, flushFix: false }); }); - it('should return data in chunks with streaming', async () => { - const response = await fetch(`${kibanaServerUrl}/internal/aiops/explain_log_rate_spikes`, { + it('should return full data without streaming without compression with flushFix', async () => { + await requestWithoutStreaming({ ...requestBody, compressResponse: false }); + }); + + it('should return full data without streaming without compression without flushFix', async () => { + await requestWithoutStreaming({ ...requestBody, compressResponse: false, flushFix: false }); + }); + + async function requestWithStreaming(body: ApiExplainLogRateSpikes['body']) { + const resp = await fetch(`${kibanaServerUrl}/internal/aiops/explain_log_rate_spikes`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'kbn-xsrf': 'stream', }, - body: JSON.stringify(requestBody), + body: JSON.stringify(body), }); - expect(response.ok).to.be(true); - expect(response.status).to.be(200); + // compression is on by default so if the request body is undefined + // the response header should include "gzip" and otherwise be "null" + if (body.compressResponse === undefined) { + expect(resp.headers.get('content-encoding')).to.be('gzip'); + } else if (body.compressResponse === false) { + expect(resp.headers.get('content-encoding')).to.be(null); + } + + expect(resp.ok).to.be(true); + expect(resp.status).to.be(200); - const stream = response.body; + const stream = resp.body; expect(stream).not.to.be(null); if (stream !== null) { const data: any[] = []; + let chunkCounter = 0; + const parseStreamCallback = (c: number) => (chunkCounter = c); - for await (const action of parseStream(stream)) { + for await (const action of parseStream(stream, parseStreamCallback)) { expect(action.type).not.to.be('error'); data.push(action); } + // If streaming works correctly we should receive more than one chunk. + expect(chunkCounter).to.be.greaterThan(1); + expect(data.length).to.be(expected.actionsLength); + const addChangePointsActions = data.filter((d) => d.type === expected.changePointFilter); expect(addChangePointsActions.length).to.greaterThan(0); @@ -189,6 +227,22 @@ export default ({ getService }: FtrProviderContext) => { expect(h.histogram.length).to.be(20); }); } + } + + it('should return data in chunks with streaming with compression with flushFix', async () => { + await requestWithStreaming(requestBody); + }); + + it('should return data in chunks with streaming with compression without flushFix', async () => { + await requestWithStreaming({ ...requestBody, flushFix: false }); + }); + + it('should return data in chunks with streaming without compression with flushFix', async () => { + await requestWithStreaming({ ...requestBody, compressResponse: false }); + }); + + it('should return data in chunks with streaming without compression without flushFix', async () => { + await requestWithStreaming({ ...requestBody, compressResponse: false, flushFix: false }); }); it('should return an error for non existing index without streaming', async () => { diff --git a/x-pack/test/api_integration/apis/aiops/parse_stream.ts b/x-pack/test/api_integration/apis/aiops/parse_stream.ts index f3da52e6024bb..70ee86bb64fd8 100644 --- a/x-pack/test/api_integration/apis/aiops/parse_stream.ts +++ b/x-pack/test/api_integration/apis/aiops/parse_stream.ts @@ -5,11 +5,16 @@ * 2.0. */ -export async function* parseStream(stream: NodeJS.ReadableStream) { +export async function* parseStream( + stream: NodeJS.ReadableStream, + callback?: (chunkCounter: number) => void +) { let partial = ''; + let chunkCounter = 0; try { for await (const value of stream) { + chunkCounter++; const full = `${partial}${value}`; const parts = full.split('\n'); const last = parts.pop(); @@ -25,4 +30,8 @@ export async function* parseStream(stream: NodeJS.ReadableStream) { } catch (error) { yield { type: 'error', payload: error.toString() }; } + + if (typeof callback === 'function') { + callback(chunkCounter); + } } diff --git a/x-pack/test/cases_api_integration/common/config.ts b/x-pack/test/cases_api_integration/common/config.ts index 6d3f59f30dc75..535fcca87183f 100644 --- a/x-pack/test/cases_api_integration/common/config.ts +++ b/x-pack/test/cases_api_integration/common/config.ts @@ -18,6 +18,7 @@ interface CreateTestConfigOptions { disabledPlugins?: string[]; ssl?: boolean; testFiles?: string[]; + publicBaseUrl?: boolean; } const enabledActionTypes = [ @@ -115,6 +116,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ...xPackApiIntegrationTestsConfig.get('kbnTestServer'), serverArgs: [ ...xPackApiIntegrationTestsConfig.get('kbnTestServer.serverArgs'), + ...(options.publicBaseUrl ? ['--server.publicBaseUrl=https://localhost:5601'] : []), `--xpack.actions.allowedHosts=${JSON.stringify(['localhost', 'some.non.existent.com'])}`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, '--xpack.eventLog.logEntries=true', diff --git a/x-pack/test/cases_api_integration/common/lib/mock.ts b/x-pack/test/cases_api_integration/common/lib/mock.ts index 0d7185a1fabd7..f81d7e45cce9e 100644 --- a/x-pack/test/cases_api_integration/common/lib/mock.ts +++ b/x-pack/test/cases_api_integration/common/lib/mock.ts @@ -70,6 +70,14 @@ export const postCommentAlertReq: CommentRequestAlertType = { owner: 'securitySolutionFixture', }; +export const postCommentAlertMultipleIdsReq: CommentRequestAlertType = { + alertId: ['test-id-1', 'test-id-2'], + index: ['test-index', 'test-index-2'], + rule: { id: 'test-rule-id', name: 'test-index-id' }, + type: CommentType.alert, + owner: 'securitySolutionFixture', +}; + export const postCommentActionsReq: CommentRequestActionsType = { comment: 'comment text', actions: { @@ -85,6 +93,21 @@ export const postCommentActionsReq: CommentRequestActionsType = { owner: 'securitySolutionFixture', }; +export const postCommentActionsReleaseReq: CommentRequestActionsType = { + comment: 'comment text', + actions: { + targets: [ + { + hostname: 'host-name', + endpointId: 'endpoint-id', + }, + ], + type: 'unisolate', + }, + type: CommentType.actions, + owner: 'securitySolutionFixture', +}; + export const postExternalReferenceESReq: CommentRequestExternalReferenceNoSOType = { type: CommentType.externalReference, externalReferenceStorage: { type: ExternalReferenceStorageType.elasticSearchDoc }, diff --git a/x-pack/test/cases_api_integration/security_and_spaces/config_basic.ts b/x-pack/test/cases_api_integration/security_and_spaces/config_basic.ts index 98b7b1abe98e7..ce01d6132d601 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/config_basic.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/config_basic.ts @@ -12,4 +12,5 @@ export default createTestConfig('security_and_spaces', { license: 'basic', ssl: true, testFiles: [require.resolve('./tests/basic')], + publicBaseUrl: true, }); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/config_no_public_base_url.ts b/x-pack/test/cases_api_integration/security_and_spaces/config_no_public_base_url.ts new file mode 100644 index 0000000000000..a47dcdbfcbab7 --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/config_no_public_base_url.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../common/config'; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig('security_and_spaces', { + license: 'trial', + ssl: true, + testFiles: [require.resolve('./tests/no_public_base_url')], + publicBaseUrl: false, +}); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/config_trial.ts b/x-pack/test/cases_api_integration/security_and_spaces/config_trial.ts index b5328fd83c2cb..f6e7e39922091 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/config_trial.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/config_trial.ts @@ -12,4 +12,5 @@ export default createTestConfig('security_and_spaces', { license: 'trial', ssl: true, testFiles: [require.resolve('./tests/trial')], + publicBaseUrl: true, }); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/index.ts new file mode 100644 index 0000000000000..3b7fc6128b97c --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { createSpacesAndUsers, deleteSpacesAndUsers } from '../../../common/lib/authentication'; + +// eslint-disable-next-line import/no-default-export +export default ({ loadTestFile, getService }: FtrProviderContext): void => { + describe('cases security and spaces enabled: no_public_base_url', function () { + before(async () => { + await createSpacesAndUsers(getService); + }); + + after(async () => { + await deleteSpacesAndUsers(getService); + }); + + loadTestFile(require.resolve('./push')); + }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts new file mode 100644 index 0000000000000..0f3e521988c55 --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts @@ -0,0 +1,142 @@ +/* + * 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 expect from '@kbn/expect'; +import { + postCommentUserReq, + postCommentAlertReq, + postCommentAlertMultipleIdsReq, + postCommentActionsReq, + postCommentActionsReleaseReq, + postExternalReferenceESReq, + persistableStateAttachment, +} from '../../../common/lib/mock'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { ObjectRemover as ActionsRemover } from '../../../../alerting_api_integration/common/lib'; + +import { + pushCase, + deleteAllCaseItems, + createCaseWithConnector, + getRecordingServiceNowSimulatorServer, + bulkCreateAttachments, +} from '../../../common/lib/utils'; +import { RecordingServiceNowSimulator } from '../../../../alerting_api_integration/common/fixtures/plugins/actions_simulators/server/servicenow_simulation'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('push_case', () => { + describe('incident recorder server', () => { + const actionsRemover = new ActionsRemover(supertest); + let serviceNowSimulatorURL: string = ''; + let serviceNowServer: RecordingServiceNowSimulator; + + beforeEach(async () => { + const { server, url } = await getRecordingServiceNowSimulatorServer(); + serviceNowServer = server; + serviceNowSimulatorURL = url; + }); + + afterEach(async () => { + await deleteAllCaseItems(es); + await actionsRemover.removeAll(); + serviceNowServer.close(); + }); + + it('should push correctly without a publicBaseUrl', async () => { + const { postedCase, connector } = await createCaseWithConnector({ + supertest, + serviceNowSimulatorURL, + actionsRemover, + }); + + await pushCase({ + supertest, + caseId: postedCase.id, + connectorId: connector.id, + }); + + expect(serviceNowServer.incident).eql({ + short_description: postedCase.title, + description: `${postedCase.description}\n\nAdded by elastic.`, + severity: '2', + urgency: '2', + impact: '2', + category: 'software', + subcategory: 'os', + correlation_id: postedCase.id, + correlation_display: 'Elastic Case', + caller_id: 'admin', + opened_by: 'admin', + }); + }); + + it('should format the comments correctly', async () => { + const { postedCase, connector } = await createCaseWithConnector({ + supertest, + serviceNowSimulatorURL, + actionsRemover, + }); + + const patchedCase = await bulkCreateAttachments({ + supertest, + caseId: postedCase.id, + params: [ + postCommentUserReq, + postCommentAlertReq, + postCommentAlertMultipleIdsReq, + postCommentActionsReq, + postCommentActionsReleaseReq, + postExternalReferenceESReq, + persistableStateAttachment, + ], + }); + + await pushCase({ + supertest, + caseId: patchedCase.id, + connectorId: connector.id, + }); + + /** + * If the request contains the work_notes property then + * it is a create comment request + */ + const allCommentRequests = serviceNowServer.allRequestData.filter((request) => + Boolean(request.work_notes) + ); + + /** + * For each of these comments a request is made: + * postCommentUserReq, postCommentActionsReq, postCommentActionsReleaseReq, and a comment with the + * total alerts attach to a case. All other type of comments should be filtered. Specifically, + * postCommentAlertReq, postCommentAlertMultipleIdsReq, postExternalReferenceESReq, and persistableStateAttachment + */ + expect(allCommentRequests.length).be(4); + + // User comment: postCommentUserReq + expect(allCommentRequests[0].work_notes).eql('This is a cool comment\n\nAdded by elastic.'); + + // Isolate host comment: postCommentActionsReq + expect(allCommentRequests[1].work_notes).eql( + 'Isolated host host-name with comment: comment text\n\nAdded by elastic.' + ); + + // Unisolate host comment: postCommentActionsReleaseReq + expect(allCommentRequests[2].work_notes).eql( + 'Released host host-name with comment: comment text\n\nAdded by elastic.' + ); + + // Total alerts + expect(allCommentRequests[3].work_notes).eql('Elastic Alerts attached to the case: 3'); + }); + }); + }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts index cbc1b95fb3f47..1a7f53d4e3db9 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts @@ -19,6 +19,12 @@ import { defaultUser, postCommentUserReq, getPostCaseRequest, + postCommentAlertReq, + postCommentActionsReq, + postCommentActionsReleaseReq, + postCommentAlertMultipleIdsReq, + persistableStateAttachment, + postExternalReferenceESReq, } from '../../../../common/lib/mock'; import { getConfigurationRequest, @@ -40,6 +46,7 @@ import { calculateDuration, getRecordingServiceNowSimulatorServer, getComment, + bulkCreateAttachments, } from '../../../../common/lib/utils'; import { globalRead, @@ -108,9 +115,103 @@ export default ({ getService }: FtrProviderContext): void => { }); // the full string should look something like this: - // This is a brand new case of a bad meanie defacing data (created at 2022-09-12T20:22:14.328Z by super_full_name) + // This is a brand new case of a bad meanie defacing data.\n\nAdded by super_full_name. expect(serviceNowServer.incident?.description).to.contain('by super_full_name'); }); + + it('should map the fields and add the backlink to Kibana correctly', async () => { + const cookies = await loginUsers({ supertest: supertestWithoutAuth, users: [superUser] }); + + const { postedCase, connector } = await createCaseWithConnector({ + supertest, + serviceNowSimulatorURL, + actionsRemover, + auth: null, + headers: { Cookie: cookies[0].cookieString() }, + }); + + await pushCase({ + supertest, + caseId: postedCase.id, + connectorId: connector.id, + }); + + expect(serviceNowServer.incident).eql({ + short_description: postedCase.title, + description: `${postedCase.description}\n\nAdded by super_full_name.\nFor more details, view this case in Kibana.\nCase URL: https://localhost:5601/app/management/insightsAndAlerting/cases/${postedCase.id}`, + severity: '2', + urgency: '2', + impact: '2', + category: 'software', + subcategory: 'os', + correlation_id: postedCase.id, + correlation_display: 'Elastic Case', + caller_id: 'admin', + opened_by: 'admin', + }); + }); + + it('should format the comments correctly', async () => { + const { postedCase, connector } = await createCaseWithConnector({ + supertest, + serviceNowSimulatorURL, + actionsRemover, + }); + + const patchedCase = await bulkCreateAttachments({ + supertest, + caseId: postedCase.id, + params: [ + postCommentUserReq, + postCommentAlertReq, + postCommentAlertMultipleIdsReq, + postCommentActionsReq, + postCommentActionsReleaseReq, + postExternalReferenceESReq, + persistableStateAttachment, + ], + }); + + await pushCase({ + supertest, + caseId: patchedCase.id, + connectorId: connector.id, + }); + + /** + * If the request contains the work_notes property then + * it is a create comment request + */ + const allCommentRequests = serviceNowServer.allRequestData.filter((request) => + Boolean(request.work_notes) + ); + + /** + * For each of these comments a request is made: + * postCommentUserReq, postCommentActionsReq, postCommentActionsReleaseReq, and a comment with the + * total alerts attach to a case. All other type of comments should be filtered. Specifically, + * postCommentAlertReq, postCommentAlertMultipleIdsReq, postExternalReferenceESReq, and persistableStateAttachment + */ + expect(allCommentRequests.length).be(4); + + // User comment: postCommentUserReq + expect(allCommentRequests[0].work_notes).eql('This is a cool comment\n\nAdded by elastic.'); + + // Isolate host comment: postCommentActionsReq + expect(allCommentRequests[1].work_notes).eql( + 'Isolated host host-name with comment: comment text\n\nAdded by elastic.' + ); + + // Unisolate host comment: postCommentActionsReleaseReq + expect(allCommentRequests[2].work_notes).eql( + 'Released host host-name with comment: comment text\n\nAdded by elastic.' + ); + + // Total alerts + expect(allCommentRequests[3].work_notes).eql( + `Elastic Alerts attached to the case: 3\n\nFor more details, view the alerts in Kibana\nAlerts URL: https://localhost:5601/app/management/insightsAndAlerting/cases/${patchedCase.id}/?tabId=alerts` + ); + }); }); describe('memoryless server', () => { @@ -224,7 +325,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(theCaseAfterUpdate.external_service?.connector_id).to.eql(pushConnector.id); }); - it('should create the mappings when pushing a case', async () => { + it('should push to a connector without mapping', async () => { // create a connector but not a configuration so that the mapping will not be present const connector = await createConnector({ supertest, @@ -257,7 +358,7 @@ export default ({ getService }: FtrProviderContext): void => { ); // there should be no mappings initially - let mappings = await getConnectorMappingsFromES({ es }); + const mappings = await getConnectorMappingsFromES({ es }); expect(mappings.body.hits.hits.length).to.eql(0); await pushCase({ @@ -265,13 +366,6 @@ export default ({ getService }: FtrProviderContext): void => { caseId: postedCase.id, connectorId: connector.id, }); - - // the mappings should now be created after the push - mappings = await getConnectorMappingsFromES({ es }); - expect(mappings.body.hits.hits.length).to.be(1); - expect( - mappings.body.hits.hits[0]._source?.['cases-connector-mappings'].mappings.length - ).to.be.above(0); }); it('pushes a comment appropriately', async () => { diff --git a/x-pack/test/fleet_api_integration/apis/data_streams/list.ts b/x-pack/test/fleet_api_integration/apis/data_streams/list.ts index 72fea406bcbf9..3866002647285 100644 --- a/x-pack/test/fleet_api_integration/apis/data_streams/list.ts +++ b/x-pack/test/fleet_api_integration/apis/data_streams/list.ts @@ -23,6 +23,7 @@ export default function (providerContext: FtrProviderContext) { const pkgVersion = '0.1.0'; const logsTemplateName = `logs-${pkgName}.test_logs`; const metricsTemplateName = `metrics-${pkgName}.test_metrics`; + const notFleetTemplateName = `metrics-${pkgName}.test_metrics_not_fleet`; const uninstallPackage = async (name: string, version: string) => { await supertest.delete(`/api/fleet/epm/packages/${name}/${version}`).set('kbn-xsrf', 'xxxx'); @@ -69,6 +70,24 @@ export default function (providerContext: FtrProviderContext) { }) ); + // This stream should never be returned as it is not + // managed by fleet (it isnt added to a fleet managed data stream) + responses.push( + await es.transport.request({ + method: 'POST', + path: `/${notFleetTemplateName}-default/_doc`, + body: { + '@timestamp': '2015-01-01', + logs_test_name: 'test', + data_stream: { + dataset: `${pkgName}.test_metrics_not_fleet`, + namespace: 'default', + type: 'metrics', + }, + }, + }) + ); + return responses as IndexResponse[]; }; @@ -104,6 +123,10 @@ export default function (providerContext: FtrProviderContext) { method: 'DELETE', path: `/_data_stream/${metricsTemplateName}-default`, }); + await es.transport.request({ + method: 'DELETE', + path: `/_data_stream/${notFleetTemplateName}-default`, + }); } catch (e) { // Silently swallow errors here as not all tests seed data streams } diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/gauge.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/gauge.ts index a5d348b4ed6f5..076be5b663ee3 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/gauge.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/gauge.ts @@ -11,8 +11,6 @@ import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const { visualize, lens, timePicker } = getPageObjects(['visualize', 'lens', 'timePicker']); - const testSubjects = getService('testSubjects'); - describe('Gauge', function describeIndexTests() { const isNewChartsLibraryEnabled = true; @@ -28,13 +26,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should show the "Edit Visualization in Lens" menu item', async () => { - const button = await testSubjects.exists('visualizeEditInLensButton'); - expect(button).to.eql(true); + expect(await visualize.hasNavigateToLensButton()).to.eql(true); }); it('should convert to Lens', async () => { - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('gaugeChart'); }); }); diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/goal.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/goal.ts index 809de4cfab008..38d943e48a826 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/goal.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/goal.ts @@ -11,8 +11,6 @@ import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const { visualize, lens, timePicker } = getPageObjects(['visualize', 'lens', 'timePicker']); - const testSubjects = getService('testSubjects'); - describe('Goal', function describeIndexTests() { const isNewChartsLibraryEnabled = true; @@ -28,13 +26,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should show the "Edit Visualization in Lens" menu item', async () => { - const button = await testSubjects.exists('visualizeEditInLensButton'); - expect(button).to.eql(true); + expect(await visualize.hasNavigateToLensButton()).to.eql(true); }); it('should convert to Lens', async () => { - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('mtrVis'); expect((await lens.getMetricVisualizationData()).length).to.be.equal(1); expect(await lens.getMetricVisualizationData()).to.eql([ diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts index cc28ead0c55cd..0737c7ffeeb50 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts @@ -11,7 +11,9 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Agg based Vis to Lens', function () { loadTestFile(require.resolve('./pie')); loadTestFile(require.resolve('./metric')); + loadTestFile(require.resolve('./xy')); loadTestFile(require.resolve('./gauge')); loadTestFile(require.resolve('./goal')); + loadTestFile(require.resolve('./table')); }); } diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/metric.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/metric.ts index 15b7c8b7e5dfd..c29166723f7eb 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/metric.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/metric.ts @@ -11,8 +11,6 @@ import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const { visualize, lens, timePicker } = getPageObjects(['visualize', 'lens', 'timePicker']); - const testSubjects = getService('testSubjects'); - describe('Metric', function describeIndexTests() { const isNewChartsLibraryEnabled = true; @@ -28,13 +26,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should show the "Edit Visualization in Lens" menu item', async () => { - const button = await testSubjects.exists('visualizeEditInLensButton'); - expect(button).to.eql(true); + expect(await visualize.hasNavigateToLensButton()).to.eql(true); }); it('should convert to Lens', async () => { - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('mtrVis'); expect((await lens.getMetricVisualizationData()).length).to.be.equal(1); expect(await lens.getMetricVisualizationData()).to.eql([ diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/pie.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/pie.ts index 1640a6a14631d..357305c67a69b 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/pie.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/pie.ts @@ -17,7 +17,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'header', ]); - const testSubjects = getService('testSubjects'); const pieChart = getService('pieChart'); describe('Pie', function describeIndexTests() { @@ -35,8 +34,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should hide the "Edit Visualization in Lens" menu item if no split slices were defined', async () => { - const button = await testSubjects.exists('visualizeEditInLensButton'); - expect(button).to.eql(false); + expect(await visualize.hasNavigateToLensButton()).to.eql(false); }); it('should show the "Edit Visualization in Lens" menu item', async () => { @@ -46,8 +44,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); await visEditor.clickGo(isNewChartsLibraryEnabled); - const button = await testSubjects.exists('visualizeEditInLensButton'); - expect(button).to.eql(true); + expect(await visualize.hasNavigateToLensButton()).to.eql(true); }); it('should convert to Lens', async () => { @@ -58,8 +55,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); await visEditor.clickGo(isNewChartsLibraryEnabled); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('partitionVisChart'); await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/table.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/table.ts new file mode 100644 index 0000000000000..09466bd7f1c70 --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/table.ts @@ -0,0 +1,190 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visEditor, lens, timePicker, header } = getPageObjects([ + 'visualize', + 'lens', + 'visEditor', + 'timePicker', + 'header', + ]); + + const testSubjects = getService('testSubjects'); + + describe('Table', function describeIndexTests() { + const isNewChartsLibraryEnabled = true; + + before(async () => { + await visualize.initTests(isNewChartsLibraryEnabled); + }); + + beforeEach(async () => { + await visualize.navigateToNewAggBasedVisualization(); + await visualize.clickDataTable(); + await visualize.clickNewSearch(); + await timePicker.setDefaultAbsoluteRange(); + }); + + it('should not allow converting of unsupported aggregations', async () => { + await visEditor.clickMetricEditor(); + await visEditor.selectAggregation('Serial diff', 'metrics'); + await visEditor.clickBucket('Split rows'); + await visEditor.selectAggregation('Date histogram'); + await visEditor.clickGo(); + await header.waitUntilLoadingHasFinished(); + const button = await testSubjects.exists('visualizeEditInLensButton'); + expect(button).to.eql(false); + }); + + it('should show the "Edit Visualization in Lens" menu item', async () => { + const button = await testSubjects.exists('visualizeEditInLensButton'); + expect(button).to.eql(true); + }); + + it('should convert aggregation with params', async () => { + await visEditor.clickMetricEditor(); + await visEditor.selectAggregation('Average', 'metrics'); + await visEditor.selectField('machine.ram', 'metrics'); + await visEditor.clickGo(); + await header.waitUntilLoadingHasFinished(); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('lnsDataTable'); + + expect(await lens.getLayerCount()).to.be(1); + + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(1); + expect(await dimensions[0].getVisibleText()).to.be('Average machine.ram'); + }); + + it('should convert total function to summary row', async () => { + await visEditor.clickMetricEditor(); + await visEditor.selectAggregation('Average', 'metrics'); + await visEditor.selectField('machine.ram', 'metrics'); + await visEditor.clickOptionsTab(); + const showTotalSwitch = await testSubjects.find('showTotal'); + await showTotalSwitch.click(); + await visEditor.clickGo(); + await header.waitUntilLoadingHasFinished(); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('lnsDataTable'); + + expect(await lens.getLayerCount()).to.be(1); + + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(1); + expect(await dimensions[0].getVisibleText()).to.be('Average machine.ram'); + + await lens.openDimensionEditor('lnsDatatable_metrics > lns-dimensionTrigger'); + const summaryRowFunction = await testSubjects.find('lnsDatatable_summaryrow_function'); + expect(await summaryRowFunction.getVisibleText()).to.be('Sum'); + }); + + it('should convert sibling pipeline aggregation', async () => { + await visEditor.clickMetricEditor(); + await visEditor.selectAggregation('Max Bucket', 'metrics'); + await visEditor.clickGo(); + await header.waitUntilLoadingHasFinished(); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('lnsDataTable'); + + expect(await lens.getLayerCount()).to.be(1); + + const metricText = await lens.getDimensionTriggerText('lnsDatatable_metrics', 0); + const splitRowText = await lens.getDimensionTriggerText('lnsDatatable_rows', 0); + + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(2); + expect(metricText).to.be('Overall Max of Count'); + expect(splitRowText).to.be('@timestamp'); + }); + + it('should convert parent pipeline aggregation', async () => { + await visEditor.clickMetricEditor(); + await visEditor.selectAggregation('Cumulative sum', 'metrics'); + await visEditor.clickBucket('Split rows'); + await visEditor.selectAggregation('Date histogram'); + await visEditor.clickGo(); + await header.waitUntilLoadingHasFinished(); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('lnsDataTable'); + + expect(await lens.getLayerCount()).to.be(1); + + const metricText = await lens.getDimensionTriggerText('lnsDatatable_metrics', 0); + const splitRowText = await lens.getDimensionTriggerText('lnsDatatable_rows', 0); + + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(2); + expect(metricText).to.be('Cumulative Sum of Count'); + expect(splitRowText).to.be('@timestamp'); + }); + + it('should convert split rows and split table to split table rows', async () => { + await visEditor.clickBucket('Split rows'); + await visEditor.selectAggregation('Date histogram'); + await visEditor.clickBucket('Split table'); + await visEditor.selectAggregation('Terms', 'buckets', false, 1); + await visEditor.selectField('bytes', 'buckets', false, 1); + await visEditor.clickGo(); + await header.waitUntilLoadingHasFinished(); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('lnsDataTable'); + + expect(await lens.getLayerCount()).to.be(1); + + const metricText = await lens.getDimensionTriggerText('lnsDatatable_metrics', 0); + const splitRowText1 = await lens.getDimensionTriggerText('lnsDatatable_rows', 0); + const splitRowText2 = await lens.getDimensionTriggerText('lnsDatatable_rows', 1); + + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(3); + expect(metricText).to.be('Count'); + expect(splitRowText1).to.be('@timestamp'); + expect(splitRowText2).to.be('bytes: Descending'); + }); + + it('should convert percentage column', async () => { + await visEditor.clickOptionsTab(); + await visEditor.setSelectByOptionText('datatableVisualizationPercentageCol', 'Count'); + await visEditor.clickGo(); + await header.waitUntilLoadingHasFinished(); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('lnsDataTable'); + + expect(await lens.getLayerCount()).to.be(1); + + const metricText = await lens.getDimensionTriggerText('lnsDatatable_metrics', 0); + const percentageColumnText = await lens.getDimensionTriggerText('lnsDatatable_metrics', 1); + + await lens.openDimensionEditor('lnsDatatable_metrics > lns-dimensionTrigger', 0, 1); + const format = await testSubjects.find('indexPattern-dimension-format'); + expect(await format.getVisibleText()).to.be('Percent'); + + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(2); + expect(metricText).to.be('Count'); + expect(percentageColumnText).to.be('Count percentages'); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/xy.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/xy.ts new file mode 100644 index 0000000000000..161a3549e856c --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/xy.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visEditor, lens, timePicker, header, visChart } = getPageObjects([ + 'visualize', + 'lens', + 'visEditor', + 'timePicker', + 'header', + 'visChart', + ]); + + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + describe('XY', function describeIndexTests() { + const isNewChartsLibraryEnabled = true; + + before(async () => { + await visualize.initTests(isNewChartsLibraryEnabled); + }); + + beforeEach(async () => { + await visualize.navigateToNewAggBasedVisualization(); + await visualize.clickLineChart(); + await visualize.clickNewSearch(); + await timePicker.setDefaultAbsoluteRange(); + }); + + it('should show the "Edit Visualization in Lens" menu item', async () => { + const button = await testSubjects.exists('visualizeEditInLensButton'); + expect(button).to.eql(true); + }); + + it('should hide the "Edit Visualization in Lens" menu item if dot size aggregation is defined', async () => { + await visEditor.clickBucket('Dot size', 'metrics'); + await visEditor.selectAggregation('Max', 'metrics'); + await visEditor.selectField('memory', 'metrics'); + await visEditor.clickGo(isNewChartsLibraryEnabled); + const button = await testSubjects.exists('visualizeEditInLensButton'); + expect(button).to.eql(false); + }); + + it('should convert to Lens', async () => { + await visEditor.clickBucket('Split series'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await header.waitUntilLoadingHasFinished(); + await visEditor.clickGo(isNewChartsLibraryEnabled); + const expectedData = await visChart.getLegendEntriesXYCharts('xyVisChart'); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('xyVisChart'); + const data = await lens.getCurrentChartDebugState('xyVisChart'); + await retry.try(async () => { + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(2); + expect(await dimensions[0].getVisibleText()).to.be('Count'); + expect(await dimensions[1].getVisibleText()).to.be('machine.os.raw: Descending'); + }); + expect(data?.legend?.items.map((item) => item.name)).to.eql(expectedData); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/dashboard.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/dashboard.ts index bd74e109326bf..292aaa3a36f05 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/dashboard.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/dashboard.ts @@ -45,8 +45,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await panelActions.openContextMenu(); await panelActions.clickEdit(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('xyVisChart'); await retry.try(async () => { const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); @@ -74,8 +73,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await panelActions.openContextMenu(); await panelActions.clickEdit(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('legacyMtrVis'); await retry.try(async () => { const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/gauge.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/gauge.ts index bb2bc9a15ce9b..872ce7a58a22c 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/gauge.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/gauge.ts @@ -9,9 +9,16 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const { visualize, visualBuilder, lens } = getPageObjects(['visualBuilder', 'visualize', 'lens']); + const { visualize, visualBuilder, lens, header } = getPageObjects([ + 'visualBuilder', + 'visualize', + 'lens', + 'header', + ]); const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const find = getService('find'); describe('Gauge', function describeIndexTests() { before(async () => { @@ -28,17 +35,94 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should show the "Edit Visualization in Lens" menu item', async () => { - const button = await testSubjects.exists('visualizeEditInLensButton'); - expect(button).to.eql(true); + expect(await visualize.hasNavigateToLensButton()).to.eql(true); }); it('should convert to Lens', async () => { - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('mtrVis'); const metricData = await lens.getMetricVisualizationData(); expect(metricData[0].title).to.eql('Count of records'); }); + + it('should convert metric with params', async () => { + await visualBuilder.selectAggType('Value Count'); + await visualBuilder.setFieldForAggregation('bytes'); + + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('mtrVis'); + await retry.try(async () => { + const layers = await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`); + expect(layers).to.have.length(1); + + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(2); + expect(await dimensions[0].getVisibleText()).to.be('Count of bytes'); + expect(await dimensions[1].getVisibleText()).to.be('overall_max(count(bytes))'); + }); + }); + + it('should not allow converting of unsupported metric', async () => { + await visualBuilder.selectAggType('Counter Rate'); + await visualBuilder.setFieldForAggregation('machine.ram'); + + await header.waitUntilLoadingHasFinished(); + + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting of not valid panel', async () => { + await visualBuilder.selectAggType('Value Count'); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should convert color ranges', async () => { + await visualBuilder.setMetricsGroupByTerms('extension.raw'); + + await visualBuilder.clickPanelOptions('gauge'); + + await visualBuilder.setColorRuleOperator('>= greater than or equal'); + await visualBuilder.setColorRuleValue(10); + await visualBuilder.setColorPickerValue('#54B399', 2); + + await visualBuilder.createColorRule(); + + await visualBuilder.setColorRuleOperator('>= greater than or equal'); + await visualBuilder.setColorRuleValue(100, 1); + await visualBuilder.setColorPickerValue('#54A000', 4); + + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + + await lens.waitForVisualization('mtrVis'); + await retry.try(async () => { + const closePalettePanels = await testSubjects.findAll( + 'lns-indexPattern-PalettePanelContainerBack' + ); + if (closePalettePanels.length) { + await lens.closePalettePanel(); + await lens.closeDimensionEditor(); + } + + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(3); + + dimensions[0].click(); + + await lens.openPalettePanel('lnsMetric'); + const colorStops = await lens.getPaletteColorStops(); + + expect(colorStops).to.eql([ + { stop: '', color: 'rgba(104, 188, 0, 1)' }, + { stop: '10', color: 'rgba(84, 179, 153, 1)' }, + { stop: '100', color: 'rgba(84, 160, 0, 1)' }, + { stop: '', color: undefined }, + ]); + }); + }); }); } diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/index.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/index.ts index 1093b0e154947..ea859195e6346 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/index.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/index.ts @@ -13,5 +13,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./gauge')); loadTestFile(require.resolve('./timeseries')); loadTestFile(require.resolve('./dashboard')); + loadTestFile(require.resolve('./top_n')); }); } diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts index 6ec2ef5cc984a..794a2be110a32 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts @@ -18,7 +18,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); - const find = getService('find'); describe('Metric', function describeIndexTests() { before(async () => { @@ -35,13 +34,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should show the "Edit Visualization in Lens" menu item', async () => { - const button = await testSubjects.exists('visualizeEditInLensButton'); - expect(button).to.eql(true); + expect(await visualize.hasNavigateToLensButton()).to.eql(true); }); it('should convert to Lens', async () => { - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('mtrVis'); const metricData = await lens.getMetricVisualizationData(); @@ -54,12 +51,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('mtrVis'); await retry.try(async () => { - const layers = await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`); - expect(layers).to.have.length(1); + expect(await lens.getLayerCount()).to.be(1); const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); expect(dimensions).to.have.length(1); @@ -73,12 +68,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('mtrVis'); await retry.try(async () => { - const layers = await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`); - expect(layers).to.have.length(1); + expect(await lens.getLayerCount()).to.be(1); const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); expect(dimensions).to.have.length(1); @@ -92,15 +85,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); - const canEdit = await testSubjects.exists('visualizeEditInLensButton'); - expect(canEdit).to.be(false); + expect(await visualize.hasNavigateToLensButton()).to.be(false); }); it('should not allow converting of not valid panel', async () => { await visualBuilder.selectAggType('Value Count'); await header.waitUntilLoadingHasFinished(); - const canEdit = await testSubjects.exists('visualizeEditInLensButton'); - expect(canEdit).to.be(false); + expect(await visualize.hasNavigateToLensButton()).to.be(false); }); it('should convert color ranges', async () => { @@ -110,8 +101,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await visualBuilder.setColorPickerValue('#54B399'); await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('mtrVis'); await retry.try(async () => { diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/timeseries.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/timeseries.ts index 87f9a196b5fba..4c0c7e66b1ba3 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/timeseries.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/timeseries.ts @@ -35,15 +35,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should show the "Edit Visualization in Lens" menu item for a count aggregation', async () => { - const isMenuItemVisible = await find.existsByCssSelector( - '[data-test-subj="visualizeEditInLensButton"]' - ); - expect(isMenuItemVisible).to.be(true); + expect(await visualize.hasNavigateToLensButton()).to.be(true); }); it('visualizes field to Lens and loads fields to the dimesion editor', async () => { - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('xyVisChart'); await retry.try(async () => { const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); @@ -54,8 +50,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('navigates back to TSVB when the Back button is clicked', async () => { - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('xyVisChart'); const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton'); @@ -70,8 +65,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should preserve app filters in lens', async () => { await filterBar.addFilter('extension', 'is', 'css'); await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('xyVisChart'); expect(await filterBar.hasFilter('extension', 'css')).to.be(true); @@ -81,8 +75,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await queryBar.setQuery('machine.os : ios'); await queryBar.submitQuery(); await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('xyVisChart'); expect(await queryBar.getQueryString()).to.equal('machine.os : ios'); @@ -95,8 +88,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('xyVisChart'); await retry.try(async () => { const layers = await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`); @@ -121,8 +113,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('xyVisChart'); await retry.try(async () => { expect(await lens.getLayerCount()).to.be(1); @@ -139,17 +130,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should not allow converting of not valid panel', async () => { await visualBuilder.selectAggType('Counter Rate'); await header.waitUntilLoadingHasFinished(); - const canEdit = await testSubjects.exists('visualizeEditInLensButton'); - expect(canEdit).to.be(false); + expect(await visualize.hasNavigateToLensButton()).to.be(false); }); it('should not allow converting of unsupported aggregations', async () => { - await visualBuilder.selectAggType('Variance'); + await visualBuilder.selectAggType('Sum of Squares'); await visualBuilder.setFieldForAggregation('machine.ram'); await header.waitUntilLoadingHasFinished(); - const canEdit = await testSubjects.exists('visualizeEditInLensButton'); - expect(canEdit).to.be(false); + expect(await visualize.hasNavigateToLensButton()).to.be(false); }); it('should convert parent pipeline aggregation with terms', async () => { @@ -161,8 +150,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await visualBuilder.setMetricsGroupByTerms('extension.raw'); await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('xyVisChart'); await retry.try(async () => { @@ -185,8 +173,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await visualBuilder.setMetricsGroupByTerms('extension.raw'); await header.waitUntilLoadingHasFinished(); - const button = await testSubjects.find('visualizeEditInLensButton'); - await button.click(); + await visualize.navigateToLensFromAnotherVisulization(); await lens.waitForVisualization('xyVisChart'); await retry.try(async () => { diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/top_n.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/top_n.ts new file mode 100644 index 0000000000000..0631872fc9bd4 --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/top_n.ts @@ -0,0 +1,186 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visualBuilder, lens, header } = getPageObjects([ + 'visualBuilder', + 'visualize', + 'header', + 'lens', + ]); + + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const filterBar = getService('filterBar'); + const queryBar = getService('queryBar'); + + describe('Top N', function describeIndexTests() { + before(async () => { + await visualize.initTests(); + }); + + beforeEach(async () => { + await visualize.navigateToNewVisualization(); + await visualize.clickVisualBuilder(); + await visualBuilder.checkVisualBuilderIsPresent(); + await visualBuilder.resetPage(); + await visualBuilder.clickTopN(); + await visualBuilder.checkTopNTabIsPresent(); + }); + + it('should not allow converting of not valid panel', async () => { + await visualBuilder.selectAggType('Max'); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting of unsupported aggregations', async () => { + await visualBuilder.selectAggType('Sum of Squares'); + await visualBuilder.setFieldForAggregation('machine.ram'); + + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should hide the "Edit Visualization in Lens" menu item for a sibling pipeline aggregations', async () => { + await visualBuilder.createNewAgg(); + + await visualBuilder.selectAggType('Overall Average', 1); + await visualBuilder.setFieldForAggregation('Count', 1); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should hide the "Edit Visualization in Lens" menu item for a parent pipeline aggregations', async () => { + await visualBuilder.clickPanelOptions('topN'); + await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.clickDataTab('topN'); + await visualBuilder.createNewAgg(); + + await visualBuilder.selectAggType('Cumulative Sum', 1); + await visualBuilder.setFieldForAggregation('Count', 1); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should show the "Edit Visualization in Lens" menu item for a count aggregation', async () => { + expect(await visualize.hasNavigateToLensButton()).to.be(true); + }); + + it('should convert to horizontal bar', async () => { + await visualBuilder.selectAggType('Max'); + await visualBuilder.setFieldForAggregation('memory', 0); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + const chartSwitcher = await testSubjects.find('lnsChartSwitchPopover'); + const type = await chartSwitcher.getVisibleText(); + expect(type).to.be('Bar horizontal'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + + const yDimensionText = await lens.getDimensionTriggerText('lnsXY_yDimensionPanel', 0); + expect(yDimensionText).to.be('Maximum of memory'); + }); + }); + + it('should convert group by to vertical axis', async () => { + await visualBuilder.setMetricsGroupByTerms('extension.raw'); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + + const xDimensionText = await lens.getDimensionTriggerText('lnsXY_xDimensionPanel', 0); + const yDimensionText = await lens.getDimensionTriggerText('lnsXY_yDimensionPanel', 0); + expect(xDimensionText).to.be('Top 10 values of extension.raw'); + expect(yDimensionText).to.be('Count of records'); + }); + }); + + it('should convert last value mode to reduced time range', async () => { + await visualBuilder.clickPanelOptions('topN'); + await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.setIntervalValue('1m'); + await visualBuilder.clickDataTab('topN'); + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + await lens.openDimensionEditor('lnsXY_yDimensionPanel > lns-dimensionTrigger'); + await testSubjects.click('indexPattern-advanced-accordion'); + const reducedTimeRange = await testSubjects.find('indexPattern-dimension-reducedTimeRange'); + expect(await reducedTimeRange.getVisibleText()).to.be('1 minute (1m)'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + const yDimensionText = await lens.getDimensionTriggerText('lnsXY_yDimensionPanel', 0); + expect(yDimensionText).to.be('Count of records last 1m'); + }); + }); + + it('should convert static value to the separate layer with y dimension', async () => { + await visualBuilder.createNewAggSeries(); + await visualBuilder.selectAggType('Static Value', 1); + await visualBuilder.setStaticValue(10); + + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(2); + const yDimensionText1 = await lens.getDimensionTriggerText('lnsXY_yDimensionPanel', 0); + const yDimensionText2 = await lens.getDimensionTriggerText('lnsXY_yDimensionPanel', 1); + expect(yDimensionText1).to.be('Count of records'); + expect(yDimensionText2).to.be('10'); + }); + }); + + it('visualizes field to Lens and loads fields to the dimesion editor', async () => { + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + await retry.try(async () => { + const yDimensionText = await lens.getDimensionTriggerText('lnsXY_yDimensionPanel', 0); + expect(yDimensionText).to.be('Count of records'); + }); + }); + + it('navigates back to TSVB when the Back button is clicked', async () => { + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton'); + goBackBtn.click(); + await visualBuilder.checkTopNTabIsPresent(); + }); + + it('should preserve app filters in lens', async () => { + await filterBar.addFilter('extension', 'is', 'css'); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + + expect(await filterBar.hasFilter('extension', 'css')).to.be(true); + }); + + it('should preserve query in lens', async () => { + await queryBar.setQuery('machine.os : ios'); + await queryBar.submitQuery(); + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('xyVisChart'); + + expect(await queryBar.getQueryString()).to.equal('machine.os : ios'); + }); + }); +} diff --git a/x-pack/test/functional/apps/management/feature_controls/management_security.ts b/x-pack/test/functional/apps/management/feature_controls/management_security.ts index c1e09d9f3eaab..87e6e4611a3fd 100644 --- a/x-pack/test/functional/apps/management/feature_controls/management_security.ts +++ b/x-pack/test/functional/apps/management/feature_controls/management_security.ts @@ -64,7 +64,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(sections).to.have.length(2); expect(sections[0]).to.eql({ sectionId: 'insightsAndAlerting', - sectionLinks: ['triggersActions', 'cases', 'jobsListLink'], + sectionLinks: ['triggersActions', 'cases', 'triggersActionsConnectors', 'jobsListLink'], }); expect(sections[1]).to.eql({ sectionId: 'kibana', diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index be0d8c9aaf07f..3ad5b00279926 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -556,6 +556,7 @@ export class GisPageObject extends FtrService { this.log.debug(`Remove layer ${layerName}`); await this.openLayerPanel(layerName); await this.testSubjects.click(`mapRemoveLayerButton`); + await this.common.clickConfirmOnModal(); await this.waitForLayerDeleted(layerName); } diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 793d6184c4532..c11a9a37de7cf 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -543,9 +543,12 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont * @param dimension - the selector of the dimension panel to open * @param layerIndex - the index of the layer */ - async openDimensionEditor(dimension: string, layerIndex = 0) { + async openDimensionEditor(dimension: string, layerIndex = 0, dimensionIndex = 0) { await retry.try(async () => { - await testSubjects.click(`lns-layerPanel-${layerIndex} > ${dimension}`); + const dimensionEditor = ( + await testSubjects.findAll(`lns-layerPanel-${layerIndex} > ${dimension}`) + )[dimensionIndex]; + await dimensionEditor.click(); }); }, diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index d2ad05486a18a..a83d8de0a2cb8 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -122,7 +122,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { afterEach(async () => { // Reset the Rules tab without reloading the entire page // This is safer than trying to close the alert flyout, which may or may not be open at the end of a test - await testSubjects.click('connectorsTab'); + await testSubjects.click('logsTab'); await testSubjects.click('rulesTab'); }); @@ -134,6 +134,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('onThrottleInterval'); await testSubjects.setValue('throttleInput', '10'); + // filterKuery validation + await testSubjects.setValue('filterKuery', 'group:'); + const filterKueryInput = await testSubjects.find('filterKuery'); + expect(await filterKueryInput.elementHasClass('euiFieldSearch-isInvalid')).to.eql(true); + await testSubjects.setValue('filterKuery', 'group: group-0'); + expect(await filterKueryInput.elementHasClass('euiFieldSearch-isInvalid')).to.eql(false); + await testSubjects.click('.slack-alerting-ActionTypeSelectOption'); await testSubjects.click('addNewActionConnectorButton-.slack'); const slackConnectorName = generateUniqueKey(); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index cabbadb43ac57..c0e9a145fe47e 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -18,6 +18,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); const supertest = getService('supertest'); const objectRemover = new ObjectRemover(supertest); + const browser = getService('browser'); describe('Connectors', function () { before(async () => { @@ -26,8 +27,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { .set('kbn-xsrf', 'foo') .send(getTestActionData()) .expect(200); - await pageObjects.common.navigateToApp('triggersActions'); - await testSubjects.click('connectorsTab'); + await pageObjects.common.navigateToApp('triggersActionsConnectors'); objectRemover.add(createdAction.id, 'action', 'actions'); }); @@ -75,6 +75,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const updatedConnectorName = `${connectorName}updated`; const createdAction = await createConnector(connectorName); objectRemover.add(createdAction.id, 'action', 'actions'); + await browser.refresh(); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -112,6 +113,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const indexName = generateUniqueKey(); const createdAction = await createIndexConnector(connectorName, indexName); objectRemover.add(createdAction.id, 'action', 'actions'); + await browser.refresh(); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -141,6 +143,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const indexName = generateUniqueKey(); const createdAction = await createIndexConnector(connectorName, indexName); objectRemover.add(createdAction.id, 'action', 'actions'); + await browser.refresh(); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -168,6 +171,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const connectorName = generateUniqueKey(); const createdAction = await createConnector(connectorName); objectRemover.add(createdAction.id, 'action', 'actions'); + await browser.refresh(); + await pageObjects.triggersActionsUI.searchConnectors(connectorName); const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList(); @@ -195,6 +200,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await createConnector(connectorName); const createdAction = await createConnector(generateUniqueKey()); objectRemover.add(createdAction.id, 'action', 'actions'); + await browser.refresh(); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -220,6 +226,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await createConnector(connectorName); const createdAction = await createConnector(generateUniqueKey()); objectRemover.add(createdAction.id, 'action', 'actions'); + await browser.refresh(); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -285,7 +292,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { connector_type_id: '.slack', }) .expect(200); - await testSubjects.click('connectorsTab'); return createdAction; } @@ -303,7 +309,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { secrets: {}, }) .expect(200); - await testSubjects.click('connectorsTab'); return createdAction; } diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 7af323fdee7c1..6c5de895ed1fd 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -437,7 +437,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.existOrFail('rulesList'); // delete connector - await pageObjects.triggersActionsUI.changeTabs('connectorsTab'); + await pageObjects.common.navigateToApp('triggersActionsConnectors'); await pageObjects.triggersActionsUI.searchConnectors(connector.name); await testSubjects.click('deleteConnector'); await testSubjects.existOrFail('deleteIdsConfirmation'); @@ -447,8 +447,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql('Deleted 1 connector'); + // Wait to ensure the table is finished loading + await pageObjects.triggersActionsUI.tableFinishedLoading(); + // click on first alert - await pageObjects.triggersActionsUI.changeTabs('rulesTab'); + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(rule.name); const editButton = await testSubjects.find('openEditRuleFlyoutButton'); @@ -501,7 +504,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.existOrFail('rulesList'); // delete connector - await pageObjects.triggersActionsUI.changeTabs('connectorsTab'); + await pageObjects.common.navigateToApp('triggersActionsConnectors'); await pageObjects.triggersActionsUI.searchConnectors(connector.name); await testSubjects.click('deleteConnector'); await testSubjects.existOrFail('deleteIdsConfirmation'); @@ -511,8 +514,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql('Deleted 1 connector'); + // Wait to ensure the table is finished loading + await pageObjects.triggersActionsUI.tableFinishedLoading(); + // click on first rule - await pageObjects.triggersActionsUI.changeTabs('rulesTab'); + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name); const editButton = await testSubjects.find('openEditRuleFlyoutButton'); @@ -553,7 +559,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // verify content await testSubjects.existOrFail('rulesList'); - await pageObjects.triggersActionsUI.changeTabs('connectorsTab'); + await pageObjects.common.navigateToApp('triggersActionsConnectors'); await pageObjects.triggersActionsUI.searchConnectors('new connector'); await testSubjects.click('deleteConnector'); await testSubjects.existOrFail('deleteIdsConfirmation'); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts index 4769f01e8d4f0..77ece6d043a59 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts @@ -31,7 +31,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('Loads the Alerts page', async () => { await pageObjects.common.navigateToApp('triggersActions'); const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText(); - expect(headingText).to.be('Rules and Connectors'); + expect(headingText).to.be('Rules'); }); }); @@ -45,8 +45,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('Loads the Alerts page but with error', async () => { await pageObjects.common.navigateToApp('triggersActions'); - const headingText = await pageObjects.triggersActionsUI.getRulesListTitle(); - expect(headingText).to.be('No permissions to create rules'); + const exists = await testSubjects.exists('noPermissionPrompt'); + expect(exists).to.be(true); }); }); @@ -60,26 +60,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('Loads the Alerts page', async () => { - await log.debug('Checking for section heading to say Rules and Connectors.'); + await log.debug('Checking for section heading to say Rules.'); const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText(); - expect(headingText).to.be('Rules and Connectors'); - }); - - describe('Connectors tab', () => { - it('renders the connectors tab', async () => { - // Navigate to the connectors tab - await pageObjects.triggersActionsUI.changeTabs('connectorsTab'); - - await pageObjects.header.waitUntilLoadingHasFinished(); - - // Verify url - const url = await browser.getCurrentUrl(); - expect(url).to.contain(`/connectors`); - - // Verify content - await testSubjects.existOrFail('actionsList'); - }); + expect(headingText).to.be('Rules'); }); describe('Alerts tab', () => { diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts index fea3ffcf9233d..36b2b0ae44aee 100644 --- a/x-pack/test/functional_with_es_ssl/config.ts +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -61,6 +61,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { triggersActions: { pathname: '/app/management/insightsAndAlerting/triggersActions', }, + triggersActionsConnectors: { + pathname: '/app/management/insightsAndAlerting/triggersActionsConnectors', + }, }, esTestCluster: { ...xpackFunctionalConfig.get('esTestCluster'), diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts index 2b17eb48a2afc..d26b1124f8271 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts @@ -61,6 +61,11 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) await this.clickCreateFirstConnectorButton(); } }, + async tableFinishedLoading() { + await find.byCssSelector( + '.euiBasicTable[data-test-subj="actionsTable"]:not(.euiBasicTable-loading)' + ); + }, async searchConnectors(searchText: string) { const searchBox = await find.byCssSelector('[data-test-subj="actionsList"] .euiFieldSearch'); await searchBox.click(); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts index ca6a72f50d00e..9b7212b20b833 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts @@ -8,7 +8,6 @@ import { wrapErrorAndRejectPromise } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/utils'; import { AGENT_POLICY_SUMMARY_ROUTE, - BASE_POLICY_RESPONSE_ROUTE, GET_PROCESSES_ROUTE, ISOLATE_HOST_ROUTE, ISOLATE_HOST_ROUTE_V2, @@ -49,11 +48,6 @@ export default function ({ getService }: FtrProviderContext) { path: '/api/endpoint/action_log/one?start_date=2021-12-01&end_date=2021-12-04', body: undefined, }, - { - method: 'get', - path: `${BASE_POLICY_RESPONSE_ROUTE}?agentId=1`, - body: undefined, - }, { method: 'post', path: ISOLATE_HOST_ROUTE, diff --git a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts index b156f909d7a5d..341d4af0e4c64 100644 --- a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import moment from 'moment'; +import semver from 'semver'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { @@ -24,7 +24,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]; const dashboardTests = [ - { name: 'flights', numPanels: 16 }, + { name: 'flights', numPanels: 15 }, { name: 'logs', numPanels: 10 }, { name: 'ecommerce', numPanels: 11 }, ]; @@ -41,12 +41,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { dashboardTests.forEach(({ name, numPanels }) => { it('should launch sample ' + name + ' data set dashboard', async () => { await PageObjects.home.launchSampleDashboard(name); + await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); - const todayYearMonthDay = moment().format('MMM D, YYYY'); - const fromTime = `${todayYearMonthDay} @ 00:00:00.000`; - const toTime = `${todayYearMonthDay} @ 23:59:59.999`; - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.be.above(numPanels); }); @@ -58,7 +55,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { log.debug('Checking saved searches rendered'); await dashboardExpect.savedSearchRowCount(49); log.debug('Checking input controls rendered'); - await dashboardExpect.controlCount(3); + if (semver.lt(process.env.ORIGINAL_VERSION!, '8.6.0-SNAPSHOT')) { + await dashboardExpect.inputControlItemCount(3); + } else { + await dashboardExpect.controlCount(3); + } log.debug('Checking tag cloud rendered'); await dashboardExpect.tagCloudWithValuesFound([ 'Sunny', diff --git a/yarn.lock b/yarn.lock index bfdb7a7a171cb..37d42451920af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9475,10 +9475,10 @@ object.fromentries "^2.0.0" prop-types "^15.7.0" -"@xmldom/xmldom@^0.7.0": - version "0.7.4" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.4.tgz#93b2f9486c88b6464e97f76c9ab49b0a548fbe57" - integrity sha512-wdxC79cvO7PjSM34jATd/RYZuYWQ8y/R7MidZl1NYYlbpFn1+spfjkiR3ZsJfcaTs2IyslBN7VwBBJwrYKM+zw== +"@xmldom/xmldom@^0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.3.tgz#beaf980612532aa9a3004aff7e428943aeaa0711" + integrity sha512-Lv2vySXypg4nfa51LY1nU8yDAGo/5YwF+EY/rUZgIbfvwVARcd67ttCM8SMsTeJy51YhHYavEq+FS6R0hW9PFQ== "@xobotyi/scrollbar-width@1.9.5": version "1.9.5" @@ -23500,10 +23500,10 @@ react-grid-layout@^1.3.4: react-draggable "^4.0.0" react-resizable "^3.0.4" -react-hook-form@^7.36.1: - version "7.36.1" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.36.1.tgz#82a311fe8cbe75e689fd4529f083b7c983da6520" - integrity sha512-EbYYkCG2p8ywe7ikOH2l02lAFMrrrslZi1I8fqd8ifDGNAkhomHZQzQsP6ksvzrWBKntRe8b5L5L7Zsd+Gm02Q== +react-hook-form@^7.37.0: + version "7.37.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.37.0.tgz#4d1738f092d3d8a3ade34ee892d97350b1032b19" + integrity sha512-6NFTxsnw+EXSpNNvLr5nFMjPdYKRryQcelTHg7zwBB6vAzfPIcZq4AExP4heVlwdzntepQgwiOQW4z7Mr99Lsg== react-input-autosize@^3.0.0: version "3.0.0" @@ -29066,12 +29066,12 @@ xdg-basedir@^4.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== -xml-crypto@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-2.1.4.tgz#85b3c62fa0debc4956ee72cb2dfee65651e865b5" - integrity sha512-ModFeGOy67L/XXHcuepnYGF7DASEDw7fhvy+qIs1ORoH55G1IIr+fN0kaMtttwvmNFFMskD9AHro8wx352/mUg== +xml-crypto@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-3.0.0.tgz#e3342f9c9a94455d4700431ac9803493bf51cf81" + integrity sha512-vdmZOsWgjnFxYGY7OwCgxs+HLWzwvLgX2n0NSYWh3gudckQyNOmtJTT6ooOWEvDZSpC9qRjRs2bEXqKFi1oCHw== dependencies: - "@xmldom/xmldom" "^0.7.0" + "@xmldom/xmldom" "^0.8.3" xpath "0.0.32" xml-name-validator@^3.0.0: