diff --git a/.buildkite/pipelines/flaky_tests/pipeline.js b/.buildkite/pipelines/flaky_tests/pipeline.js index d62156a08c55a..56a6f66806838 100644 --- a/.buildkite/pipelines/flaky_tests/pipeline.js +++ b/.buildkite/pipelines/flaky_tests/pipeline.js @@ -174,6 +174,12 @@ for (const testSuite of testSuites) { concurrency: concurrency, concurrency_group: process.env.UUID, concurrency_method: 'eager', + env: { + // disable split of test cases between parallel jobs when running them in flaky test runner + // by setting chunks vars to value 1, which means all test will run in one job + CLI_NUMBER: 1, + CLI_COUNT: 1, + }, }); break; default: diff --git a/.buildkite/pull_requests.json b/.buildkite/pull_requests.json index 09aa3b3427e98..a27cbcff8b7e4 100644 --- a/.buildkite/pull_requests.json +++ b/.buildkite/pull_requests.json @@ -35,8 +35,7 @@ "^\\.buildkite/pull_requests\\.json$" ], "always_require_ci_on_changed": [ - "^docs/developer/plugin-list.asciidoc$", - "/plugins/[^/]+/readme\\.(md|asciidoc)$" + "^docs/developer/plugin-list.asciidoc$" ], "kibana_versions_check": true } diff --git a/.buildkite/scripts/steps/es_snapshots/create_manifest.js b/.buildkite/scripts/steps/es_snapshots/create_manifest.js index e20a3262e66e0..f71daed5c7f92 100644 --- a/.buildkite/scripts/steps/es_snapshots/create_manifest.js +++ b/.buildkite/scripts/steps/es_snapshots/create_manifest.js @@ -96,7 +96,7 @@ const { BASE_BUCKET_DAILY } = require('./bucket_config'); cd "${destination}" gsutil -m cp -r *.* gs://${BASE_BUCKET_DAILY}/${DESTINATION} cp manifest.json manifest-latest.json - gsutil cp manifest-latest.json gs://${BASE_BUCKET_DAILY}/${VERSION} + gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest-latest.json gs://${BASE_BUCKET_DAILY}/${VERSION} buildkite-agent meta-data set ES_SNAPSHOT_MANIFEST 'https://storage.googleapis.com/${BASE_BUCKET_DAILY}/${DESTINATION}/manifest.json' buildkite-agent meta-data set ES_SNAPSHOT_VERSION '${VERSION}' diff --git a/.buildkite/scripts/steps/es_snapshots/promote_manifest.js b/.buildkite/scripts/steps/es_snapshots/promote_manifest.js index 1ba60ae6cd25e..5c352710c724a 100644 --- a/.buildkite/scripts/steps/es_snapshots/promote_manifest.js +++ b/.buildkite/scripts/steps/es_snapshots/promote_manifest.js @@ -39,11 +39,11 @@ const { BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT } = require('./bucket_config'); ` set -euo pipefail cp manifest.json manifest-latest-verified.json - gsutil cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ + gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ rm manifest.json cp manifest-permanent.json manifest.json gsutil -m cp -r gs://${bucket}/* gs://${BASE_BUCKET_PERMANENT}/${version}/ - gsutil cp manifest.json gs://${BASE_BUCKET_PERMANENT}/${version}/ + gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest.json gs://${BASE_BUCKET_PERMANENT}/${version}/ `, { shell: '/bin/bash' } ); diff --git a/.buildkite/scripts/steps/functional/security_solution.sh b/.buildkite/scripts/steps/functional/security_solution.sh index 5e3b1513826f9..fa1908683d157 100755 --- a/.buildkite/scripts/steps/functional/security_solution.sh +++ b/.buildkite/scripts/steps/functional/security_solution.sh @@ -5,8 +5,8 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh export JOB=kibana-security-solution-chrome -export CLI_NUMBER=$((BUILDKITE_PARALLEL_JOB+1)) -export CLI_COUNT=$BUILDKITE_PARALLEL_JOB_COUNT +export CLI_NUMBER=${CLI_NUMBER:-$((BUILDKITE_PARALLEL_JOB+1))} +export CLI_COUNT=${CLI_COUNT:-$BUILDKITE_PARALLEL_JOB_COUNT} echo "--- Security Solution tests (Chrome)" diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index d86602e8acd94..4d28ab700d769 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github summary: API docs for the actions plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index bb09e549eb6f1..96832d772c1ac 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github summary: API docs for the advancedSettings plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 977d546d52871..88bfb18a342f7 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github summary: API docs for the aiops plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 510ff71b918b1..3afa8876a6202 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github summary: API docs for the alerting plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index a106b91e5fbf7..fd5a7531ff270 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github summary: API docs for the apm plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 31491656f4e55..deeb88b763c5d 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github summary: API docs for the banners plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index f426d88135e54..3ff01f989cc54 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github summary: API docs for the bfetch plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 2418c0413d20c..930c69ee86ffe 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github summary: API docs for the canvas plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 50388b6b902dc..ee67d893f5d4d 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github summary: API docs for the cases plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 6446baff5bfd0..208b573144445 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github summary: API docs for the charts plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 78139cb8d2dfe..d3972a83409d5 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github summary: API docs for the cloud plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 9d0fea5c7afa1..6e039b8066b35 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github summary: API docs for the cloudSecurityPosture plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/console.mdx b/api_docs/console.mdx index c0aefea5a9648..1fe970f82ca0c 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github summary: API docs for the console plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 327d486e99c8b..3f0209131ac98 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github summary: API docs for the controls plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/core.mdx b/api_docs/core.mdx index faee1a0c5580c..76c0c9eed54b1 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github summary: API docs for the core plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/core_application.mdx b/api_docs/core_application.mdx index 61cdad115cff3..d0ef203875d56 100644 --- a/api_docs/core_application.mdx +++ b/api_docs/core_application.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core-application title: "core.application" image: https://source.unsplash.com/400x175/?github summary: API docs for the core.application plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core.application'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/core_chrome.mdx b/api_docs/core_chrome.mdx index 9c52551f7065c..9237f3bf4d578 100644 --- a/api_docs/core_chrome.mdx +++ b/api_docs/core_chrome.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core-chrome title: "core.chrome" image: https://source.unsplash.com/400x175/?github summary: API docs for the core.chrome plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core.chrome'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/core_http.mdx b/api_docs/core_http.mdx index 2da75bff2081f..9c68cd0c4afe4 100644 --- a/api_docs/core_http.mdx +++ b/api_docs/core_http.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core-http title: "core.http" image: https://source.unsplash.com/400x175/?github summary: API docs for the core.http plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core.http'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/core_saved_objects.mdx b/api_docs/core_saved_objects.mdx index 4c89db52bf6e1..146d5a9ee9add 100644 --- a/api_docs/core_saved_objects.mdx +++ b/api_docs/core_saved_objects.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core-savedObjects title: "core.savedObjects" image: https://source.unsplash.com/400x175/?github summary: API docs for the core.savedObjects plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core.savedObjects'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 14f218c3c60d3..d087595293399 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github summary: API docs for the customIntegrations plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 9e39eb7fadae7..eed342c9cd546 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github summary: API docs for the dashboard plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 74971c18a575b..d14c8bdd43f85 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github summary: API docs for the dashboardEnhanced plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 929bf98f0d119..bdb2a45c2fa52 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github summary: API docs for the data plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 9c896e53970a2..d7765e9834e9f 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github summary: API docs for the data.query plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 20a8da01c1bc8..4841f85cc8320 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github summary: API docs for the data.search plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 999c8df43bdaa..573a6c5cc2414 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataViewEditor plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index acf95f0dd489c..383c1b67c843c 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataViewFieldEditor plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 0f1f00687c543..de0e359fd5f0a 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataViewManagement plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index cacc9051e3e97..f47371d893291 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataViews plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 75b1e71e24210..6358b2fb6dad8 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataVisualizer plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 9abb633b1fb28..3586f39e6001a 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -3,7 +3,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API summary: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index f39dc52c2b7e4..4f7b414967b0f 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -3,7 +3,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin summary: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 1c2a8adbfafba..f27558d24406e 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -3,7 +3,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team summary: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 8327185f46ccc..aa196cbfe5c3d 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github summary: API docs for the devTools plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 56eb0abfdcbc5..cf505d782d95e 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github summary: API docs for the discover plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 9ec2858ccdfd9..ef0fa4b796a60 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github summary: API docs for the discoverEnhanced plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/elastic_apm_synthtrace.mdx b/api_docs/elastic_apm_synthtrace.mdx index cc4aaf7ec9e03..34e430a447bb3 100644 --- a/api_docs/elastic_apm_synthtrace.mdx +++ b/api_docs/elastic_apm_synthtrace.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/elastic-apm-synthtrace title: "@elastic/apm-synthtrace" image: https://source.unsplash.com/400x175/?github summary: API docs for the @elastic/apm-synthtrace plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@elastic/apm-synthtrace'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index 6e404f6c0d58a..e258ad0fe7031 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -4221,6 +4221,20 @@ "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", "deprecated": false, "isRequired": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.ErrorEmbeddable.Unnamed.$4", + "type": "boolean", + "tags": [], + "label": "compact", + "description": [], + "signature": [ + "boolean" + ], + "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", + "deprecated": false, + "isRequired": true } ], "returnComment": [] diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 3caeed73786d6..72fe88b5faafd 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github summary: API docs for the embeddable plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 487 | 0 | 397 | 3 | +| 488 | 0 | 398 | 3 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 2b81f83999c2a..9e9b3c9a07111 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github summary: API docs for the embeddableEnhanced plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 25e6ad780ad7e..effd93686b7ce 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github summary: API docs for the encryptedSavedObjects plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index ebe1bb7e12ab9..9d6941e0d31e2 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github summary: API docs for the enterpriseSearch plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index b3b0176f8b989..52983caf8e0f8 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github summary: API docs for the esUiShared plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 6bc5816aa2f34..b65e56704097f 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github summary: API docs for the eventAnnotation plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 2e57ecedce5c3..9796ae9597399 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github summary: API docs for the eventLog plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 782ee551e8274..a0c223ecfafc8 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionError plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index a5faffdb51aad..5afad07465b02 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionGauge plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 6e9d26c8a680a..10bbe685b8a85 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionHeatmap plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index fa31939ab7353..659458a80f989 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionImage plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 2a8a502bb1beb..5523e9a5d9b22 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionMetric plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index f26c086629cec..81ad91e425bd4 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionMetricVis plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index ffce6549928e6..53db2d5afb59a 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionPartitionVis plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 336b36375034e..d9c0f593df339 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionRepeatImage plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index a6a6608322adf..febd4e7102bc5 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionRevealImage plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 48f8d1df0cd29..63698129558ea 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionShape plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index faffa9dc49964..fae0b0703fcf5 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionTagcloud plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 64e726f04aa8e..ec4c89d362b8e 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionXY plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 10c81dd361830..d09308f991b92 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressions plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 834d3f2a8bfdb..08abd876b378b 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github summary: API docs for the features plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index ac1756a4316a0..2aafc2dae0353 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github summary: API docs for the fieldFormats plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 95c5ccc921617..2c9d6aca1295a 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github summary: API docs for the fileUpload plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index c8144953d235b..e33d0ea894a81 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github summary: API docs for the fleet plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 31245c28ce744..2fae29cc07c47 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github summary: API docs for the globalSearch plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 94b91f3b312e3..1ca07ffb82c6c 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github summary: API docs for the home plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 7bc17aeef228d..3c10da180b897 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the indexLifecycleManagement plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index cc3ce3826de3f..322663333105e 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the indexManagement plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 2795a5752377a..64b862b209f95 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github summary: API docs for the infra plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index a88198d2d95ea..2c3ac7eb456c8 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github summary: API docs for the inspector plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 202744164a72a..cf1596c2f6455 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github summary: API docs for the interactiveSetup plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index d39c9d0173fb3..4c52cf51431e4 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ace plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 03ed48b2153d2..ea97f81828020 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/aiops-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 375c1453f1a39..1cbb2574f3890 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/alerts plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index ff03cf2949858..ee0b434e4b97f 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/analytics plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 1db4313f9ae56..2c43946be4cfc 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/analytics-client plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 3eedf85a7fd0b..c5197df8e8c5c 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 6855d992d66c2..b50ed3226aee9 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index c558efc1d3c65..a3c49c7b2eee7 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 481b3cc4c68c3..818eb28b38f72 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 20aac394e0104..294463866bb3c 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/apm-config-loader plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index ae8f214858f95..065993bafaf25 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/apm-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 5f959a3ad1b08..d234f4cdae936 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/axe-config plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_bazel_packages.mdx b/api_docs/kbn_bazel_packages.mdx index 1efcf6265528b..450809b74c16d 100644 --- a/api_docs/kbn_bazel_packages.mdx +++ b/api_docs/kbn_bazel_packages.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-bazel-packages title: "@kbn/bazel-packages" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/bazel-packages plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bazel-packages'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_bazel_runner.mdx b/api_docs/kbn_bazel_runner.mdx index 006d6801c1857..93a83ad186c78 100644 --- a/api_docs/kbn_bazel_runner.mdx +++ b/api_docs/kbn_bazel_runner.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-bazel-runner title: "@kbn/bazel-runner" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/bazel-runner plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bazel-runner'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 4d05c8bd5ccce..6cd91b7bf01d4 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ci-stats-core plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index be4521e075e6c..0534bda96362a 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index 461e952c8f6e9..0061a09a4d4bd 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/cli-dev-mode plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index c3fc107f5338a..c4820898d29b3 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/coloring plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 4ca6c101a3f02..4800f5377e8f8 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/config plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 7414f0c744c47..e8de463192c51 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/config-mocks plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index fb59873f2d567..529fd3574cc57 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/config-schema plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 0d917f3d93125..72c2c0980276c 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-base-common plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index fdb1aa628db3a..787d170d2999f 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 84ad4059cbf5e..1e330436a0ade 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 2f1c08705c017..ed8e1d1140e2f 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index cae4734c9b729..36ebea2c7f0f3 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-doc-links-server plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index b321b85c7cb71..281328a03d6e7 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 52e243c7d176f..06277e6747b8b 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 0cfcd8540c282..4d3a2fd970b4e 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 071a02e97a43e..ccedd8bbb046a 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-theme-browser plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 52a6404df0c56..c5876c0a67dc5 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index cb9b00421142e..6834c06269c11 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/crypto plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 80a8cc0f69e99..6df1aa7371d1d 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/datemath plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index b9b7a7c04bdc7..d4d1d0fc73720 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/dev-cli-errors plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 1962aa7bb456a..a75484107ace4 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/dev-cli-runner plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 0b12e633b1d54..4251ec667fab0 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/dev-proc-runner plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 6ca284c679d2f..0bbf9439d5873 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/dev-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 2f56e8866af11..3de32f29e3948 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/doc-links plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index da30747e4cc39..99b674a89b489 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/docs-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 9b6191c4100b4..7dbb52b283616 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/es-archiver plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index c9f8a09b2c438..bc84717443339 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/es-query plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index acacee8b9d16c..60a0f79abb762 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 5dae14d774fd7..1005c2bea691f 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/field-types plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 1e8fca8a31f9b..59d18084baeee 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/find-used-node-modules plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 20cb679ba93b8..d5bf4dd29bbfc 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/generate plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 19614ac85372e..80e605d285752 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/handlebars plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 2156fdfb41750..5e386b9a573a8 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/i18n plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 2ee657516631f..0dbdac2fb6e5f 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/import-resolver plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index f6ce6ccb3e272..6d9bda35f0ce8 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/interpreter plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 9b7fd9802092e..3408b86d289ed 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/io-ts-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 25adac94de89f..4eadab6a8a434 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/jest-serializers plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_kibana_json_schema.mdx b/api_docs/kbn_kibana_json_schema.mdx index 18c49003a73e2..a028ea2818a63 100644 --- a/api_docs/kbn_kibana_json_schema.mdx +++ b/api_docs/kbn_kibana_json_schema.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-json-schema title: "@kbn/kibana-json-schema" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/kibana-json-schema plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-json-schema'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 725617c5a24ac..4ab1195c94323 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/logging plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 2eb8055f697da..f0a5d54da56e8 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/logging-mocks plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index e048d9d079ede..1e2273b6fba5d 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/mapbox-gl plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 1284a24038800..48cc0d76f4079 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/monaco plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index c2dac42c4bd9b..a6f3fcd10d02f 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/optimizer plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 5b18fcf556c91..e3be04849895b 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index b47168dddf7af..32e132773b18d 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_plugin_discovery.mdx b/api_docs/kbn_plugin_discovery.mdx index 18d5e814b8061..ba61f1d92322e 100644 --- a/api_docs/kbn_plugin_discovery.mdx +++ b/api_docs/kbn_plugin_discovery.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-discovery title: "@kbn/plugin-discovery" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/plugin-discovery plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-discovery'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 568af5150b308..830e24508a95e 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/plugin-generator plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 8a9423e7b2ec7..221dcc20a5baf 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/plugin-helpers plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_pm.mdx b/api_docs/kbn_pm.mdx index 7c5c88396993f..68ecdaa011c7f 100644 --- a/api_docs/kbn_pm.mdx +++ b/api_docs/kbn_pm.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-pm title: "@kbn/pm" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/pm plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/pm'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index ba9b7a3a5311b..66a1c9888ea1c 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/react-field plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 0d6d4f02101a6..9459554d95b74 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/rule-data-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_scalability_simulation_generator.mdx b/api_docs/kbn_scalability_simulation_generator.mdx index 9d7da9596ac81..73b7c83f52ae1 100644 --- a/api_docs/kbn_scalability_simulation_generator.mdx +++ b/api_docs/kbn_scalability_simulation_generator.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-scalability-simulation-generator title: "@kbn/scalability-simulation-generator" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/scalability-simulation-generator plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/scalability-simulation-generator'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 9ffdb57a8dfd1..cf71b5468640a 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 54765bfbc3190..191c8c4b74769 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index b7445763113bc..416c5bb57d30c 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 578a5b6c0adb9..1635127fd5d2d 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index af30a3160c910..3ef212040e182 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 9fabc07a7eb86..623afbeed9b83 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index ae80df725f543..06fd7e9469a3d 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index d2f2aa90253ea..48dac19441912 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 9f8c6344f0115..df75588d4d575 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 7c4c427c87ca0..6d655aac0aef3 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 6ddc33ca8a82e..67e0ec1be06cb 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 5f77df5714b34..f422a119097a6 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-rules plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 9196face9bd29..7058b40f4c5ac 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 0f4eedf1a8a75..9fecb35c397d4 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 0b5e888db052e..7695de91fa574 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/server-http-tools plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index f3886cc95f59a..17374e7faccaf 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/server-route-repository plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_components.mdx b/api_docs/kbn_shared_ux_components.mdx index 9eba4c3c0fd03..7557aa98eb1a1 100644 --- a/api_docs/kbn_shared_ux_components.mdx +++ b/api_docs/kbn_shared_ux_components.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-components title: "@kbn/shared-ux-components" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-components plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-components'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- 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 a134a72af29c6..9e858743aca43 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- 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 fa05d1843b7ef..2c89e6a111f62 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- 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 f2f98e566ca92..24797800e3ad6 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -4,7 +4,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 summary: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_services.mdx b/api_docs/kbn_shared_ux_services.mdx index 4748b05d1d909..639f9ede5025d 100644 --- a/api_docs/kbn_shared_ux_services.mdx +++ b/api_docs/kbn_shared_ux_services.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-services title: "@kbn/shared-ux-services" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-services plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-services'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_storybook.mdx b/api_docs/kbn_shared_ux_storybook.mdx index 05f42d53c2289..c8cab050fb5f0 100644 --- a/api_docs/kbn_shared_ux_storybook.mdx +++ b/api_docs/kbn_shared_ux_storybook.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook title: "@kbn/shared-ux-storybook" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-storybook plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 7dbd8865a53f0..bb54681d731e4 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-utility plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index cf7e941e45b85..9f8a72cb41b84 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/sort-package-json plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index e8ffea10be9d9..c37401c0aa6fb 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/std plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 47767b8ab24f0..0e72be0e49c15 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 97d277c501b58..b72301ee0e856 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/storybook plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 9e52af56bd122..100149cff3ef2 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/telemetry-tools plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 6738bd1c324f0..ab97811f0ce3a 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/test plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 73022299ce794..3d9854af58aaa 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/test-jest-helpers plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index a69a01f4b0053..9f4c5e6ccccba 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/tooling-log plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index ecc4144f77ac4..a49f048a50d29 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/type-summarizer plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 4d1bfcc6f707f..98b3f6deb66f6 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/typed-react-router-config plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index d70c06c48275b..87738602829e4 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ui-theme plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 32cecbd98c98d..df4a858e5df42 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/utility-types plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index b7861c6387183..cbd1b729a195d 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/utility-types-jest plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index c748a92b9263e..d726ead3e62eb 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/utils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index ee0598b4defcd..ea49fd4401444 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github summary: API docs for the kibanaOverview plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 6ef0262e49c0d..b4d3fcaea8d48 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github summary: API docs for the kibanaReact plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 76f471f60d8ed..81408535d4cd1 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github summary: API docs for the kibanaUtils plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 78582df542b22..058c1cbe5700b 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github summary: API docs for the kubernetesSecurity plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 18f77782e6591..fdbee38c92fc4 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github summary: API docs for the lens plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index fcfde4058e469..dd8bf621ec14c 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github summary: API docs for the licenseApiGuard plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index d1d32135b1c18..c25f78dce1a58 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the licenseManagement plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 42b0233d82e2d..aa648f86afce0 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github summary: API docs for the licensing plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 201d231016e45..4c63d3e08a297 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github summary: API docs for the lists plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 3ba791db3590d..4c673466335ba 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github summary: API docs for the management plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index fdd773a14d4b9..75449a04fff5b 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github summary: API docs for the maps plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 46963c3857ef0..7ef0840638c70 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github summary: API docs for the mapsEms plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 272757777b631..bb0d485e98714 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github summary: API docs for the ml plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 1d8e5a27511bf..42d653f944912 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github summary: API docs for the monitoring plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 55209fdda65d5..b752aff4d64a5 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github summary: API docs for the monitoringCollection plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index ad6532f26507b..d714e58941a58 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github summary: API docs for the navigation plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index c7a53163e694c..9d5bc311a2517 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github summary: API docs for the newsfeed plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index ba4e034463c1e..c05e264dfe97d 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github summary: API docs for the observability plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 27a1a9bffabaf..aeb1d77a08452 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github summary: API docs for the osquery plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 07bf043a33734..bbf0d8f40b09b 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -3,7 +3,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory summary: Directory of public APIs available through plugins or packages. -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 26520 | 172 | 19083 | 1238 | +| 26521 | 172 | 19084 | 1238 | ## Plugin Directory @@ -52,7 +52,7 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 10 | 0 | 8 | 2 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 78 | 0 | 62 | 7 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 487 | 0 | 397 | 3 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 488 | 0 | 398 | 3 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 44 | 0 | | | [Enterprise Search](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 2 | 0 | 2 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 01c126cd1655a..20930abddf141 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github summary: API docs for the presentationUtil plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index fdaa3039e53fb..7478e66b31c92 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github summary: API docs for the remoteClusters plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 113681c60be8b..eff6da111ac5b 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github summary: API docs for the reporting plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index a702b610f133a..b0f56a234ac5b 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github summary: API docs for the rollup plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 848b0b2002e83..a0a0a693a26b9 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github summary: API docs for the ruleRegistry plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 2fa0b96d26d7e..2087f6f1ec55f 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github summary: API docs for the runtimeFields plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 86dfc1360ad91..b673115fe860b 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github summary: API docs for the savedObjects plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 8cc69994b52d4..bbd52111d216c 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the savedObjectsManagement plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index b4ef521603556..548f76a8b6fe2 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github summary: API docs for the savedObjectsTagging plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 977585766ba38..0c40d26d95d7f 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github summary: API docs for the savedObjectsTaggingOss plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index e5e072ec0385e..8b7bc2df3a995 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github summary: API docs for the screenshotMode plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 9b7b22523d824..f9390f7570258 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github summary: API docs for the screenshotting plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 235ffb3879f12..16d9f6a9ff667 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github summary: API docs for the security plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 8e893cbe70ad1..930ff2ae95240 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github summary: API docs for the securitySolution plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 71551eab239bd..3ced7ddd2ac58 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github summary: API docs for the sessionView plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 9b9be1d4e29a4..0a71613e949f2 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github summary: API docs for the share plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/shared_u_x.mdx b/api_docs/shared_u_x.mdx index 2484ec7c34bc0..b72b705e16565 100644 --- a/api_docs/shared_u_x.mdx +++ b/api_docs/shared_u_x.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/sharedUX title: "sharedUX" image: https://source.unsplash.com/400x175/?github summary: API docs for the sharedUX plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sharedUX'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 4369ddc41147b..c536b0d7c1519 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github summary: API docs for the snapshotRestore plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index a900f193a9d0f..896ff51177780 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github summary: API docs for the spaces plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 33d9b4b6cf21e..1c483f7415e98 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github summary: API docs for the stackAlerts plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 80c488b4d3d4a..b5aefb462d8f0 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github summary: API docs for the taskManager plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index addcdd9826495..62375b5cfdbd0 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github summary: API docs for the telemetry plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 58326932df82a..d90ce413ad60b 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github summary: API docs for the telemetryCollectionManager plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 055943fdbd397..b2c67c19bff4a 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github summary: API docs for the telemetryCollectionXpack plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index cddd6ad42682a..5236f864bbec8 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github summary: API docs for the telemetryManagementSection plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 33e67e8cb851e..2383a376db94f 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github summary: API docs for the timelines plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index d141cf14d31f1..30975b87dc4db 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github summary: API docs for the transform plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index eb61c4c88d4bb..8629252346fbc 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github summary: API docs for the triggersActionsUi plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index c77e7a7dfd1ad..8a2dd272ec7a1 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github summary: API docs for the uiActions plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 43a6ccd645db7..a5a6b8b157fdb 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github summary: API docs for the uiActionsEnhanced plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index ddf4a432dba3d..fa0e583851fa5 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github summary: API docs for the unifiedSearch plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 643eb135405c3..33e6e6a372462 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github summary: API docs for the unifiedSearch.autocomplete plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 0c6c756a2292d..68cc63dfa8720 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github summary: API docs for the urlForwarding plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 62f2dbff8c9a5..55b150d9c9e7c 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github summary: API docs for the usageCollection plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index dfe6da6282e70..de6b40411703c 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github summary: API docs for the ux plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 93df29ec1140a..583915307439a 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github summary: API docs for the visDefaultEditor plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index a0e424b91ae48..7b5951cce5dde 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeGauge plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index b34ef4476375d..bc518c8156412 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeHeatmap plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index c3aa0b5d73342..a44f52447f08a 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypePie plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 5e08d46980b49..7a31d8844fa79 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeTable plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 476c563e8af37..bea439614069e 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeTimelion plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 976114269d0c0..30b0159f0778b 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeTimeseries plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 4a40ef60306cd..404db107ebe8e 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeVega plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 9e7d2b71f740b..ca7c8a49ad004 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeVislib plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 1dccfe5b6f44c..7ea6323e773dc 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeXy plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 79a0ce925a0ad..25c46f17f18c4 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github summary: API docs for the visualizations plugin -date: 2022-06-15 +date: 2022-06-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/dev_docs/key_concepts/audit_logging.mdx b/dev_docs/key_concepts/audit_logging.mdx new file mode 100644 index 0000000000000..6ec4de320d582 --- /dev/null +++ b/dev_docs/key_concepts/audit_logging.mdx @@ -0,0 +1,119 @@ +--- +id: kibAuditLogging +slug: /kibana-dev-docs/key-concepts/audit-logging +title: Audit Logging +summary: Audit Logging +date: 2022-06-15 +tags: ['kibana', 'onboarding', 'dev', 'logging', 'audit'] +--- + +## Audit logging + +Audit logging is a subscription feature that users can enable to keep track of security-related events, such as authorization success and failures. Logging these events enables you to monitor Kibana for suspicious activity and provides evidence in the event of an attack. + +Use the Kibana audit logs in conjunction with Elasticsearch audit logging to get a holistic view of all security related events. Kibana defers to the Elasticsearch security model for authentication, data index authorization, and features that are driven by cluster-wide privileges. + +### Automatic audit logging + +The Kibana Platform automatically records audit events for the following operations: +- Calling HTTP endpoints +- CRUD operations on Saved Objects [1] +- CRUD operations on Spaces +- Login / Logout events + + + [1] Saved Object operations are only audited when using the Scoped Saved Objects Client. Audit logging will not be performed if you create an unscoped client, or choose to exclude the `security` wrapper. + + +More information on these events can be found in our [audit logging documentation](https://www.elastic.co/guide/en/kibana/current/xpack-security-audit-logging.html#xpack-security-ecs-audit-logging) + +### Custom audit logging + +There may be times when it makes sense for a feature to implement its own audit logging, in order to suppliment our automatic audit logging. +Access to the audit logging service is exposed through the `security` plugin. + +#### Example + +```typescript +const auditLogger = securitySetup.audit.asScoped(request); +auditLogger.log({ + message: 'User is updating dashboard [id=123]', + event: { + action: 'saved_object_update', + category: ['database'], + type: ['change'], + outcome: 'unknown', + }, + kibana: { + saved_object: { type: 'dashboard', id: '123' }, + }, +}); +``` + +### What events should be logged? + +The purpose of an audit log is to support compliance, accountability and +security by capturing who performed an action, what action was performed and +when it occurred. It is not the purpose of an audit log to aid with debugging +the system or provide usage statistics. + +**Kibana guidelines:** + +Each API call to Kibana will result in a record in the audit log that captures +general information about the request (`http_request` event). + +In addition to that, any operation that is performed on a resource owned by +Kibana (e.g. saved objects) and that falls in the following categories, should +be included in the audit log: + +- System access (incl. failed attempts due to authentication errors) +- Data reads (incl. failed attempts due to authorisation errors) +- Data writes (incl. failed attempts due to authorisation errors) + +If Kibana does not own the resource (e.g. when running queries against user +indices), then auditing responsibilities are deferred to Elasticsearch and no +additional events will be logged. + +**Examples:** + +For a list of audit events that Kibana currently logs see: +`docs/user/security/audit-logging.asciidoc` + +### When should an event be logged? + +Due to the asynchronous nature of most operations in Kibana, there is an +inherent tradeoff between the following logging approaches: + +- Logging the **intention** before performing an operation, leading to false + positives if the operation fails downstream. +- Logging the **outcome** after completing an operation, leading to missing + records if Kibana crashes before the response is received. +- Logging **both**, intention and outcome, leading to unnecessary duplication + and noisy/difficult to analyse logs. + +**Kibana guidelines:** + +- **Write operations** should be logged immediately after all authorisation + checks have passed, but before the response is received (logging the + intention). This ensures that a record of every operation is persisted even in + case of an unexpected error. +- **Read operations**, on the other hand, should be logged after the operation + completed (logging the outcome) since we won't know what resources were + accessed before receiving the response. +- Be explicit about the timing and outcome of an action in your messaging. (e.g. + "User has logged in" vs. "User is creating dashboard") + +### Can an action trigger multiple events? + +- A request to Kibana can perform a combination of different operations, each of + which should be captured as separate events. +- Operations that are performed on multiple resources (**bulk operations**) + should be logged as separate events, one for each resource. +- Actions that kick off **background tasks** should be logged as separate + events, one for creating the task and another one for executing it. +- **Internal checks**, which have been carried out in order to perform an + operation, or **errors** that occured as a result of an operation should be + logged as an outcome of the operation itself, using the ECS `event.outcome` + and `error` fields, instead of logging a separate event. +- Multiple events that were part of the same request can be correlated in the + audit log using the ECS `trace.id` property. diff --git a/docs/api/cases/cases-api-add-comment.asciidoc b/docs/api/cases/cases-api-add-comment.asciidoc index b179c9ac2e4fb..67a201a1a2369 100644 --- a/docs/api/cases/cases-api-add-comment.asciidoc +++ b/docs/api/cases/cases-api-add-comment.asciidoc @@ -4,7 +4,7 @@ Add comment ++++ -Adds a comment to a case. +Adds a comment or alert to a case. === {api-request-title} @@ -78,7 +78,7 @@ Add a comment to case ID `293f1bc0-74f6-11ea-b83a-553aecdb28b6`: POST api/cases/293f1bc0-74f6-11ea-b83a-553aecdb28b6/comments { "type": "user", - "comment": "That is nothing - Ethan Hunt answered a targeted social media campaign promoting phishy pension schemes to IMF operatives.", + "comment": "A new comment.", "owner": "cases" } -------------------------------------------------- @@ -93,30 +93,30 @@ The API returns details about the case and its comments. For example: { "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", "version": "WzIwNDMxLDFd", - "type":"user", - "owner":"cases", - "comment":"That is nothing - Ethan Hunt answered a targeted social media campaign promoting phishy pension schemes to IMF operatives.", - "created_at":"2022-03-24T00:49:47.716Z", + "type": "user", + "owner": "cases", + "comment": "A new comment.", + "created_at": "2022-03-24T00:49:47.716Z", "created_by": { - "email": "moneypenny@hms.gov.uk", - "full_name": "Ms Moneypenny", - "username": "moneypenny" + "email": null, + "full_name": null, + "username": "elastic" }, - "pushed_at":null, - "pushed_by":null, - "updated_at":null, - "updated_by":null + "pushed_at": null, + "pushed_by": null, + "updated_at": null, + "updated_by": null } ], - "totalAlerts":0, - "id":"293f1bc0-74f6-11ea-b83a-553aecdb28b6", - "version":"WzIzMzgsMV0=", - "totalComment":1, - "title": "This case will self-destruct in 5 seconds", - "tags": ["phishing","social engineering"], - "description": "James Bond clicked on a highly suspicious email banner advertising cheap holidays for underpaid civil servants.", + "totalAlerts": 0, + "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIzMzgsMV0=", + "totalComment": 1, + "title": "Case title 1", + "tags": ["tag 1"], + "description": "A case description.", "settings": { - "syncAlerts":false + "syncAlerts": false }, "owner": "cases", "duration": null, @@ -125,16 +125,16 @@ The API returns details about the case and its comments. For example: "closed_by": null, "created_at": "2022-03-24T00:37:03.906Z", "created_by": { - "email": "ahunley@imf.usa.gov", - "full_name": "Alan Hunley", - "username": "ahunley" + "email": null, + "full_name": null, + "username": "elastic" }, "status": "open", "updated_at": "2022-03-24T00:49:47.716Z", "updated_by": { - "email": "moneypenny@hms.gov.uk", - "full_name": "Ms Moneypenny", - "username": "moneypenny" + "email": null, + "full_name": null, + "username": "elastic" }, "connector": { "id": "none", @@ -152,13 +152,13 @@ Add an alert to the case: -------------------------------------------------- POST api/cases/293f1bc0-74f6-11ea-b83a-553aecdb28b6/comments { -"alertId": "6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42", -"index": ".internal.alerts-security.alerts-default-000001", -"type": "alert", -"owner": "cases", -"rule": { - "id":"94d80550-aaf4-11ec-985f-97e55adae8b9", - "name":"security_rule" + "alertId": "6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42", + "index": ".internal.alerts-security.alerts-default-000001", + "type": "alert", + "owner": "cases", + "rule": { + "id":"94d80550-aaf4-11ec-985f-97e55adae8b9", + "name":"security_rule" } } -------------------------------------------------- diff --git a/docs/api/cases/cases-api-delete-comments.asciidoc b/docs/api/cases/cases-api-delete-comments.asciidoc index c89407fb69ab8..30a4ab4ea3637 100644 --- a/docs/api/cases/cases-api-delete-comments.asciidoc +++ b/docs/api/cases/cases-api-delete-comments.asciidoc @@ -4,7 +4,7 @@ Delete comments ++++ -Deletes one or all comments from a case. +Deletes one or all comments and alerts from a case. === {api-request-title} diff --git a/docs/api/cases/cases-api-update-comment.asciidoc b/docs/api/cases/cases-api-update-comment.asciidoc index a4ea53ec19468..f1e11b5905341 100644 --- a/docs/api/cases/cases-api-update-comment.asciidoc +++ b/docs/api/cases/cases-api-update-comment.asciidoc @@ -4,7 +4,7 @@ Update comment ++++ -Updates a comment in a case. +Updates a comment or alert in a case. === {api-request-title} @@ -40,7 +40,8 @@ default space is used. `user`. `id`:: -(Required, string) The identifier for the comment. To retrieve comment IDs, use <>. +(Required, string) The identifier for the comment. To retrieve comment IDs, use +<>. `index`:: (Required*, string) The alert index. It is required only when `type` is `alert`. @@ -74,7 +75,8 @@ The rule that is associated with the alert. It is required only when `type` is NOTE: You cannot change the comment type. `version`:: -(Required, string) The current comment version. To retrieve version values, use <>. +(Required, string) The current comment version. To retrieve version values, use +<>. === {api-response-codes-title} @@ -93,7 +95,7 @@ PATCH api/cases/293f1bc0-74f6-11ea-b83a-553aecdb28b6/comments "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", "version": "Wzk1LDFd", "type": "user", - "comment": "That is nothing - Ethan Hunt answered a targeted social media campaign promoting phishy pension schemes to IMF operatives. Even worse, he likes baked beans." + "comment": "An updated comment." } -------------------------------------------------- // KIBANA @@ -106,22 +108,22 @@ The API returns details about the case and its comments. For example: "comments":[{ "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", "version": "WzIwNjM3LDFd", - "comment": "That is nothing - Ethan Hunt answered a targeted social media campaign promoting phishy pension schemes to IMF operatives. Even worse, he likes baked beans.", + "comment": "An updated comment.", "type": "user", "owner": "cases", "created_at": "2022-03-24T00:37:10.832Z", "created_by": { - "email": "moneypenny@hms.gov.uk", - "full_name": "Ms Moneypenny", - "username": "moneypenny" + "email": null, + "full_name": null, + "username": "elastic" }, "pushed_at": null, "pushed_by": null, "updated_at": "2022-03-24T01:27:06.210Z", "updated_by": { - "email": "jbond@hms.gov.uk", - "full_name": "James Bond", - "username": "_007" + "email": null, + "full_name": null, + "username": "elastic" } } ], @@ -129,9 +131,9 @@ The API returns details about the case and its comments. For example: "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", "version": "WzIwNjM2LDFd", "totalComment": 1, - "title": "This case will self-destruct in 5 seconds", - "tags": ["phishing","social engineering"], - "description": "James Bond clicked on a highly suspicious email banner advertising cheap holidays for underpaid civil servants.", + "title": "Case title 1", + "tags": ["tag 1"], + "description": "A case description.", "settings": {"syncAlerts":false}, "owner": "cases", "duration": null, @@ -140,18 +142,23 @@ The API returns details about the case and its comments. For example: "closed_by": null, "created_at": "2022-03-24T00:37:03.906Z", "created_by": { - "email": "ahunley@imf.usa.gov", - "full_name": "Alan Hunley", - "username": "ahunley" + "email": null, + "full_name": null, + "username": "elastic" }, "status": "open", "updated_at": "2022-03-24T01:27:06.210Z", "updated_by": { - "email": "jbond@hms.gov.uk", - "full_name": "James Bond", - "username": "_007" + "email": null, + "full_name": null, + "username": "elastic" + }, + "connector": { + "id": "none", + "name": "none", + "type": ".none", + "fields": null }, - "connector": {"id":"none","name":"none","type":".none","fields":null}, "external_service": null } -------------------------------------------------- @@ -162,16 +169,16 @@ Update an alert in the case: -------------------------------------------------- PATCH api/cases/293f1bc0-74f6-11ea-b83a-553aecdb28b6/comments { -"id": "73362370-ab1a-11ec-985f-97e55adae8b9", -"version": "WzMwNDgsMV0=", -"type": "alert", -"owner": "cases", -"alertId": "c8789278659fdf88b7bf7709b90a082be070d0ba4c23c9c4b552e476c2a667c4", -"index": ".internal.alerts-security.alerts-default-000001", -"rule": -{ - "id":"94d80550-aaf4-11ec-985f-97e55adae8b9", - "name":"security_rule" + "id": "73362370-ab1a-11ec-985f-97e55adae8b9", + "version": "WzMwNDgsMV0=", + "type": "alert", + "owner": "cases", + "alertId": "c8789278659fdf88b7bf7709b90a082be070d0ba4c23c9c4b552e476c2a667c4", + "index": ".internal.alerts-security.alerts-default-000001", + "rule": + { + "id":"94d80550-aaf4-11ec-985f-97e55adae8b9", + "name":"security_rule" } } -------------------------------------------------- diff --git a/docs/apm/images/time-series-comparison.png b/docs/apm/images/time-series-comparison.png deleted file mode 100644 index 6d3cdf4a1634f..0000000000000 Binary files a/docs/apm/images/time-series-comparison.png and /dev/null differ diff --git a/docs/apm/images/time-series-expected-bounds-comparison.png b/docs/apm/images/time-series-expected-bounds-comparison.png new file mode 100644 index 0000000000000..6e705064e65b3 Binary files /dev/null and b/docs/apm/images/time-series-expected-bounds-comparison.png differ diff --git a/docs/apm/service-overview.asciidoc b/docs/apm/service-overview.asciidoc index 05537cef58c98..22da9a132b4fa 100644 --- a/docs/apm/service-overview.asciidoc +++ b/docs/apm/service-overview.asciidoc @@ -14,17 +14,19 @@ high-level visibility into how a service is performing across your infrastructur [discrete] [[service-time-comparison]] -=== Time series comparison +=== Time series and expected bounds comparison -Comparing how a service performs relative to a previous time frame can offer additional insight into -the health of your services. For example, has latency been slowly increasing over time, or did the service -experience a sudden spike--enabling a time series comparison can provide the answer. +For insight into the health of your services, you can compare how a service +performs relative to a previous time frame or to the expected bounds from the +corresponding {anomaly-job}. For example, has latency been slowly increasing +over time, did the service experience a sudden spike, is the throughput similar +to what the {ml} job expects – enabling a comparison can provide the answer. [role="screenshot"] -image::apm/images/time-series-comparison.png[Time series comparison] +image::apm/images/time-series-expected-bounds-comparison.png[Time series and expected bounds comparison] -Select the *Comparison* box to enable or disable time series comparison. -The time comparison options are based on the selected time filter range: +Select the *Comparison* box to apply a time-based or expected bounds comparison. +The time-based comparison options are based on the selected time filter range: [options="header"] |==== @@ -40,6 +42,10 @@ The time comparison options are based on the selected time filter range: |An identical amount of time immediately before the selected time range |==== +You can use the expected bounds comparison if {ml-jobs} exist in your selected +environment and you have +{ml-docs}/setup.html#kib-visibility-spaces[access to the {ml-features}]. + [discrete] [[service-latency]] === Latency diff --git a/docs/development/core/public/kibana-plugin-core-public.analyticsservicesetup.md b/docs/development/core/public/kibana-plugin-core-public.analyticsservicesetup.md deleted file mode 100644 index 8cee67f0110dd..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.analyticsservicesetup.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AnalyticsServiceSetup](./kibana-plugin-core-public.analyticsservicesetup.md) - -## AnalyticsServiceSetup type - -Exposes the public APIs of the AnalyticsClient during the setup phase. - -Signature: - -```typescript -export declare type AnalyticsServiceSetup = Omit; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.analyticsservicestart.md b/docs/development/core/public/kibana-plugin-core-public.analyticsservicestart.md deleted file mode 100644 index 9198bc7275046..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.analyticsservicestart.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AnalyticsServiceStart](./kibana-plugin-core-public.analyticsservicestart.md) - -## AnalyticsServiceStart type - -Exposes the public APIs of the AnalyticsClient during the start phase - -Signature: - -```typescript -export declare type AnalyticsServiceStart = Pick; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.analytics.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.analytics.md index 13c5fbdbee32d..209a4e862589b 100644 --- a/docs/development/core/public/kibana-plugin-core-public.coresetup.analytics.md +++ b/docs/development/core/public/kibana-plugin-core-public.coresetup.analytics.md @@ -4,7 +4,6 @@ ## CoreSetup.analytics property -[AnalyticsServiceSetup](./kibana-plugin-core-public.analyticsservicesetup.md) Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.md index 8a35451da2999..051aa0218eca7 100644 --- a/docs/development/core/public/kibana-plugin-core-public.coresetup.md +++ b/docs/development/core/public/kibana-plugin-core-public.coresetup.md @@ -16,7 +16,7 @@ export interface CoreSetupSignature: diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.md b/docs/development/core/public/kibana-plugin-core-public.corestart.md index 6981a0cfb24e4..3ced931f53c7e 100644 --- a/docs/development/core/public/kibana-plugin-core-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.md @@ -16,7 +16,7 @@ export interface CoreStart | Property | Type | Description | | --- | --- | --- | -| [analytics](./kibana-plugin-core-public.corestart.analytics.md) | AnalyticsServiceStart | [AnalyticsServiceStart](./kibana-plugin-core-public.analyticsservicestart.md) | +| [analytics](./kibana-plugin-core-public.corestart.analytics.md) | AnalyticsServiceStart | | | [application](./kibana-plugin-core-public.corestart.application.md) | ApplicationStart | [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) | | [chrome](./kibana-plugin-core-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-core-public.chromestart.md) | | [deprecations](./kibana-plugin-core-public.corestart.deprecations.md) | DeprecationsServiceStart | [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index 609166bff1ac5..1d2b07d13f6a8 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -148,8 +148,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | Type Alias | Description | | --- | --- | -| [AnalyticsServiceSetup](./kibana-plugin-core-public.analyticsservicesetup.md) | Exposes the public APIs of the AnalyticsClient during the setup phase. | -| [AnalyticsServiceStart](./kibana-plugin-core-public.analyticsservicestart.md) | Exposes the public APIs of the AnalyticsClient during the start phase | | [AppDeepLink](./kibana-plugin-core-public.appdeeplink.md) | Input type for registering secondary in-app locations for an application.Deep links must include at least one of path or deepLinks. A deep link that does not have a path represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible. | | [AppLeaveAction](./kibana-plugin-core-public.appleaveaction.md) | Possible actions to return from a [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md)See [AppLeaveConfirmAction](./kibana-plugin-core-public.appleaveconfirmaction.md) and [AppLeaveDefaultAction](./kibana-plugin-core-public.appleavedefaultaction.md) | | [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return confirm to prompt a message to the user before leaving the page, or default to keep the default behavior (doing nothing).See [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) for detailed usage examples. | diff --git a/docs/discover/document-explorer.asciidoc b/docs/discover/document-explorer.asciidoc index e0cead0292cf3..665d668a41ee7 100644 --- a/docs/discover/document-explorer.asciidoc +++ b/docs/discover/document-explorer.asciidoc @@ -74,8 +74,6 @@ By default, columns are sorted in the order they are added. image::images/document-explorer-multi-field.png[Multi field sort in the document table, width="75%"] . To change the sort order, select a field in the pop-up, and then drag it to the new location. -+ -For example, to sort by `geo.country_iso_code` then `order_date`, make sure `geo.country_iso_code` appears first. [float] diff --git a/docs/discover/images/customer.png b/docs/discover/images/customer.png index 5e2015499d8ef..2ab177eba28bf 100644 Binary files a/docs/discover/images/customer.png and b/docs/discover/images/customer.png differ diff --git a/docs/discover/images/discover-add-filter.png b/docs/discover/images/discover-add-filter.png index 5ee2f01960248..5a5a1a173b547 100644 Binary files a/docs/discover/images/discover-add-filter.png and b/docs/discover/images/discover-add-filter.png differ diff --git a/docs/discover/images/discover-context.png b/docs/discover/images/discover-context.png index dd8a29ba29336..b625a18b35aa2 100644 Binary files a/docs/discover/images/discover-context.png and b/docs/discover/images/discover-context.png differ diff --git a/docs/discover/images/discover-data-view.png b/docs/discover/images/discover-data-view.png index df9f48792f2a3..c73af3db24140 100644 Binary files a/docs/discover/images/discover-data-view.png and b/docs/discover/images/discover-data-view.png differ diff --git a/docs/discover/images/discover-search-field.png b/docs/discover/images/discover-search-field.png index f04abfa566fce..c47a779ca4192 100644 Binary files a/docs/discover/images/discover-search-field.png and b/docs/discover/images/discover-search-field.png differ diff --git a/docs/discover/images/discover.png b/docs/discover/images/discover.png index 6b60f74154a1b..818b9ed5d2599 100644 Binary files a/docs/discover/images/discover.png and b/docs/discover/images/discover.png differ diff --git a/docs/discover/images/document-explorer-compare-data.png b/docs/discover/images/document-explorer-compare-data.png index 1164fb0d1fdba..d5cb84af5bb37 100644 Binary files a/docs/discover/images/document-explorer-compare-data.png and b/docs/discover/images/document-explorer-compare-data.png differ diff --git a/docs/discover/images/document-explorer-configure-table.png b/docs/discover/images/document-explorer-configure-table.png new file mode 100644 index 0000000000000..e455583d527e8 Binary files /dev/null and b/docs/discover/images/document-explorer-configure-table.png differ diff --git a/docs/discover/images/document-explorer-expand.png b/docs/discover/images/document-explorer-expand.png index 7404e758bf902..98ed1480204cd 100644 Binary files a/docs/discover/images/document-explorer-expand.png and b/docs/discover/images/document-explorer-expand.png differ diff --git a/docs/discover/images/document-explorer-multi-field.png b/docs/discover/images/document-explorer-multi-field.png index 2b4abb7416a81..abc96062e39d4 100644 Binary files a/docs/discover/images/document-explorer-multi-field.png and b/docs/discover/images/document-explorer-multi-field.png differ diff --git a/docs/discover/images/document-explorer-sort-data.png b/docs/discover/images/document-explorer-sort-data.png index 389c94ba27b46..29d9143871f4a 100644 Binary files a/docs/discover/images/document-explorer-sort-data.png and b/docs/discover/images/document-explorer-sort-data.png differ diff --git a/docs/discover/images/document-table-expanded.png b/docs/discover/images/document-table-expanded.png index 10e8b269efe66..0248c31f894e0 100644 Binary files a/docs/discover/images/document-table-expanded.png and b/docs/discover/images/document-table-expanded.png differ diff --git a/docs/discover/images/document-table.png b/docs/discover/images/document-table.png index 0df7ad265369c..388f3c4344a09 100644 Binary files a/docs/discover/images/document-table.png and b/docs/discover/images/document-table.png differ diff --git a/docs/discover/images/field-sorting-popover.png b/docs/discover/images/field-sorting-popover.png new file mode 100644 index 0000000000000..87a97529962a0 Binary files /dev/null and b/docs/discover/images/field-sorting-popover.png differ diff --git a/docs/discover/images/hello-field.png b/docs/discover/images/hello-field.png index a8750fac0cb9b..8dd38a9f94209 100644 Binary files a/docs/discover/images/hello-field.png and b/docs/discover/images/hello-field.png differ diff --git a/docs/discover/images/sort-by-relevance.png b/docs/discover/images/sort-by-relevance.png index 893950508e9f1..67cf7b89f37f0 100644 Binary files a/docs/discover/images/sort-by-relevance.png and b/docs/discover/images/sort-by-relevance.png differ diff --git a/docs/discover/images/update-button.png b/docs/discover/images/update-button.png new file mode 100644 index 0000000000000..04170716aea51 Binary files /dev/null and b/docs/discover/images/update-button.png differ diff --git a/docs/discover/search-for-relevance.asciidoc b/docs/discover/search-for-relevance.asciidoc index d14fb438e2b7e..df581634877f1 100644 --- a/docs/discover/search-for-relevance.asciidoc +++ b/docs/discover/search-for-relevance.asciidoc @@ -22,10 +22,11 @@ Warsaw OR Venice OR Clear + At this point, you're sorting by the`timestamp` field. . To turn off sorting by the `timestamp` field, click the *field sorted* option, and then click *Clear sorting.* -. To turn on sorting by the `_score` field in descending order, open the dropdown menu and click *_score*, and then select *High-Low*. +. Open the *Pick fields to sort by* menu, and then click *_score*. +. Select *High-Low*. + [role="screenshot"] -image::images/sort-by-relevance.png["Field sorting popover", width=75%] +image::images/field-sorting-popover.png["Field sorting popover", width=60%] + Your table now sorts documents from most to least relevant. diff --git a/docs/user/dashboard/make-dashboards-interactive.asciidoc b/docs/user/dashboard/make-dashboards-interactive.asciidoc index eaff5bc1bda60..0e20237c2ebc0 100644 --- a/docs/user/dashboard/make-dashboards-interactive.asciidoc +++ b/docs/user/dashboard/make-dashboards-interactive.asciidoc @@ -28,8 +28,6 @@ data-type="inline" *Controls* are interactive panels you add to your dashboards to filter and display only the data you want to explore. -deprecated::[8.3.0, "The new controls replace the previous link:https://www.elastic.co/guide/en/kibana/8.1/drilldowns.html#add-controls[*Input controls*]. Input controls are deprecated in 8.3.0, and will be removed in a future release."] - There are two types of controls: * *Options list* — Adds a dropdown that allows you to filter the data with one or more options that you select. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 257cd9c856dab..26a6eafd8086d 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -54,15 +54,13 @@ Tell {kib} where to find the data you want to explore, and then specify the time + {kib} uses a <> to tell it where to find your {es} data. -To view the ecommerce sample data, select **kibana_sample_data_ecommerce** -from the {data-source} dropdown. +To view the ecommerce sample data, open the {data-source} menu, and select **kibana_sample_data_ecommerce**. + [role="screenshot"] image::images/discover-data-view.png[How to set the {data-source} in Discover, width=50%] + To create a data view for your own data, -click -image:images/actions-icon.png[three dots icon next to data view dropdown], and then click *Create new data view*. +click *Create a data view*. For details, refer to <> . Adjust the <> to view data for the *Last 7 days*. @@ -120,11 +118,7 @@ You can add a runtime field to your {data-source} from inside of **Discover**, and then use that field for analysis and visualizations, the same way you do with other fields. -. Click -image:images/actions-icon.png[three dots icon next to data view dropdown], and then click *Add field*. -+ -[role="screenshot"] -image:images/add-field-to-data-view.png[Dropdown menu located next to {data-source} field with item for adding a field to a {data-source}, width=50%] +. Open the data view menu, and then click *Add a field to this data view*. . In the *Create field* form, enter `hello` for the name. @@ -138,7 +132,7 @@ emit("Hello World!"); . Click *Save*. -. In the fields list, search for the *hello* field, and then add it to the document table to view it's value. +. In the fields list, search for the *hello* field, and then add it to the document table. + [role="screenshot"] image:images/hello-field.png[hello field in the document tables] @@ -179,7 +173,7 @@ you can use to build a structured query. Search the ecommerce data for documents where the country matches US: . Enter `g`, and then select *geoip.country_iso_code*. -. Select *:* for equals some value and *US*, and then click *Update*. +. Select *:* for equals some value and *US*, and then click *Refresh*. . For a more complex search, try: + ```ts @@ -197,13 +191,13 @@ and more. Exclude documents where day of week is not Wednesday: -. Click **Add filter**. -. Set **Field** to *day_of_week*, **Operator** to *is not*, and **Value** to *Wednesday*. +. Click image:images/add-icon.png[Add icon] to the left of the query bar. +. In the *Add filter* pop-up, set *Field* to *day_of_week*, *Operator* to *is not*, and *Value* to *Wednesday*. + [role="screenshot"] image:images/discover-add-filter.png[Add filter dialog in Discover] -. Save the filter. +. Click **Add filter**. . Continue your exploration by adding more filters. . To remove a filter, click the close icon (x) next to its name in the filter bar. @@ -226,10 +220,11 @@ image:images/document-table-expanded.png[Table view with document expanded] click image:images/actions-icon.png[three dots icon next to data view dropdown] in the *Actions* column for filters and other controls. +. To create a view of the document that you can bookmark and share, click **Single document**. + . To view documents that occurred before or after the event you are looking at, click **Surrounding documents**. -. To create a view of the document that you can bookmark and share, click **Single document**. [float] @@ -275,7 +270,26 @@ image:images/discover-maps.png[Map containing documents] [[share-your-findings]] === Share your findings -To share your findings with a larger audience, click *Share* in the *Discover* toolbar. For detailed information about the sharing options, refer to <>. +To share your findings with a larger audience, click *Share* in the *Discover* toolbar. +For detailed information about the sharing options, refer to <>. + +[float] +[[alert-from-Discover]] +=== Generate alerts + +From *Discover*, you can create a rule to periodically +check when data goes above or below a certain threshold within a given time interval. + +. Ensure that your data view, +query, and filters fetch the data for which you want an alert. +. In the toolbar, click *Alerts > Create search threshold rule*. ++ +The *Create rule* form is pre-filled with the latest query sent to {es}. +. <> and <>. + +. Click *Save*. + +For more about this and other rules provided in {kib} {alert-features}, go to <>. [float] diff --git a/docs/user/reporting/automating-report-generation.asciidoc b/docs/user/reporting/automating-report-generation.asciidoc index 3ddb4a3694994..f2102e7c0e2db 100644 --- a/docs/user/reporting/automating-report-generation.asciidoc +++ b/docs/user/reporting/automating-report-generation.asciidoc @@ -12,7 +12,7 @@ Create the POST URL that triggers a report to generate PDF and CSV reports. To create the POST URL for PDF reports: -. Open the main menu, then click *Dashboard, *Visualize Library*, or *Canvas*. +. Open the main menu, then click *Dashboard*, *Visualize Library*, or *Canvas*. . Open the dashboard, visualization, or **Canvas** workpad you want to view as a report. diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index 0f2a349e60f01..7b066bf357d32 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -8,210 +8,515 @@ { "label": "Getting started", "items": [ - { "id": "kibDevDocsWelcome" }, - { "id": "kibDevTutorialSetupDevEnv" }, - { "id": "kibHelloWorldApp" }, - { "id": "kibDevAddData" }, - { "id": "kibTroubleshooting" } + { + "id": "kibDevDocsWelcome" + }, + { + "id": "kibDevTutorialSetupDevEnv" + }, + { + "id": "kibHelloWorldApp" + }, + { + "id": "kibDevAddData" + }, + { + "id": "kibTroubleshooting" + } ] }, { "label": "Contributing", "items": [ - { "id": "kibDevPrinciples" }, - { "id": "kibRepoStructure" }, - { "id": "kibStandards" }, - { "id": "kibBestPractices" }, - { "id": "kibDocumentation" }, - { "id": "kibStyleGuide" }, - { "id": "ktRFCProcess" }, - { "id": "kibGitHub" } + { + "id": "kibDevPrinciples" + }, + { + "id": "kibRepoStructure" + }, + { + "id": "kibStandards" + }, + { + "id": "kibBestPractices" + }, + { + "id": "kibDocumentation" + }, + { + "id": "kibStyleGuide" + }, + { + "id": "ktRFCProcess" + }, + { + "id": "kibGitHub" + } ] }, { "label": "Key concepts", "items": [ - { "id": "kibPlatformIntro" }, - { "id": "kibDevAnatomyOfAPlugin" }, - { "id": "kibDevPerformance" }, - { "id": "kibBuildingBlocks" }, - { "id": "kibDevDocsSavedObjectsIntro", "label": "Saved objects" }, - { "id": "kibDevDocsPersistableStateIntro" }, - { "id": "kibDataPlugin", "label": "Data" }, - { "id": "kibCoreLogging" }, - { "id": "kibUsageCollectionPlugin" }, - { "id": "kibDataViewsKeyConcepts" }, - { "id": "kibDevKeyConceptsNavigation" } + { + "id": "kibPlatformIntro" + }, + { + "id": "kibDevAnatomyOfAPlugin" + }, + { + "id": "kibDevPerformance" + }, + { + "id": "kibBuildingBlocks" + }, + { + "id": "kibDevDocsSavedObjectsIntro", + "label": "Saved objects" + }, + { + "id": "kibDevDocsPersistableStateIntro" + }, + { + "id": "kibDataPlugin", + "label": "Data" + }, + { + "id": "kibCoreLogging" + }, + { + "id": "kibAuditLogging" + }, + { + "id": "kibUsageCollectionPlugin" + }, + { + "id": "kibDataViewsKeyConcepts" + }, + { + "id": "kibDevKeyConceptsNavigation" + } ] }, { "label": "Tutorials", "items": [ - { "id": "kibDevTutorialTestingPlugins" }, - { "id": "kibDevTutorialSavedObject" }, - { "id": "kibDevTutorialSubmitPullRequest" }, - { "id": "kibDevTutorialExpressions" }, - { "id": "kibDevDocsKPTTutorial" }, - { "id": "kibDevTutorialDataSearchAndSessions", "label": "data.search" }, - { "id": "kibDevTutorialDataViews" }, - { "id": "kibDevTutorialDebugging" }, + { + "id": "kibDevTutorialTestingPlugins" + }, + { + "id": "kibDevTutorialSavedObject" + }, + { + "id": "kibDevTutorialSubmitPullRequest" + }, + { + "id": "kibDevTutorialExpressions" + }, + { + "id": "kibDevDocsKPTTutorial" + }, + { + "id": "kibDevTutorialDataSearchAndSessions", + "label": "data.search" + }, + { + "id": "kibDevTutorialDataViews" + }, + { + "id": "kibDevTutorialDebugging" + }, { "id": "kibDevTutorialBuildingDistributable", "label": "Building a Kibana distributable" }, - { "id": "kibDevTutorialCI" }, - { "id": "kibDevTutorialServerEndpoint" }, - { "id": "kibDevTutorialAdvancedSettings" }, - { "id": "kibDevSharePluginReadme" }, - { "id": "kibDevTutorialScreenshotting" } + { + "id": "kibDevTutorialCI" + }, + { + "id": "kibDevTutorialServerEndpoint" + }, + { + "id": "kibDevTutorialAdvancedSettings" + }, + { + "id": "kibDevSharePluginReadme" + }, + { + "id": "kibDevTutorialScreenshotting" + } ] }, { "label": "Contributors Newsletters", "items": [ - { "id": "kibMay2022ContributorNewsletter" }, - { "id": "kibApril2022ContributorNewsletter" }, - { "id": "kibMarch2022ContributorNewsletter" }, - { "id": "kibFebruary2022ContributorNewsletter" }, - { "id": "kibJanuary2022ContributorNewsletter" }, - { "id": "kibDecember2021ContributorNewsletter" }, - { "id": "kibNovember2021ContributorNewsletter" }, - { "id": "kibOctober2021ContributorNewsletter" }, - { "id": "kibSeptember2021ContributorNewsletter" }, - { "id": "kibAugust2021ContributorNewsletter" }, - { "id": "kibJuly2021ContributorNewsletter" }, - { "id": "kibJune2021ContributorNewsletter" }, - { "id": "kibMay2021ContributorNewsletter" }, - { "id": "kibApril2021ContributorNewsletter" }, - { "id": "kibMarch2021ContributorNewsletter" } + { + "id": "kibMay2022ContributorNewsletter" + }, + { + "id": "kibApril2022ContributorNewsletter" + }, + { + "id": "kibMarch2022ContributorNewsletter" + }, + { + "id": "kibFebruary2022ContributorNewsletter" + }, + { + "id": "kibJanuary2022ContributorNewsletter" + }, + { + "id": "kibDecember2021ContributorNewsletter" + }, + { + "id": "kibNovember2021ContributorNewsletter" + }, + { + "id": "kibOctober2021ContributorNewsletter" + }, + { + "id": "kibSeptember2021ContributorNewsletter" + }, + { + "id": "kibAugust2021ContributorNewsletter" + }, + { + "id": "kibJuly2021ContributorNewsletter" + }, + { + "id": "kibJune2021ContributorNewsletter" + }, + { + "id": "kibMay2021ContributorNewsletter" + }, + { + "id": "kibApril2021ContributorNewsletter" + }, + { + "id": "kibMarch2021ContributorNewsletter" + } ] }, { "label": "API documentation", "items": [ - { "id": "kibDevDocsApiWelcome" }, - { "id": "kibDevDocsPluginDirectory" }, - { "id": "kibDevDocsDeprecationsDueByTeam" }, - { "id": "kibDevDocsDeprecationsByPlugin" }, - { "id": "kibDevDocsDeprecationsByApi" }, - { "id": "kibCorePluginApi" }, - { "id": "kibCoreApplicationPluginApi" }, - { "id": "kibCoreChromePluginApi" }, - { "id": "kibCoreHttpPluginApi" }, - { "id": "kibCoreSavedObjectsPluginApi" }, - { "id": "kibFieldFormatsPluginApi" }, - { "id": "kibDataPluginApi" }, - { "id": "kibDataViewsPluginApi" }, - { "id": "kibDataQueryPluginApi" }, - { "id": "kibDataSearchPluginApi" }, - { "id": "kibBfetchPluginApi" }, - { "id": "kibAlertingPluginApi" }, - { "id": "kibTaskManagerPluginApi" }, - { "id": "kibActionsPluginApi" }, - { "id": "kibEventLogPluginApi" }, - { "id": "kibTriggersActionsUiPluginApi" }, - { "id": "kibCasesPluginApi" }, - { "id": "kibChartsPluginApi" }, - { "id": "kibDashboardPluginApi" }, - { "id": "kibDevToolsPluginApi" }, - { "id": "kibDiscoverPluginApi" }, - { "id": "kibEmbeddablePluginApi" }, - { "id": "kibEncryptedSavedObjectsPluginApi" }, - { "id": "kibEnterpriseSearchPluginApi" }, - { "id": "kibEsUiSharedPluginApi" }, - { "id": "kibExpressionsPluginApi" }, - { "id": "kibFeaturesPluginApi" }, - { "id": "kibFileUploadPluginApi" }, - { "id": "kibFleetPluginApi" }, - { "id": "kibGlobalSearchPluginApi" }, - { "id": "kibHomePluginApi" }, - { "id": "kibInspectorPluginApi" }, - { "id": "kibKibanaReactPluginApi" }, - { "id": "kibKibanaUtilsPluginApi" }, - { "id": "kibLensPluginApi" }, - { "id": "kibLicenseManagementPluginApi" }, - { "id": "kibLicensingPluginApi" }, - { "id": "kibListsPluginApi" }, - { "id": "kibManagementPluginApi" }, - { "id": "kibMapsPluginApi" }, - { "id": "kibMlPluginApi" }, - { "id": "kibMonitoringPluginApi" }, - { "id": "kibNavigationPluginApi" }, - { "id": "kibNewsfeedPluginApi" }, - { "id": "kibObservabilityPluginApi" }, - { "id": "kibRemoteClustersPluginApi" }, - { "id": "kibReportingPluginApi" }, - { "id": "kibRollupPluginApi" }, - { "id": "kibRuntimeFieldsPluginApi" }, - { "id": "kibSavedObjectsManagementPluginApi" }, - { "id": "kibSavedObjectsTaggingOssPluginApi" }, - { "id": "kibSavedObjectsTaggingPluginApi" }, - { "id": "kibSavedObjectsPluginApi" }, - { "id": "kibScreenshottingPluginApi" }, - { "id": "kibSecuritySolutionPluginApi" }, - { "id": "kibSecurityPluginApi" }, - { "id": "kibSharePluginApi" }, - { "id": "kibSnapshotRestorePluginApi" }, - { "id": "kibSpacesPluginApi" }, - { "id": "kibStackAlertsPluginApi" }, - { "id": "kibTelemetryCollectionManagerPluginApi" }, - { "id": "kibTelemetryCollectionXpackPluginApi" }, - { "id": "kibTelemetryManagementSectionPluginApi" }, - { "id": "kibTelemetryPluginApi" }, - { "id": "kibUiActionsEnhancedPluginApi" }, - { "id": "kibUiActionsPluginApi" }, - { "id": "kibUrlForwardingPluginApi" }, - { "id": "kibUsageCollectionPluginApi" }, - { "id": "kibVisTypeTimeseriesPluginApi" }, - { "id": "kibVisualizationsPluginApi" } + { + "id": "kibDevDocsApiWelcome" + }, + { + "id": "kibDevDocsPluginDirectory" + }, + { + "id": "kibDevDocsDeprecationsDueByTeam" + }, + { + "id": "kibDevDocsDeprecationsByPlugin" + }, + { + "id": "kibDevDocsDeprecationsByApi" + }, + { + "id": "kibCorePluginApi" + }, + { + "id": "kibCoreApplicationPluginApi" + }, + { + "id": "kibCoreChromePluginApi" + }, + { + "id": "kibCoreHttpPluginApi" + }, + { + "id": "kibCoreSavedObjectsPluginApi" + }, + { + "id": "kibFieldFormatsPluginApi" + }, + { + "id": "kibDataPluginApi" + }, + { + "id": "kibDataViewsPluginApi" + }, + { + "id": "kibDataQueryPluginApi" + }, + { + "id": "kibDataSearchPluginApi" + }, + { + "id": "kibBfetchPluginApi" + }, + { + "id": "kibAlertingPluginApi" + }, + { + "id": "kibTaskManagerPluginApi" + }, + { + "id": "kibActionsPluginApi" + }, + { + "id": "kibEventLogPluginApi" + }, + { + "id": "kibTriggersActionsUiPluginApi" + }, + { + "id": "kibCasesPluginApi" + }, + { + "id": "kibChartsPluginApi" + }, + { + "id": "kibDashboardPluginApi" + }, + { + "id": "kibDevToolsPluginApi" + }, + { + "id": "kibDiscoverPluginApi" + }, + { + "id": "kibEmbeddablePluginApi" + }, + { + "id": "kibEncryptedSavedObjectsPluginApi" + }, + { + "id": "kibEnterpriseSearchPluginApi" + }, + { + "id": "kibEsUiSharedPluginApi" + }, + { + "id": "kibExpressionsPluginApi" + }, + { + "id": "kibFeaturesPluginApi" + }, + { + "id": "kibFileUploadPluginApi" + }, + { + "id": "kibFleetPluginApi" + }, + { + "id": "kibGlobalSearchPluginApi" + }, + { + "id": "kibHomePluginApi" + }, + { + "id": "kibInspectorPluginApi" + }, + { + "id": "kibKibanaReactPluginApi" + }, + { + "id": "kibKibanaUtilsPluginApi" + }, + { + "id": "kibLensPluginApi" + }, + { + "id": "kibLicenseManagementPluginApi" + }, + { + "id": "kibLicensingPluginApi" + }, + { + "id": "kibListsPluginApi" + }, + { + "id": "kibManagementPluginApi" + }, + { + "id": "kibMapsPluginApi" + }, + { + "id": "kibMlPluginApi" + }, + { + "id": "kibMonitoringPluginApi" + }, + { + "id": "kibNavigationPluginApi" + }, + { + "id": "kibNewsfeedPluginApi" + }, + { + "id": "kibObservabilityPluginApi" + }, + { + "id": "kibRemoteClustersPluginApi" + }, + { + "id": "kibReportingPluginApi" + }, + { + "id": "kibRollupPluginApi" + }, + { + "id": "kibRuntimeFieldsPluginApi" + }, + { + "id": "kibSavedObjectsManagementPluginApi" + }, + { + "id": "kibSavedObjectsTaggingOssPluginApi" + }, + { + "id": "kibSavedObjectsTaggingPluginApi" + }, + { + "id": "kibSavedObjectsPluginApi" + }, + { + "id": "kibScreenshottingPluginApi" + }, + { + "id": "kibSecuritySolutionPluginApi" + }, + { + "id": "kibSecurityPluginApi" + }, + { + "id": "kibSharePluginApi" + }, + { + "id": "kibSnapshotRestorePluginApi" + }, + { + "id": "kibSpacesPluginApi" + }, + { + "id": "kibStackAlertsPluginApi" + }, + { + "id": "kibTelemetryCollectionManagerPluginApi" + }, + { + "id": "kibTelemetryCollectionXpackPluginApi" + }, + { + "id": "kibTelemetryManagementSectionPluginApi" + }, + { + "id": "kibTelemetryPluginApi" + }, + { + "id": "kibUiActionsEnhancedPluginApi" + }, + { + "id": "kibUiActionsPluginApi" + }, + { + "id": "kibUrlForwardingPluginApi" + }, + { + "id": "kibUsageCollectionPluginApi" + }, + { + "id": "kibVisTypeTimeseriesPluginApi" + }, + { + "id": "kibVisualizationsPluginApi" + } ] }, { "label": "Operations", "items": [ - { "id": "kibDevDocsOpsOverview", "label": "Overview" }, + { + "id": "kibDevDocsOpsOverview", + "label": "Overview" + }, { "label": "CI", "items": [ - { "id": "kibDevDocsOpsCiStats" } + { + "id": "kibDevDocsOpsCiStats" + } ] }, { "label": "Build tooling", "items": [ - { "id": "kibDevDocsOpsOptimizer" }, - { "id": "kibDevDocsOpsBabelPreset" }, - { "id": "kibDevDocsOpsTypeSummarizer" }, - { "id": "kibDevDocsOpsBabelPluginSyntheticPackages" }, - { "id": "kibDevDocsOpsUiSharedDepsNpm" }, - { "id": "kibDevDocsOpsUiSharedDepsSrc" }, - { "id": "kibDevDocsOpsPluginDiscovery" } + { + "id": "kibDevDocsOpsOptimizer" + }, + { + "id": "kibDevDocsOpsBabelPreset" + }, + { + "id": "kibDevDocsOpsTypeSummarizer" + }, + { + "id": "kibDevDocsOpsBabelPluginSyntheticPackages" + }, + { + "id": "kibDevDocsOpsUiSharedDepsNpm" + }, + { + "id": "kibDevDocsOpsUiSharedDepsSrc" + }, + { + "id": "kibDevDocsOpsPluginDiscovery" + } ] }, { "label": "Linting & Validation", "items": [ - { "id": "kibDevDocsOpsEslintConfig" }, - { "id": "kibDevDocsOpsEslintPluginEslint" }, - { "id": "kibDevDocsOpsEslintWithTypes" }, - { "id": "kibDevDocsOpsEslintPluginImports" } + { + "id": "kibDevDocsOpsEslintConfig" + }, + { + "id": "kibDevDocsOpsEslintPluginEslint" + }, + { + "id": "kibDevDocsOpsEslintWithTypes" + }, + { + "id": "kibDevDocsOpsEslintPluginImports" + } ] }, { "label": "Utilities", "items": [ - { "id": "kibDevDocsToolingLog" }, - { "id": "kibDevDocsOpsJestSerializers" }, - { "id": "kibDevDocsOpsExpect" }, - { "id": "kibDevDocsOpsAmbientStorybookTypes" }, - { "id": "kibDevDocsOpsAmbientUiTypes" }, - { "id": "kibDevDocsOpsTestSubjSelector" }, - { "id": "kibDevDocsOpsBazelRunner" }, - { "id": "kibDevDocsOpsCliDevMode" }, - { "id": "kibDevDocsOpsEs" } + { + "id": "kibDevDocsToolingLog" + }, + { + "id": "kibDevDocsOpsJestSerializers" + }, + { + "id": "kibDevDocsOpsExpect" + }, + { + "id": "kibDevDocsOpsAmbientStorybookTypes" + }, + { + "id": "kibDevDocsOpsAmbientUiTypes" + }, + { + "id": "kibDevDocsOpsTestSubjSelector" + }, + { + "id": "kibDevDocsOpsBazelRunner" + }, + { + "id": "kibDevDocsOpsCliDevMode" + }, + { + "id": "kibDevDocsOpsEs" + } ] } ] } ] -} +} \ No newline at end of file diff --git a/package.json b/package.json index 79fa1c415cb56..f1dd078bd6cbd 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,11 @@ "@kbn/config": "link:bazel-bin/packages/kbn-config", "@kbn/config-mocks": "link:bazel-bin/packages/kbn-config-mocks", "@kbn/config-schema": "link:bazel-bin/packages/kbn-config-schema", + "@kbn/core-analytics-browser": "link:bazel-bin/packages/core/analytics/core-analytics-browser", + "@kbn/core-analytics-browser-internal": "link:bazel-bin/packages/core/analytics/core-analytics-browser-internal", + "@kbn/core-analytics-browser-mocks": "link:bazel-bin/packages/core/analytics/core-analytics-browser-mocks", "@kbn/core-base-browser-internal": "link:bazel-bin/packages/core/base/core-base-browser-internal", + "@kbn/core-base-browser-mocks": "link:bazel-bin/packages/core/base/core-base-browser-mocks", "@kbn/core-base-common": "link:bazel-bin/packages/core/base/core-base-common", "@kbn/core-base-common-internal": "link:bazel-bin/packages/core/base/core-base-common-internal", "@kbn/core-base-server-internal": "link:bazel-bin/packages/core/base/core-base-server-internal", @@ -659,8 +663,12 @@ "@types/kbn__config": "link:bazel-bin/packages/kbn-config/npm_module_types", "@types/kbn__config-mocks": "link:bazel-bin/packages/kbn-config-mocks/npm_module_types", "@types/kbn__config-schema": "link:bazel-bin/packages/kbn-config-schema/npm_module_types", + "@types/kbn__core-analytics-browser": "link:bazel-bin/packages/core/analytics/core-analytics-browser/npm_module_types", + "@types/kbn__core-analytics-browser-internal": "link:bazel-bin/packages/core/analytics/core-analytics-browser-internal/npm_module_types", + "@types/kbn__core-analytics-browser-mocks": "link:bazel-bin/packages/core/analytics/core-analytics-browser-mocks/npm_module_types", "@types/kbn__core-base-browser": "link:bazel-bin/packages/core/base/core-base-browser/npm_module_types", "@types/kbn__core-base-browser-internal": "link:bazel-bin/packages/core/base/core-base-browser-internal/npm_module_types", + "@types/kbn__core-base-browser-mocks": "link:bazel-bin/packages/core/base/core-base-browser-mocks/npm_module_types", "@types/kbn__core-base-common": "link:bazel-bin/packages/core/base/core-base-common/npm_module_types", "@types/kbn__core-base-common-internal": "link:bazel-bin/packages/core/base/core-base-common-internal/npm_module_types", "@types/kbn__core-base-server": "link:bazel-bin/packages/core/base/core-base-server/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index c21ac171a7849..3b897596cd49c 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -14,7 +14,11 @@ filegroup( "//packages/analytics/shippers/elastic_v3/common:build", "//packages/analytics/shippers/elastic_v3/server:build", "//packages/analytics/shippers/fullstory:build", + "//packages/core/analytics/core-analytics-browser-internal:build", + "//packages/core/analytics/core-analytics-browser-mocks:build", + "//packages/core/analytics/core-analytics-browser:build", "//packages/core/base/core-base-browser-internal:build", + "//packages/core/base/core-base-browser-mocks:build", "//packages/core/base/core-base-common-internal:build", "//packages/core/base/core-base-common:build", "//packages/core/base/core-base-server-internal:build", @@ -154,7 +158,11 @@ filegroup( "//packages/analytics/shippers/elastic_v3/common:build_types", "//packages/analytics/shippers/elastic_v3/server:build_types", "//packages/analytics/shippers/fullstory:build_types", + "//packages/core/analytics/core-analytics-browser-internal:build_types", + "//packages/core/analytics/core-analytics-browser-mocks:build_types", + "//packages/core/analytics/core-analytics-browser:build_types", "//packages/core/base/core-base-browser-internal:build_types", + "//packages/core/base/core-base-browser-mocks:build_types", "//packages/core/base/core-base-common-internal:build_types", "//packages/core/base/core-base-common:build_types", "//packages/core/base/core-base-server-internal:build_types", diff --git a/packages/core/analytics/core-analytics-browser-internal/BUILD.bazel b/packages/core/analytics/core-analytics-browser-internal/BUILD.bazel new file mode 100644 index 0000000000000..866e08370e490 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/BUILD.bazel @@ -0,0 +1,117 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-analytics-browser-internal" +PKG_REQUIRE_NAME = "@kbn/core-analytics-browser-internal" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + "src/**/*.tsx", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "@npm//rxjs", + "@npm//uuid", + "//packages/analytics/client", + "//packages/core/base/core-base-browser-mocks", + "//packages/core/injected-metadata/core-injected-metadata-browser-mocks", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//@types/uuid", + "@npm//rxjs", + "//packages/kbn-logging:npm_module_types", + "//packages/analytics/client:npm_module_types", + "//packages/core/base/core-base-browser-internal:npm_module_types", + "//packages/core/injected-metadata/core-injected-metadata-browser-internal:npm_module_types", + "//packages/core/analytics/core-analytics-browser:npm_module_types", + "//packages/core/base/core-base-browser-mocks:npm_module_types", + "//packages/core/injected-metadata/core-injected-metadata-browser-mocks:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + emit_declaration_only = True, + out_dir = "target_types", + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/analytics/core-analytics-browser-internal/README.md b/packages/core/analytics/core-analytics-browser-internal/README.md new file mode 100644 index 0000000000000..555d92ac3f753 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/README.md @@ -0,0 +1,3 @@ +# @kbn/core-analytics-browser-internal + +This package contains the internal types and implementation for Core's browser-side analytics service. diff --git a/packages/core/analytics/core-analytics-browser-internal/jest.config.js b/packages/core/analytics/core-analytics-browser-internal/jest.config.js new file mode 100644 index 0000000000000..b85010698be02 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/packages/core/analytics/core-analytics-browser-internal'], +}; diff --git a/packages/core/analytics/core-analytics-browser-internal/package.json b/packages/core/analytics/core-analytics-browser-internal/package.json new file mode 100644 index 0000000000000..143ecaaeb9428 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-analytics-browser-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/public/analytics/analytics_service.test.mocks.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts similarity index 100% rename from src/core/public/analytics/analytics_service.test.mocks.ts rename to packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts diff --git a/src/core/public/analytics/analytics_service.test.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts similarity index 94% rename from src/core/public/analytics/analytics_service.test.ts rename to packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts index e2298a79ff134..8ae572ae34528 100644 --- a/src/core/public/analytics/analytics_service.test.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts @@ -7,15 +7,16 @@ */ import { firstValueFrom, Observable } from 'rxjs'; +import { coreContextMock } from '@kbn/core-base-browser-mocks'; +import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; import { analyticsClientMock } from './analytics_service.test.mocks'; -import { coreMock, injectedMetadataServiceMock } from '../mocks'; import { AnalyticsService } from './analytics_service'; describe('AnalyticsService', () => { let analyticsService: AnalyticsService; beforeEach(() => { jest.clearAllMocks(); - analyticsService = new AnalyticsService(coreMock.createCoreContext()); + analyticsService = new AnalyticsService(coreContextMock.create()); }); test('should register some context providers on creation', async () => { expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledTimes(3); diff --git a/src/core/public/analytics/analytics_service.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts similarity index 93% rename from src/core/public/analytics/analytics_service.ts rename to packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts index b774109e79d61..580fbac92aa5d 100644 --- a/src/core/public/analytics/analytics_service.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts @@ -11,26 +11,11 @@ import type { AnalyticsClient } from '@kbn/analytics-client'; import { createAnalytics } from '@kbn/analytics-client'; import type { CoreContext } from '@kbn/core-base-browser-internal'; import type { InternalInjectedMetadataSetup } from '@kbn/core-injected-metadata-browser-internal'; +import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser'; import { trackClicks } from './track_clicks'; import { getSessionId } from './get_session_id'; import { createLogger } from './logger'; -/** - * Exposes the public APIs of the AnalyticsClient during the setup phase. - * {@link AnalyticsClient} - * @public - */ -export type AnalyticsServiceSetup = Omit; -/** - * Exposes the public APIs of the AnalyticsClient during the start phase - * {@link AnalyticsClient} - * @public - */ -export type AnalyticsServiceStart = Pick< - AnalyticsClient, - 'optIn' | 'reportEvent' | 'telemetryCounter$' ->; - /** @internal */ export interface AnalyticsServiceSetupDeps { injectedMetadata: InternalInjectedMetadataSetup; diff --git a/src/core/public/analytics/get_session_id.test.ts b/packages/core/analytics/core-analytics-browser-internal/src/get_session_id.test.ts similarity index 100% rename from src/core/public/analytics/get_session_id.test.ts rename to packages/core/analytics/core-analytics-browser-internal/src/get_session_id.test.ts diff --git a/src/core/public/analytics/get_session_id.ts b/packages/core/analytics/core-analytics-browser-internal/src/get_session_id.ts similarity index 100% rename from src/core/public/analytics/get_session_id.ts rename to packages/core/analytics/core-analytics-browser-internal/src/get_session_id.ts diff --git a/packages/core/analytics/core-analytics-browser-internal/src/index.ts b/packages/core/analytics/core-analytics-browser-internal/src/index.ts new file mode 100644 index 0000000000000..26fa9d359cf4f --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/src/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { AnalyticsServiceSetupDeps } from './analytics_service'; +export { AnalyticsService } from './analytics_service'; diff --git a/src/core/public/analytics/logger.test.ts b/packages/core/analytics/core-analytics-browser-internal/src/logger.test.ts similarity index 100% rename from src/core/public/analytics/logger.test.ts rename to packages/core/analytics/core-analytics-browser-internal/src/logger.test.ts diff --git a/src/core/public/analytics/logger.ts b/packages/core/analytics/core-analytics-browser-internal/src/logger.ts similarity index 100% rename from src/core/public/analytics/logger.ts rename to packages/core/analytics/core-analytics-browser-internal/src/logger.ts diff --git a/src/core/public/analytics/track_clicks.test.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts similarity index 100% rename from src/core/public/analytics/track_clicks.test.ts rename to packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts diff --git a/src/core/public/analytics/track_clicks.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts similarity index 100% rename from src/core/public/analytics/track_clicks.ts rename to packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts diff --git a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json new file mode 100644 index 0000000000000..97a3644c3c703 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "rootDir": "src", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/core/analytics/core-analytics-browser-mocks/BUILD.bazel b/packages/core/analytics/core-analytics-browser-mocks/BUILD.bazel new file mode 100644 index 0000000000000..e8622b0893a31 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-mocks/BUILD.bazel @@ -0,0 +1,106 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-analytics-browser-mocks" +PKG_REQUIRE_NAME = "@kbn/core-analytics-browser-mocks" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + "src/**/*.tsx", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/kbn-utility-types:npm_module_types", + "//packages/core/analytics/core-analytics-browser:npm_module_types", + "//packages/core/analytics/core-analytics-browser-internal:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + emit_declaration_only = True, + out_dir = "target_types", + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/analytics/core-analytics-browser-mocks/README.md b/packages/core/analytics/core-analytics-browser-mocks/README.md new file mode 100644 index 0000000000000..5add942622871 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-mocks/README.md @@ -0,0 +1,3 @@ +# @kbn/core-analytics-browser-mocks + +This package contains the mocks for Core's browser-side analytics service. diff --git a/packages/core/analytics/core-analytics-browser-mocks/jest.config.js b/packages/core/analytics/core-analytics-browser-mocks/jest.config.js new file mode 100644 index 0000000000000..50f956009a92f --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-mocks/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/packages/core/analytics/core-analytics-browser-mocks'], +}; diff --git a/packages/core/analytics/core-analytics-browser-mocks/package.json b/packages/core/analytics/core-analytics-browser-mocks/package.json new file mode 100644 index 0000000000000..ed5e35fb28e04 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-mocks/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-analytics-browser-mocks", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/public/analytics/analytics_service.mock.ts b/packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts similarity index 89% rename from src/core/public/analytics/analytics_service.mock.ts rename to packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts index 9037a756473c3..5bbf3b2e7e6b5 100644 --- a/src/core/public/analytics/analytics_service.mock.ts +++ b/packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts @@ -8,11 +8,8 @@ import { Subject } from 'rxjs'; import type { PublicMethodsOf } from '@kbn/utility-types'; -import type { - AnalyticsService, - AnalyticsServiceSetup, - AnalyticsServiceStart, -} from './analytics_service'; +import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser'; +import type { AnalyticsService } from '@kbn/core-analytics-browser-internal'; type AnalyticsServiceContract = PublicMethodsOf; diff --git a/packages/core/analytics/core-analytics-browser-mocks/src/index.ts b/packages/core/analytics/core-analytics-browser-mocks/src/index.ts new file mode 100644 index 0000000000000..0cf55aeb9a349 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-mocks/src/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { analyticsServiceMock } from './analytics_service.mock'; diff --git a/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json b/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json new file mode 100644 index 0000000000000..97a3644c3c703 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "rootDir": "src", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/core/analytics/core-analytics-browser/BUILD.bazel b/packages/core/analytics/core-analytics-browser/BUILD.bazel new file mode 100644 index 0000000000000..462de88c73c5d --- /dev/null +++ b/packages/core/analytics/core-analytics-browser/BUILD.bazel @@ -0,0 +1,104 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-analytics-browser" +PKG_REQUIRE_NAME = "@kbn/core-analytics-browser" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + "src/**/*.tsx", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/analytics/client:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + emit_declaration_only = True, + out_dir = "target_types", + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/analytics/core-analytics-browser/README.md b/packages/core/analytics/core-analytics-browser/README.md new file mode 100644 index 0000000000000..3d46f30137fb0 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser/README.md @@ -0,0 +1,3 @@ +# @kbn/core-analytics-browser + +This package contains the public types for Core's browser-side analytics service. diff --git a/packages/core/analytics/core-analytics-browser/jest.config.js b/packages/core/analytics/core-analytics-browser/jest.config.js new file mode 100644 index 0000000000000..c0589dab312f3 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/packages/core/analytics/core-analytics-browser'], +}; diff --git a/packages/core/analytics/core-analytics-browser/package.json b/packages/core/analytics/core-analytics-browser/package.json new file mode 100644 index 0000000000000..b53883055987c --- /dev/null +++ b/packages/core/analytics/core-analytics-browser/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-analytics-browser", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/public/analytics/index.ts b/packages/core/analytics/core-analytics-browser/src/index.ts similarity index 53% rename from src/core/public/analytics/index.ts rename to packages/core/analytics/core-analytics-browser/src/index.ts index 0524f7ffe73a2..04f8b150fd9a4 100644 --- a/src/core/public/analytics/index.ts +++ b/packages/core/analytics/core-analytics-browser/src/index.ts @@ -6,20 +6,4 @@ * Side Public License, v 1. */ -export { AnalyticsService } from './analytics_service'; -export type { AnalyticsServiceSetup, AnalyticsServiceStart } from './analytics_service'; - -export type { - AnalyticsClient, - Event, - EventContext, - EventType, - EventTypeOpts, - IShipper, - ShipperClassConstructor, - OptInConfig, - ContextProviderOpts, - TelemetryCounter, -} from '@kbn/analytics-client'; - -export { TelemetryCounterType } from '@kbn/analytics-client'; +export type { AnalyticsServiceSetup, AnalyticsServiceStart } from './types'; diff --git a/packages/core/analytics/core-analytics-browser/src/types.ts b/packages/core/analytics/core-analytics-browser/src/types.ts new file mode 100644 index 0000000000000..e18a5faba8fbc --- /dev/null +++ b/packages/core/analytics/core-analytics-browser/src/types.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 type { AnalyticsClient } from '@kbn/analytics-client'; + +/** + * Exposes the public APIs of the AnalyticsClient during the setup phase. + * {@link AnalyticsClient} + * @public + */ +export type AnalyticsServiceSetup = Omit; + +/** + * Exposes the public APIs of the AnalyticsClient during the start phase + * {@link AnalyticsClient} + * @public + */ +export type AnalyticsServiceStart = Pick< + AnalyticsClient, + 'optIn' | 'reportEvent' | 'telemetryCounter$' +>; diff --git a/packages/core/analytics/core-analytics-browser/tsconfig.json b/packages/core/analytics/core-analytics-browser/tsconfig.json new file mode 100644 index 0000000000000..97a3644c3c703 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "rootDir": "src", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/core/base/core-base-browser-mocks/BUILD.bazel b/packages/core/base/core-base-browser-mocks/BUILD.bazel new file mode 100644 index 0000000000000..7b9a2a3027a2f --- /dev/null +++ b/packages/core/base/core-base-browser-mocks/BUILD.bazel @@ -0,0 +1,104 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-base-browser-mocks" +PKG_REQUIRE_NAME = "@kbn/core-base-browser-mocks" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + "src/**/*.tsx", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/base/core-base-browser-internal:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + emit_declaration_only = True, + out_dir = "target_types", + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/base/core-base-browser-mocks/README.md b/packages/core/base/core-base-browser-mocks/README.md new file mode 100644 index 0000000000000..3803099ba73a4 --- /dev/null +++ b/packages/core/base/core-base-browser-mocks/README.md @@ -0,0 +1,3 @@ +# @kbn/core-base-browser-mocks + +This package contains the base Core browser mocks. diff --git a/packages/core/base/core-base-browser-mocks/jest.config.js b/packages/core/base/core-base-browser-mocks/jest.config.js new file mode 100644 index 0000000000000..7a6577f658915 --- /dev/null +++ b/packages/core/base/core-base-browser-mocks/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/packages/core/base/core-base-browser-mocks'], +}; diff --git a/packages/core/base/core-base-browser-mocks/package.json b/packages/core/base/core-base-browser-mocks/package.json new file mode 100644 index 0000000000000..706a7f6ef4d81 --- /dev/null +++ b/packages/core/base/core-base-browser-mocks/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-base-browser-mocks", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/base/core-base-browser-mocks/src/core_context.mock.ts b/packages/core/base/core-base-browser-mocks/src/core_context.mock.ts new file mode 100644 index 0000000000000..e43efe1246ffa --- /dev/null +++ b/packages/core/base/core-base-browser-mocks/src/core_context.mock.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 type { CoreContext } from '@kbn/core-base-browser-internal'; + +function createCoreContext({ production = false }: { production?: boolean } = {}): CoreContext { + return { + coreId: Symbol('core context mock'), + env: { + mode: { + dev: !production, + name: production ? 'production' : 'development', + prod: production, + }, + packageInfo: { + version: 'version', + branch: 'branch', + buildNum: 100, + buildSha: 'buildSha', + dist: false, + }, + }, + }; +} + +export const coreContextMock = { + create: createCoreContext, +}; diff --git a/packages/core/base/core-base-browser-mocks/src/index.ts b/packages/core/base/core-base-browser-mocks/src/index.ts new file mode 100644 index 0000000000000..1ac88d5de594c --- /dev/null +++ b/packages/core/base/core-base-browser-mocks/src/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { coreContextMock } from './core_context.mock'; diff --git a/packages/core/base/core-base-browser-mocks/tsconfig.json b/packages/core/base/core-base-browser-mocks/tsconfig.json new file mode 100644 index 0000000000000..97a3644c3c703 --- /dev/null +++ b/packages/core/base/core-base-browser-mocks/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "rootDir": "src", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/core/base/core-base-server-mocks/README.md b/packages/core/base/core-base-server-mocks/README.md index a5ece8abc3d7a..daf483e7014a3 100644 --- a/packages/core/base/core-base-server-mocks/README.md +++ b/packages/core/base/core-base-server-mocks/README.md @@ -1,3 +1,3 @@ # @kbn/core-base-server-mocks -Empty package generated by @kbn/generate +This package contains the base Core server mocks. diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 0f4ce67cd2cad..b45e359355911 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -343,6 +343,9 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { trustedApps: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/trusted-apps-ov.html`, eventFilters: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/event-filters.html`, blocklist: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/blocklist.html`, + policyResponseTroubleshooting: { + full_disk_access: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/deploy-elastic-endpoint.html#enable-fda-endpoint`, + }, }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index e3c6cf786d681..c6d084fabf286 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -248,6 +248,9 @@ export interface DocLinks { readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; + readonly policyResponseTroubleshooting: { + full_disk_access: string; + }; }; readonly query: { readonly eql: string; diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel index d5a56d37fde45..42ccc6cdd4641 100644 --- a/packages/kbn-test/BUILD.bazel +++ b/packages/kbn-test/BUILD.bazel @@ -65,6 +65,7 @@ RUNTIME_DEPS = [ "@npm//jest-styled-components", "@npm//joi", "@npm//js-yaml", + "@npm//minimatch", "@npm//mustache", "@npm//normalize-path", "@npm//prettier", @@ -113,6 +114,7 @@ TYPES_DEPS = [ "@npm//@types/js-yaml", "@npm//@types/joi", "@npm//@types/lodash", + "@npm//@types/minimatch", "@npm//@types/mustache", "@npm//@types/normalize-path", "@npm//@types/node", diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts b/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts index 427bb159f542c..346e75e965353 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts @@ -7,16 +7,27 @@ */ import execa from 'execa'; import { readFileSync } from 'fs'; -import { resolve } from 'path'; +import Path from 'path'; import { REPO_ROOT } from '@kbn/utils'; import { run } from '@kbn/dev-cli-runner'; import { createFailError } from '@kbn/dev-cli-errors'; import { FTR_CONFIGS_MANIFEST_PATHS } from './ftr_configs_manifest'; +const THIS_PATH = Path.resolve( + REPO_ROOT, + 'packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts' +); +const THIS_REL = Path.relative(REPO_ROOT, THIS_PATH); + +const IGNORED_PATHS = [ + THIS_PATH, + Path.resolve(REPO_ROOT, 'packages/kbn-test/src/jest/run_check_jest_configs_cli.ts'), +]; + export async function runCheckFtrConfigsCli() { run( - async () => { + async ({ log }) => { const { stdout } = await execa('git', [ 'ls-tree', '--full-tree', @@ -28,10 +39,10 @@ export async function runCheckFtrConfigsCli() { const files = stdout .trim() .split('\n') - .map((file) => resolve(REPO_ROOT, file)); + .map((file) => Path.resolve(REPO_ROOT, file)); const possibleConfigs = files.filter((file) => { - if (file.includes('run_check_ftr_configs_cli.ts')) { + if (IGNORED_PATHS.includes(file)) { return false; } @@ -56,12 +67,15 @@ export async function runCheckFtrConfigsCli() { .match(/(testRunner)|(testFiles)/); }); - for (const config of possibleConfigs) { - if (!FTR_CONFIGS_MANIFEST_PATHS.includes(config)) { - throw createFailError( - `${config} looks like a new FTR config. Please add it to .buildkite/ftr_configs.yml. If it's not an FTR config, please contact #kibana-operations` - ); - } + const invalid = possibleConfigs.filter((path) => !FTR_CONFIGS_MANIFEST_PATHS.includes(path)); + if (invalid.length) { + const invalidList = invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - '); + log.error( + `The following files look like FTR configs which are not listed in .buildkite/ftr_configs.yml:\n - ${invalidList}` + ); + throw createFailError( + `Please add the listed paths to .buildkite/ftr_configs.yml. If it's not an FTR config, you can add it to the IGNORED_PATHS in ${THIS_REL} or contact #kibana-operations` + ); } }, { diff --git a/packages/kbn-test/src/jest/configs/__snapshots__/jest_configs.test.ts.snap b/packages/kbn-test/src/jest/configs/__snapshots__/jest_configs.test.ts.snap deleted file mode 100644 index 8de7ea9a41367..0000000000000 --- a/packages/kbn-test/src/jest/configs/__snapshots__/jest_configs.test.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`jestConfigs #expected throws if test file outside root 1`] = `[Error: Test file (bad.test.js) can not exist outside roots (packages/b/nested, packages). Move it to a root or configure additional root.]`; diff --git a/packages/kbn-test/src/jest/configs/find_missing_config_files.test.ts b/packages/kbn-test/src/jest/configs/find_missing_config_files.test.ts new file mode 100644 index 0000000000000..93365157692f5 --- /dev/null +++ b/packages/kbn-test/src/jest/configs/find_missing_config_files.test.ts @@ -0,0 +1,84 @@ +/* + * 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 mockFs from 'mock-fs'; + +import { GroupedTestFiles } from './group_test_files'; +import { + findMissingConfigFiles, + INTEGRATION_CONFIG_NAME, + UNIT_CONFIG_NAME, +} from './find_missing_config_files'; + +beforeEach(async () => { + mockFs({ + '/packages': { + a: { + [UNIT_CONFIG_NAME]: '{}', + }, + }, + '/src': { + c: { + [UNIT_CONFIG_NAME]: '{}', + }, + d: { + [INTEGRATION_CONFIG_NAME]: '{}', + }, + }, + }); +}); + +afterEach(mockFs.restore); + +it('returns a list of config files which are not found on disk, or are not files', async () => { + const groups: GroupedTestFiles = new Map([ + [ + { + type: 'pkg', + path: '/packages/a', + }, + { + unit: ['/packages/a/test.js'], + }, + ], + [ + { + type: 'pkg', + path: '/packages/b', + }, + { + integration: ['/packages/b/integration_tests/test.js'], + }, + ], + [ + { + type: 'src', + path: '/src/c', + }, + { + unit: ['/src/c/test.js'], + integration: ['/src/c/integration_tests/test.js'], + }, + ], + [ + { + type: 'src', + path: '/src/d', + }, + { + unit: ['/src/d/test.js'], + }, + ], + ]); + + await expect(findMissingConfigFiles(groups)).resolves.toEqual([ + '/packages/b/jest.integration.config.js', + '/src/c/jest.integration.config.js', + '/src/d/jest.config.js', + ]); +}); diff --git a/packages/kbn-test/src/jest/configs/find_missing_config_files.ts b/packages/kbn-test/src/jest/configs/find_missing_config_files.ts new file mode 100644 index 0000000000000..b612643360651 --- /dev/null +++ b/packages/kbn-test/src/jest/configs/find_missing_config_files.ts @@ -0,0 +1,49 @@ +/* + * 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 Fsp from 'fs/promises'; +import Path from 'path'; + +import { asyncMapWithLimit } from '@kbn/std'; + +import { GroupedTestFiles } from './group_test_files'; + +export const UNIT_CONFIG_NAME = 'jest.config.js'; +export const INTEGRATION_CONFIG_NAME = 'jest.integration.config.js'; + +async function isFile(path: string) { + try { + const stats = await Fsp.stat(path); + return stats.isFile(); + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } + + throw error; + } +} + +export async function findMissingConfigFiles(groups: GroupedTestFiles) { + const expectedConfigs = [...groups].flatMap(([owner, tests]) => { + const configs: string[] = []; + if (tests.unit?.length) { + configs.push(Path.resolve(owner.path, UNIT_CONFIG_NAME)); + } + if (tests.integration?.length) { + configs.push(Path.resolve(owner.path, INTEGRATION_CONFIG_NAME)); + } + return configs; + }); + + return ( + await asyncMapWithLimit(expectedConfigs, 20, async (path) => + !(await isFile(path)) ? [path] : [] + ) + ).flat(); +} diff --git a/packages/kbn-test/src/jest/configs/get_all_test_files.ts b/packages/kbn-test/src/jest/configs/get_all_test_files.ts new file mode 100644 index 0000000000000..695ec07eaf2dc --- /dev/null +++ b/packages/kbn-test/src/jest/configs/get_all_test_files.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 Path from 'path'; + +import execa from 'execa'; +import minimatch from 'minimatch'; +import { REPO_ROOT } from '@kbn/utils'; + +// @ts-expect-error jest-preset is necessarily a JS file +import { testMatch } from '../../../jest-preset'; + +export async function getAllTestFiles() { + const proc = await execa('git', ['ls-files', '-co', '--exclude-standard'], { + cwd: REPO_ROOT, + stdio: ['ignore', 'pipe', 'pipe'], + buffer: true, + }); + + const patterns: RegExp[] = testMatch.map((p: string) => minimatch.makeRe(p)); + + return proc.stdout + .split('\n') + .flatMap((l) => l.trim() || []) + .filter((l) => patterns.some((p) => p.test(l))) + .map((p) => Path.resolve(REPO_ROOT, p)); +} diff --git a/packages/kbn-test/src/jest/configs/group_test_files.test.ts b/packages/kbn-test/src/jest/configs/group_test_files.test.ts new file mode 100644 index 0000000000000..640100d86f402 --- /dev/null +++ b/packages/kbn-test/src/jest/configs/group_test_files.test.ts @@ -0,0 +1,117 @@ +/* + * 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 { groupTestFiles } from './group_test_files'; + +it('properly assigns tests to src roots and packages based on location', () => { + const grouped = groupTestFiles( + [ + '/packages/pkg1/test.js', + '/packages/pkg1/integration_tests/test.js', + '/packages/pkg2/integration_tests/test.js', + '/packages/group/pkg3/test.js', + '/packages/group/subgroup/pkg4/test.js', + '/packages/group/subgroup/pkg4/integration_tests/test.js', + '/src/a/integration_tests/test.js', + '/src/b/test.js', + '/tests/b/test.js', + '/src/group/c/test.js', + '/src/group/c/integration_tests/test.js', + '/src/group/subgroup/d/test.js', + '/src/group/subgroup/d/integration_tests/test.js', + ], + ['/src/group/subgroup', '/src/group', '/src'], + ['/packages/pkg1', '/packages/pkg2', '/packages/group/pkg3', '/packages/group/subgroup/pkg4'] + ); + + expect(grouped).toMatchInlineSnapshot(` + Object { + "grouped": Map { + Object { + "path": "/packages/pkg1", + "type": "pkg", + } => Object { + "integration": Array [ + "/packages/pkg1/integration_tests/test.js", + ], + "unit": Array [ + "/packages/pkg1/test.js", + ], + }, + Object { + "path": "/packages/pkg2", + "type": "pkg", + } => Object { + "integration": Array [ + "/packages/pkg2/integration_tests/test.js", + ], + }, + Object { + "path": "/packages/group/pkg3", + "type": "pkg", + } => Object { + "unit": Array [ + "/packages/group/pkg3/test.js", + ], + }, + Object { + "path": "/packages/group/subgroup/pkg4", + "type": "pkg", + } => Object { + "integration": Array [ + "/packages/group/subgroup/pkg4/integration_tests/test.js", + ], + "unit": Array [ + "/packages/group/subgroup/pkg4/test.js", + ], + }, + Object { + "path": "/src/a", + "type": "src", + } => Object { + "integration": Array [ + "/src/a/integration_tests/test.js", + ], + }, + Object { + "path": "/src/b", + "type": "src", + } => Object { + "unit": Array [ + "/src/b/test.js", + ], + }, + Object { + "path": "/src/group/c", + "type": "src", + } => Object { + "integration": Array [ + "/src/group/c/integration_tests/test.js", + ], + "unit": Array [ + "/src/group/c/test.js", + ], + }, + Object { + "path": "/src/group/subgroup/d", + "type": "src", + } => Object { + "integration": Array [ + "/src/group/subgroup/d/integration_tests/test.js", + ], + "unit": Array [ + "/src/group/subgroup/d/test.js", + ], + }, + }, + "invalid": Array [ + "/tests/b/test.js", + ], + } + `); +}); diff --git a/packages/kbn-test/src/jest/configs/group_test_files.ts b/packages/kbn-test/src/jest/configs/group_test_files.ts new file mode 100644 index 0000000000000..90d68e1f125ee --- /dev/null +++ b/packages/kbn-test/src/jest/configs/group_test_files.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 Path from 'path'; +import isPathInside from 'is-path-inside'; + +export interface Owner { + type: 'pkg' | 'src'; + path: string; +} +export interface TestGroups { + unit?: string[]; + integration?: string[]; +} +export type GroupedTestFiles = Map; + +/** + * Consumes the list of test files discovered along with the srcRoots and packageDirs to assign + * each test file to a specific "owner", either a package or src directory, were we will eventually + * expect to find relevant config files + */ +export function groupTestFiles( + testFiles: string[], + srcRoots: string[], + packageDirs: string[] +): { grouped: GroupedTestFiles; invalid: string[] } { + const invalid: string[] = []; + const testsByOwner = new Map(); + + for (const testFile of testFiles) { + const type = testFile.includes('integration_tests') ? 'integration' : 'unit'; + let ownerKey; + // try to match the test file to a package first + for (const pkgDir of packageDirs) { + if (isPathInside(testFile, pkgDir)) { + ownerKey = `pkg:${pkgDir}`; + break; + } + } + + // try to match the test file to a src root + if (!ownerKey) { + for (const srcRoot of srcRoots) { + if (isPathInside(testFile, srcRoot)) { + const segments = Path.relative(srcRoot, testFile).split(Path.sep); + if (segments.length > 1) { + ownerKey = `src:${Path.join(srcRoot, segments[0])}`; + break; + } + + // if there are <= 1 relative segments then this file is directly in the "root" + // which isn't supported, roots are directories which have test dirs in them. + // We should ignore this match and match a higher-level root if possible + continue; + } + } + } + + if (!ownerKey) { + invalid.push(testFile); + continue; + } + + const tests = testsByOwner.get(ownerKey); + if (!tests) { + testsByOwner.set(ownerKey, { [type]: [testFile] }); + } else { + const byType = tests[type]; + if (!byType) { + tests[type] = [testFile]; + } else { + byType.push(testFile); + } + } + } + + return { + invalid, + grouped: new Map( + [...testsByOwner.entries()].map(([key, tests]) => { + const [type, ...path] = key.split(':'); + const owner: Owner = { + type: type as Owner['type'], + path: path.join(':'), + }; + return [owner, tests]; + }) + ), + }; +} diff --git a/packages/kbn-test/src/jest/configs/index.ts b/packages/kbn-test/src/jest/configs/index.ts index 155c385ec761d..b66cb8d89cad9 100644 --- a/packages/kbn-test/src/jest/configs/index.ts +++ b/packages/kbn-test/src/jest/configs/index.ts @@ -6,4 +6,10 @@ * Side Public License, v 1. */ -export * from './jest_configs'; +export { getAllTestFiles } from './get_all_test_files'; +export { groupTestFiles } from './group_test_files'; +export { + findMissingConfigFiles, + UNIT_CONFIG_NAME, + INTEGRATION_CONFIG_NAME, +} from './find_missing_config_files'; diff --git a/packages/kbn-test/src/jest/configs/jest_configs.test.ts b/packages/kbn-test/src/jest/configs/jest_configs.test.ts deleted file mode 100644 index 4d68733f58d32..0000000000000 --- a/packages/kbn-test/src/jest/configs/jest_configs.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 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 mockFs from 'mock-fs'; -import fs from 'fs'; - -import { JestConfigs } from './jest_configs'; - -describe('jestConfigs', () => { - let jestConfigs: JestConfigs; - - beforeEach(async () => { - mockFs({ - '/kbn-test/packages': { - a: { - 'jest.config.js': '', - 'a_first.test.js': '', - 'a_second.test.js': '', - }, - b: { - 'b.test.js': '', - integration_tests: { - 'b_integration.test.js': '', - }, - nested: { - d: { - 'd.test.js': '', - }, - }, - }, - c: { - 'jest.integration.config.js': '', - integration_tests: { - 'c_integration.test.js': '', - }, - }, - }, - }); - jestConfigs = new JestConfigs('/kbn-test', ['packages/b/nested', 'packages']); - }); - - afterEach(mockFs.restore); - - describe('#files', () => { - it('lists unit test files', async () => { - const files = await jestConfigs.files('unit'); - expect(files).toEqual([ - 'packages/a/a_first.test.js', - 'packages/a/a_second.test.js', - 'packages/b/b.test.js', - 'packages/b/nested/d/d.test.js', - ]); - }); - - it('lists integration test files', async () => { - const files = await jestConfigs.files('integration'); - expect(files).toEqual([ - 'packages/b/integration_tests/b_integration.test.js', - 'packages/c/integration_tests/c_integration.test.js', - ]); - }); - }); - - describe('#expected', () => { - it('expects unit config files', async () => { - const files = await jestConfigs.expected('unit'); - expect(files).toEqual([ - 'packages/a/jest.config.js', - 'packages/b/jest.config.js', - 'packages/b/nested/d/jest.config.js', - ]); - }); - - it('expects integration config files', async () => { - const files = await jestConfigs.expected('integration'); - expect(files).toEqual([ - 'packages/b/jest.integration.config.js', - 'packages/c/jest.integration.config.js', - ]); - }); - - it('throws if test file outside root', async () => { - fs.writeFileSync('/kbn-test/bad.test.js', ''); - await expect(() => jestConfigs.expected('unit')).rejects.toMatchSnapshot(); - }); - }); - - describe('#existing', () => { - it('lists existing unit test config files', async () => { - const files = await jestConfigs.existing('unit'); - expect(files).toEqual(['packages/a/jest.config.js']); - }); - - it('lists existing integration test config files', async () => { - const files = await jestConfigs.existing('integration'); - expect(files).toEqual(['packages/c/jest.integration.config.js']); - }); - }); - - describe('#missing', () => { - it('lists existing unit test config files', async () => { - const files = await jestConfigs.missing('unit'); - expect(files).toEqual(['packages/b/jest.config.js', 'packages/b/nested/d/jest.config.js']); - }); - - it('lists existing integration test config files', async () => { - const files = await jestConfigs.missing('integration'); - expect(files).toEqual(['packages/b/jest.integration.config.js']); - }); - }); -}); diff --git a/packages/kbn-test/src/jest/configs/jest_configs.ts b/packages/kbn-test/src/jest/configs/jest_configs.ts deleted file mode 100644 index 39b284854826f..0000000000000 --- a/packages/kbn-test/src/jest/configs/jest_configs.ts +++ /dev/null @@ -1,86 +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 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 path from 'path'; -import globby from 'globby'; - -// @ts-ignore -import { testMatch } from '../../../jest-preset'; - -export const CONFIG_NAMES = { - unit: 'jest.config.js', - integration: 'jest.integration.config.js', -}; - -export class JestConfigs { - cwd: string; - roots: string[]; - allFiles: string[] | undefined; - - constructor(cwd: string, roots: string[]) { - this.cwd = cwd; - // sort roots by length so when we use `file.startsWith()` we will find the most specific root first - this.roots = roots.slice().sort((a, b) => b.length - a.length); - } - - async files(type: 'unit' | 'integration') { - if (!this.allFiles) { - this.allFiles = await globby(testMatch, { - gitignore: true, - cwd: this.cwd, - }); - } - - return this.allFiles.filter((f) => - type === 'integration' ? f.includes('integration_tests') : !f.includes('integration_tests') - ); - } - - async expected(type: 'unit' | 'integration') { - const filesForType = await this.files(type); - const directories: Set = new Set(); - - filesForType.forEach((file) => { - const root = this.roots.find((r) => file.startsWith(r)); - - if (root) { - const splitPath = file.substring(root.length).split(path.sep); - - if (splitPath.length > 2) { - const name = splitPath[1]; - directories.add([root, name].join(path.sep)); - } - } else { - throw new Error( - `Test file (${file}) can not exist outside roots (${this.roots.join( - ', ' - )}). Move it to a root or configure additional root.` - ); - } - }); - - return [...directories].map((d) => [d, CONFIG_NAMES[type]].join(path.sep)); - } - - async existing(type: 'unit' | 'integration') { - return await globby(`**/${CONFIG_NAMES[type]}`, { - gitignore: true, - cwd: this.cwd, - }); - } - - async missing(type: 'unit' | 'integration') { - const expectedConfigs = await this.expected(type); - const existingConfigs = await this.existing(type); - return await expectedConfigs.filter((x) => !existingConfigs.includes(x)); - } - - async allMissing() { - return (await this.missing('unit')).concat(await this.missing('integration')); - } -} diff --git a/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts b/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts index 6ada077c842e7..c2672b02beced 100644 --- a/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts +++ b/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts @@ -6,16 +6,21 @@ * Side Public License, v 1. */ -import { writeFileSync } from 'fs'; -import path from 'path'; +import Fsp from 'fs/promises'; +import Path from 'path'; import Mustache from 'mustache'; import { run } from '@kbn/dev-cli-runner'; import { createFailError } from '@kbn/dev-cli-errors'; import { REPO_ROOT } from '@kbn/utils'; -import { getAllRepoRelativeBazelPackageDirs } from '@kbn/bazel-packages'; +import { discoverBazelPackageLocations } from '@kbn/bazel-packages'; -import { JestConfigs, CONFIG_NAMES } from './configs'; +import { + getAllTestFiles, + groupTestFiles, + findMissingConfigFiles, + UNIT_CONFIG_NAME, +} from './configs'; const unitTestingTemplate: string = `module.exports = { preset: '@kbn/test/jest_node', @@ -40,15 +45,31 @@ const roots: string[] = [ 'test', 'src/core', 'src', - ...getAllRepoRelativeBazelPackageDirs(), -]; +].map((rel) => Path.resolve(REPO_ROOT, rel)); export async function runCheckJestConfigsCli() { run( async ({ flags: { fix = false }, log }) => { - const jestConfigs = new JestConfigs(REPO_ROOT, roots); + const packageDirs = [ + ...discoverBazelPackageLocations(REPO_ROOT), + // kbn-pm is a weird package currently and needs to be added explicitly + Path.resolve(REPO_ROOT, 'packages/kbn-pm'), + ]; - const missing = await jestConfigs.allMissing(); + const testFiles = await getAllTestFiles(); + const { grouped, invalid } = groupTestFiles(testFiles, roots, packageDirs); + + if (invalid.length) { + const paths = invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - '); + log.error( + `The following test files exist outside packages or pre-defined roots:\n - ${paths}` + ); + throw createFailError( + `Move the above files a pre-defined test root, a package, or configure an additional root to handle this file.` + ); + } + + const missing = await findMissingConfigFiles(grouped); if (missing.length) { log.error( @@ -60,20 +81,21 @@ export async function runCheckJestConfigsCli() { ); if (fix) { - missing.forEach((file) => { - const template = file.endsWith(CONFIG_NAMES.unit) - ? unitTestingTemplate - : integrationTestingTemplate; + for (const file of missing) { + const template = + Path.basename(file) === UNIT_CONFIG_NAME + ? unitTestingTemplate + : integrationTestingTemplate; - const modulePath = path.dirname(file); + const modulePath = Path.dirname(file); const content = Mustache.render(template, { - relToRoot: path.relative(modulePath, '.'), + relToRoot: Path.relative(modulePath, REPO_ROOT), modulePath, }); - writeFileSync(file, content); + await Fsp.writeFile(file, content); log.info('created %s', file); - }); + } } else { throw createFailError( `Run 'node scripts/check_jest_configs --fix' to create the missing config files` @@ -86,8 +108,8 @@ export async function runCheckJestConfigsCli() { flags: { boolean: ['fix'], help: ` - --fix Attempt to create missing config files - `, + --fix Attempt to create missing config files + `, }, } ); diff --git a/src/core/public/core_system.test.mocks.ts b/src/core/public/core_system.test.mocks.ts index 417828fc94118..e2b9d361abe85 100644 --- a/src/core/public/core_system.test.mocks.ts +++ b/src/core/public/core_system.test.mocks.ts @@ -9,6 +9,7 @@ import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; import { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks'; import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; import { applicationServiceMock } from './application/application_service.mock'; import { chromeServiceMock } from './chrome/chrome_service.mock'; import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; @@ -21,13 +22,12 @@ import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { renderingServiceMock } from './rendering/rendering_service.mock'; import { integrationsServiceMock } from './integrations/integrations_service.mock'; import { coreAppMock } from './core_app/core_app.mock'; -import { analyticsServiceMock } from './analytics/analytics_service.mock'; export const analyticsServiceStartMock = analyticsServiceMock.createAnalyticsServiceStart(); export const MockAnalyticsService = analyticsServiceMock.create(); MockAnalyticsService.start.mockReturnValue(analyticsServiceStartMock); export const AnalyticsServiceConstructor = jest.fn().mockReturnValue(MockAnalyticsService); -jest.doMock('./analytics', () => ({ +jest.doMock('@kbn/core-analytics-browser-internal', () => ({ AnalyticsService: AnalyticsServiceConstructor, })); diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 6e0d9f3843689..b49849d84eccd 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -15,6 +15,8 @@ import { } from '@kbn/core-injected-metadata-browser-internal'; import { DocLinksService } from '@kbn/core-doc-links-browser-internal'; import { ThemeService } from '@kbn/core-theme-browser-internal'; +import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; +import { AnalyticsService } from '@kbn/core-analytics-browser-internal'; import { CoreSetup, CoreStart } from '.'; import { ChromeService } from './chrome'; import { FatalErrorsService, FatalErrorsSetup } from './fatal_errors'; @@ -32,8 +34,6 @@ import { DeprecationsService } from './deprecations'; import { CoreApp } from './core_app'; import type { InternalApplicationSetup, InternalApplicationStart } from './application/types'; import { ExecutionContextService } from './execution_context'; -import type { AnalyticsServiceSetup } from './analytics'; -import { AnalyticsService } from './analytics'; import { fetchOptionalMemoryInfo } from './fetch_optional_memory_info'; interface Params { diff --git a/src/core/public/execution_context/execution_context_service.test.ts b/src/core/public/execution_context/execution_context_service.test.ts index 5c8f8bfae89f8..4556df6c873ad 100644 --- a/src/core/public/execution_context/execution_context_service.test.ts +++ b/src/core/public/execution_context/execution_context_service.test.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ import { BehaviorSubject, firstValueFrom } from 'rxjs'; +import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; +import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; import { ExecutionContextService, ExecutionContextSetup } from './execution_context_service'; -import type { AnalyticsServiceSetup } from '../analytics'; -import { analyticsServiceMock } from '../analytics/analytics_service.mock'; describe('ExecutionContextService', () => { let execContext: ExecutionContextSetup; diff --git a/src/core/public/execution_context/execution_context_service.ts b/src/core/public/execution_context/execution_context_service.ts index 045be76a43cf0..af44355a161a0 100644 --- a/src/core/public/execution_context/execution_context_service.ts +++ b/src/core/public/execution_context/execution_context_service.ts @@ -9,7 +9,7 @@ import { compact, isEqual, isUndefined, omitBy } from 'lodash'; import { BehaviorSubject, Observable, Subscription, map } from 'rxjs'; import type { CoreService } from '@kbn/core-base-browser-internal'; -import { AnalyticsServiceSetup } from '../analytics'; +import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; import { KibanaExecutionContext } from '../../types'; // Should be exported from elastic/apm-rum diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 27a7a9b0a005d..b32d872c40acf 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -32,6 +32,7 @@ import type { } from '@kbn/core-injected-metadata-browser'; import { DocLinksStart } from '@kbn/core-doc-links-browser'; import type { ThemeServiceSetup, ThemeServiceStart } from '@kbn/core-theme-browser'; +import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser'; import { ChromeBadge, ChromeBreadcrumb, @@ -65,7 +66,6 @@ import { ApplicationSetup, Capabilities, ApplicationStart } from './application' import { SavedObjectsStart } from './saved_objects'; import { DeprecationsServiceStart } from './deprecations'; import { ExecutionContextSetup, ExecutionContextStart } from './execution_context'; -import type { AnalyticsServiceSetup, AnalyticsServiceStart } from './analytics'; export type { PackageInfo, @@ -78,9 +78,8 @@ export type { CoreSystem } from './core_system'; export { DEFAULT_APP_CATEGORIES, APP_WRAPPER_CLASS } from '../utils'; export type { AppCategory, UiSettingsParams, UserProvidedValues, UiSettingsType } from '../types'; +export type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser'; export type { - AnalyticsServiceSetup, - AnalyticsServiceStart, AnalyticsClient, Event, EventContext, @@ -91,8 +90,8 @@ export type { OptInConfig, ContextProviderOpts, TelemetryCounter, -} from './analytics'; -export { TelemetryCounterType } from './analytics'; +} from '@kbn/analytics-client'; +export { TelemetryCounterType } from '@kbn/analytics-client'; export { AppNavLinkStatus, AppStatus, ScopedHistory } from './application'; export type { diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 5f573453f5d0c..6493bd7420f11 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -7,17 +7,16 @@ */ import { createMemoryHistory } from 'history'; -import type { CoreContext } from '@kbn/core-base-browser-internal'; import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; import { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks'; import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { coreContextMock } from '@kbn/core-base-browser-mocks'; +import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; // Only import types from '.' to avoid triggering default Jest mocks. import { PluginInitializerContext, AppMountParameters } from '.'; // Import values from their individual modules instead. import { ScopedHistory } from './application'; - -import { analyticsServiceMock } from './analytics/analytics_service.mock'; import { applicationServiceMock } from './application/application_service.mock'; import { chromeServiceMock } from './chrome/chrome_service.mock'; import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; @@ -33,9 +32,9 @@ import { executionContextServiceMock } from './execution_context/execution_conte export { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; export { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks'; export { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +export { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; export { chromeServiceMock } from './chrome/chrome_service.mock'; export { executionContextServiceMock } from './execution_context/execution_context_service.mock'; -export { analyticsServiceMock } from './analytics/analytics_service.mock'; export { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; export { httpServiceMock } from './http/http_service.mock'; export { i18nServiceMock } from './i18n/i18n_service.mock'; @@ -127,26 +126,6 @@ function pluginInitializerContextMock(config: any = {}) { return mock; } -function createCoreContext({ production = false }: { production?: boolean } = {}): CoreContext { - return { - coreId: Symbol('core context mock'), - env: { - mode: { - dev: !production, - name: production ? 'production' : 'development', - prod: production, - }, - packageInfo: { - version: 'version', - branch: 'branch', - buildNum: 100, - buildSha: 'buildSha', - dist: false, - }, - }, - }; -} - function createStorageMock() { const storageMock: jest.Mocked = { getItem: jest.fn(), @@ -178,7 +157,7 @@ function createAppMountParametersMock(appBasePath = '') { } export const coreMock = { - createCoreContext, + createCoreContext: coreContextMock.create, createSetup: createCoreSetupMock, createStart: createCoreStartMock, createPluginInitializerContext: pluginInitializerContextMock, diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index a7b7120351649..6e91392abde06 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -24,6 +24,7 @@ import { import type { InjectedMetadataPlugin } from '@kbn/core-injected-metadata-common-internal'; import { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks'; import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; import { notificationServiceMock } from '../notifications/notifications_service.mock'; import { applicationServiceMock } from '../application/application_service.mock'; import { i18nServiceMock } from '../i18n/i18n_service.mock'; @@ -37,7 +38,6 @@ import { CoreSetup, CoreStart, PluginInitializerContext } from '..'; import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; import { deprecationsServiceMock } from '../deprecations/deprecations_service.mock'; import { executionContextServiceMock } from '../execution_context/execution_context_service.mock'; -import { analyticsServiceMock } from '../analytics/analytics_service.mock'; export let mockPluginInitializers: Map; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 1c761a3165adc..ddb6522c7afa8 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -8,6 +8,8 @@ import { Action } from 'history'; import { AnalyticsClient } from '@kbn/analytics-client'; +import { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; +import { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; import type { ButtonColor } from '@elastic/eui'; import { ContextProviderOpts } from '@kbn/analytics-client'; import { CoreContext } from '@kbn/core-base-browser-internal'; @@ -58,15 +60,9 @@ export function __kbnBootstrap__(): Promise; export { AnalyticsClient } -// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver -// -// @public -export type AnalyticsServiceSetup = Omit; +export { AnalyticsServiceSetup } -// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver -// -// @public -export type AnalyticsServiceStart = Pick; +export { AnalyticsServiceStart } // @public (undocumented) export interface App extends AppNavOptions { @@ -399,6 +395,8 @@ export { CoreContext } // @public export interface CoreSetup { + // Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver + // // (undocumented) analytics: AnalyticsServiceSetup; // (undocumented) @@ -427,6 +425,8 @@ export interface CoreSetup { const embeddableRoot: React.RefObject = useMemo(() => React.createRef(), []); + const [hasFatalError, setHasFatalError] = useState(false); + const { useEmbeddableSelector, containerActions: { untilEmbeddableLoaded, removeEmbeddable }, } = useReduxContainerContext(); + const { controlStyle } = useEmbeddableSelector((state) => state); // Controls Services Context @@ -58,9 +62,20 @@ export const ControlFrame = ({ if (embeddableRoot.current && embeddable) { embeddable.render(embeddableRoot.current); } - const subscription = embeddable?.getInput$().subscribe((newInput) => setTitle(newInput.title)); + const inputSubscription = embeddable + ?.getInput$() + .subscribe((newInput) => setTitle(newInput.title)); + const errorSubscription = embeddable?.getOutput$().subscribe({ + error: (error: Error) => { + if (!embeddableRoot.current) return; + const errorEmbeddable = new ErrorEmbeddable(error, { id: embeddable.id }, undefined, true); + errorEmbeddable.render(embeddableRoot.current); + setHasFatalError(true); + }, + }); return () => { - subscription?.unsubscribe(); + inputSubscription?.unsubscribe(); + errorSubscription?.unsubscribe(); }; }, [embeddable, embeddableRoot]); @@ -71,9 +86,11 @@ export const ControlFrame = ({ 'controlFrameFloatingActions--oneLine': !usingTwoLineLayout, })} > - - - + {!hasFatalError && ( + + + + )} => { const { dataViewId, fieldName, parentFieldName, childFieldName } = this.getInput(); if (!this.dataView || this.dataView.id !== dataViewId) { - this.dataView = await this.dataViewsService.get(dataViewId); - if (this.dataView === undefined) { - this.onFatalError( - new Error(OptionsListStrings.errors.getDataViewNotFoundError(dataViewId)) - ); + try { + this.dataView = await this.dataViewsService.get(dataViewId); + if (!this.dataView) + throw new Error(OptionsListStrings.errors.getDataViewNotFoundError(dataViewId)); + } catch (e) { + this.onFatalError(e); } - this.updateOutput({ dataViews: [this.dataView] }); + this.updateOutput({ dataViews: this.dataView && [this.dataView] }); } - if (!this.field || this.field.name !== fieldName) { - const originalField = this.dataView.getFieldByName(fieldName); + if (this.dataView && (!this.field || this.field.name !== fieldName)) { + const originalField = this.dataView?.getFieldByName(fieldName); const childField = - (childFieldName && this.dataView.getFieldByName(childFieldName)) || undefined; + (childFieldName && this.dataView?.getFieldByName(childFieldName)) || undefined; const parentField = - (parentFieldName && this.dataView.getFieldByName(parentFieldName)) || undefined; + (parentFieldName && this.dataView?.getFieldByName(parentFieldName)) || undefined; const textFieldName = childField?.esTypes?.includes('text') ? childField.name @@ -225,6 +226,8 @@ export class OptionsListEmbeddable extends Embeddable { const { dataView, field } = await this.getCurrentDataViewAndField(); + if (!dataView || !field) return; + this.updateComponentState({ loading: true }); this.updateOutput({ loading: true, dataViews: [dataView] }); const { ignoreParentSettings, filters, query, selectedOptions, timeRange, runPastTimeout } = @@ -282,6 +285,7 @@ export class OptionsListEmbeddable extends Embeddable => { const { dataViewId, fieldName } = this.getInput(); + if (!this.dataView || this.dataView.id !== dataViewId) { - this.dataView = await this.dataViewsService.get(dataViewId); - if (this.dataView === undefined) { - this.onFatalError( - new Error(RangeSliderStrings.errors.getDataViewNotFoundError(dataViewId)) - ); + try { + this.dataView = await this.dataViewsService.get(dataViewId); + if (!this.dataView) + throw new Error(RangeSliderStrings.errors.getDataViewNotFoundError(dataViewId)); + } catch (e) { + this.onFatalError(e); } } if (!this.field || this.field.name !== fieldName) { - this.field = this.dataView.getFieldByName(fieldName); + this.field = this.dataView?.getFieldByName(fieldName); if (this.field === undefined) { this.onFatalError(new Error(RangeSliderStrings.errors.getDataViewNotFoundError(fieldName))); } @@ -176,7 +178,7 @@ export class RangeSliderEmbeddable extends Embeddable value, }); } @@ -196,6 +198,8 @@ export class RangeSliderEmbeddable extends Embeddable { expect(getByText(/some error occurred/i)).toBeVisible(); }); +test('ErrorEmbeddable renders in compact mode', async () => { + const embeddable = new ErrorEmbeddable( + 'some error occurred', + { id: '123', title: 'Error' }, + undefined, + true + ); + const component = render(); + + expect(component.baseElement).toMatchInlineSnapshot(` + +
+
+
+
+
+ +
+
+
+
+
+ + `); +}); + test('ErrorEmbeddable renders an embeddable with markdown message', async () => { const error = '[some link](http://localhost:5601/takeMeThere)'; const embeddable = new ErrorEmbeddable(error, { id: '123', title: 'Error' }); diff --git a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx index f769d44cd2756..605e0be94a780 100644 --- a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -import { EuiText, EuiIcon, EuiSpacer } from '@elastic/eui'; -import React from 'react'; +import { EuiText, EuiIcon, EuiSpacer, EuiPopover, EuiLink } from '@elastic/eui'; +import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import { KibanaThemeProvider, Markdown } from '@kbn/kibana-react-plugin/public'; +import { i18n } from '@kbn/i18n'; import { Embeddable } from './embeddable'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { IContainer } from '../containers'; import { getTheme } from '../../services'; +import './error_embedabble.scss'; export const ERROR_EMBEDDABLE_TYPE = 'error'; @@ -28,7 +30,12 @@ export class ErrorEmbeddable extends Embeddable + ); + + const node = this.compact ? ( + {errorMarkdown} + ) : (
- + {errorMarkdown}
); @@ -73,3 +82,33 @@ export class ErrorEmbeddable extends Embeddable { + const [isPopoverOpen, setPopoverOpen] = useState(false); + + const popoverButton = ( + + setPopoverOpen((open) => !open)} + > + + {i18n.translate('embeddableApi.panel.errorEmbeddable.message', { + defaultMessage: 'An error has occurred. Read more', + })} + + + ); + + return ( + setPopoverOpen(false)} + > + {children} + + ); +}; diff --git a/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap new file mode 100644 index 0000000000000..958f1d2ace966 --- /dev/null +++ b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getting started should render getting started component 1`] = ` + +
+ +

+ What would you like to do first? +

+
+ + +

+ Select an option below to get a quick tour of the most valuable features based on your preferences. +

+
+ + + + + + + + + + + + + + + + +
+ + I'd like to do something else (Skip) + +
+
+
+`; diff --git a/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/use_case_card.test.tsx.snap b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/use_case_card.test.tsx.snap new file mode 100644 index 0000000000000..b32d4cf76acfe --- /dev/null +++ b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/use_case_card.test.tsx.snap @@ -0,0 +1,145 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`use case card should render use case card component for observability 1`] = ` + +

+ Monitor your infrastructure by consolidating your logs, metrics, and traces for end‑to‑end observability. +

+ + } + display="subdued" + icon={ + + } + image={ +
+ } + onClick={[Function]} + textAlign="left" + title={ + +

+ + Monitor my infrastructure + +

+
+ } +/> +`; + +exports[`use case card should render use case card component for search 1`] = ` + +

+ Create a search experience for your websites, applications, workplace content, or anything in between. +

+ + } + display="subdued" + icon={ + + } + image={ +
+ } + onClick={[Function]} + textAlign="left" + title={ + +

+ + Search my data + +

+
+ } +/> +`; + +exports[`use case card should render use case card component for security 1`] = ` + +

+ Protect your environment by unifying SIEM, endpoint security, and cloud security to protect against threats. +

+ + } + display="subdued" + icon={ + + } + image={ +
+ } + onClick={[Function]} + textAlign="left" + title={ + +

+ + Protect my environment + +

+
+ } +/> +`; diff --git a/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx b/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx new file mode 100644 index 0000000000000..4a65f486acf68 --- /dev/null +++ b/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { GettingStarted } from './getting_started'; + +jest.mock('../../kibana_services', () => { + const { chromeServiceMock, applicationServiceMock } = + jest.requireActual('@kbn/core/public/mocks'); + return { + getServices: () => ({ + chrome: chromeServiceMock.createStartContract(), + application: applicationServiceMock.createStartContract(), + trackUiMetric: jest.fn(), + }), + }; +}); +describe('getting started', () => { + test('should render getting started component', async () => { + const component = await shallow(); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx b/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx new file mode 100644 index 0000000000000..eba487f5b553b --- /dev/null +++ b/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useEffect } from 'react'; +import { + EuiFlexGrid, + EuiFlexItem, + EuiHorizontalRule, + EuiLink, + EuiSpacer, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; + +import { css } from '@emotion/react'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { i18n } from '@kbn/i18n'; +import { KibanaPageTemplate } from '@kbn/shared-ux-components'; + +import { getServices } from '../../kibana_services'; +import { UseCaseCard } from './use_case_card'; + +const homeBreadcrumb = i18n.translate('home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); +const gettingStartedBreadcrumb = i18n.translate('home.breadcrumbs.gettingStartedTitle', { + defaultMessage: 'Getting Started', +}); +const title = i18n.translate('home.guidedOnboarding.gettingStarted.useCaseSelectionTitle', { + defaultMessage: 'What would you like to do first?', +}); +const subtitle = i18n.translate('home.guidedOnboarding.gettingStarted.useCaseSelectionSubtitle', { + defaultMessage: + 'Select an option below to get a quick tour of the most valuable features based on your preferences.', +}); +const skipText = i18n.translate('home.guidedOnboarding.gettingStarted.skip.buttonLabel', { + defaultMessage: `I'd like to do something else (Skip)`, +}); + +export const GettingStarted = () => { + const { application, trackUiMetric, chrome } = getServices(); + + useEffect(() => { + chrome.setBreadcrumbs([ + { + // using # prevents a reloading of the whole app when clicking the breadcrumb + href: '#', + text: homeBreadcrumb, + onClick: () => { + trackUiMetric(METRIC_TYPE.CLICK, 'guided_onboarding__home_breadcrumb'); + }, + }, + { + text: gettingStartedBreadcrumb, + }, + ]); + }, [chrome, trackUiMetric]); + + const onSkip = () => { + trackUiMetric(METRIC_TYPE.CLICK, 'guided_onboarding__skipped'); + application.navigateToApp('home'); + }; + const { euiTheme } = useEuiTheme(); + const paddingCss = css` + padding: calc(${euiTheme.size.base}*3) calc(${euiTheme.size.base}*4); + `; + return ( + +
+ +

{title}

+
+ + +

{subtitle}

+
+ + + + + + + + + + + + + + + + +
+ {skipText} +
+
+
+ ); +}; diff --git a/src/plugins/home/public/application/components/guided_onboarding/index.ts b/src/plugins/home/public/application/components/guided_onboarding/index.ts new file mode 100644 index 0000000000000..91932af283e2d --- /dev/null +++ b/src/plugins/home/public/application/components/guided_onboarding/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { GettingStarted } from './getting_started'; diff --git a/src/plugins/home/public/application/components/guided_onboarding/use_case_card.test.tsx b/src/plugins/home/public/application/components/guided_onboarding/use_case_card.test.tsx new file mode 100644 index 0000000000000..b22bd49841f2f --- /dev/null +++ b/src/plugins/home/public/application/components/guided_onboarding/use_case_card.test.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { UseCaseCard } from './use_case_card'; + +jest.mock('../../kibana_services', () => { + const { applicationServiceMock } = jest.requireActual('@kbn/core/public/mocks'); + return { + getServices: () => ({ + application: applicationServiceMock.createStartContract(), + trackUiMetric: jest.fn(), + }), + }; +}); +describe('use case card', () => { + test('should render use case card component for search', async () => { + const component = await shallow(); + + expect(component).toMatchSnapshot(); + }); + test('should render use case card component for observability', async () => { + const component = await shallow(); + + expect(component).toMatchSnapshot(); + }); + test('should render use case card component for security', async () => { + const component = await shallow(); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/home/public/application/components/guided_onboarding/use_case_card.tsx b/src/plugins/home/public/application/components/guided_onboarding/use_case_card.tsx new file mode 100644 index 0000000000000..692c1e86c875e --- /dev/null +++ b/src/plugins/home/public/application/components/guided_onboarding/use_case_card.tsx @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo } from 'react'; +import { + EuiAvatar, + EuiCard, + EuiText, + EuiTitle, + IconType, + useEuiTheme, + useIsWithinBreakpoints, +} from '@elastic/eui'; + +import { METRIC_TYPE } from '@kbn/analytics'; +import { i18n } from '@kbn/i18n'; + +import { getServices } from '../../kibana_services'; + +type UseCaseConstants = { + [key in UseCase]: { + i18nTexts: { + title: string; + description: string; + }; + icon: { + type: IconType; + name: string; + }; + navigateOptions: { + appId: string; + path?: string; + }; + }; +}; +const constants: UseCaseConstants = { + search: { + i18nTexts: { + title: i18n.translate('home.guidedOnboarding.gettingStarted.search.cardTitle', { + defaultMessage: 'Search my data', + }), + description: i18n.translate('home.guidedOnboarding.gettingStarted.search.cardDescription', { + defaultMessage: + 'Create a search experience for your websites, applications, workplace content, or anything in between.', + }), + }, + icon: { + type: 'inspect', + name: i18n.translate('home.guidedOnboarding.gettingStarted.search.iconName', { + defaultMessage: 'Enterprise Search icon', + }), + }, + navigateOptions: { + appId: 'enterpriseSearch', + // when navigating to ent search, do not provide path + }, + }, + observability: { + i18nTexts: { + title: i18n.translate('home.guidedOnboarding.gettingStarted.observability.cardTitle', { + defaultMessage: 'Monitor my infrastructure', + }), + description: i18n.translate( + 'home.guidedOnboarding.gettingStarted.observability.cardDescription', + { + defaultMessage: + 'Monitor your infrastructure by consolidating your logs, metrics, and traces for end‑to‑end observability.', + } + ), + }, + icon: { + type: 'eye', + name: i18n.translate('home.guidedOnboarding.gettingStarted.observability.iconName', { + defaultMessage: 'Observability icon', + }), + }, + navigateOptions: { + appId: 'observability', + path: '/overview', + }, + }, + security: { + i18nTexts: { + title: i18n.translate('home.guidedOnboarding.gettingStarted.security.cardTitle', { + defaultMessage: 'Protect my environment', + }), + description: i18n.translate('home.guidedOnboarding.gettingStarted.security.cardDescription', { + defaultMessage: + 'Protect your environment by unifying SIEM, endpoint security, and cloud security to protect against threats.', + }), + }, + icon: { + type: 'securitySignal', + name: i18n.translate('home.guidedOnboarding.gettingStarted.security.iconName', { + defaultMessage: 'Security icon', + }), + }, + navigateOptions: { + appId: 'securitySolutionUI', + path: '/overview', + }, + }, +}; + +export type UseCase = 'search' | 'observability' | 'security'; +export interface UseCaseProps { + useCase: UseCase; +} + +export const UseCaseCard = ({ useCase }: UseCaseProps) => { + const { application, trackUiMetric } = getServices(); + + const onUseCaseSelection = () => { + trackUiMetric(METRIC_TYPE.CLICK, `guided_onboarding__use_case__${useCase}`); + + localStorage.setItem(`guidedOnboarding.${useCase}.tourActive`, JSON.stringify(true)); + application.navigateToApp(constants[useCase].navigateOptions.appId, { + path: constants[useCase].navigateOptions.path, + }); + }; + + const title = ( + +

+ {constants[useCase].i18nTexts.title} +

+
+ ); + const description = ( + +

{constants[useCase].i18nTexts.description}

+
+ ); + + const { euiTheme } = useEuiTheme(); + const isSmallerBreakpoint = useIsWithinBreakpoints(['xs', 's']); + const isMediumBreakpoint = useIsWithinBreakpoints(['m']); + const cardCss = useMemo(() => { + return { + backgroundColor: + useCase === 'search' + ? euiTheme.colors.warning + : useCase === 'security' + ? euiTheme.colors.accent + : euiTheme.colors.success, + // smaller screens: taller cards (250px) + // medium screens: lower cards (150px) + // larger screens: tall but not too tall cards (200px) + minHeight: isSmallerBreakpoint ? 250 : isMediumBreakpoint ? 150 : 200, + }; + }, [euiTheme, isMediumBreakpoint, isSmallerBreakpoint, useCase]); + + return ( + + } + image={
} + title={title} + description={description} + onClick={onUseCaseSelection} + /> + ); +}; diff --git a/src/plugins/home/public/application/components/home_app.js b/src/plugins/home/public/application/components/home_app.js index a634573aaf21e..af7b1dec48669 100644 --- a/src/plugins/home/public/application/components/home_app.js +++ b/src/plugins/home/public/application/components/home_app.js @@ -16,6 +16,7 @@ import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom' import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; import { getServices } from '../kibana_services'; +import { GettingStarted } from './guided_onboarding'; const REDIRECT_TO_INTEGRATIONS_TAB_IDS = ['all', 'logging', 'metrics', 'security']; @@ -67,6 +68,9 @@ export function HomeApp({ directories, solutions }) { + + + ; pivot_label?: string; - pivot_rows?: string; + pivot_rows?: number | string; pivot_type?: KBN_FIELD_TYPES | Array; series: Series[]; show_grid: number; diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/top_hit.js b/src/plugins/vis_types/timeseries/public/application/components/aggs/top_hit.js index 32ca15eabc39e..332bda82ff838 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/top_hit.js +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/top_hit.js @@ -13,7 +13,7 @@ import { FieldSelect } from './field_select'; import { i18n } from '@kbn/i18n'; import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; -import { createTextHandler } from '../lib/create_text_handler'; +import { createNumberHandler } from '../lib/create_number_handler'; import { htmlIdGenerator, EuiFlexGroup, @@ -22,6 +22,7 @@ import { EuiComboBox, EuiSpacer, EuiFormRow, + EuiFieldNumber, } from '@elastic/eui'; import { injectI18n, FormattedMessage } from '@kbn/i18n-react'; import { KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; @@ -96,7 +97,6 @@ const AGG_WITH_KEY = 'agg_with'; const ORDER_DATE_RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; const getModelDefaults = () => ({ - size: 1, order: 'desc', [AGG_WITH_KEY]: 'noop', }); @@ -118,7 +118,7 @@ const TopHitAggUi = (props) => { const handleChange = createChangeHandler(props.onChange, model); const handleSelectChange = createSelectHandler(handleChange); - const handleTextChange = createTextHandler(handleChange); + const handleNumberChange = createNumberHandler(handleChange); const fieldsSelector = getIndexPatternKey(indexPattern); const field = fields?.[fieldsSelector]?.find((f) => f.name === model.field); const aggWithOptions = getAggWithOptions(field, aggWithOptionsRestrictFields); @@ -199,14 +199,10 @@ const TopHitAggUi = (props) => { } > - {/* - EUITODO: The following input couldn't be converted to EUI because of type mis-match. - Should it be text or number? - */} - diff --git a/src/plugins/vis_types/timeseries/public/application/components/panel_config/table.tsx b/src/plugins/vis_types/timeseries/public/application/components/panel_config/table.tsx index 346aa47cad6cb..90b0f0cc251f6 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/panel_config/table.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/panel_config/table.tsx @@ -23,6 +23,7 @@ import { EuiHorizontalRule, EuiCode, EuiText, + EuiFieldNumber, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -91,13 +92,18 @@ export class TablePanelConfig extends Component< (name: keyof TimeseriesVisParams) => (e: React.ChangeEvent) => this.props.onChange({ [name]: e.target.value }); + handleNumberChange = + (name: keyof TimeseriesVisParams) => (e: React.ChangeEvent) => + this.props.onChange({ + [name]: !e.target.value ? undefined : Number(e.target.value), + }); + render() { const { selectedTab } = this.state; const defaults = { drilldown_url: '', filter: { query: '', language: getDefaultQueryLanguage() }, pivot_label: '', - pivot_rows: 10, pivot_type: '', }; const model = { ...defaults, ...this.props.model }; @@ -172,15 +178,10 @@ export class TablePanelConfig extends Component< /> } > - {/* - EUITODO: The following input couldn't be converted to EUI because of type mis-match. - Should it be number or string? - */} - diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js index 8e5008184b5e4..f32523b77d033 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js @@ -13,6 +13,7 @@ import React, { useMemo, useState, useEffect, useCallback } from 'react'; import { DATA_FORMATTERS } from '../../../../../common/enums'; import { DataFormatPicker } from '../../data_format_picker'; import { createSelectHandler } from '../../lib/create_select_handler'; +import { createNumberHandler } from '../../lib/create_number_handler'; import { YesNo } from '../../yes_no'; import { createTextHandler } from '../../lib/create_text_handler'; import { IndexPattern } from '../../index_pattern'; @@ -40,11 +41,12 @@ import { tsvbEditorRowStyles } from '../../../styles/common.styles'; export const TimeseriesConfig = injectI18n(function (props) { const handleSelectChange = createSelectHandler(props.onChange); const handleTextChange = createTextHandler(props.onChange); + const handleNumberChange = createNumberHandler(props.onChange); const defaults = { value_template: '{{value}}', offset_time: '', - axis_min: '', - axis_max: '', + axis_min: undefined, + axis_max: undefined, stacked: STACKED_OPTIONS.NONE, steps: 0, }; @@ -494,16 +496,10 @@ export const TimeseriesConfig = injectI18n(function (props) { /> } > - {/* - EUITODO: The following input couldn't be converted to EUI because of type mis-match. - It accepts a null value, but is passed a empty string. - */} - @@ -517,15 +513,10 @@ export const TimeseriesConfig = injectI18n(function (props) { /> } > - {/* - EUITODO: The following input couldn't be converted to EUI because of type mis-match. - It accepts a null value, but is passed a empty string. - */} - diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/bucket_transform.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/bucket_transform.js index 0d5012b11a351..bd96e95edd15b 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/bucket_transform.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/bucket_transform.js @@ -102,7 +102,7 @@ export const bucketTransform = { std_deviation: extendStats, top_hit: (bucket) => { - checkMetric(bucket, ['type', 'field', 'size']); + checkMetric(bucket, ['type', 'field']); const body = { filter: { exists: { field: bucket.field }, @@ -110,7 +110,7 @@ export const bucketTransform = { aggs: { docs: { top_hits: { - size: bucket.size, + size: bucket.size ?? 1, fields: [bucket.field], }, }, diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/pivot.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/pivot.ts index 4c28cfb442e14..ed57c680411c5 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/pivot.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/pivot.ts @@ -34,7 +34,7 @@ export const pivot: TableRequestProcessorsFunction = overwrite(doc, `aggs.pivot.${termsType}.field`, pivotIds[0]); } - overwrite(doc, `aggs.pivot.${termsType}.size`, panel.pivot_rows); + overwrite(doc, `aggs.pivot.${termsType}.size`, panel.pivot_rows ?? 10); if (sort) { const series = panel.series.find((item) => item.id === sort.column); diff --git a/test/functional/apps/visualize/group6/_tag_cloud.ts b/test/functional/apps/visualize/group6/_tag_cloud.ts index 93a7fb22a2ca8..fed99547d6eaf 100644 --- a/test/functional/apps/visualize/group6/_tag_cloud.ts +++ b/test/functional/apps/visualize/group6/_tag_cloud.ts @@ -29,7 +29,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'tagCloud', ]); - describe('tag cloud chart', function () { + // Failing: See https://github.com/elastic/kibana/issues/134515 + describe.skip('tag cloud chart', function () { const vizName1 = 'Visualization tagCloud'; const termsField = 'machine.ram'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/apply_bulk_edit_operation.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/apply_bulk_edit_operation.test.ts index 8ba3b551073cf..03d688b6bcb3f 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/apply_bulk_edit_operation.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/apply_bulk_edit_operation.test.ts @@ -168,4 +168,58 @@ describe('applyBulkEditOperation', () => { ]); }); }); + + describe('throttle operations', () => { + test('should rewrite throttle', () => { + const ruleMock = { + actions: [{ id: 'mock-action-id', group: 'default', params: {} }], + }; + expect( + applyBulkEditOperation( + { + field: 'throttle', + value: '1d', + operation: 'set', + }, + ruleMock + ) + ).toHaveProperty('throttle', '1d'); + }); + }); + + describe('notifyWhen operations', () => { + test('should rewrite notifyWhen', () => { + const ruleMock = { + actions: [{ id: 'mock-action-id', group: 'default', params: {} }], + }; + expect( + applyBulkEditOperation( + { + field: 'notifyWhen', + value: 'onThrottleInterval', + operation: 'set', + }, + ruleMock + ) + ).toHaveProperty('notifyWhen', 'onThrottleInterval'); + }); + }); + + describe('schedule operations', () => { + test('should rewrite schedule', () => { + const ruleMock = { + actions: [{ id: 'mock-action-id', group: 'default', params: {} }], + }; + expect( + applyBulkEditOperation( + { + field: 'schedule', + value: { interval: '1d' }, + operation: 'set', + }, + ruleMock + ) + ).toHaveProperty('schedule', { interval: '1d' }); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index fe61148b6350d..30861ee1ab5a4 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -1245,20 +1245,23 @@ export class RulesClient { (async () => { if ( updateResult.scheduledTaskId && + updateResult.schedule && !isEqual(alertSavedObject.attributes.schedule, updateResult.schedule) ) { - this.taskManager - .runNow(updateResult.scheduledTaskId) - .then(() => { - this.logger.debug( - `Alert update has rescheduled the underlying task: ${updateResult.scheduledTaskId}` - ); - }) - .catch((err: Error) => { - this.logger.error( - `Alert update failed to run its underlying task. TaskManager runNow failed with Error: ${err.message}` - ); - }); + try { + const { tasks } = await this.taskManager.bulkUpdateSchedules( + [updateResult.scheduledTaskId], + updateResult.schedule + ); + + this.logger.debug( + `Rule update has rescheduled the underlying task: ${updateResult.scheduledTaskId} to run at: ${tasks?.[0]?.runAt}` + ); + } catch (err) { + this.logger.error( + `Rule update failed to run its underlying task. TaskManager bulkUpdateSchedules failed with Error: ${err.message}` + ); + } } })(), ]); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index 1508d49fe5851..28b7ad273b456 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -17,7 +17,6 @@ import { RecoveredActionGroup } from '../../../common'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; -import { resolvable } from '../../test_utils'; import { ActionsAuthorization, ActionsClient } from '@kbn/actions-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; @@ -1585,7 +1584,7 @@ describe('update()', () => { taskManager.runNow.mockReturnValueOnce(Promise.resolve({ id: alertId })); } - test('updating the alert schedule should rerun the task immediately', async () => { + test('updating the alert schedule should call taskManager.bulkUpdateSchedules', async () => { const alertId = uuid.v4(); const taskId = uuid.v4(); @@ -1614,10 +1613,10 @@ describe('update()', () => { }, }); - expect(taskManager.runNow).toHaveBeenCalledWith(taskId); + expect(taskManager.bulkUpdateSchedules).toHaveBeenCalledWith([taskId], { interval: '1m' }); }); - test('updating the alert without changing the schedule should not rerun the task', async () => { + test('updating the alert without changing the schedule should not call taskManager.bulkUpdateSchedules', async () => { const alertId = uuid.v4(); const taskId = uuid.v4(); @@ -1646,55 +1645,17 @@ describe('update()', () => { }, }); - expect(taskManager.runNow).not.toHaveBeenCalled(); + expect(taskManager.bulkUpdateSchedules).not.toHaveBeenCalled(); }); - test('updating the alert should not wait for the rerun the task to complete', async () => { + test('logs when update of schedule of an alerts underlying task fails', async () => { const alertId = uuid.v4(); const taskId = uuid.v4(); mockApiCalls(alertId, taskId, { interval: '1m' }, { interval: '30s' }); - const resolveAfterAlertUpdatedCompletes = resolvable<{ id: string }>(); - - taskManager.runNow.mockReset(); - taskManager.runNow.mockReturnValue(resolveAfterAlertUpdatedCompletes); - - await rulesClient.update({ - id: alertId, - data: { - schedule: { interval: '1m' }, - name: 'abc', - tags: ['foo'], - params: { - bar: true, - }, - throttle: null, - notifyWhen: null, - actions: [ - { - group: 'default', - id: '1', - params: { - foo: true, - }, - }, - ], - }, - }); - - expect(taskManager.runNow).toHaveBeenCalled(); - resolveAfterAlertUpdatedCompletes.resolve({ id: alertId }); - }); - - test('logs when the rerun of an alerts underlying task fails', async () => { - const alertId = uuid.v4(); - const taskId = uuid.v4(); - - mockApiCalls(alertId, taskId, { interval: '1m' }, { interval: '30s' }); - - taskManager.runNow.mockReset(); - taskManager.runNow.mockRejectedValue(new Error('Failed to run alert')); + taskManager.bulkUpdateSchedules.mockReset(); + taskManager.bulkUpdateSchedules.mockRejectedValue(new Error('Failed to run alert')); await rulesClient.update({ id: alertId, @@ -1719,10 +1680,10 @@ describe('update()', () => { }, }); - expect(taskManager.runNow).toHaveBeenCalled(); + expect(taskManager.bulkUpdateSchedules).toHaveBeenCalled(); expect(rulesClientParams.logger.error).toHaveBeenCalledWith( - `Alert update failed to run its underlying task. TaskManager runNow failed with Error: Failed to run alert` + `Rule update failed to run its underlying task. TaskManager bulkUpdateSchedules failed with Error: Failed to run alert` ); }); }); diff --git a/x-pack/plugins/apm/server/routes/rum_client/has_rum_data.ts b/x-pack/plugins/apm/server/routes/rum_client/has_rum_data.ts deleted file mode 100644 index 67689c9e718f2..0000000000000 --- a/x-pack/plugins/apm/server/routes/rum_client/has_rum_data.ts +++ /dev/null @@ -1,71 +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 moment from 'moment'; -import { rangeQuery } from '@kbn/observability-plugin/server'; -import { SetupUX } from './route'; -import { - SERVICE_NAME, - TRANSACTION_TYPE, -} from '../../../common/elasticsearch_fieldnames'; -import { ProcessorEvent } from '../../../common/processor_event'; -import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types'; - -export async function hasRumData({ - setup, - start = moment().subtract(24, 'h').valueOf(), - end = moment().valueOf(), -}: { - setup: SetupUX; - start?: number; - end?: number; -}) { - try { - const params = { - apm: { - events: [ProcessorEvent.transaction], - }, - body: { - size: 0, - query: { - bool: { - filter: [{ term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }], - }, - }, - aggs: { - services: { - filter: rangeQuery(start, end)[0], - aggs: { - mostTraffic: { - terms: { - field: SERVICE_NAME, - size: 1, - }, - }, - }, - }, - }, - }, - }; - - const { apmEventClient } = setup; - - const response = await apmEventClient.search('has_rum_data', params); - return { - indices: setup.indices.transaction, - hasData: response.hits.total.value > 0, - serviceName: - response.aggregations?.services?.mostTraffic?.buckets?.[0]?.key, - }; - } catch (e) { - return { - hasData: false, - serviceName: undefined, - indices: setup.indices.transaction, - }; - } -} diff --git a/x-pack/plugins/apm/server/routes/rum_client/route.ts b/x-pack/plugins/apm/server/routes/rum_client/route.ts index 612b00b932e24..b1312822aaaff 100644 --- a/x-pack/plugins/apm/server/routes/rum_client/route.ts +++ b/x-pack/plugins/apm/server/routes/rum_client/route.ts @@ -6,7 +6,6 @@ */ import * as t from 'io-ts'; import { Logger } from '@kbn/core/server'; -import { isoToEpochRt } from '@kbn/io-ts-utils'; import { setupRequest, Setup } from '../../lib/helpers/setup_request'; import { getClientMetrics } from './get_client_metrics'; import { getLongTaskMetrics } from './get_long_task_metrics'; @@ -14,7 +13,6 @@ import { getPageLoadDistribution } from './get_page_load_distribution'; import { getPageViewTrends } from './get_page_view_trends'; import { getPageLoadDistBreakdown } from './get_pl_dist_breakdown'; import { getVisitorBreakdown } from './get_visitor_breakdown'; -import { hasRumData } from './has_rum_data'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { rangeRt } from '../default_api_types'; import { APMRouteHandlerResources } from '../typings'; @@ -242,31 +240,6 @@ const rumLongTaskMetrics = createApmServerRoute({ }, }); -const rumHasDataRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/observability_overview/has_rum_data', - params: t.partial({ - query: t.partial({ - uiFilters: t.string, - start: isoToEpochRt, - end: isoToEpochRt, - }), - }), - options: { tags: ['access:apm'] }, - handler: async ( - resources - ): Promise<{ - indices: string; - hasData: boolean; - serviceName: string | number | undefined; - }> => { - const setup = await setupUXRequest(resources); - const { - query: { start, end }, - } = resources.params; - return await hasRumData({ setup, start, end }); - }, -}); - function decodeUiFilters( logger: Logger, uiFiltersEncoded?: string @@ -303,5 +276,4 @@ export const rumRouteRepository = { ...rumPageViewsTrendRoute, ...rumVisitorsBreakdownRoute, ...rumLongTaskMetrics, - ...rumHasDataRoute, }; diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json index 35004b297fc45..2ce462bfcd3fc 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.json +++ b/x-pack/plugins/cases/docs/openapi/bundled.json @@ -226,7 +226,14 @@ "comments": { "type": "array", "items": { - "type": "string" + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] }, "example": [] }, @@ -717,7 +724,14 @@ "comments": { "type": "array", "items": { - "type": "string" + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] }, "example": [] }, @@ -1194,7 +1208,14 @@ "comments": { "type": "array", "items": { - "type": "string" + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] }, "example": [] }, @@ -2497,10 +2518,10 @@ } ] }, - "/s/{spaceId}/api/cases": { + "/api/cases/{caseId}/comments": { "post": { - "summary": "Creates a case.", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n", + "summary": "Adds a comment or alert to a case.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n", "tags": [ "cases", "kibana" @@ -2510,155 +2531,25 @@ "$ref": "#/components/parameters/kbn_xsrf" }, { - "$ref": "#/components/parameters/space_id" + "$ref": "#/components/parameters/case_id" } ], "requestBody": { "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "connector": { - "description": "An object that contains the connector configuration.", - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - }, - "required": [ - "fields", - "id", - "name", - "type" - ] - }, - "description": { - "description": "The description for the case.", - "type": "string" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "description": "An object that contains the case settings.", - "type": "object", - "properties": { - "syncAlerts": { - "description": "Turns alert syncing on or off.", - "type": "boolean" - } - } + "oneOf": [ + { + "$ref": "#/components/schemas/add_alert_comment_request_properties" }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "tags": { - "description": "The words and phrases that help categorize cases. It can be an empty array.", - "type": "array", - "items": { - "type": "string" - } - }, - "title": { - "description": "A title for the case.", - "type": "string" + { + "$ref": "#/components/schemas/add_user_comment_request_properties" } - }, - "required": [ - "connector", - "description", - "owner", - "settings", - "tags", - "title" ] }, "examples": { - "createCaseRequest": { - "$ref": "#/components/examples/create_case_request" + "createCaseCommentRequest": { + "$ref": "#/components/examples/add_comment_request" } } } @@ -2697,7 +2588,14 @@ "comments": { "type": "array", "items": { - "type": "string" + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] }, "example": [] }, @@ -2933,8 +2831,8 @@ } }, "examples": { - "createCaseResponse": { - "$ref": "#/components/examples/create_case_response" + "createCaseCommentResponse": { + "$ref": "#/components/examples/add_comment_response" } } } @@ -2948,8 +2846,8 @@ ] }, "delete": { - "summary": "Deletes one or more cases.", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", + "summary": "Deletes all comments and alerts from a case.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", "tags": [ "cases", "kibana" @@ -2959,17 +2857,7 @@ "$ref": "#/components/parameters/kbn_xsrf" }, { - "$ref": "#/components/parameters/space_id" - }, - { - "name": "ids", - "description": "The cases that you want to removed. All non-ASCII characters must be URL encoded.", - "in": "query", - "required": true, - "schema": { - "type": "string" - }, - "example": "d4e7abb0-b462-11ec-9a8d-698504725a43" + "$ref": "#/components/parameters/case_id" } ], "responses": { @@ -2984,8 +2872,8 @@ ] }, "patch": { - "summary": "Updates one or more cases.", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating.\n", + "summary": "Updates a comment or alert in a case.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.\n", "tags": [ "cases", "kibana" @@ -2995,215 +2883,80 @@ "$ref": "#/components/parameters/kbn_xsrf" }, { - "$ref": "#/components/parameters/space_id" + "$ref": "#/components/parameters/case_id" } ], "requestBody": { "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "cases": { - "type": "array", - "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/update_alert_comment_request_properties" + }, + { + "$ref": "#/components/schemas/update_user_comment_request_properties" + } + ] + }, + "examples": { + "updateCaseCommentRequest": { + "$ref": "#/components/examples/update_comment_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "object", + "properties": { + "closed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "closed_by": { "type": "object", "properties": { - "connector": { - "description": "An object that contains the connector configuration.", - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - }, - "required": [ - "fields", - "id", - "name", - "type" - ] - }, - "description": { - "description": "The description for the case.", - "type": "string" - }, - "id": { - "description": "The identifier for the case.", - "type": "string" - }, - "settings": { - "description": "An object that contains the case settings.", - "type": "object", - "properties": { - "syncAlerts": { - "description": "Turns alert syncing on or off.", - "type": "boolean" - } - } - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "description": "The words and phrases that help categorize cases.", - "type": "array", - "items": { - "type": "string" - } - }, - "title": { - "description": "A title for the case.", - "type": "string" - }, - "version": { - "description": "The current version of the case.", - "type": "string" - } - }, - "required": [ - "id", - "version" - ] - } - } - } - }, - "examples": { - "updateCaseRequest": { - "$ref": "#/components/examples/update_case_request" - } - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "object", - "properties": { - "closed_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": null - }, - "closed_by": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "username": { - "type": "string" - } - }, - "nullable": true, - "example": null - }, - "comments": { - "type": "array", - "items": { - "type": "string" - }, - "example": [] - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + }, + "comments": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] + }, + "example": [] + }, + "connector": { + "type": "object", + "properties": { + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, "type": "object", "properties": { "caseId": { @@ -3430,8 +3183,8 @@ } }, "examples": { - "updateCaseResponse": { - "$ref": "#/components/examples/update_case_response" + "updateCaseCommentResponse": { + "$ref": "#/components/examples/update_comment_response" } } } @@ -3450,94 +3203,1061 @@ } ] }, - "/s/{spaceId}/api/cases/_find": { - "get": { - "summary": "Retrieves a paginated subset of cases.", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", + "/s/{spaceId}/api/cases": { + "post": { + "summary": "Creates a case.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n", "tags": [ "cases", "kibana" ], "parameters": [ { - "$ref": "#/components/parameters/space_id" - }, - { - "name": "defaultSearchOperator", - "in": "query", - "description": "The default operator to use for the simple_query_string.", - "schema": { - "type": "string", - "default": "OR" - }, - "example": "OR" - }, - { - "name": "fields", - "in": "query", - "description": "The fields in the entity to return in the response.", - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "name": "from", - "in": "query", - "description": "[preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", - "schema": { - "type": "string" - }, - "example": "now-1d" - }, - { - "$ref": "#/components/parameters/owner" - }, - { - "name": "page", - "in": "query", - "description": "The page number to return.", - "schema": { - "type": "integer", - "default": 1 - }, - "example": 1 - }, - { - "name": "perPage", - "in": "query", - "description": "The number of rules to return per page.", - "schema": { - "type": "integer", - "default": 20 - }, - "example": 20 - }, - { - "name": "reporters", - "in": "query", - "description": "Filters the returned cases by the user name of the reporter.", - "schema": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "example": "elastic" + "$ref": "#/components/parameters/kbn_xsrf" }, { - "name": "search", - "in": "query", - "description": "An Elasticsearch simple_query_string query that filters the objects in the response.", + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "connector": { + "description": "An object that contains the connector configuration.", + "type": "object", + "properties": { + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, + "type": "object", + "properties": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", + "type": "string" + }, + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", + "type": "string" + }, + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" + }, + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" + }, + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { + "type": "number" + } + }, + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" + }, + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", + "type": "string" + }, + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", + "type": "string" + }, + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", + "type": "string" + }, + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" + } + }, + "example": null + }, + "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "type": { + "$ref": "#/components/schemas/connector_types" + } + }, + "required": [ + "fields", + "id", + "name", + "type" + ] + }, + "description": { + "description": "The description for the case.", + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "settings": { + "description": "An object that contains the case settings.", + "type": "object", + "properties": { + "syncAlerts": { + "description": "Turns alert syncing on or off.", + "type": "boolean" + } + } + }, + "severity": { + "$ref": "#/components/schemas/severity_property" + }, + "tags": { + "description": "The words and phrases that help categorize cases. It can be an empty array.", + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "description": "A title for the case.", + "type": "string" + } + }, + "required": [ + "connector", + "description", + "owner", + "settings", + "tags", + "title" + ] + }, + "examples": { + "createCaseRequest": { + "$ref": "#/components/examples/create_case_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "object", + "properties": { + "closed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "closed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + }, + "comments": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] + }, + "example": [] + }, + "connector": { + "type": "object", + "properties": { + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, + "type": "object", + "properties": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", + "type": "string" + }, + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", + "type": "string" + }, + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" + }, + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" + }, + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { + "type": "number" + } + }, + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" + }, + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", + "type": "string" + }, + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", + "type": "string" + }, + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", + "type": "string" + }, + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" + } + }, + "example": null + }, + "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "type": { + "$ref": "#/components/schemas/connector_types" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-05-13T09:16:17.416Z" + }, + "created_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null + }, + "full_name": { + "type": "string", + "example": null + }, + "username": { + "type": "string", + "example": "elastic" + } + } + }, + "description": { + "type": "string", + "example": "A case description." + }, + "duration": { + "type": "integer", + "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", + "example": 120 + }, + "external_service": { + "type": "object", + "properties": { + "connector_id": { + "type": "string" + }, + "connector_name": { + "type": "string" + }, + "external_id": { + "type": "string" + }, + "external_title": { + "type": "string" + }, + "external_url": { + "type": "string" + }, + "pushed_at": { + "type": "string", + "format": "date-time" + }, + "pushed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + } + } + }, + "id": { + "type": "string", + "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "settings": { + "type": "object", + "properties": { + "syncAlerts": { + "type": "boolean", + "example": true + } + } + }, + "severity": { + "$ref": "#/components/schemas/severity_property" + }, + "status": { + "$ref": "#/components/schemas/status" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "tag-1" + ] + }, + "title": { + "type": "string", + "example": "Case title 1" + }, + "totalAlerts": { + "type": "integer", + "example": 0 + }, + "totalComment": { + "type": "integer", + "example": 0 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "updated_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + }, + "version": { + "type": "string", + "example": "WzUzMiwxXQ==" + } + } + }, + "examples": { + "createCaseResponse": { + "$ref": "#/components/examples/create_case_response" + } + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "delete": { + "summary": "Deletes one or more cases.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", + "tags": [ + "cases", + "kibana" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "name": "ids", + "description": "The cases that you want to removed. All non-ASCII characters must be URL encoded.", + "in": "query", + "required": true, + "schema": { + "type": "string" + }, + "example": "d4e7abb0-b462-11ec-9a8d-698504725a43" + } + ], + "responses": { + "204": { + "description": "Indicates a successful call." + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "patch": { + "summary": "Updates one or more cases.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating.\n", + "tags": [ + "cases", + "kibana" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cases": { + "type": "array", + "items": { + "type": "object", + "properties": { + "connector": { + "description": "An object that contains the connector configuration.", + "type": "object", + "properties": { + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, + "type": "object", + "properties": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", + "type": "string" + }, + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", + "type": "string" + }, + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" + }, + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" + }, + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { + "type": "number" + } + }, + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" + }, + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", + "type": "string" + }, + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", + "type": "string" + }, + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", + "type": "string" + }, + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" + } + }, + "example": null + }, + "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "type": { + "$ref": "#/components/schemas/connector_types" + } + }, + "required": [ + "fields", + "id", + "name", + "type" + ] + }, + "description": { + "description": "The description for the case.", + "type": "string" + }, + "id": { + "description": "The identifier for the case.", + "type": "string" + }, + "settings": { + "description": "An object that contains the case settings.", + "type": "object", + "properties": { + "syncAlerts": { + "description": "Turns alert syncing on or off.", + "type": "boolean" + } + } + }, + "severity": { + "$ref": "#/components/schemas/severity_property" + }, + "status": { + "$ref": "#/components/schemas/status" + }, + "tags": { + "description": "The words and phrases that help categorize cases.", + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "description": "A title for the case.", + "type": "string" + }, + "version": { + "description": "The current version of the case.", + "type": "string" + } + }, + "required": [ + "id", + "version" + ] + } + } + } + }, + "examples": { + "updateCaseRequest": { + "$ref": "#/components/examples/update_case_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "object", + "properties": { + "closed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "closed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + }, + "comments": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] + }, + "example": [] + }, + "connector": { + "type": "object", + "properties": { + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, + "type": "object", + "properties": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", + "type": "string" + }, + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", + "type": "string" + }, + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" + }, + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" + }, + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { + "type": "number" + } + }, + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" + }, + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", + "type": "string" + }, + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", + "type": "string" + }, + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", + "type": "string" + }, + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" + } + }, + "example": null + }, + "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "type": { + "$ref": "#/components/schemas/connector_types" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-05-13T09:16:17.416Z" + }, + "created_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null + }, + "full_name": { + "type": "string", + "example": null + }, + "username": { + "type": "string", + "example": "elastic" + } + } + }, + "description": { + "type": "string", + "example": "A case description." + }, + "duration": { + "type": "integer", + "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", + "example": 120 + }, + "external_service": { + "type": "object", + "properties": { + "connector_id": { + "type": "string" + }, + "connector_name": { + "type": "string" + }, + "external_id": { + "type": "string" + }, + "external_title": { + "type": "string" + }, + "external_url": { + "type": "string" + }, + "pushed_at": { + "type": "string", + "format": "date-time" + }, + "pushed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + } + } + }, + "id": { + "type": "string", + "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "settings": { + "type": "object", + "properties": { + "syncAlerts": { + "type": "boolean", + "example": true + } + } + }, + "severity": { + "$ref": "#/components/schemas/severity_property" + }, + "status": { + "$ref": "#/components/schemas/status" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "tag-1" + ] + }, + "title": { + "type": "string", + "example": "Case title 1" + }, + "totalAlerts": { + "type": "integer", + "example": 0 + }, + "totalComment": { + "type": "integer", + "example": 0 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "updated_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + }, + "version": { + "type": "string", + "example": "WzUzMiwxXQ==" + } + } + }, + "examples": { + "updateCaseResponse": { + "$ref": "#/components/examples/update_case_response" + } + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "/s/{spaceId}/api/cases/_find": { + "get": { + "summary": "Retrieves a paginated subset of cases.", + "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", + "tags": [ + "cases", + "kibana" + ], + "parameters": [ + { + "$ref": "#/components/parameters/space_id" + }, + { + "name": "defaultSearchOperator", + "in": "query", + "description": "The default operator to use for the simple_query_string.", + "schema": { + "type": "string", + "default": "OR" + }, + "example": "OR" + }, + { + "name": "fields", + "in": "query", + "description": "The fields in the entity to return in the response.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "from", + "in": "query", + "description": "[preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "schema": { + "type": "string" + }, + "example": "now-1d" + }, + { + "$ref": "#/components/parameters/owner" + }, + { + "name": "page", + "in": "query", + "description": "The page number to return.", + "schema": { + "type": "integer", + "default": 1 + }, + "example": 1 + }, + { + "name": "perPage", + "in": "query", + "description": "The number of rules to return per page.", + "schema": { + "type": "integer", + "default": 20 + }, + "example": 20 + }, + { + "name": "reporters", + "in": "query", + "description": "Filters the returned cases by the user name of the reporter.", + "schema": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "example": "elastic" + }, + { + "name": "search", + "in": "query", + "description": "An Elasticsearch simple_query_string query that filters the objects in the response.", "schema": { "type": "string" } @@ -3551,87 +4271,488 @@ { "type": "string" }, - { - "type": "array", - "items": { - "type": "string" + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + { + "$ref": "#/components/parameters/severity" + }, + { + "name": "sortField", + "in": "query", + "description": "Determines which field is used to sort the results.", + "schema": { + "type": "string", + "enum": [ + "createdAt", + "updatedAt" + ], + "default": "createdAt" + }, + "example": "updatedAt" + }, + { + "name": "sortOrder", + "in": "query", + "description": "Determines the sort order.", + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "desc" + }, + "example": "asc" + }, + { + "name": "status", + "in": "query", + "description": "Filters the returned cases by state.", + "schema": { + "type": "string", + "enum": [ + "closed", + "in-progress", + "open" + ] + }, + "example": "open" + }, + { + "name": "tags", + "in": "query", + "description": "Filters the returned cases by tags.", + "schema": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "example": "tag-1" + }, + { + "name": "to", + "in": "query", + "description": "[preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "schema": { + "type": "string" + }, + "example": "now+1d" + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "object", + "properties": { + "cases": { + "type": "array", + "items": { + "type": "object", + "properties": { + "closed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "closed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + }, + "comments": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] + }, + "example": [] + }, + "connector": { + "type": "object", + "properties": { + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, + "type": "object", + "properties": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", + "type": "string" + }, + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", + "type": "string" + }, + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" + }, + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" + }, + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { + "type": "number" + } + }, + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" + }, + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", + "type": "string" + }, + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", + "type": "string" + }, + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", + "type": "string" + }, + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" + } + }, + "example": null + }, + "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "type": { + "$ref": "#/components/schemas/connector_types" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-05-13T09:16:17.416Z" + }, + "created_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null + }, + "full_name": { + "type": "string", + "example": null + }, + "username": { + "type": "string", + "example": "elastic" + } + } + }, + "description": { + "type": "string", + "example": "A case description." + }, + "duration": { + "type": "integer", + "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", + "example": 120 + }, + "external_service": { + "type": "object", + "properties": { + "connector_id": { + "type": "string" + }, + "connector_name": { + "type": "string" + }, + "external_id": { + "type": "string" + }, + "external_title": { + "type": "string" + }, + "external_url": { + "type": "string" + }, + "pushed_at": { + "type": "string", + "format": "date-time" + }, + "pushed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + } + } + }, + "id": { + "type": "string", + "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "settings": { + "type": "object", + "properties": { + "syncAlerts": { + "type": "boolean", + "example": true + } + } + }, + "severity": { + "$ref": "#/components/schemas/severity_property" + }, + "status": { + "$ref": "#/components/schemas/status" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "tag-1" + ] + }, + "title": { + "type": "string", + "example": "Case title 1" + }, + "totalAlerts": { + "type": "integer", + "example": 0 + }, + "totalComment": { + "type": "integer", + "example": 0 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "updated_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + }, + "version": { + "type": "string", + "example": "WzUzMiwxXQ==" + } + } + } + }, + "count_closed_cases": { + "type": "integer" + }, + "count_in_progress_cases": { + "type": "integer" + }, + "count_open_cases": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "examples": { + "findCaseResponse": { + "$ref": "#/components/examples/find_case_response" } } - ] + } } - }, - { - "$ref": "#/components/parameters/severity" - }, + } + }, + "servers": [ { - "name": "sortField", - "in": "query", - "description": "Determines which field is used to sort the results.", - "schema": { - "type": "string", - "enum": [ - "createdAt", - "updatedAt" - ], - "default": "createdAt" - }, - "example": "updatedAt" - }, + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "/s/{spaceId}/api/cases/alerts/{alertId}": { + "get": { + "summary": "Returns the cases associated with a specific alert.", + "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", + "x-technical-preview": true, + "tags": [ + "cases", + "kibana" + ], + "parameters": [ { - "name": "sortOrder", - "in": "query", - "description": "Determines the sort order.", - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ], - "default": "desc" - }, - "example": "asc" + "$ref": "#/components/parameters/alert_id" }, { - "name": "status", - "in": "query", - "description": "Filters the returned cases by state.", - "schema": { - "type": "string", - "enum": [ - "closed", - "in-progress", - "open" - ] - }, - "example": "open" + "$ref": "#/components/parameters/space_id" }, { - "name": "tags", - "in": "query", - "description": "Filters the returned cases by tags.", - "schema": { - "oneOf": [ - { - "type": "string" - }, - { + "$ref": "#/components/parameters/owner" + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json; charset=utf-8": { + "schema": { "type": "array", "items": { - "type": "string" - } + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The case identifier." + }, + "title": { + "type": "string", + "description": "The case title." + } + } + }, + "example": [ + { + "id": "06116b80-e1c3-11ec-be9b-9b1838238ee6", + "title": "security_case" + } + ] } - ] - }, - "example": "tag-1" + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "/s/{spaceId}/api/cases/configure": { + "get": { + "summary": "Retrieves external connection details, such as the closure type and default connector for cases.", + "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration.\n", + "tags": [ + "cases", + "kibana" + ], + "parameters": [ + { + "$ref": "#/components/parameters/space_id" }, { - "name": "to", - "in": "query", - "description": "[preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", - "schema": { - "type": "string" - }, - "example": "now+1d" + "$ref": "#/components/parameters/owner" } ], "responses": { @@ -3640,298 +4761,185 @@ "content": { "application/json; charset=utf-8": { "schema": { - "type": "object", - "properties": { - "cases": { - "type": "array", - "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "closure_type": { + "$ref": "#/components/schemas/closure_types" + }, + "connector": { "type": "object", "properties": { - "closed_at": { - "type": "string", - "format": "date-time", + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", "nullable": true, - "example": null - }, - "closed_by": { "type": "object", "properties": { - "email": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", "type": "string" }, - "full_name": { + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", "type": "string" }, - "username": { + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", "type": "string" - } - }, - "nullable": true, - "example": null - }, - "comments": { - "type": "array", - "items": { - "type": "string" - }, - "example": [] - }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" - } - }, - "example": null }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" }, - "type": { - "$ref": "#/components/schemas/connector_types" - } - } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-05-13T09:16:17.416Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { + "type": "number" + } }, - "full_name": { - "type": "string", - "example": null + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" }, - "username": { - "type": "string", - "example": "elastic" - } - } - }, - "description": { - "type": "string", - "example": "A case description." - }, - "duration": { - "type": "integer", - "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", - "example": 120 - }, - "external_service": { - "type": "object", - "properties": { - "connector_id": { + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", "type": "string" }, - "connector_name": { + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", "type": "string" }, - "external_id": { + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", "type": "string" }, - "external_title": { + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", "type": "string" }, - "external_url": { + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", "type": "string" }, - "pushed_at": { - "type": "string", - "format": "date-time" - }, - "pushed_by": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "username": { - "type": "string" - } - }, - "nullable": true, - "example": null + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" } - } + }, + "example": null }, "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", "type": "string", - "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" - }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "type": "object", - "properties": { - "syncAlerts": { - "type": "boolean", - "example": true - } - } - }, - "severity": { - "$ref": "#/components/schemas/severity_property" - }, - "status": { - "$ref": "#/components/schemas/status" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "tag-1" - ] + "example": "none" }, - "title": { + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", "type": "string", - "example": "Case title 1" - }, - "totalAlerts": { - "type": "integer", - "example": 0 + "example": "none" }, - "totalComment": { - "type": "integer", - "example": 0 + "type": { + "$ref": "#/components/schemas/connector_types" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-06-01T17:07:17.767Z" + }, + "created_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null }, - "updated_at": { + "full_name": { "type": "string", - "format": "date-time", - "nullable": true, "example": null }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "username": { - "type": "string" - } + "username": { + "type": "string", + "example": "elastic" + } + } + }, + "error": { + "type": "string", + "example": null + }, + "id": { + "type": "string", + "example": "4a97a440-e1cd-11ec-be9b-9b1838238ee6" + }, + "mappings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "action_type": { + "type": "string", + "example": "overwrite" }, - "nullable": true, + "source": { + "type": "string", + "example": "title" + }, + "target": { + "type": "string", + "example": "summary" + } + } + } + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": "2022-06-01T19:58:48.169Z" + }, + "updated_by": { + "type": "object", + "properties": { + "email": { + "type": "string", "example": null }, - "version": { + "full_name": { "type": "string", - "example": "WzUzMiwxXQ==" + "example": null + }, + "username": { + "type": "string", + "example": "elastic" } - } + }, + "nullable": true + }, + "version": { + "type": "string", + "example": "WzIwNzMsMV0=" } - }, - "count_closed_cases": { - "type": "integer" - }, - "count_in_progress_cases": { - "type": "integer" - }, - "count_open_cases": { - "type": "integer" - }, - "page": { - "type": "integer" - }, - "per_page": { - "type": "integer" - }, - "total": { - "type": "integer" } } - }, - "examples": { - "findCaseResponse": { - "$ref": "#/components/examples/find_case_response" - } } } } @@ -3943,91 +4951,153 @@ } ] }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/s/{spaceId}/api/cases/alerts/{alertId}": { - "get": { - "summary": "Returns the cases associated with a specific alert.", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", - "x-technical-preview": true, + "post": { + "summary": "Sets external connection details, such as the closure type and default connector for cases.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API. If you set a default connector, it is automatically selected when you create cases in Kibana. If you use the create case API, however, you must still specify all of the connector details.\n", "tags": [ "cases", "kibana" ], "parameters": [ { - "$ref": "#/components/parameters/alert_id" + "$ref": "#/components/parameters/kbn_xsrf" }, { "$ref": "#/components/parameters/space_id" - }, - { - "$ref": "#/components/parameters/owner" } ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json; charset=utf-8": { - "schema": { - "type": "array", - "items": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "closure_type": { + "$ref": "#/components/schemas/closure_types" + }, + "connector": { + "description": "An object that contains the connector configuration.", "type": "object", "properties": { + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, + "type": "object", + "properties": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", + "type": "string" + }, + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", + "type": "string" + }, + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" + }, + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" + }, + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { + "type": "number" + } + }, + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" + }, + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", + "type": "string" + }, + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", + "type": "string" + }, + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", + "type": "string" + }, + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" + } + }, + "example": null + }, "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", "type": "string", - "description": "The case identifier." + "example": "none" }, - "title": { + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", "type": "string", - "description": "The case title." + "example": "none" + }, + "type": { + "$ref": "#/components/schemas/connector_types" } - } + }, + "required": [ + "fields", + "id", + "name", + "type" + ] }, - "example": [ - { - "id": "06116b80-e1c3-11ec-be9b-9b1838238ee6", - "title": "security_case" - } - ] - } + "owner": { + "$ref": "#/components/schemas/owners" + }, + "settings": { + "description": "An object that contains the case settings.", + "type": "object", + "properties": { + "syncAlerts": { + "description": "Turns alert syncing on or off.", + "type": "boolean", + "example": true + } + }, + "required": [ + "syncAlerts" + ] + } + }, + "required": [ + "closure_type", + "connector", + "owner" + ] } } } }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/s/{spaceId}/api/cases/configure": { - "get": { - "summary": "Retrieves external connection details, such as the closure type and default connector for cases.", - "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration.\n", - "tags": [ - "cases", - "kibana" - ], - "parameters": [ - { - "$ref": "#/components/parameters/space_id" - }, - { - "$ref": "#/components/parameters/owner" - } - ], "responses": { "200": { "description": "Indicates a successful call.", @@ -4224,9 +5294,16 @@ } ] }, - "post": { - "summary": "Sets external connection details, such as the closure type and default connector for cases.", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API. If you set a default connector, it is automatically selected when you create cases in Kibana. If you use the create case API, however, you must still specify all of the connector details.\n", + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "/s/{spaceId}/api/cases/configure/{configurationId}": { + "patch": { + "summary": "Updates external connection details, such as the closure type and default connector for cases.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API.\n", "tags": [ "cases", "kibana" @@ -4235,6 +5312,9 @@ { "$ref": "#/components/parameters/kbn_xsrf" }, + { + "$ref": "#/components/parameters/configuration_id" + }, { "$ref": "#/components/parameters/space_id" } @@ -4344,28 +5424,14 @@ "type" ] }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "settings": { - "description": "An object that contains the case settings.", - "type": "object", - "properties": { - "syncAlerts": { - "description": "Turns alert syncing on or off.", - "type": "boolean", - "example": true - } - }, - "required": [ - "syncAlerts" - ] + "version": { + "description": "The version of the connector. To retrieve the version value, use the get configuration API.\n", + "type": "string", + "example": "WzIwMiwxXQ==" } }, "required": [ - "closure_type", - "connector", - "owner" + "version" ] } } @@ -4573,139 +5639,122 @@ } ] }, - "/s/{spaceId}/api/cases/configure/{configurationId}": { - "patch": { - "summary": "Updates external connection details, such as the closure type and default connector for cases.", - "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API.\n", + "/s/{spaceId}/api/cases/configure/connectors/_find": { + "get": { + "summary": "Retrieves information about connectors.", + "description": "In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", "tags": [ "cases", "kibana" ], "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "$ref": "#/components/parameters/configuration_id" - }, { "$ref": "#/components/parameters/space_id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "closure_type": { - "$ref": "#/components/schemas/closure_types" - }, - "connector": { - "description": "An object that contains the connector configuration.", + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "array", + "items": { "type": "object", "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, + "actionTypeId": { + "$ref": "#/components/schemas/connector_types" + }, + "config": { "type": "object", "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", - "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "apiUrl": { "type": "string" }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "projectKey": { "type": "string" } }, - "example": null + "additionalProperties": true }, "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" + "type": "string" + }, + "isDeprecated": { + "type": "boolean" + }, + "isMissingSecrets": { + "type": "boolean" + }, + "isPreconfigured": { + "type": "boolean" }, "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" + "type": "string" }, - "type": { - "$ref": "#/components/schemas/connector_types" + "referencedByCount": { + "type": "integer" } - }, - "required": [ - "fields", - "id", - "name", - "type" - ] - }, - "version": { - "description": "The version of the connector. To retrieve the version value, use the get configuration API.\n", - "type": "string", - "example": "WzIwMiwxXQ==" + } } }, - "required": [ - "version" + "examples": { + "findConnectorResponse": { + "$ref": "#/components/examples/find_connector_response" + } + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "/s/{spaceId}/api/cases/{caseId}/comments": { + "post": { + "summary": "Adds a comment or alert to a case.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n", + "tags": [ + "cases", + "kibana" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/case_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/add_alert_comment_request_properties" + }, + { + "$ref": "#/components/schemas/add_user_comment_request_properties" + } ] + }, + "examples": { + "createCaseCommentRequest": { + "$ref": "#/components/examples/add_comment_request" + } } } } @@ -4716,185 +5765,279 @@ "content": { "application/json; charset=utf-8": { "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "closure_type": { - "$ref": "#/components/schemas/closure_types" + "type": "object", + "properties": { + "closed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "closed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } }, - "connector": { - "type": "object", - "properties": { - "fields": { - "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", - "nullable": true, - "type": "object", - "properties": { - "caseId": { - "description": "The case identifier for Swimlane connectors.", - "type": "string" - }, - "category": { - "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", - "type": "string" - }, - "destIp": { - "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "impact": { - "description": "The effect an incident had on business for ServiceNow ITSM connectors.", - "type": "string" - }, - "issueType": { - "description": "The type of issue for Jira connectors.", - "type": "string" - }, - "issueTypes": { - "description": "The type of incident for IBM Resilient connectors.", - "type": "array", - "items": { - "type": "number" - } - }, - "malwareHash": { - "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", - "type": "string" - }, - "malwareUrl": { - "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", - "type": "string" - }, - "parent": { - "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", - "type": "string" - }, - "priority": { - "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", - "type": "string" - }, - "severity": { - "description": "The severity of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "severityCode": { - "description": "The severity code of the incident for IBM Resilient connectors.", + "nullable": true, + "example": null + }, + "comments": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" + }, + { + "$ref": "#/components/schemas/user_comment_response_properties" + } + ] + }, + "example": [] + }, + "connector": { + "type": "object", + "properties": { + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, + "type": "object", + "properties": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", + "type": "string" + }, + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", + "type": "string" + }, + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" + }, + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" + }, + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { "type": "number" - }, - "sourceIp": { - "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", - "type": "string" - }, - "subcategory": { - "description": "The subcategory of the incident for ServiceNow ITSM connectors.", - "type": "string" - }, - "urgency": { - "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", - "type": "string" } }, - "example": null - }, - "id": { - "description": "The identifier for the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" - }, - "name": { - "description": "The name of the connector. To create a case without a connector, use `none`.", - "type": "string", - "example": "none" + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" + }, + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", + "type": "string" + }, + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", + "type": "string" + }, + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", + "type": "string" + }, + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" + } }, - "type": { - "$ref": "#/components/schemas/connector_types" - } + "example": null + }, + "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "type": { + "$ref": "#/components/schemas/connector_types" } - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-06-01T17:07:17.767Z" - }, - "created_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-05-13T09:16:17.416Z" + }, + "created_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null + }, + "full_name": { + "type": "string", + "example": null + }, + "username": { + "type": "string", + "example": "elastic" } - }, - "error": { - "type": "string", - "example": null - }, - "id": { - "type": "string", - "example": "4a97a440-e1cd-11ec-be9b-9b1838238ee6" - }, - "mappings": { - "type": "array", - "items": { + } + }, + "description": { + "type": "string", + "example": "A case description." + }, + "duration": { + "type": "integer", + "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", + "example": 120 + }, + "external_service": { + "type": "object", + "properties": { + "connector_id": { + "type": "string" + }, + "connector_name": { + "type": "string" + }, + "external_id": { + "type": "string" + }, + "external_title": { + "type": "string" + }, + "external_url": { + "type": "string" + }, + "pushed_at": { + "type": "string", + "format": "date-time" + }, + "pushed_by": { "type": "object", "properties": { - "action_type": { - "type": "string", - "example": "overwrite" + "email": { + "type": "string" }, - "source": { - "type": "string", - "example": "title" + "full_name": { + "type": "string" }, - "target": { - "type": "string", - "example": "summary" + "username": { + "type": "string" } - } + }, + "nullable": true, + "example": null } + } + }, + "id": { + "type": "string", + "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "settings": { + "type": "object", + "properties": { + "syncAlerts": { + "type": "boolean", + "example": true + } + } + }, + "severity": { + "$ref": "#/components/schemas/severity_property" + }, + "status": { + "$ref": "#/components/schemas/status" + }, + "tags": { + "type": "array", + "items": { + "type": "string" }, - "owner": { - "$ref": "#/components/schemas/owners" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": "2022-06-01T19:58:48.169Z" - }, - "updated_by": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": null - }, - "full_name": { - "type": "string", - "example": null - }, - "username": { - "type": "string", - "example": "elastic" - } + "example": [ + "tag-1" + ] + }, + "title": { + "type": "string", + "example": "Case title 1" + }, + "totalAlerts": { + "type": "integer", + "example": 0 + }, + "totalComment": { + "type": "integer", + "example": 0 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "updated_by": { + "type": "object", + "properties": { + "email": { + "type": "string" }, - "nullable": true + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } }, - "version": { - "type": "string", - "example": "WzIwNzMsMV0=" - } + "nullable": true, + "example": null + }, + "version": { + "type": "string", + "example": "WzUzMiwxXQ==" } } + }, + "examples": { + "createCaseCommentResponse": { + "$ref": "#/components/examples/add_comment_response" + } } } } @@ -4906,74 +6049,352 @@ } ] }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/s/{spaceId}/api/cases/configure/connectors/_find": { - "get": { - "summary": "Retrieves information about connectors.", - "description": "In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", + "delete": { + "summary": "Deletes all comments and alerts from a case.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n", + "tags": [ + "cases", + "kibana" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/case_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "responses": { + "204": { + "description": "Indicates a successful call." + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "patch": { + "summary": "Updates a comment or alert in a case.", + "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.\n", "tags": [ "cases", "kibana" ], "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/case_id" + }, { "$ref": "#/components/parameters/space_id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/update_alert_comment_request_properties" + }, + { + "$ref": "#/components/schemas/update_user_comment_request_properties" + } + ] + }, + "examples": { + "updateCaseCommentRequest": { + "$ref": "#/components/examples/update_comment_request" + } + } + } + } + }, "responses": { "200": { "description": "Indicates a successful call.", "content": { "application/json; charset=utf-8": { "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "actionTypeId": { - "$ref": "#/components/schemas/connector_types" + "type": "object", + "properties": { + "closed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "closed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } }, - "config": { - "type": "object", - "properties": { - "apiUrl": { - "type": "string" + "nullable": true, + "example": null + }, + "comments": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/alert_comment_response_properties" }, - "projectKey": { - "type": "string" + { + "$ref": "#/components/schemas/user_comment_response_properties" } - }, - "additionalProperties": true + ] }, - "id": { + "example": [] + }, + "connector": { + "type": "object", + "properties": { + "fields": { + "description": "An object containing the connector fields. To create a case without a connector, specify null. If you want to omit any individual field, specify null as its value.", + "nullable": true, + "type": "object", + "properties": { + "caseId": { + "description": "The case identifier for Swimlane connectors.", + "type": "string" + }, + "category": { + "description": "The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors.", + "type": "string" + }, + "destIp": { + "description": "A comma-separated list of destination IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "impact": { + "description": "The effect an incident had on business for ServiceNow ITSM connectors.", + "type": "string" + }, + "issueType": { + "description": "The type of issue for Jira connectors.", + "type": "string" + }, + "issueTypes": { + "description": "The type of incident for IBM Resilient connectors.", + "type": "array", + "items": { + "type": "number" + } + }, + "malwareHash": { + "description": "A comma-separated list of malware hashes for ServiceNow SecOps connectors.", + "type": "string" + }, + "malwareUrl": { + "description": "A comma-separated list of malware URLs for ServiceNow SecOps connectors.", + "type": "string" + }, + "parent": { + "description": "The key of the parent issue, when the issue type is sub-task for Jira connectors.", + "type": "string" + }, + "priority": { + "description": "The priority of the issue for Jira and ServiceNow SecOps connectors.", + "type": "string" + }, + "severity": { + "description": "The severity of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "severityCode": { + "description": "The severity code of the incident for IBM Resilient connectors.", + "type": "number" + }, + "sourceIp": { + "description": "A comma-separated list of source IPs for ServiceNow SecOps connectors.", + "type": "string" + }, + "subcategory": { + "description": "The subcategory of the incident for ServiceNow ITSM connectors.", + "type": "string" + }, + "urgency": { + "description": "The extent to which the incident resolution can be delayed for ServiceNow ITSM connectors.", + "type": "string" + } + }, + "example": null + }, + "id": { + "description": "The identifier for the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "name": { + "description": "The name of the connector. To create a case without a connector, use `none`.", + "type": "string", + "example": "none" + }, + "type": { + "$ref": "#/components/schemas/connector_types" + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-05-13T09:16:17.416Z" + }, + "created_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null + }, + "full_name": { + "type": "string", + "example": null + }, + "username": { + "type": "string", + "example": "elastic" + } + } + }, + "description": { + "type": "string", + "example": "A case description." + }, + "duration": { + "type": "integer", + "description": "The elapsed time from the creation of the case to its closure (in seconds). If the case has not been closed, the duration is set to null. If the case was closed after less than half a second, the duration is rounded down to zero.\n", + "example": 120 + }, + "external_service": { + "type": "object", + "properties": { + "connector_id": { + "type": "string" + }, + "connector_name": { + "type": "string" + }, + "external_id": { + "type": "string" + }, + "external_title": { + "type": "string" + }, + "external_url": { + "type": "string" + }, + "pushed_at": { + "type": "string", + "format": "date-time" + }, + "pushed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + } + } + }, + "id": { + "type": "string", + "example": "66b9aa00-94fa-11ea-9f74-e7e108796192" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "settings": { + "type": "object", + "properties": { + "syncAlerts": { + "type": "boolean", + "example": true + } + } + }, + "severity": { + "$ref": "#/components/schemas/severity_property" + }, + "status": { + "$ref": "#/components/schemas/status" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, - "isDeprecated": { - "type": "boolean" - }, - "isMissingSecrets": { - "type": "boolean" - }, - "isPreconfigured": { - "type": "boolean" - }, - "name": { - "type": "string" + "example": [ + "tag-1" + ] + }, + "title": { + "type": "string", + "example": "Case title 1" + }, + "totalAlerts": { + "type": "integer", + "example": 0 + }, + "totalComment": { + "type": "integer", + "example": 0 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "updated_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } }, - "referencedByCount": { - "type": "integer" - } + "nullable": true, + "example": null + }, + "version": { + "type": "string", + "example": "WzUzMiwxXQ==" } } }, "examples": { - "findConnectorResponse": { - "$ref": "#/components/examples/find_connector_response" + "updateCaseCommentResponse": { + "$ref": "#/components/examples/update_comment_response" } } } @@ -5067,6 +6488,16 @@ "example": "3297a0f0-b5ec-11ec-b141-0fdb20a7f9a9" } }, + "case_id": { + "in": "path", + "name": "caseId", + "description": "The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded.", + "required": true, + "schema": { + "type": "string", + "example": "9c235210-6834-11ea-a78c-6ffb38a34414" + } + }, "space_id": { "in": "path", "name": "spaceId", @@ -5113,6 +6544,205 @@ ], "default": "low" }, + "alert_comment_response_properties": { + "type": "object", + "properties": { + "alertId": { + "type": "string", + "example": "6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-03-24T02:31:03.210Z" + }, + "created_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null + }, + "full_name": { + "type": "string", + "example": null + }, + "username": { + "type": "string", + "example": "elastic" + } + } + }, + "id": { + "type": "string", + "example": "73362370-ab1a-11ec-985f-97e55adae8b9" + }, + "index": { + "type": "string", + "example": ".internal.alerts-security.alerts-default-000001" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "pushed_at": { + "type": "string", + "format": "date-time", + "example": null, + "nullable": true + }, + "pushed_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null + }, + "full_name": { + "type": "string", + "example": null + }, + "username": { + "type": "string", + "example": "elastic" + } + }, + "nullable": true + }, + "rule": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "94d80550-aaf4-11ec-985f-97e55adae8b9" + }, + "name": { + "type": "string", + "example": "security_rule" + } + } + }, + "type": { + "type": "string", + "example": "alert" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "example": null + }, + "updated_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null + }, + "full_name": { + "type": "string", + "example": null + }, + "username": { + "type": "string", + "example": "elastic" + } + } + }, + "version": { + "type": "string", + "example": "WzMwNDgsMV0=" + } + } + }, + "user_comment_response_properties": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "example": "A new comment." + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-05-13T09:16:17.416Z" + }, + "created_by": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": null + }, + "full_name": { + "type": "string", + "example": null + }, + "username": { + "type": "string", + "example": "elastic" + } + } + }, + "id": { + "type": "string", + "example": "8af6ac20-74f6-11ea-b83a-553aecdb28b6" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "pushed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "pushed_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + }, + "type": { + "type": "string", + "example": "user" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "updated_by": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "nullable": true, + "example": null + }, + "version": { + "type": "string", + "example": "WzIwNDMxLDFd" + } + } + }, "status": { "type": "string", "description": "The status of the case.", @@ -5130,6 +6760,188 @@ "close-by-user" ], "example": "close-by-user" + }, + "add_alert_comment_request_properties": { + "type": "object", + "properties": { + "alertId": { + "description": "The alert identifier. It is required only when `type` is `alert`. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "type": "string", + "x-technical-preview": true, + "example": "6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42" + }, + "index": { + "description": "The alert index. It is required only when `type` is `alert`. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "type": "string", + "x-technical-preview": true + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "rule": { + "description": "The rule that is associated with the alert. It is required only when `type` is `alert`. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "type": "object", + "x-technical-preview": true, + "properties": { + "id": { + "description": "The rule identifier.", + "type": "string", + "x-technical-preview": true, + "example": "94d80550-aaf4-11ec-985f-97e55adae8b9" + }, + "name": { + "description": "The rule name.", + "type": "string", + "x-technical-preview": true, + "example": "security_rule" + } + } + }, + "type": { + "description": "The type of comment.", + "type": "string", + "enum": [ + "alert" + ], + "example": "alert" + } + }, + "required": [ + "alertId", + "index", + "owner", + "rule", + "type" + ] + }, + "add_user_comment_request_properties": { + "type": "object", + "properties": { + "comment": { + "description": "The new comment. It is required only when `type` is `user`.", + "type": "string", + "example": "A new comment." + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "type": { + "type": "string", + "description": "The type of comment.", + "enum": [ + "user" + ], + "example": "user" + } + }, + "required": [ + "comment", + "owner", + "type" + ] + }, + "update_alert_comment_request_properties": { + "type": "object", + "properties": { + "alertId": { + "description": "The alert identifier. It is required only when `type` is `alert`. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "type": "string", + "x-technical-preview": true, + "example": "6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42" + }, + "id": { + "type": "string", + "description": "The identifier for the comment. To retrieve comment IDs, use the get comments API.\n", + "example": "8af6ac20-74f6-11ea-b83a-553aecdb28b6" + }, + "index": { + "description": "The alert index. It is required only when `type` is `alert`. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "type": "string", + "x-technical-preview": true + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "rule": { + "description": "The rule that is associated with the alert. It is required only when `type` is `alert`. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "type": "object", + "x-technical-preview": true, + "properties": { + "id": { + "description": "The rule identifier.", + "type": "string", + "x-technical-preview": true, + "example": "94d80550-aaf4-11ec-985f-97e55adae8b9" + }, + "name": { + "description": "The rule name.", + "type": "string", + "x-technical-preview": true, + "example": "security_rule" + } + } + }, + "type": { + "description": "The type of comment.", + "type": "string", + "enum": [ + "alert" + ], + "example": "alert" + }, + "version": { + "description": "The current comment version. To retrieve version values, use the get comments API.\n", + "type": "string", + "example": "Wzk1LDFd" + } + }, + "required": [ + "alertId", + "id", + "index", + "owner", + "rule", + "type", + "version" + ] + }, + "update_user_comment_request_properties": { + "type": "object", + "properties": { + "comment": { + "description": "The new comment. It is required only when `type` is `user`.", + "type": "string", + "example": "A new comment." + }, + "id": { + "type": "string", + "description": "The identifier for the comment. To retrieve comment IDs, use the get comments API.\n", + "example": "8af6ac20-74f6-11ea-b83a-553aecdb28b6" + }, + "owner": { + "$ref": "#/components/schemas/owners" + }, + "type": { + "type": "string", + "description": "The type of comment.", + "enum": [ + "user" + ], + "example": "user" + }, + "version": { + "description": "The current comment version. To retrieve version values, use the get comments API.\n", + "type": "string", + "example": "Wzk1LDFd" + } + }, + "required": [ + "comment", + "id", + "owner", + "type", + "version" + ] } }, "examples": { @@ -5359,6 +7171,149 @@ "referencedByCount": 0 } ] + }, + "add_comment_request": { + "summary": "Adds a comment to a case.", + "value": { + "type": "user", + "comment": "A new comment.", + "owner": "cases" + } + }, + "add_comment_response": { + "summary": "The add comment to case API returns a JSON object that contains details about the case and its comments.", + "value": { + "comments": [ + { + "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIwNDMxLDFd", + "type": "user", + "owner": "cases", + "comment": "A new comment.", + "created_at": "2022-06-02T00:49:47.716Z", + "created_by": { + "username": "elastic", + "email": null, + "full_name": null + }, + "pushed_at": null, + "pushed_by": null, + "updated_at": null, + "updated_by": null + } + ], + "totalAlerts": 0, + "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIzMzgsMV0=", + "totalComment": 1, + "title": "Case title 1", + "tags": [ + "tag 1" + ], + "description": "A case description.", + "settings": { + "syncAlerts": false + }, + "owner": "cases", + "duration": null, + "severity": "low", + "closed_at": null, + "closed_by": null, + "created_at": "2022-03-24T00:37:03.906Z", + "created_by": { + "email": null, + "full_name": null, + "username": "elastic" + }, + "status": "open", + "updated_at": "2022-06-03T00:49:47.716Z", + "updated_by": { + "username": "elastic", + "email": null, + "full_name": null + }, + "connector": { + "id": "none", + "name": "none", + "type": ".none", + "fields": null + }, + "external_service": null + } + }, + "update_comment_request": { + "summary": "Updates a comment of a case.", + "value": { + "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", + "version": "Wzk1LDFd", + "type": "user", + "comment": "An updated comment." + } + }, + "update_comment_response": { + "summary": "The add comment to case API returns a JSON object that contains details about the case and its comments.", + "value": { + "comments": [ + { + "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIwNjM3LDFd", + "comment": "An updated comment.", + "type": "user", + "owner": "cases", + "created_at": "2022-03-24T00:37:10.832Z", + "created_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "pushed_at": null, + "pushed_by": null, + "updated_at": "2022-03-24T01:27:06.210Z", + "updated_by": { + "username": "elastic", + "full_name": null, + "email": null + } + } + ], + "totalAlerts": 0, + "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIwNjM2LDFd", + "totalComment": 1, + "title": "Case title 1", + "tags": [ + "tag 1" + ], + "description": "A case description.", + "settings": { + "syncAlerts": false + }, + "owner": "cases", + "duration": null, + "severity": "low", + "closed_at": null, + "closed_by": null, + "created_at": "2022-03-24T00:37:03.906Z", + "created_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "status": "open", + "updated_at": "2022-03-24T01:27:06.210Z", + "updated_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "connector": { + "id": "none", + "name": "none", + "type": ".none", + "fields": null + }, + "external_service": null + } } } }, diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml index 4bab3dbb0ca06..bdbce1c645991 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml @@ -199,7 +199,11 @@ paths: comments: type: array items: - type: string + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties example: [] connector: type: object @@ -620,7 +624,11 @@ paths: comments: type: array items: - type: string + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties example: [] connector: type: object @@ -990,7 +998,11 @@ paths: comments: type: array items: - type: string + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties example: [] connector: type: object @@ -2122,163 +2134,29 @@ paths: - url: https://localhost:5601 servers: - url: https://localhost:5601 - /s/{spaceId}/api/cases: + /api/cases/{caseId}/comments: post: - summary: Creates a case. + summary: Adds a comment or alert to a case. description: > You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the - Kibana feature privileges, depending on the owner of the case you're - creating. + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the case you're creating. tags: - cases - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' + - $ref: '#/components/parameters/case_id' requestBody: content: application/json: schema: - type: object - properties: - connector: - description: An object that contains the connector configuration. - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow ITSM and - ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: The type of incident for IBM Resilient connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue type is - sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and ServiceNow - SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow ITSM - connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM Resilient - connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for ServiceNow - SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow ITSM - connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution can be - delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case without a - connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - required: - - fields - - id - - name - - type - description: - description: The description for the case. - type: string - owner: - $ref: '#/components/schemas/owners' - settings: - description: An object that contains the case settings. - type: object - properties: - syncAlerts: - description: Turns alert syncing on or off. - type: boolean - severity: - $ref: '#/components/schemas/severity_property' - tags: - description: >- - The words and phrases that help categorize cases. It can be - an empty array. - type: array - items: - type: string - title: - description: A title for the case. - type: string - required: - - connector - - description - - owner - - settings - - tags - - title + oneOf: + - $ref: '#/components/schemas/add_alert_comment_request_properties' + - $ref: '#/components/schemas/add_user_comment_request_properties' examples: - createCaseRequest: - $ref: '#/components/examples/create_case_request' + createCaseCommentRequest: + $ref: '#/components/examples/add_comment_request' responses: '200': description: Indicates a successful call. @@ -2306,7 +2184,11 @@ paths: comments: type: array items: - type: string + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties example: [] connector: type: object @@ -2509,245 +2391,98 @@ paths: type: string example: WzUzMiwxXQ== examples: - createCaseResponse: - $ref: '#/components/examples/create_case_response' + createCaseCommentResponse: + $ref: '#/components/examples/add_comment_response' servers: - url: https://localhost:5601 delete: - summary: Deletes one or more cases. + summary: Deletes all comments and alerts from a case. description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the - Kibana feature privileges, depending on the owner of the cases you're - deleting. + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're deleting. tags: - cases - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' - - name: ids - description: >- - The cases that you want to removed. All non-ASCII characters must be - URL encoded. - in: query - required: true - schema: - type: string - example: d4e7abb0-b462-11ec-9a8d-698504725a43 + - $ref: '#/components/parameters/case_id' responses: '204': description: Indicates a successful call. servers: - url: https://localhost:5601 patch: - summary: Updates one or more cases. + summary: Updates a comment or alert in a case. description: > - You must have `all` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the - Kibana feature privileges, depending on the owner of the case you're - updating. + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the case you're updating. + NOTE: You cannot change the comment type or the owner of a comment. tags: - cases - kibana parameters: - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' + - $ref: '#/components/parameters/case_id' requestBody: content: application/json: schema: - type: object - properties: - cases: - type: array - items: + oneOf: + - $ref: '#/components/schemas/update_alert_comment_request_properties' + - $ref: '#/components/schemas/update_user_comment_request_properties' + examples: + updateCaseCommentRequest: + $ref: '#/components/examples/update_comment_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: object + properties: + closed_at: + type: string + format: date-time + nullable: true + example: null + closed_by: type: object properties: - connector: - description: An object that contains the connector configuration. + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + comments: + type: array + items: + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties + example: [] + connector: + type: object + properties: + fields: + description: >- + An object containing the connector fields. To create a + case without a connector, specify null. If you want to + omit any individual field, specify null as its value. + nullable: true type: object properties: - fields: - description: >- - An object containing the connector fields. To - create a case without a connector, specify null. - If you want to omit any individual field, specify - null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow - ITSM and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs for - ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: - description: The type of issue for Jira connectors. - type: string - issueTypes: - description: >- - The type of incident for IBM Resilient - connectors. - type: array - items: - type: number - malwareHash: - description: >- - A comma-separated list of malware hashes for - ServiceNow SecOps connectors. - type: string - malwareUrl: - description: >- - A comma-separated list of malware URLs for - ServiceNow SecOps connectors. - type: string - parent: - description: >- - The key of the parent issue, when the issue - type is sub-task for Jira connectors. - type: string - priority: - description: >- - The priority of the issue for Jira and - ServiceNow SecOps connectors. - type: string - severity: - description: >- - The severity of the incident for ServiceNow - ITSM connectors. - type: string - severityCode: - description: >- - The severity code of the incident for IBM - Resilient connectors. - type: number - sourceIp: - description: >- - A comma-separated list of source IPs for - ServiceNow SecOps connectors. - type: string - subcategory: - description: >- - The subcategory of the incident for ServiceNow - ITSM connectors. - type: string - urgency: - description: >- - The extent to which the incident resolution - can be delayed for ServiceNow ITSM connectors. - type: string - example: null - id: - description: >- - The identifier for the connector. To create a case - without a connector, use `none`. - type: string - example: none - name: - description: >- - The name of the connector. To create a case - without a connector, use `none`. - type: string - example: none - type: - $ref: '#/components/schemas/connector_types' - required: - - fields - - id - - name - - type - description: - description: The description for the case. - type: string - id: - description: The identifier for the case. - type: string - settings: - description: An object that contains the case settings. - type: object - properties: - syncAlerts: - description: Turns alert syncing on or off. - type: boolean - severity: - $ref: '#/components/schemas/severity_property' - status: - $ref: '#/components/schemas/status' - tags: - description: The words and phrases that help categorize cases. - type: array - items: - type: string - title: - description: A title for the case. - type: string - version: - description: The current version of the case. - type: string - required: - - id - - version - examples: - updateCaseRequest: - $ref: '#/components/examples/update_case_request' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: object - properties: - closed_at: - type: string - format: date-time - nullable: true - example: null - closed_by: - type: object - properties: - email: - type: string - full_name: - type: string - username: - type: string - nullable: true - example: null - comments: - type: array - items: - type: string - example: [] - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To create a - case without a connector, specify null. If you want to - omit any individual field, specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: + caseId: + description: The case identifier for Swimlane connectors. + type: string + category: description: >- The category of the incident for ServiceNow ITSM and ServiceNow SecOps connectors. @@ -2934,144 +2669,169 @@ paths: type: string example: WzUzMiwxXQ== examples: - updateCaseResponse: - $ref: '#/components/examples/update_case_response' + updateCaseCommentResponse: + $ref: '#/components/examples/update_comment_response' servers: - url: https://localhost:5601 servers: - url: https://localhost:5601 - /s/{spaceId}/api/cases/_find: - get: - summary: Retrieves a paginated subset of cases. + /s/{spaceId}/api/cases: + post: + summary: Creates a case. description: > - You must have `read` privileges for the **Cases** feature in the - **Management**, **Observability**, or **Security** section of the Kibana - feature privileges, depending on the owner of the cases you're seeking. + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the + Kibana feature privileges, depending on the owner of the case you're + creating. tags: - cases - kibana parameters: + - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/space_id' - - name: defaultSearchOperator - in: query - description: The default operator to use for the simple_query_string. - schema: - type: string - default: OR - example: OR - - name: fields - in: query - description: The fields in the entity to return in the response. - schema: - type: array - items: - type: string - - name: from - in: query - description: > - [preview] Returns only cases that were created after a specific - date. The date must be specified as a KQL data range or date match - expression. This functionality is in technical preview and may be - changed or removed in a future release. Elastic will apply best - effort to fix any issues, but features in technical preview are not - subject to the support SLA of official GA features. - schema: - type: string - example: now-1d - - $ref: '#/components/parameters/owner' - - name: page - in: query - description: The page number to return. - schema: - type: integer - default: 1 - example: 1 - - name: perPage - in: query - description: The number of rules to return per page. - schema: - type: integer - default: 20 - example: 20 - - name: reporters - in: query - description: Filters the returned cases by the user name of the reporter. - schema: - oneOf: - - type: string - - type: array - items: - type: string - example: elastic - - name: search - in: query - description: >- - An Elasticsearch simple_query_string query that filters the objects - in the response. - schema: - type: string - - name: searchFields - in: query - description: The fields to perform the simple_query_string parsed query against. - schema: - oneOf: - - type: string - - type: array - items: - type: string - - $ref: '#/components/parameters/severity' - - name: sortField - in: query - description: Determines which field is used to sort the results. - schema: - type: string - enum: - - createdAt - - updatedAt - default: createdAt - example: updatedAt - - name: sortOrder - in: query - description: Determines the sort order. - schema: - type: string - enum: - - asc - - desc - default: desc - example: asc - - name: status - in: query - description: Filters the returned cases by state. - schema: - type: string - enum: - - closed - - in-progress - - open - example: open - - name: tags - in: query - description: Filters the returned cases by tags. - schema: - oneOf: - - type: string - - type: array - items: + requestBody: + content: + application/json: + schema: + type: object + properties: + connector: + description: An object that contains the connector configuration. + type: object + properties: + fields: + description: >- + An object containing the connector fields. To create a + case without a connector, specify null. If you want to + omit any individual field, specify null as its value. + nullable: true + type: object + properties: + caseId: + description: The case identifier for Swimlane connectors. + type: string + category: + description: >- + The category of the incident for ServiceNow ITSM and + ServiceNow SecOps connectors. + type: string + destIp: + description: >- + A comma-separated list of destination IPs for + ServiceNow SecOps connectors. + type: string + impact: + description: >- + The effect an incident had on business for + ServiceNow ITSM connectors. + type: string + issueType: + description: The type of issue for Jira connectors. + type: string + issueTypes: + description: The type of incident for IBM Resilient connectors. + type: array + items: + type: number + malwareHash: + description: >- + A comma-separated list of malware hashes for + ServiceNow SecOps connectors. + type: string + malwareUrl: + description: >- + A comma-separated list of malware URLs for + ServiceNow SecOps connectors. + type: string + parent: + description: >- + The key of the parent issue, when the issue type is + sub-task for Jira connectors. + type: string + priority: + description: >- + The priority of the issue for Jira and ServiceNow + SecOps connectors. + type: string + severity: + description: >- + The severity of the incident for ServiceNow ITSM + connectors. + type: string + severityCode: + description: >- + The severity code of the incident for IBM Resilient + connectors. + type: number + sourceIp: + description: >- + A comma-separated list of source IPs for ServiceNow + SecOps connectors. + type: string + subcategory: + description: >- + The subcategory of the incident for ServiceNow ITSM + connectors. + type: string + urgency: + description: >- + The extent to which the incident resolution can be + delayed for ServiceNow ITSM connectors. + type: string + example: null + id: + description: >- + The identifier for the connector. To create a case + without a connector, use `none`. + type: string + example: none + name: + description: >- + The name of the connector. To create a case without a + connector, use `none`. + type: string + example: none + type: + $ref: '#/components/schemas/connector_types' + required: + - fields + - id + - name + - type + description: + description: The description for the case. type: string - example: tag-1 - - name: to - in: query - description: > - [preview] Returns only cases that were created before a specific - date. The date must be specified as a KQL data range or date match - expression. This functionality is in technical preview and may be - changed or removed in a future release. Elastic will apply best - effort to fix any issues, but features in technical preview are not - subject to the support SLA of official GA features. - schema: - type: string - example: now+1d + owner: + $ref: '#/components/schemas/owners' + settings: + description: An object that contains the case settings. + type: object + properties: + syncAlerts: + description: Turns alert syncing on or off. + type: boolean + severity: + $ref: '#/components/schemas/severity_property' + tags: + description: >- + The words and phrases that help categorize cases. It can be + an empty array. + type: array + items: + type: string + title: + description: A title for the case. + type: string + required: + - connector + - description + - owner + - settings + - tags + - title + examples: + createCaseRequest: + $ref: '#/components/examples/create_case_request' responses: '200': description: Indicates a successful call. @@ -3080,63 +2840,868 @@ paths: schema: type: object properties: - cases: + closed_at: + type: string + format: date-time + nullable: true + example: null + closed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + comments: type: array items: - type: object - properties: - closed_at: - type: string - format: date-time - nullable: true - example: null - closed_by: - type: object - properties: - email: - type: string - full_name: - type: string - username: - type: string - nullable: true - example: null - comments: - type: array - items: + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties + example: [] + connector: + type: object + properties: + fields: + description: >- + An object containing the connector fields. To create a + case without a connector, specify null. If you want to + omit any individual field, specify null as its value. + nullable: true + type: object + properties: + caseId: + description: The case identifier for Swimlane connectors. type: string - example: [] - connector: - type: object - properties: - fields: - description: >- - An object containing the connector fields. To - create a case without a connector, specify null. - If you want to omit any individual field, - specify null as its value. - nullable: true - type: object - properties: - caseId: - description: The case identifier for Swimlane connectors. - type: string - category: - description: >- - The category of the incident for ServiceNow - ITSM and ServiceNow SecOps connectors. - type: string - destIp: - description: >- - A comma-separated list of destination IPs - for ServiceNow SecOps connectors. - type: string - impact: - description: >- - The effect an incident had on business for - ServiceNow ITSM connectors. - type: string - issueType: + category: + description: >- + The category of the incident for ServiceNow ITSM + and ServiceNow SecOps connectors. + type: string + destIp: + description: >- + A comma-separated list of destination IPs for + ServiceNow SecOps connectors. + type: string + impact: + description: >- + The effect an incident had on business for + ServiceNow ITSM connectors. + type: string + issueType: + description: The type of issue for Jira connectors. + type: string + issueTypes: + description: The type of incident for IBM Resilient connectors. + type: array + items: + type: number + malwareHash: + description: >- + A comma-separated list of malware hashes for + ServiceNow SecOps connectors. + type: string + malwareUrl: + description: >- + A comma-separated list of malware URLs for + ServiceNow SecOps connectors. + type: string + parent: + description: >- + The key of the parent issue, when the issue type + is sub-task for Jira connectors. + type: string + priority: + description: >- + The priority of the issue for Jira and ServiceNow + SecOps connectors. + type: string + severity: + description: >- + The severity of the incident for ServiceNow ITSM + connectors. + type: string + severityCode: + description: >- + The severity code of the incident for IBM + Resilient connectors. + type: number + sourceIp: + description: >- + A comma-separated list of source IPs for + ServiceNow SecOps connectors. + type: string + subcategory: + description: >- + The subcategory of the incident for ServiceNow + ITSM connectors. + type: string + urgency: + description: >- + The extent to which the incident resolution can be + delayed for ServiceNow ITSM connectors. + type: string + example: null + id: + description: >- + The identifier for the connector. To create a case + without a connector, use `none`. + type: string + example: none + name: + description: >- + The name of the connector. To create a case without a + connector, use `none`. + type: string + example: none + type: + $ref: '#/components/schemas/connector_types' + created_at: + type: string + format: date-time + example: '2022-05-13T09:16:17.416Z' + created_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + description: + type: string + example: A case description. + duration: + type: integer + description: > + The elapsed time from the creation of the case to its + closure (in seconds). If the case has not been closed, the + duration is set to null. If the case was closed after less + than half a second, the duration is rounded down to zero. + example: 120 + external_service: + type: object + properties: + connector_id: + type: string + connector_name: + type: string + external_id: + type: string + external_title: + type: string + external_url: + type: string + pushed_at: + type: string + format: date-time + pushed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + id: + type: string + example: 66b9aa00-94fa-11ea-9f74-e7e108796192 + owner: + $ref: '#/components/schemas/owners' + settings: + type: object + properties: + syncAlerts: + type: boolean + example: true + severity: + $ref: '#/components/schemas/severity_property' + status: + $ref: '#/components/schemas/status' + tags: + type: array + items: + type: string + example: + - tag-1 + title: + type: string + example: Case title 1 + totalAlerts: + type: integer + example: 0 + totalComment: + type: integer + example: 0 + updated_at: + type: string + format: date-time + nullable: true + example: null + updated_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + version: + type: string + example: WzUzMiwxXQ== + examples: + createCaseResponse: + $ref: '#/components/examples/create_case_response' + servers: + - url: https://localhost:5601 + delete: + summary: Deletes one or more cases. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the + Kibana feature privileges, depending on the owner of the cases you're + deleting. + tags: + - cases + - kibana + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/space_id' + - name: ids + description: >- + The cases that you want to removed. All non-ASCII characters must be + URL encoded. + in: query + required: true + schema: + type: string + example: d4e7abb0-b462-11ec-9a8d-698504725a43 + responses: + '204': + description: Indicates a successful call. + servers: + - url: https://localhost:5601 + patch: + summary: Updates one or more cases. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the + Kibana feature privileges, depending on the owner of the case you're + updating. + tags: + - cases + - kibana + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/space_id' + requestBody: + content: + application/json: + schema: + type: object + properties: + cases: + type: array + items: + type: object + properties: + connector: + description: An object that contains the connector configuration. + type: object + properties: + fields: + description: >- + An object containing the connector fields. To + create a case without a connector, specify null. + If you want to omit any individual field, specify + null as its value. + nullable: true + type: object + properties: + caseId: + description: The case identifier for Swimlane connectors. + type: string + category: + description: >- + The category of the incident for ServiceNow + ITSM and ServiceNow SecOps connectors. + type: string + destIp: + description: >- + A comma-separated list of destination IPs for + ServiceNow SecOps connectors. + type: string + impact: + description: >- + The effect an incident had on business for + ServiceNow ITSM connectors. + type: string + issueType: + description: The type of issue for Jira connectors. + type: string + issueTypes: + description: >- + The type of incident for IBM Resilient + connectors. + type: array + items: + type: number + malwareHash: + description: >- + A comma-separated list of malware hashes for + ServiceNow SecOps connectors. + type: string + malwareUrl: + description: >- + A comma-separated list of malware URLs for + ServiceNow SecOps connectors. + type: string + parent: + description: >- + The key of the parent issue, when the issue + type is sub-task for Jira connectors. + type: string + priority: + description: >- + The priority of the issue for Jira and + ServiceNow SecOps connectors. + type: string + severity: + description: >- + The severity of the incident for ServiceNow + ITSM connectors. + type: string + severityCode: + description: >- + The severity code of the incident for IBM + Resilient connectors. + type: number + sourceIp: + description: >- + A comma-separated list of source IPs for + ServiceNow SecOps connectors. + type: string + subcategory: + description: >- + The subcategory of the incident for ServiceNow + ITSM connectors. + type: string + urgency: + description: >- + The extent to which the incident resolution + can be delayed for ServiceNow ITSM connectors. + type: string + example: null + id: + description: >- + The identifier for the connector. To create a case + without a connector, use `none`. + type: string + example: none + name: + description: >- + The name of the connector. To create a case + without a connector, use `none`. + type: string + example: none + type: + $ref: '#/components/schemas/connector_types' + required: + - fields + - id + - name + - type + description: + description: The description for the case. + type: string + id: + description: The identifier for the case. + type: string + settings: + description: An object that contains the case settings. + type: object + properties: + syncAlerts: + description: Turns alert syncing on or off. + type: boolean + severity: + $ref: '#/components/schemas/severity_property' + status: + $ref: '#/components/schemas/status' + tags: + description: The words and phrases that help categorize cases. + type: array + items: + type: string + title: + description: A title for the case. + type: string + version: + description: The current version of the case. + type: string + required: + - id + - version + examples: + updateCaseRequest: + $ref: '#/components/examples/update_case_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: object + properties: + closed_at: + type: string + format: date-time + nullable: true + example: null + closed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + comments: + type: array + items: + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties + example: [] + connector: + type: object + properties: + fields: + description: >- + An object containing the connector fields. To create a + case without a connector, specify null. If you want to + omit any individual field, specify null as its value. + nullable: true + type: object + properties: + caseId: + description: The case identifier for Swimlane connectors. + type: string + category: + description: >- + The category of the incident for ServiceNow ITSM + and ServiceNow SecOps connectors. + type: string + destIp: + description: >- + A comma-separated list of destination IPs for + ServiceNow SecOps connectors. + type: string + impact: + description: >- + The effect an incident had on business for + ServiceNow ITSM connectors. + type: string + issueType: + description: The type of issue for Jira connectors. + type: string + issueTypes: + description: The type of incident for IBM Resilient connectors. + type: array + items: + type: number + malwareHash: + description: >- + A comma-separated list of malware hashes for + ServiceNow SecOps connectors. + type: string + malwareUrl: + description: >- + A comma-separated list of malware URLs for + ServiceNow SecOps connectors. + type: string + parent: + description: >- + The key of the parent issue, when the issue type + is sub-task for Jira connectors. + type: string + priority: + description: >- + The priority of the issue for Jira and ServiceNow + SecOps connectors. + type: string + severity: + description: >- + The severity of the incident for ServiceNow ITSM + connectors. + type: string + severityCode: + description: >- + The severity code of the incident for IBM + Resilient connectors. + type: number + sourceIp: + description: >- + A comma-separated list of source IPs for + ServiceNow SecOps connectors. + type: string + subcategory: + description: >- + The subcategory of the incident for ServiceNow + ITSM connectors. + type: string + urgency: + description: >- + The extent to which the incident resolution can be + delayed for ServiceNow ITSM connectors. + type: string + example: null + id: + description: >- + The identifier for the connector. To create a case + without a connector, use `none`. + type: string + example: none + name: + description: >- + The name of the connector. To create a case without a + connector, use `none`. + type: string + example: none + type: + $ref: '#/components/schemas/connector_types' + created_at: + type: string + format: date-time + example: '2022-05-13T09:16:17.416Z' + created_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + description: + type: string + example: A case description. + duration: + type: integer + description: > + The elapsed time from the creation of the case to its + closure (in seconds). If the case has not been closed, the + duration is set to null. If the case was closed after less + than half a second, the duration is rounded down to zero. + example: 120 + external_service: + type: object + properties: + connector_id: + type: string + connector_name: + type: string + external_id: + type: string + external_title: + type: string + external_url: + type: string + pushed_at: + type: string + format: date-time + pushed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + id: + type: string + example: 66b9aa00-94fa-11ea-9f74-e7e108796192 + owner: + $ref: '#/components/schemas/owners' + settings: + type: object + properties: + syncAlerts: + type: boolean + example: true + severity: + $ref: '#/components/schemas/severity_property' + status: + $ref: '#/components/schemas/status' + tags: + type: array + items: + type: string + example: + - tag-1 + title: + type: string + example: Case title 1 + totalAlerts: + type: integer + example: 0 + totalComment: + type: integer + example: 0 + updated_at: + type: string + format: date-time + nullable: true + example: null + updated_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + version: + type: string + example: WzUzMiwxXQ== + examples: + updateCaseResponse: + $ref: '#/components/examples/update_case_response' + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 + /s/{spaceId}/api/cases/_find: + get: + summary: Retrieves a paginated subset of cases. + description: > + You must have `read` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're seeking. + tags: + - cases + - kibana + parameters: + - $ref: '#/components/parameters/space_id' + - name: defaultSearchOperator + in: query + description: The default operator to use for the simple_query_string. + schema: + type: string + default: OR + example: OR + - name: fields + in: query + description: The fields in the entity to return in the response. + schema: + type: array + items: + type: string + - name: from + in: query + description: > + [preview] Returns only cases that were created after a specific + date. The date must be specified as a KQL data range or date match + expression. This functionality is in technical preview and may be + changed or removed in a future release. Elastic will apply best + effort to fix any issues, but features in technical preview are not + subject to the support SLA of official GA features. + schema: + type: string + example: now-1d + - $ref: '#/components/parameters/owner' + - name: page + in: query + description: The page number to return. + schema: + type: integer + default: 1 + example: 1 + - name: perPage + in: query + description: The number of rules to return per page. + schema: + type: integer + default: 20 + example: 20 + - name: reporters + in: query + description: Filters the returned cases by the user name of the reporter. + schema: + oneOf: + - type: string + - type: array + items: + type: string + example: elastic + - name: search + in: query + description: >- + An Elasticsearch simple_query_string query that filters the objects + in the response. + schema: + type: string + - name: searchFields + in: query + description: The fields to perform the simple_query_string parsed query against. + schema: + oneOf: + - type: string + - type: array + items: + type: string + - $ref: '#/components/parameters/severity' + - name: sortField + in: query + description: Determines which field is used to sort the results. + schema: + type: string + enum: + - createdAt + - updatedAt + default: createdAt + example: updatedAt + - name: sortOrder + in: query + description: Determines the sort order. + schema: + type: string + enum: + - asc + - desc + default: desc + example: asc + - name: status + in: query + description: Filters the returned cases by state. + schema: + type: string + enum: + - closed + - in-progress + - open + example: open + - name: tags + in: query + description: Filters the returned cases by tags. + schema: + oneOf: + - type: string + - type: array + items: + type: string + example: tag-1 + - name: to + in: query + description: > + [preview] Returns only cases that were created before a specific + date. The date must be specified as a KQL data range or date match + expression. This functionality is in technical preview and may be + changed or removed in a future release. Elastic will apply best + effort to fix any issues, but features in technical preview are not + subject to the support SLA of official GA features. + schema: + type: string + example: now+1d + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: object + properties: + cases: + type: array + items: + type: object + properties: + closed_at: + type: string + format: date-time + nullable: true + example: null + closed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + comments: + type: array + items: + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties + example: [] + connector: + type: object + properties: + fields: + description: >- + An object containing the connector fields. To + create a case without a connector, specify null. + If you want to omit any individual field, + specify null as its value. + nullable: true + type: object + properties: + caseId: + description: The case identifier for Swimlane connectors. + type: string + category: + description: >- + The category of the incident for ServiceNow + ITSM and ServiceNow SecOps connectors. + type: string + destIp: + description: >- + A comma-separated list of destination IPs + for ServiceNow SecOps connectors. + type: string + impact: + description: >- + The effect an incident had on business for + ServiceNow ITSM connectors. + type: string + issueType: description: The type of issue for Jira connectors. type: string issueTypes: @@ -4130,8 +4695,47 @@ paths: created_at: type: string format: date-time - example: '2022-06-01T17:07:17.767Z' - created_by: + example: '2022-06-01T17:07:17.767Z' + created_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + error: + type: string + example: null + id: + type: string + example: 4a97a440-e1cd-11ec-be9b-9b1838238ee6 + mappings: + type: array + items: + type: object + properties: + action_type: + type: string + example: overwrite + source: + type: string + example: title + target: + type: string + example: summary + owner: + $ref: '#/components/schemas/owners' + updated_at: + type: string + format: date-time + nullable: true + example: '2022-06-01T19:58:48.169Z' + updated_by: type: object properties: email: @@ -4143,101 +4747,606 @@ paths: username: type: string example: elastic - error: + nullable: true + version: type: string - example: null + example: WzIwNzMsMV0= + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 + /s/{spaceId}/api/cases/configure/connectors/_find: + get: + summary: Retrieves information about connectors. + description: > + In particular, only the connectors that are supported for use in cases + are returned. You must have `read` privileges for the **Actions and + Connectors** feature in the **Management** section of the Kibana feature + privileges. + tags: + - cases + - kibana + parameters: + - $ref: '#/components/parameters/space_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: array + items: + type: object + properties: + actionTypeId: + $ref: '#/components/schemas/connector_types' + config: + type: object + properties: + apiUrl: + type: string + projectKey: + type: string + additionalProperties: true id: type: string - example: 4a97a440-e1cd-11ec-be9b-9b1838238ee6 - mappings: - type: array - items: + isDeprecated: + type: boolean + isMissingSecrets: + type: boolean + isPreconfigured: + type: boolean + name: + type: string + referencedByCount: + type: integer + examples: + findConnectorResponse: + $ref: '#/components/examples/find_connector_response' + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 + /s/{spaceId}/api/cases/{caseId}/comments: + post: + summary: Adds a comment or alert to a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the case you're creating. + tags: + - cases + - kibana + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/case_id' + - $ref: '#/components/parameters/space_id' + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/add_alert_comment_request_properties' + - $ref: '#/components/schemas/add_user_comment_request_properties' + examples: + createCaseCommentRequest: + $ref: '#/components/examples/add_comment_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: object + properties: + closed_at: + type: string + format: date-time + nullable: true + example: null + closed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + comments: + type: array + items: + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties + example: [] + connector: + type: object + properties: + fields: + description: >- + An object containing the connector fields. To create a + case without a connector, specify null. If you want to + omit any individual field, specify null as its value. + nullable: true + type: object + properties: + caseId: + description: The case identifier for Swimlane connectors. + type: string + category: + description: >- + The category of the incident for ServiceNow ITSM + and ServiceNow SecOps connectors. + type: string + destIp: + description: >- + A comma-separated list of destination IPs for + ServiceNow SecOps connectors. + type: string + impact: + description: >- + The effect an incident had on business for + ServiceNow ITSM connectors. + type: string + issueType: + description: The type of issue for Jira connectors. + type: string + issueTypes: + description: The type of incident for IBM Resilient connectors. + type: array + items: + type: number + malwareHash: + description: >- + A comma-separated list of malware hashes for + ServiceNow SecOps connectors. + type: string + malwareUrl: + description: >- + A comma-separated list of malware URLs for + ServiceNow SecOps connectors. + type: string + parent: + description: >- + The key of the parent issue, when the issue type + is sub-task for Jira connectors. + type: string + priority: + description: >- + The priority of the issue for Jira and ServiceNow + SecOps connectors. + type: string + severity: + description: >- + The severity of the incident for ServiceNow ITSM + connectors. + type: string + severityCode: + description: >- + The severity code of the incident for IBM + Resilient connectors. + type: number + sourceIp: + description: >- + A comma-separated list of source IPs for + ServiceNow SecOps connectors. + type: string + subcategory: + description: >- + The subcategory of the incident for ServiceNow + ITSM connectors. + type: string + urgency: + description: >- + The extent to which the incident resolution can be + delayed for ServiceNow ITSM connectors. + type: string + example: null + id: + description: >- + The identifier for the connector. To create a case + without a connector, use `none`. + type: string + example: none + name: + description: >- + The name of the connector. To create a case without a + connector, use `none`. + type: string + example: none + type: + $ref: '#/components/schemas/connector_types' + created_at: + type: string + format: date-time + example: '2022-05-13T09:16:17.416Z' + created_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + description: + type: string + example: A case description. + duration: + type: integer + description: > + The elapsed time from the creation of the case to its + closure (in seconds). If the case has not been closed, the + duration is set to null. If the case was closed after less + than half a second, the duration is rounded down to zero. + example: 120 + external_service: + type: object + properties: + connector_id: + type: string + connector_name: + type: string + external_id: + type: string + external_title: + type: string + external_url: + type: string + pushed_at: + type: string + format: date-time + pushed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + id: + type: string + example: 66b9aa00-94fa-11ea-9f74-e7e108796192 + owner: + $ref: '#/components/schemas/owners' + settings: + type: object + properties: + syncAlerts: + type: boolean + example: true + severity: + $ref: '#/components/schemas/severity_property' + status: + $ref: '#/components/schemas/status' + tags: + type: array + items: + type: string + example: + - tag-1 + title: + type: string + example: Case title 1 + totalAlerts: + type: integer + example: 0 + totalComment: + type: integer + example: 0 + updated_at: + type: string + format: date-time + nullable: true + example: null + updated_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + version: + type: string + example: WzUzMiwxXQ== + examples: + createCaseCommentResponse: + $ref: '#/components/examples/add_comment_response' + servers: + - url: https://localhost:5601 + delete: + summary: Deletes all comments and alerts from a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're deleting. + tags: + - cases + - kibana + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/case_id' + - $ref: '#/components/parameters/space_id' + responses: + '204': + description: Indicates a successful call. + servers: + - url: https://localhost:5601 + patch: + summary: Updates a comment or alert in a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the case you're updating. + NOTE: You cannot change the comment type or the owner of a comment. + tags: + - cases + - kibana + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/case_id' + - $ref: '#/components/parameters/space_id' + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/update_alert_comment_request_properties' + - $ref: '#/components/schemas/update_user_comment_request_properties' + examples: + updateCaseCommentRequest: + $ref: '#/components/examples/update_comment_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: object + properties: + closed_at: + type: string + format: date-time + nullable: true + example: null + closed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + comments: + type: array + items: + oneOf: + - $ref: >- + #/components/schemas/alert_comment_response_properties + - $ref: >- + #/components/schemas/user_comment_response_properties + example: [] + connector: + type: object + properties: + fields: + description: >- + An object containing the connector fields. To create a + case without a connector, specify null. If you want to + omit any individual field, specify null as its value. + nullable: true + type: object + properties: + caseId: + description: The case identifier for Swimlane connectors. + type: string + category: + description: >- + The category of the incident for ServiceNow ITSM + and ServiceNow SecOps connectors. + type: string + destIp: + description: >- + A comma-separated list of destination IPs for + ServiceNow SecOps connectors. + type: string + impact: + description: >- + The effect an incident had on business for + ServiceNow ITSM connectors. + type: string + issueType: + description: The type of issue for Jira connectors. + type: string + issueTypes: + description: The type of incident for IBM Resilient connectors. + type: array + items: + type: number + malwareHash: + description: >- + A comma-separated list of malware hashes for + ServiceNow SecOps connectors. + type: string + malwareUrl: + description: >- + A comma-separated list of malware URLs for + ServiceNow SecOps connectors. + type: string + parent: + description: >- + The key of the parent issue, when the issue type + is sub-task for Jira connectors. + type: string + priority: + description: >- + The priority of the issue for Jira and ServiceNow + SecOps connectors. + type: string + severity: + description: >- + The severity of the incident for ServiceNow ITSM + connectors. + type: string + severityCode: + description: >- + The severity code of the incident for IBM + Resilient connectors. + type: number + sourceIp: + description: >- + A comma-separated list of source IPs for + ServiceNow SecOps connectors. + type: string + subcategory: + description: >- + The subcategory of the incident for ServiceNow + ITSM connectors. + type: string + urgency: + description: >- + The extent to which the incident resolution can be + delayed for ServiceNow ITSM connectors. + type: string + example: null + id: + description: >- + The identifier for the connector. To create a case + without a connector, use `none`. + type: string + example: none + name: + description: >- + The name of the connector. To create a case without a + connector, use `none`. + type: string + example: none + type: + $ref: '#/components/schemas/connector_types' + created_at: + type: string + format: date-time + example: '2022-05-13T09:16:17.416Z' + created_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + description: + type: string + example: A case description. + duration: + type: integer + description: > + The elapsed time from the creation of the case to its + closure (in seconds). If the case has not been closed, the + duration is set to null. If the case was closed after less + than half a second, the duration is rounded down to zero. + example: 120 + external_service: + type: object + properties: + connector_id: + type: string + connector_name: + type: string + external_id: + type: string + external_title: + type: string + external_url: + type: string + pushed_at: + type: string + format: date-time + pushed_by: type: object properties: - action_type: + email: type: string - example: overwrite - source: + full_name: type: string - example: title - target: + username: type: string - example: summary - owner: - $ref: '#/components/schemas/owners' - updated_at: - type: string - format: date-time - nullable: true - example: '2022-06-01T19:58:48.169Z' - updated_by: - type: object - properties: - email: - type: string - example: null - full_name: - type: string - example: null - username: - type: string - example: elastic - nullable: true - version: - type: string - example: WzIwNzMsMV0= - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /s/{spaceId}/api/cases/configure/connectors/_find: - get: - summary: Retrieves information about connectors. - description: > - In particular, only the connectors that are supported for use in cases - are returned. You must have `read` privileges for the **Actions and - Connectors** feature in the **Management** section of the Kibana feature - privileges. - tags: - - cases - - kibana - parameters: - - $ref: '#/components/parameters/space_id' - responses: - '200': - description: Indicates a successful call. - content: - application/json; charset=utf-8: - schema: - type: array - items: - type: object - properties: - actionTypeId: - $ref: '#/components/schemas/connector_types' - config: - type: object - properties: - apiUrl: - type: string - projectKey: - type: string - additionalProperties: true - id: - type: string - isDeprecated: - type: boolean - isMissingSecrets: - type: boolean - isPreconfigured: - type: boolean - name: + nullable: true + example: null + id: + type: string + example: 66b9aa00-94fa-11ea-9f74-e7e108796192 + owner: + $ref: '#/components/schemas/owners' + settings: + type: object + properties: + syncAlerts: + type: boolean + example: true + severity: + $ref: '#/components/schemas/severity_property' + status: + $ref: '#/components/schemas/status' + tags: + type: array + items: type: string - referencedByCount: - type: integer + example: + - tag-1 + title: + type: string + example: Case title 1 + totalAlerts: + type: integer + example: 0 + totalComment: + type: integer + example: 0 + updated_at: + type: string + format: date-time + nullable: true + example: null + updated_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + version: + type: string + example: WzUzMiwxXQ== examples: - findConnectorResponse: - $ref: '#/components/examples/find_connector_response' + updateCaseCommentResponse: + $ref: '#/components/examples/update_comment_response' servers: - url: https://localhost:5601 servers: @@ -4299,6 +5408,16 @@ components: schema: type: string example: 3297a0f0-b5ec-11ec-b141-0fdb20a7f9a9 + case_id: + in: path + name: caseId + description: >- + The identifier for the case. To retrieve case IDs, use the find cases + API. All non-ASCII characters must be URL encoded. + required: true + schema: + type: string + example: 9c235210-6834-11ea-a78c-6ffb38a34414 space_id: in: path name: spaceId @@ -4338,6 +5457,150 @@ components: - low - medium default: low + alert_comment_response_properties: + type: object + properties: + alertId: + type: string + example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + created_at: + type: string + format: date-time + example: '2022-03-24T02:31:03.210Z' + created_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + id: + type: string + example: 73362370-ab1a-11ec-985f-97e55adae8b9 + index: + type: string + example: .internal.alerts-security.alerts-default-000001 + owner: + $ref: '#/components/schemas/owners' + pushed_at: + type: string + format: date-time + example: null + nullable: true + pushed_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + nullable: true + rule: + type: object + properties: + id: + type: string + example: 94d80550-aaf4-11ec-985f-97e55adae8b9 + name: + type: string + example: security_rule + type: + type: string + example: alert + updated_at: + type: string + format: date-time + example: null + updated_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + version: + type: string + example: WzMwNDgsMV0= + user_comment_response_properties: + type: object + properties: + comment: + type: string + example: A new comment. + created_at: + type: string + format: date-time + example: '2022-05-13T09:16:17.416Z' + created_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + id: + type: string + example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + owner: + $ref: '#/components/schemas/owners' + pushed_at: + type: string + format: date-time + nullable: true + example: null + pushed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + type: + type: string + example: user + updated_at: + type: string + format: date-time + nullable: true + example: null + updated_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + version: + type: string + example: WzIwNDMxLDFd status: type: string description: The status of the case. @@ -4355,6 +5618,184 @@ components: - close-by-pushing - close-by-user example: close-by-user + add_alert_comment_request_properties: + type: object + properties: + alertId: + description: > + The alert identifier. It is required only when `type` is `alert`. + This functionality is in technical preview and may be changed or + removed in a future release. Elastic will apply best effort to fix + any issues, but features in technical preview are not subject to the + support SLA of official GA features. + type: string + x-technical-preview: true + example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + index: + description: > + The alert index. It is required only when `type` is `alert`. This + functionality is in technical preview and may be changed or removed + in a future release. Elastic will apply best effort to fix any + issues, but features in technical preview are not subject to the + support SLA of official GA features. + type: string + x-technical-preview: true + owner: + $ref: '#/components/schemas/owners' + rule: + description: > + The rule that is associated with the alert. It is required only when + `type` is `alert`. This functionality is in technical preview and + may be changed or removed in a future release. Elastic will apply + best effort to fix any issues, but features in technical preview are + not subject to the support SLA of official GA features. + type: object + x-technical-preview: true + properties: + id: + description: The rule identifier. + type: string + x-technical-preview: true + example: 94d80550-aaf4-11ec-985f-97e55adae8b9 + name: + description: The rule name. + type: string + x-technical-preview: true + example: security_rule + type: + description: The type of comment. + type: string + enum: + - alert + example: alert + required: + - alertId + - index + - owner + - rule + - type + add_user_comment_request_properties: + type: object + properties: + comment: + description: The new comment. It is required only when `type` is `user`. + type: string + example: A new comment. + owner: + $ref: '#/components/schemas/owners' + type: + type: string + description: The type of comment. + enum: + - user + example: user + required: + - comment + - owner + - type + update_alert_comment_request_properties: + type: object + properties: + alertId: + description: > + The alert identifier. It is required only when `type` is `alert`. + This functionality is in technical preview and may be changed or + removed in a future release. Elastic will apply best effort to fix + any issues, but features in technical preview are not subject to the + support SLA of official GA features. + type: string + x-technical-preview: true + example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + id: + type: string + description: > + The identifier for the comment. To retrieve comment IDs, use the get + comments API. + example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + index: + description: > + The alert index. It is required only when `type` is `alert`. This + functionality is in technical preview and may be changed or removed + in a future release. Elastic will apply best effort to fix any + issues, but features in technical preview are not subject to the + support SLA of official GA features. + type: string + x-technical-preview: true + owner: + $ref: '#/components/schemas/owners' + rule: + description: > + The rule that is associated with the alert. It is required only when + `type` is `alert`. This functionality is in technical preview and + may be changed or removed in a future release. Elastic will apply + best effort to fix any issues, but features in technical preview are + not subject to the support SLA of official GA features. + type: object + x-technical-preview: true + properties: + id: + description: The rule identifier. + type: string + x-technical-preview: true + example: 94d80550-aaf4-11ec-985f-97e55adae8b9 + name: + description: The rule name. + type: string + x-technical-preview: true + example: security_rule + type: + description: The type of comment. + type: string + enum: + - alert + example: alert + version: + description: > + The current comment version. To retrieve version values, use the get + comments API. + type: string + example: Wzk1LDFd + required: + - alertId + - id + - index + - owner + - rule + - type + - version + update_user_comment_request_properties: + type: object + properties: + comment: + description: The new comment. It is required only when `type` is `user`. + type: string + example: A new comment. + id: + type: string + description: > + The identifier for the comment. To retrieve comment IDs, use the get + comments API. + example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + owner: + $ref: '#/components/schemas/owners' + type: + type: string + description: The type of comment. + enum: + - user + example: user + version: + description: > + The current comment version. To retrieve version values, use the get + comments API. + type: string + example: Wzk1LDFd + required: + - comment + - id + - owner + - type + - version examples: create_case_request: summary: Create a security case that uses a Jira connector. @@ -4540,6 +5981,126 @@ components: isPreconfigured: false isDeprecated: false referencedByCount: 0 + add_comment_request: + summary: Adds a comment to a case. + value: + type: user + comment: A new comment. + owner: cases + add_comment_response: + summary: >- + The add comment to case API returns a JSON object that contains details + about the case and its comments. + value: + comments: + - id: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + version: WzIwNDMxLDFd + type: user + owner: cases + comment: A new comment. + created_at: '2022-06-02T00:49:47.716Z' + created_by: + username: elastic + email: null + full_name: null + pushed_at: null + pushed_by: null + updated_at: null + updated_by: null + totalAlerts: 0 + id: 293f1bc0-74f6-11ea-b83a-553aecdb28b6 + version: WzIzMzgsMV0= + totalComment: 1 + title: Case title 1 + tags: + - tag 1 + description: A case description. + settings: + syncAlerts: false + owner: cases + duration: null + severity: low + closed_at: null + closed_by: null + created_at: '2022-03-24T00:37:03.906Z' + created_by: + email: null + full_name: null + username: elastic + status: open + updated_at: '2022-06-03T00:49:47.716Z' + updated_by: + username: elastic + email: null + full_name: null + connector: + id: none + name: none + type: .none + fields: null + external_service: null + update_comment_request: + summary: Updates a comment of a case. + value: + id: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + version: Wzk1LDFd + type: user + comment: An updated comment. + update_comment_response: + summary: >- + The add comment to case API returns a JSON object that contains details + about the case and its comments. + value: + comments: + - id: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + version: WzIwNjM3LDFd + comment: An updated comment. + type: user + owner: cases + created_at: '2022-03-24T00:37:10.832Z' + created_by: + username: elastic + full_name: null + email: null + pushed_at: null + pushed_by: null + updated_at: '2022-03-24T01:27:06.210Z' + updated_by: + username: elastic + full_name: null + email: null + totalAlerts: 0 + id: 293f1bc0-74f6-11ea-b83a-553aecdb28b6 + version: WzIwNjM2LDFd + totalComment: 1 + title: Case title 1 + tags: + - tag 1 + description: A case description. + settings: + syncAlerts: false + owner: cases + duration: null + severity: low + closed_at: null + closed_by: null + created_at: '2022-03-24T00:37:03.906Z' + created_by: + username: elastic + full_name: null + email: null + status: open + updated_at: '2022-03-24T01:27:06.210Z' + updated_by: + username: elastic + full_name: null + email: null + connector: + id: none + name: none + type: .none + fields: null + external_service: null security: - basicAuth: [] - apiKeyAuth: [] diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_request.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_request.yaml new file mode 100644 index 0000000000000..e243fabc4fc90 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_request.yaml @@ -0,0 +1,7 @@ +summary: Adds a comment to a case. +value: + { + "type": "user", + "comment": "A new comment.", + "owner": "cases" + } \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_response.yaml new file mode 100644 index 0000000000000..ea825da377a3b --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/examples/add_comment_response.yaml @@ -0,0 +1,58 @@ +summary: The add comment to case API returns a JSON object that contains details about the case and its comments. +value: + { + "comments":[ + { + "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIwNDMxLDFd", + "type":"user", + "owner":"cases", + "comment":"A new comment.", + "created_at":"2022-06-02T00:49:47.716Z", + "created_by": { + "username": "elastic", + "email": null, + "full_name": null + }, + "pushed_at":null, + "pushed_by":null, + "updated_at":null, + "updated_by":null + } + ], + "totalAlerts":0, + "id":"293f1bc0-74f6-11ea-b83a-553aecdb28b6", + "version":"WzIzMzgsMV0=", + "totalComment":1, + "title": "Case title 1", + "tags": ["tag 1"], + "description": "A case description.", + "settings": { + "syncAlerts":false + }, + "owner": "cases", + "duration": null, + "severity": "low", + "closed_at": null, + "closed_by": null, + "created_at": "2022-03-24T00:37:03.906Z", + "created_by": { + "email": null, + "full_name": null, + "username": "elastic" + }, + "status": "open", + "updated_at": "2022-06-03T00:49:47.716Z", + "updated_by": { + "username": "elastic", + "email": null, + "full_name": null + }, + "connector": { + "id": "none", + "name": "none", + "type": ".none", + "fields": null + }, + "external_service": null + } \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_request.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_request.yaml new file mode 100644 index 0000000000000..066830ce20777 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_request.yaml @@ -0,0 +1,8 @@ +summary: Updates a comment of a case. +value: + { + "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", + "version": "Wzk1LDFd", + "type": "user", + "comment": "An updated comment." + } \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_response.yaml new file mode 100644 index 0000000000000..9a3ba642d6ece --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/examples/update_comment_response.yaml @@ -0,0 +1,59 @@ +summary: The add comment to case API returns a JSON object that contains details about the case and its comments. +value: + { + "comments":[{ + "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIwNjM3LDFd", + "comment": "An updated comment.", + "type": "user", + "owner": "cases", + "created_at": "2022-03-24T00:37:10.832Z", + "created_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "pushed_at": null, + "pushed_by": null, + "updated_at": "2022-03-24T01:27:06.210Z", + "updated_by": { + "username": "elastic", + "full_name": null, + "email": null + } + } + ], + "totalAlerts": 0, + "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", + "version": "WzIwNjM2LDFd", + "totalComment": 1, + "title": "Case title 1", + "tags": ["tag 1"], + "description": "A case description.", + "settings": {"syncAlerts":false}, + "owner": "cases", + "duration": null, + "severity": "low", + "closed_at": null, + "closed_by": null, + "created_at": "2022-03-24T00:37:03.906Z", + "created_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "status": "open", + "updated_at": "2022-03-24T01:27:06.210Z", + "updated_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "connector": { + "id": "none", + "name": "none", + "type": ".none", + "fields": null + }, + "external_service": null + } diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/case_id.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/case_id.yaml new file mode 100644 index 0000000000000..beae115acce08 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/parameters/case_id.yaml @@ -0,0 +1,7 @@ +in: path +name: caseId +description: The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. +required: true +schema: + type: string + example: '9c235210-6834-11ea-a78c-6ffb38a34414' \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/comment_id.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/comment_id.yaml new file mode 100644 index 0000000000000..41c25d8a03dc5 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/parameters/comment_id.yaml @@ -0,0 +1,7 @@ +in: path +name: commentId +description: The identifier for the comment. To retrieve comment IDs, use the get case or find cases APIs. If it is not specified, all comments are deleted. +required: false +schema: + type: string + example: '71ec1870-725b-11ea-a0b2-c51ea50a58e2' \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/add_alert_comment_request_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/add_alert_comment_request_properties.yaml new file mode 100644 index 0000000000000..bab61cb5cb3c8 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/add_alert_comment_request_properties.yaml @@ -0,0 +1,55 @@ +type: object +properties: + alertId: + description: > + The alert identifier. It is required only when `type` is `alert`. This + functionality is in technical preview and may be changed or removed in a + future release. Elastic will apply best effort to fix any issues, but + features in technical preview are not subject to the support SLA of + official GA features. + type: string + x-technical-preview: true + example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + index: + description: > + The alert index. It is required only when `type` is `alert`. This + functionality is in technical preview and may be changed or removed in a + future release. Elastic will apply best effort to fix any issues, but + features in technical preview are not subject to the support SLA of + official GA features. + type: string + x-technical-preview: true + owner: + $ref: 'owners.yaml' + rule: + description: > + The rule that is associated with the alert. It is required only when + `type` is `alert`. This functionality is in technical preview and may be + changed or removed in a future release. Elastic will apply best effort to + fix any issues, but features in technical preview are not subject to the + support SLA of official GA features. + type: object + x-technical-preview: true + properties: + id: + description: The rule identifier. + type: string + x-technical-preview: true + example: 94d80550-aaf4-11ec-985f-97e55adae8b9 + name: + description: The rule name. + type: string + x-technical-preview: true + example: security_rule + type: + description: The type of comment. + type: string + enum: + - alert + example: alert +required: + - alertId + - index + - owner + - rule + - type \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/add_user_comment_request_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/add_user_comment_request_properties.yaml new file mode 100644 index 0000000000000..d09958e13fec8 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/add_user_comment_request_properties.yaml @@ -0,0 +1,18 @@ +type: object +properties: + comment: + description: The new comment. It is required only when `type` is `user`. + type: string + example: A new comment. + owner: + $ref: 'owners.yaml' + type: + type: string + description: The type of comment. + enum: + - user + example: user +required: + - comment + - owner + - type \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml new file mode 100644 index 0000000000000..638e57c8b2d16 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml @@ -0,0 +1,79 @@ + +type: object +properties: + alertId: + type: string + example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + created_at: + type: string + format: date-time + example: 2022-03-24T02:31:03.210Z + created_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + id: + type: string + example: 73362370-ab1a-11ec-985f-97e55adae8b9 + index: + type: string + example: .internal.alerts-security.alerts-default-000001 + owner: + $ref: 'owners.yaml' + pushed_at: + type: string + format: date-time + example: null + nullable: true + pushed_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + nullable: true + rule: + type: object + properties: + id: + type: string + example: 94d80550-aaf4-11ec-985f-97e55adae8b9 + name: + type: string + example: security_rule + type: + type: string + example: alert + updated_at: + type: string + format: date-time + example: null + updated_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + version: + type: string + example: WzMwNDgsMV0= \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml index dcc3715377255..4390d13f7e410 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml @@ -17,7 +17,9 @@ closed_by: comments: type: array items: - type: string + oneOf: + - $ref: 'alert_comment_response_properties.yaml' + - $ref: 'user_comment_response_properties.yaml' example: [] connector: type: object diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/comment_types.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/comment_types.yaml index a6a86ae163b20..9731b8ce4fad5 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/comment_types.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/comment_types.yaml @@ -2,4 +2,5 @@ type: string description: The type of comment. enum: - alert - - user \ No newline at end of file + - user +example: user \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/update_alert_comment_request_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/update_alert_comment_request_properties.yaml new file mode 100644 index 0000000000000..c0db63ff9374a --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/update_alert_comment_request_properties.yaml @@ -0,0 +1,69 @@ +type: object +properties: + alertId: + description: > + The alert identifier. It is required only when `type` is `alert`. This + functionality is in technical preview and may be changed or removed in a + future release. Elastic will apply best effort to fix any issues, but + features in technical preview are not subject to the support SLA of + official GA features. + type: string + x-technical-preview: true + example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + id: + type: string + description: > + The identifier for the comment. To retrieve comment IDs, use the + get comments API. + example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + index: + description: > + The alert index. It is required only when `type` is `alert`. This + functionality is in technical preview and may be changed or removed in a + future release. Elastic will apply best effort to fix any issues, but + features in technical preview are not subject to the support SLA of + official GA features. + type: string + x-technical-preview: true + owner: + $ref: 'owners.yaml' + rule: + description: > + The rule that is associated with the alert. It is required only when + `type` is `alert`. This functionality is in technical preview and may be + changed or removed in a future release. Elastic will apply best effort to + fix any issues, but features in technical preview are not subject to the + support SLA of official GA features. + type: object + x-technical-preview: true + properties: + id: + description: The rule identifier. + type: string + x-technical-preview: true + example: 94d80550-aaf4-11ec-985f-97e55adae8b9 + name: + description: The rule name. + type: string + x-technical-preview: true + example: security_rule + type: + description: The type of comment. + type: string + enum: + - alert + example: alert + version: + description: > + The current comment version. To retrieve version values, use the get + comments API. + type: string + example: Wzk1LDFd +required: + - alertId + - id + - index + - owner + - rule + - type + - version \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/update_user_comment_request_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/update_user_comment_request_properties.yaml new file mode 100644 index 0000000000000..83d7f0715da21 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/update_user_comment_request_properties.yaml @@ -0,0 +1,32 @@ +type: object +properties: + comment: + description: The new comment. It is required only when `type` is `user`. + type: string + example: A new comment. + id: + type: string + description: > + The identifier for the comment. To retrieve comment IDs, use the + get comments API. + example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + owner: + $ref: 'owners.yaml' + type: + type: string + description: The type of comment. + enum: + - user + example: user + version: + description: > + The current comment version. To retrieve version values, use the get + comments API. + type: string + example: Wzk1LDFd +required: + - comment + - id + - owner + - type + - version \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/user_comment_response_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/user_comment_response_properties.yaml new file mode 100644 index 0000000000000..b151e21fad348 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/user_comment_response_properties.yaml @@ -0,0 +1,64 @@ +type: object +properties: + comment: + type: string + example: A new comment. + created_at: + type: string + format: date-time + example: 2022-05-13T09:16:17.416Z + created_by: + type: object + properties: + email: + type: string + example: null + full_name: + type: string + example: null + username: + type: string + example: elastic + id: + type: string + example: 8af6ac20-74f6-11ea-b83a-553aecdb28b6 + owner: + $ref: 'owners.yaml' + pushed_at: + type: string + format: date-time + nullable: true + example: null + pushed_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + type: + type: string + example: user + updated_at: + type: string + format: date-time + nullable: true + example: null + updated_by: + type: object + properties: + email: + type: string + full_name: + type: string + username: + type: string + nullable: true + example: null + version: + type: string + example: WzIwNDMxLDFd \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml index 298f4df15c1c3..e5d370c94d773 100644 --- a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml +++ b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml @@ -39,8 +39,8 @@ paths: # $ref: 'paths/api@cases@{caseid}.yaml' # '/api/cases/{caseId}/alerts': # $ref: 'paths/api@cases@{caseid}@alerts.yaml' -# '/api/cases/{caseId}/comments': -# $ref: 'paths/api@cases@{caseid}@comments.yaml' + '/api/cases/{caseId}/comments': + $ref: 'paths/api@cases@{caseid}@comments.yaml' # '/api/cases/{caseId}/comments/{commentId}': # $ref: 'paths/api@cases@{caseid}@comments@{commentid}.yaml' # '/api/cases/{caseId}/connector/{connectorId}/_push': @@ -70,10 +70,10 @@ paths: # $ref: 'paths/s@{spaceid}@api@cases@{caseid}.yaml' # '/s/{spaceId}/api/cases/{caseId}/alerts': # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@alerts.yaml' - # '/s/{spaceId}/api/cases/{caseId}/comments': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments.yaml' - # '/s/{spaceId}/api/cases/{caseId}/comments/{commentId}': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml' + '/s/{spaceId}/api/cases/{caseId}/comments': + $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments.yaml' +# '/s/{spaceId}/api/cases/{caseId}/comments/{commentId}': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml' # '/s/{spaceId}/api/cases/{caseId}/connector/{connectorId}/_push': # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml' # '/s/{spaceId}/api/cases/{caseId}/user_actions': diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml new file mode 100644 index 0000000000000..8e719ad40f669 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml @@ -0,0 +1,95 @@ +post: + summary: Adds a comment or alert to a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the case you're creating. + tags: + - cases + - kibana + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/case_id.yaml' + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '../components/schemas/add_alert_comment_request_properties.yaml' + - $ref: '../components/schemas/add_user_comment_request_properties.yaml' + examples: + createCaseCommentRequest: + $ref: '../components/examples/add_comment_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: object + properties: + $ref: '../components/schemas/case_response_properties.yaml' + examples: + createCaseCommentResponse: + $ref: '../components/examples/add_comment_response.yaml' + servers: + - url: https://localhost:5601 + +delete: + summary: Deletes all comments and alerts from a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're deleting. + tags: + - cases + - kibana + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/case_id.yaml' + responses: + '204': + description: Indicates a successful call. + servers: + - url: https://localhost:5601 + +patch: + summary: Updates a comment or alert in a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the case you're updating. + NOTE: You cannot change the comment type or the owner of a comment. + tags: + - cases + - kibana + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/case_id.yaml' + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '../components/schemas/update_alert_comment_request_properties.yaml' + - $ref: '../components/schemas/update_user_comment_request_properties.yaml' + examples: + updateCaseCommentRequest: + $ref: '../components/examples/update_comment_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: object + properties: + $ref: '../components/schemas/case_response_properties.yaml' + examples: + updateCaseCommentResponse: + $ref: '../components/examples/update_comment_response.yaml' + servers: + - url: https://localhost:5601 + +servers: + - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml new file mode 100644 index 0000000000000..a89edd5247472 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml @@ -0,0 +1,21 @@ +delete: + summary: Deletes a comment or alert from a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're deleting. + tags: + - cases + - kibana + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/case_id.yaml' + - $ref: '../components/parameters/comment_id.yaml' + responses: + '204': + description: Indicates a successful call. + servers: + - url: https://localhost:5601 + +servers: + - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml new file mode 100644 index 0000000000000..0e1960bdce513 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments.yaml @@ -0,0 +1,98 @@ +post: + summary: Adds a comment or alert to a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the case you're creating. + tags: + - cases + - kibana + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/case_id.yaml' + - $ref: '../components/parameters/space_id.yaml' + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '../components/schemas/add_alert_comment_request_properties.yaml' + - $ref: '../components/schemas/add_user_comment_request_properties.yaml' + examples: + createCaseCommentRequest: + $ref: '../components/examples/add_comment_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: object + properties: + $ref: '../components/schemas/case_response_properties.yaml' + examples: + createCaseCommentResponse: + $ref: '../components/examples/add_comment_response.yaml' + servers: + - url: https://localhost:5601 + +delete: + summary: Deletes all comments and alerts from a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're deleting. + tags: + - cases + - kibana + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/case_id.yaml' + - $ref: '../components/parameters/space_id.yaml' + responses: + '204': + description: Indicates a successful call. + servers: + - url: https://localhost:5601 + +patch: + summary: Updates a comment or alert in a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the case you're updating. + NOTE: You cannot change the comment type or the owner of a comment. + tags: + - cases + - kibana + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/case_id.yaml' + - $ref: '../components/parameters/space_id.yaml' + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '../components/schemas/update_alert_comment_request_properties.yaml' + - $ref: '../components/schemas/update_user_comment_request_properties.yaml' + examples: + updateCaseCommentRequest: + $ref: '../components/examples/update_comment_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: object + properties: + $ref: '../components/schemas/case_response_properties.yaml' + examples: + updateCaseCommentResponse: + $ref: '../components/examples/update_comment_response.yaml' + servers: + - url: https://localhost:5601 + +servers: + - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml new file mode 100644 index 0000000000000..4970fa9ec7be2 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml @@ -0,0 +1,22 @@ +delete: + summary: Deletes a comment or alert from a case. + description: > + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're deleting. + tags: + - cases + - kibana + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/case_id.yaml' + - $ref: '../components/parameters/comment_id.yaml' + - $ref: '../components/parameters/space_id.yaml' + responses: + '204': + description: Indicates a successful call. + servers: + - url: https://localhost:5601 + +servers: + - url: https://localhost:5601 \ No newline at end of file diff --git a/x-pack/plugins/fleet/common/services/agent_status.ts b/x-pack/plugins/fleet/common/services/agent_status.ts index 641da154e712d..d2a217a06e5bf 100644 --- a/x-pack/plugins/fleet/common/services/agent_status.ts +++ b/x-pack/plugins/fleet/common/services/agent_status.ts @@ -34,7 +34,7 @@ export function getAgentStatus(agent: Agent): AgentStatus { if (agent.upgrade_started_at && !agent.upgraded_at) { return 'updating'; } - if (intervalsSinceLastCheckIn >= 4) { + if (intervalsSinceLastCheckIn >= 10) { return 'offline'; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx index a602c08e45102..20fbeb3aba0cf 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx @@ -355,7 +355,7 @@ export const AgentUpgradeAgentModal: React.FunctionComponent {i18n.translate('xpack.fleet.upgradeAgents.maintainanceAvailableLabel', { - defaultMessage: 'Maintainance window available', + defaultMessage: 'Maintenance window available', })} @@ -395,6 +395,7 @@ export const AgentUpgradeAgentModal: React.FunctionComponent + { @@ -80,4 +82,22 @@ describe('Agent status service', () => { const status = await getAgentStatusById(mockElasticsearchClient, 'id'); expect(status).toEqual('unenrolling'); }); + + it('should return offline when agent has not checked in for 10 intervals', async () => { + const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + mockElasticsearchClient.get.mockResponse( + // @ts-expect-error not full interface + { + _id: 'id', + _source: { + active: true, + last_checkin: new Date(Date.now() - 10 * AGENT_POLLING_THRESHOLD_MS - 1000).toISOString(), + local_metadata: {}, + user_provided_metadata: {}, + }, + } + ); + const status = await getAgentStatusById(mockElasticsearchClient, 'id'); + expect(status).toEqual('offline'); + }); }); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/helper.test.ts similarity index 98% rename from x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/helper.test.ts index f915e871b98f7..a36c53ca9bab5 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/helper.test.ts @@ -10,7 +10,7 @@ import path from 'path'; import type { RegistryDataStream } from '../../../../types'; -import { rewriteIngestPipeline, getPipelineNameForInstallation } from './install'; +import { getPipelineNameForInstallation, rewriteIngestPipeline } from './helpers'; test('a json-format pipeline with pipeline references is correctly rewritten', () => { const inputStandard = readFileSync( diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/helpers.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/helpers.ts new file mode 100644 index 0000000000000..bbddb0e454174 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/helpers.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 { ElasticsearchAssetType } from '../../../../types'; +import type { RegistryDataStream } from '../../../../types'; +import { getPathParts } from '../../archive'; + +export const isTopLevelPipeline = (path: string) => { + const pathParts = getPathParts(path); + return ( + pathParts.type === ElasticsearchAssetType.ingestPipeline && pathParts.dataset === undefined + ); +}; + +export const getPipelineNameForInstallation = ({ + pipelineName, + dataStream, + packageVersion, +}: { + pipelineName: string; + dataStream?: RegistryDataStream; + packageVersion: string; +}): string => { + if (dataStream !== undefined) { + const isPipelineEntry = pipelineName === dataStream.ingest_pipeline; + const suffix = isPipelineEntry ? '' : `-${pipelineName}`; + // if this is the pipeline entry, don't add a suffix + return `${getPipelineNameForDatastream({ dataStream, packageVersion })}${suffix}`; + } + // It's a top-level pipeline + return `${packageVersion}-${pipelineName}`; +}; + +export const getPipelineNameForDatastream = ({ + dataStream, + packageVersion, +}: { + dataStream: RegistryDataStream; + packageVersion: string; +}): string => { + return `${dataStream.type}-${dataStream.dataset}-${packageVersion}`; +}; + +export interface RewriteSubstitution { + source: string; + target: string; + templateFunction: string; +} + +export function rewriteIngestPipeline( + pipeline: string, + substitutions: RewriteSubstitution[] +): string { + substitutions.forEach((sub) => { + const { source, target, templateFunction } = sub; + // This fakes the use of the golang text/template expression {{SomeTemplateFunction 'some-param'}} + // cf. https://github.com/elastic/beats/blob/master/filebeat/fileset/fileset.go#L294 + + // "Standard style" uses '{{' and '}}' as delimiters + const matchStandardStyle = `{{\\s?${templateFunction}\\s+['"]${source}['"]\\s?}}`; + // "Beats style" uses '{<' and '>}' as delimiters because this is current practice in the beats project + const matchBeatsStyle = `{<\\s?${templateFunction}\\s+['"]${source}['"]\\s?>}`; + + const regexStandardStyle = new RegExp(matchStandardStyle); + const regexBeatsStyle = new RegExp(matchBeatsStyle); + pipeline = pipeline.replace(regexStandardStyle, target).replace(regexBeatsStyle, target); + }); + return pipeline; +} diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/index.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/index.ts index 5f093a19157f9..0c51cbcd9602a 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/index.ts @@ -6,5 +6,5 @@ */ export { prepareToInstallPipelines, isTopLevelPipeline } from './install'; - +export { getPipelineNameForDatastream } from './helpers'; export { deletePreviousPipelines, deletePipeline } from './remove'; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.test.ts new file mode 100644 index 0000000000000..5e3ba27a5357b --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.test.ts @@ -0,0 +1,79 @@ +/* + * 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. + */ +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { elasticsearchClientMock } from '@kbn/core/server/elasticsearch/client/mocks'; +import { loggerMock } from '@kbn/logging-mocks'; + +import { getArchiveEntry } from '../../archive/cache'; + +import { prepareToInstallPipelines } from './install'; + +jest.mock('../../archive/cache'); + +const mockedGetArchiveEntry = getArchiveEntry as jest.MockedFunction; + +describe('Install pipeline tests', () => { + describe('prepareToInstallPipelines', () => { + it('should work with datastream without ingest pipeline define in the package', async () => { + const res = prepareToInstallPipelines( + { + version: '1.0.0', + data_streams: [ + { + dataset: 'datasettest', + type: 'logs', + package: 'packagetest', + path: '/datasettest', + }, + ], + } as any, + [] + ); + + expect(res.assetsToAdd).toEqual([{ id: 'logs-datasettest-1.0.0', type: 'ingest_pipeline' }]); + const esClient = elasticsearchClientMock.createInternalClient(); + const logger = loggerMock.create(); + await res.install(esClient, logger); + + expect(esClient.ingest.putPipeline).toBeCalled(); + }); + + it('should work with datastream with ingest pipelines define in the package', async () => { + const res = prepareToInstallPipelines( + { + version: '1.0.0', + data_streams: [ + { + dataset: 'datasettest', + type: 'logs', + package: 'packagetest', + path: 'datasettest', + ingest_pipeline: 'default', + }, + ], + } as any, + [ + 'packagetest-1.0.0/data_stream/datasettest/elasticsearch/ingest_pipeline/default.yml', + 'packagetest-1.0.0/data_stream/datasettest/elasticsearch/ingest_pipeline/standard.yml', + ] + ); + expect(res.assetsToAdd).toEqual([ + { id: 'logs-datasettest-1.0.0', type: 'ingest_pipeline' }, + { id: 'logs-datasettest-1.0.0-standard', type: 'ingest_pipeline' }, + ]); + + const esClient = elasticsearchClientMock.createInternalClient(); + const logger = loggerMock.create(); + + mockedGetArchiveEntry.mockReturnValue(Buffer.from(`description: test`)); + + await res.install(esClient, logger); + + expect(esClient.ingest.putPipeline).toBeCalledTimes(2); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts index da035a44c9921..3401674dfda55 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts @@ -22,10 +22,17 @@ import { appendMetadataToIngestPipeline } from '../meta'; import { retryTransientEsErrors } from '../retry'; -interface RewriteSubstitution { - source: string; - target: string; - templateFunction: string; +import { + getPipelineNameForDatastream, + getPipelineNameForInstallation, + rewriteIngestPipeline, +} from './helpers'; +import type { RewriteSubstitution } from './helpers'; + +interface PipelineInstall { + nameForInstallation: string; + contentForInstallation: string; + extension: string; } export const isTopLevelPipeline = (path: string) => { @@ -59,8 +66,12 @@ export const prepareToInstallPipelines = ( const filteredPaths = pipelinePaths.filter((path) => isDataStreamPipeline(path, dataStream.path) ); + let createdDatastreamPipeline = false; const pipelineObjectRefs = filteredPaths.map((path) => { const { name } = getNameAndExtension(path); + if (name === dataStream.ingest_pipeline) { + createdDatastreamPipeline = true; + } const nameForInstallation = getPipelineNameForInstallation({ pipelineName: name, dataStream, @@ -68,6 +79,13 @@ export const prepareToInstallPipelines = ( }); return { id: nameForInstallation, type: ElasticsearchAssetType.ingestPipeline }; }); + if (!createdDatastreamPipeline) { + const nameForInstallation = getPipelineNameForDatastream({ + dataStream, + packageVersion: pkgVersion, + }); + acc.push({ id: nameForInstallation, type: ElasticsearchAssetType.ingestPipeline }); + } acc.push(...pipelineObjectRefs); return acc; }, []) @@ -75,6 +93,7 @@ export const prepareToInstallPipelines = ( const topLevelPipelineRefs = topLevelPipelinePaths.map((path) => { const { name } = getNameAndExtension(path); + const nameForInstallation = getPipelineNameForInstallation({ pipelineName: name, packageVersion: pkgVersion, @@ -89,17 +108,16 @@ export const prepareToInstallPipelines = ( install: async (esClient, logger) => { const pipelines = dataStreams ? dataStreams.reduce>>((acc, dataStream) => { - if (dataStream.ingest_pipeline) { - acc.push( - installAllPipelines({ - dataStream, - esClient, - logger, - paths: pipelinePaths, - installablePackage, - }) - ); - } + acc.push( + installAllPipelines({ + dataStream, + esClient, + logger, + paths: pipelinePaths, + installablePackage, + }) + ); + return acc; }, []) : []; @@ -121,27 +139,6 @@ export const prepareToInstallPipelines = ( }; }; -export function rewriteIngestPipeline( - pipeline: string, - substitutions: RewriteSubstitution[] -): string { - substitutions.forEach((sub) => { - const { source, target, templateFunction } = sub; - // This fakes the use of the golang text/template expression {{SomeTemplateFunction 'some-param'}} - // cf. https://github.com/elastic/beats/blob/master/filebeat/fileset/fileset.go#L294 - - // "Standard style" uses '{{' and '}}' as delimiters - const matchStandardStyle = `{{\\s?${templateFunction}\\s+['"]${source}['"]\\s?}}`; - // "Beats style" uses '{<' and '>}' as delimiters because this is current practice in the beats project - const matchBeatsStyle = `{<\\s?${templateFunction}\\s+['"]${source}['"]\\s?>}`; - - const regexStandardStyle = new RegExp(matchStandardStyle); - const regexBeatsStyle = new RegExp(matchBeatsStyle); - pipeline = pipeline.replace(regexStandardStyle, target).replace(regexBeatsStyle, target); - }); - return pipeline; -} - export async function installAllPipelines({ esClient, logger, @@ -158,18 +155,27 @@ export async function installAllPipelines({ const pipelinePaths = dataStream ? paths.filter((path) => isDataStreamPipeline(path, dataStream.path)) : paths; - let pipelines: any[] = []; + const pipelinesInfos: Array<{ + name: string; + nameForInstallation: string; + content: string; + extension: string; + }> = []; const substitutions: RewriteSubstitution[] = []; + let datastreamPipelineCreated = false; pipelinePaths.forEach((path) => { const { name, extension } = getNameAndExtension(path); + if (name === dataStream?.ingest_pipeline) { + datastreamPipelineCreated = true; + } const nameForInstallation = getPipelineNameForInstallation({ pipelineName: name, dataStream, packageVersion: installablePackage.version, }); const content = getAsset(path).toString('utf-8'); - pipelines.push({ + pipelinesInfos.push({ name, nameForInstallation, content, @@ -182,14 +188,27 @@ export async function installAllPipelines({ }); }); - pipelines = pipelines.map((pipeline) => { + const pipelinesToInstall: PipelineInstall[] = pipelinesInfos.map((pipeline) => { return { ...pipeline, contentForInstallation: rewriteIngestPipeline(pipeline.content, substitutions), }; }); - const installationPromises = pipelines.map(async (pipeline) => { + if (!datastreamPipelineCreated && dataStream) { + const nameForInstallation = getPipelineNameForDatastream({ + dataStream, + packageVersion: installablePackage.version, + }); + + pipelinesToInstall.push({ + nameForInstallation, + contentForInstallation: 'processors: []', + extension: 'yml', + }); + } + + const installationPromises = pipelinesToInstall.map(async (pipeline) => { return installPipeline({ esClient, pipeline, installablePackage, logger }); }); @@ -204,7 +223,7 @@ async function installPipeline({ }: { esClient: ElasticsearchClient; logger: Logger; - pipeline: any; + pipeline: PipelineInstall; installablePackage?: InstallablePackage; }): Promise { const pipelineWithMetadata = appendMetadataToIngestPipeline({ @@ -304,22 +323,3 @@ const getNameAndExtension = ( extension: filename.split('.')[1], }; }; - -export const getPipelineNameForInstallation = ({ - pipelineName, - dataStream, - packageVersion, -}: { - pipelineName: string; - dataStream?: RegistryDataStream; - packageVersion: string; -}): string => { - if (dataStream !== undefined) { - const isPipelineEntry = pipelineName === dataStream.ingest_pipeline; - const suffix = isPipelineEntry ? '' : `-${pipelineName}`; - // if this is the pipeline entry, don't add a suffix - return `${dataStream.type}-${dataStream.dataset}-${packageVersion}${suffix}`; - } - // It's a top-level pipeline - return `${packageVersion}-${pipelineName}`; -}; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index df6d9d84a08c5..0393c8bf91a50 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -24,7 +24,7 @@ import type { } from '../../../../types'; import { loadFieldsFromYaml, processFields } from '../../fields/field'; -import { getPipelineNameForInstallation } from '../ingest_pipeline/install'; +import { getPipelineNameForDatastream } from '../ingest_pipeline'; import { getAsset, getPathParts } from '../../archive'; import { FLEET_COMPONENT_TEMPLATES, @@ -365,14 +365,7 @@ export function prepareTemplate({ const templateIndexPattern = generateTemplateIndexPattern(dataStream); const templatePriority = getTemplatePriority(dataStream); - let pipelineName; - if (dataStream.ingest_pipeline) { - pipelineName = getPipelineNameForInstallation({ - pipelineName: dataStream.ingest_pipeline, - dataStream, - packageVersion, - }); - } + const pipelineName = getPipelineNameForDatastream({ dataStream, packageVersion }); const defaultSettings = buildDefaultSettings({ templateName, diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.test.tsx b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.test.tsx index 6da64bdb58805..bff812e7e7601 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.test.tsx +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.test.tsx @@ -17,6 +17,7 @@ import { createStartServicesAccessorMock } from '../test_helpers'; import { createLazyContainerMetricsTable } from './create_lazy_container_metrics_table'; import IntegratedContainerMetricsTable from './integrated_container_metrics_table'; import { metricByField } from './use_container_metrics_table'; +import type { MetricsExplorerSeries } from '../../../../common/http_api'; describe('ContainerMetricsTable', () => { const timerange = { @@ -113,7 +114,7 @@ function createContainer( uptimeMs: number, cpuUsagePct: number, memoryUsageBytes: number -) { +): Partial { return { id: name, rows: [ @@ -121,7 +122,7 @@ function createContainer( [metricByField['kubernetes.container.start_time']]: uptimeMs, [metricByField['kubernetes.container.cpu.usage.node.pct']]: cpuUsagePct, [metricByField['kubernetes.container.memory.usage.bytes']]: memoryUsageBytes, - }, + } as MetricsExplorerSeries['rows'][number], ], }; } diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.tsx b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.tsx index d64659781dc0f..3ad7d18bc221c 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.tsx +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.tsx @@ -111,7 +111,14 @@ function containerNodeColumns( truncateText: true, textOnly: true, render: (name: string) => { - return ; + return ( + + ); }, }, { diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts new file mode 100644 index 0000000000000..97044636cc7a1 --- /dev/null +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useContainerMetricsTable } from './use_container_metrics_table'; +import { useInfrastructureNodeMetrics } from '../shared'; +import { renderHook } from '@testing-library/react-hooks'; + +jest.mock('../shared', () => ({ + ...jest.requireActual('../shared'), + useInfrastructureNodeMetrics: jest.fn(), +})); + +describe('useContainerMetricsTable hook', () => { + const useInfrastructureNodeMetricsMock = useInfrastructureNodeMetrics as jest.MockedFunction< + typeof useInfrastructureNodeMetrics + >; + + it('should call useInfrastructureNodeMetrics hook with event.module filter in filterClauseDsl query', () => { + const filterClauseDsl = { + bool: { + filter: [{ terms: { 'container.id': 'gke-edge-oblt-pool-1-9a60016d-lgg9' } }], + }, + }; + + const filterClauseWithEventModuleFilter = { + bool: { + filter: [{ term: { 'event.dataset': 'kubernetes.container' } }, { ...filterClauseDsl }], + }, + }; + + renderHook(() => + useContainerMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + filterClauseDsl, + }) + ); + + expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( + expect.objectContaining({ + metricsExplorerOptions: expect.objectContaining({ + filterQuery: JSON.stringify(filterClauseWithEventModuleFilter), + }), + }) + ); + }); +}); diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.ts index fe570a80b6615..40cb1e0ee2763 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.ts +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.ts @@ -5,39 +5,47 @@ * 2.0. */ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import type { MetricsExplorerRow, MetricsExplorerSeries, } from '../../../../common/http_api/metrics_explorer'; -import type { MetricsMap, SortState, UseNodeMetricsTableOptions } from '../shared'; -import { metricsToApiOptions, useInfrastructureNodeMetrics } from '../shared'; +import type { MetricsQueryOptions, SortState, UseNodeMetricsTableOptions } from '../shared'; +import { + metricsToApiOptions, + useInfrastructureNodeMetrics, + createMetricByFieldLookup, +} from '../shared'; type ContainerMetricsField = | 'kubernetes.container.start_time' | 'kubernetes.container.cpu.usage.node.pct' | 'kubernetes.container.memory.usage.bytes'; -const containerMetricsMap: MetricsMap = { - 'kubernetes.container.start_time': { - aggregation: 'max', - field: 'kubernetes.container.start_time', +const containerMetricsQueryConfig: MetricsQueryOptions = { + sourceFilter: { + term: { + 'event.dataset': 'kubernetes.container', + }, }, - 'kubernetes.container.cpu.usage.node.pct': { - aggregation: 'avg', - field: 'kubernetes.container.cpu.usage.node.pct', - }, - 'kubernetes.container.memory.usage.bytes': { - aggregation: 'avg', - field: 'kubernetes.container.memory.usage.bytes', + groupByField: 'container.id', + metricsMap: { + 'kubernetes.container.start_time': { + aggregation: 'max', + field: 'kubernetes.container.start_time', + }, + 'kubernetes.container.cpu.usage.node.pct': { + aggregation: 'avg', + field: 'kubernetes.container.cpu.usage.node.pct', + }, + 'kubernetes.container.memory.usage.bytes': { + aggregation: 'avg', + field: 'kubernetes.container.memory.usage.bytes', + }, }, }; -const { options: containerMetricsOptions, metricByField } = metricsToApiOptions( - containerMetricsMap, - 'container.id' -); -export { metricByField }; +export const metricByField = createMetricByFieldLookup(containerMetricsQueryConfig.metricsMap); export interface ContainerNodeMetricsRow { name: string; @@ -56,6 +64,11 @@ export function useContainerMetricsTable({ direction: 'desc', }); + const { options: containerMetricsOptions } = useMemo( + () => metricsToApiOptions(containerMetricsQueryConfig, filterClauseDsl), + [filterClauseDsl] + ); + const { isLoading, nodes: containers, @@ -63,7 +76,6 @@ export function useContainerMetricsTable({ } = useInfrastructureNodeMetrics({ metricsExplorerOptions: containerMetricsOptions, timerange, - filterClauseDsl, transform: seriesToContainerNodeMetricsRow, sortState, currentPageIndex, diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/host_metrics_table.test.tsx b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/host_metrics_table.test.tsx index 5648c077a527a..892972a077835 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/host_metrics_table.test.tsx +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/host_metrics_table.test.tsx @@ -17,6 +17,7 @@ import { createStartServicesAccessorMock } from '../test_helpers'; import { createLazyHostMetricsTable } from './create_lazy_host_metrics_table'; import IntegratedHostMetricsTable from './integrated_host_metrics_table'; import { metricByField } from './use_host_metrics_table'; +import type { MetricsExplorerSeries } from '../../../../common/http_api'; describe('HostMetricsTable', () => { const timerange = { @@ -114,7 +115,7 @@ function createHost( cpuUsagePct: number, memoryBytes: number, memoryUsagePct: number -) { +): Partial { return { id: name, rows: [ @@ -123,7 +124,7 @@ function createHost( [metricByField['system.cpu.total.norm.pct']]: cpuUsagePct, [metricByField['system.memory.total']]: memoryBytes, [metricByField['system.memory.used.pct']]: memoryUsagePct, - }, + } as MetricsExplorerSeries['rows'][number], ], }; } diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/host_metrics_table.tsx b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/host_metrics_table.tsx index f0d77e84b4439..652af65e02a23 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/host_metrics_table.tsx +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/host_metrics_table.tsx @@ -111,7 +111,7 @@ function hostMetricsColumns( truncateText: true, textOnly: true, render: (name: string) => ( - + ), }, { diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts new file mode 100644 index 0000000000000..2956ed2d8bc87 --- /dev/null +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useHostMetricsTable } from './use_host_metrics_table'; +import { useInfrastructureNodeMetrics } from '../shared'; +import { renderHook } from '@testing-library/react-hooks'; + +jest.mock('../shared', () => ({ + ...jest.requireActual('../shared'), + useInfrastructureNodeMetrics: jest.fn(), +})); + +describe('useHostMetricsTable hook', () => { + const useInfrastructureNodeMetricsMock = useInfrastructureNodeMetrics as jest.MockedFunction< + typeof useInfrastructureNodeMetrics + >; + + it('should call useInfrastructureNodeMetrics hook with event.module filter in filterClauseDsl query', () => { + const filterClauseDsl = { + bool: { + should: [ + { + terms: { + 'host.name': 'gke-edge-oblt-pool-1-9a60016d-lgg9', + }, + }, + ], + minimum_should_match: 1, + }, + }; + + const filterClauseWithEventModuleFilter = { + bool: { + filter: [{ term: { 'event.module': 'system' } }, { ...filterClauseDsl }], + }, + }; + + renderHook(() => + useHostMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + filterClauseDsl, + }) + ); + + expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( + expect.objectContaining({ + metricsExplorerOptions: expect.objectContaining({ + filterQuery: JSON.stringify(filterClauseWithEventModuleFilter), + }), + }) + ); + }); +}); diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.ts index f82463e97a303..04212ce9c215b 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.ts +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.ts @@ -5,13 +5,17 @@ * 2.0. */ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import type { MetricsExplorerRow, MetricsExplorerSeries, } from '../../../../common/http_api/metrics_explorer'; -import type { MetricsMap, SortState, UseNodeMetricsTableOptions } from '../shared'; -import { metricsToApiOptions, useInfrastructureNodeMetrics } from '../shared'; +import type { MetricsQueryOptions, SortState, UseNodeMetricsTableOptions } from '../shared'; +import { + metricsToApiOptions, + useInfrastructureNodeMetrics, + createMetricByFieldLookup, +} from '../shared'; type HostMetricsField = | 'system.cpu.cores' @@ -19,24 +23,28 @@ type HostMetricsField = | 'system.memory.total' | 'system.memory.used.pct'; -const hostMetricsMap: MetricsMap = { - 'system.cpu.cores': { aggregation: 'max', field: 'system.cpu.cores' }, - 'system.cpu.total.norm.pct': { - aggregation: 'avg', - field: 'system.cpu.total.norm.pct', +const hostsMetricsQueryConfig: MetricsQueryOptions = { + sourceFilter: { + term: { + 'event.module': 'system', + }, }, - 'system.memory.total': { aggregation: 'max', field: 'system.memory.total' }, - 'system.memory.used.pct': { - aggregation: 'avg', - field: 'system.memory.used.pct', + groupByField: 'host.name', + metricsMap: { + 'system.cpu.cores': { aggregation: 'max', field: 'system.cpu.cores' }, + 'system.cpu.total.norm.pct': { + aggregation: 'avg', + field: 'system.cpu.total.norm.pct', + }, + 'system.memory.total': { aggregation: 'max', field: 'system.memory.total' }, + 'system.memory.used.pct': { + aggregation: 'avg', + field: 'system.memory.used.pct', + }, }, }; -const { options: hostMetricsOptions, metricByField } = metricsToApiOptions( - hostMetricsMap, - 'host.name' -); -export { metricByField }; +export const metricByField = createMetricByFieldLookup(hostsMetricsQueryConfig.metricsMap); export interface HostNodeMetricsRow { name: string; @@ -53,6 +61,11 @@ export function useHostMetricsTable({ timerange, filterClauseDsl }: UseNodeMetri direction: 'desc', }); + const { options: hostMetricsOptions } = useMemo( + () => metricsToApiOptions(hostsMetricsQueryConfig, filterClauseDsl), + [filterClauseDsl] + ); + const { isLoading, nodes: hosts, @@ -60,7 +73,6 @@ export function useHostMetricsTable({ timerange, filterClauseDsl }: UseNodeMetri } = useInfrastructureNodeMetrics({ metricsExplorerOptions: hostMetricsOptions, timerange, - filterClauseDsl, transform: seriesToHostNodeMetricsRow, sortState, currentPageIndex, diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.stories.tsx b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.stories.tsx index 44e7779a0f680..79b1f0d55f92c 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.stories.tsx +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.stories.tsx @@ -46,30 +46,35 @@ const storyArgs: Omit { const timerange = { @@ -91,8 +92,8 @@ function createFetchMock(): NodeMetricsTableFetchMock { const mockData: DataResponseMock = { series: [ - createPod('some-pod', 23000000, 76, 3671700000), - createPod('some-other-pod', 32000000, 67, 716300000), + createPod('358d96e3-026f-4440-a487-f6c2301884c0', 'some-pod', 23000000, 76, 3671700000), + createPod('358d96e3-026f-4440-a487-f6c2301884c1', 'some-other-pod', 32000000, 67, 716300000), ], }; @@ -108,15 +109,22 @@ function createFetchMock(): NodeMetricsTableFetchMock { }; } -function createPod(name: string, uptimeMs: number, cpuUsagePct: number, memoryUsageBytes: number) { +function createPod( + id: string, + name: string, + uptimeMs: number, + cpuUsagePct: number, + memoryUsageBytes: number +): Partial { return { - id: name, + id: `${id} / ${name}`, + keys: [id, name], rows: [ { [metricByField['kubernetes.pod.start_time']]: uptimeMs, [metricByField['kubernetes.pod.cpu.usage.node.pct']]: cpuUsagePct, [metricByField['kubernetes.pod.memory.usage.bytes']]: memoryUsageBytes, - }, + } as MetricsExplorerSeries['rows'][number], ], }; } diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.tsx b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.tsx index 5d7801be3c930..78edf32e1786b 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.tsx +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.tsx @@ -108,8 +108,10 @@ function podNodeColumns( field: 'name', truncateText: true, textOnly: true, - render: (name: string) => { - return ; + render: (_, { id, name }) => { + return ( + + ); }, }, { diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts new file mode 100644 index 0000000000000..d7edd86771ed7 --- /dev/null +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { usePodMetricsTable } from './use_pod_metrics_table'; +import { useInfrastructureNodeMetrics } from '../shared'; +import { renderHook } from '@testing-library/react-hooks'; + +jest.mock('../shared', () => ({ + ...jest.requireActual('../shared'), + useInfrastructureNodeMetrics: jest.fn(), +})); + +describe('usePodMetricsTable hook', () => { + const useInfrastructureNodeMetricsMock = useInfrastructureNodeMetrics as jest.MockedFunction< + typeof useInfrastructureNodeMetrics + >; + + it('should call useInfrastructureNodeMetrics hook with event.module filter in filterClauseDsl query', () => { + const filterClauseDsl = { + bool: { + filter: [{ terms: { 'container.id': 'gke-edge-oblt-pool-1-9a60016d-lgg9' } }], + }, + }; + + const filterClauseWithEventModuleFilter = { + bool: { + filter: [{ term: { 'event.dataset': 'kubernetes.pod' } }, { ...filterClauseDsl }], + }, + }; + + renderHook(() => + usePodMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + filterClauseDsl, + }) + ); + + expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( + expect.objectContaining({ + metricsExplorerOptions: expect.objectContaining({ + filterQuery: JSON.stringify(filterClauseWithEventModuleFilter), + }), + }) + ); + }); +}); diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.ts index e070d1ca9100c..116293a09a61c 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.ts +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.ts @@ -5,41 +5,50 @@ * 2.0. */ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import type { MetricsExplorerRow, MetricsExplorerSeries, } from '../../../../common/http_api/metrics_explorer'; -import type { MetricsMap, SortState, UseNodeMetricsTableOptions } from '../shared'; -import { metricsToApiOptions, useInfrastructureNodeMetrics } from '../shared'; +import type { MetricsQueryOptions, SortState, UseNodeMetricsTableOptions } from '../shared'; +import { + metricsToApiOptions, + useInfrastructureNodeMetrics, + createMetricByFieldLookup, +} from '../shared'; type PodMetricsField = | 'kubernetes.pod.start_time' | 'kubernetes.pod.cpu.usage.node.pct' | 'kubernetes.pod.memory.usage.bytes'; -const podMetricsMap: MetricsMap = { - 'kubernetes.pod.start_time': { - aggregation: 'max', - field: 'kubernetes.pod.start_time', +const podMetricsQueryConfig: MetricsQueryOptions = { + sourceFilter: { + term: { + 'event.dataset': 'kubernetes.pod', + }, }, - 'kubernetes.pod.cpu.usage.node.pct': { - aggregation: 'avg', - field: 'kubernetes.pod.cpu.usage.node.pct', - }, - 'kubernetes.pod.memory.usage.bytes': { - aggregation: 'avg', - field: 'kubernetes.pod.memory.usage.bytes', + groupByField: ['kubernetes.pod.uid', 'kubernetes.pod.name'], + metricsMap: { + 'kubernetes.pod.start_time': { + aggregation: 'max', + field: 'kubernetes.pod.start_time', + }, + 'kubernetes.pod.cpu.usage.node.pct': { + aggregation: 'avg', + field: 'kubernetes.pod.cpu.usage.node.pct', + }, + 'kubernetes.pod.memory.usage.bytes': { + aggregation: 'avg', + field: 'kubernetes.pod.memory.usage.bytes', + }, }, }; -const { options: podMetricsOptions, metricByField } = metricsToApiOptions( - podMetricsMap, - 'kubernetes.pod.name' -); -export { metricByField }; +export const metricByField = createMetricByFieldLookup(podMetricsQueryConfig.metricsMap); export interface PodNodeMetricsRow { + id: string; name: string; uptime: number | null; averageCpuUsagePercent: number | null; @@ -53,6 +62,11 @@ export function usePodMetricsTable({ timerange, filterClauseDsl }: UseNodeMetric direction: 'desc', }); + const { options: podMetricsOptions } = useMemo( + () => metricsToApiOptions(podMetricsQueryConfig, filterClauseDsl), + [filterClauseDsl] + ); + const { isLoading, nodes: pods, @@ -60,7 +74,6 @@ export function usePodMetricsTable({ timerange, filterClauseDsl }: UseNodeMetric } = useInfrastructureNodeMetrics({ metricsExplorerOptions: podMetricsOptions, timerange, - filterClauseDsl, transform: seriesToPodNodeMetricsRow, sortState, currentPageIndex, @@ -79,18 +92,21 @@ export function usePodMetricsTable({ timerange, filterClauseDsl }: UseNodeMetric } function seriesToPodNodeMetricsRow(series: MetricsExplorerSeries): PodNodeMetricsRow { + const [id, name] = series.keys ?? []; if (series.rows.length === 0) { - return rowWithoutMetrics(series.id); + return rowWithoutMetrics(id, name); } return { - name: series.id, + id, + name, ...calculateMetricAverages(series.rows), }; } -function rowWithoutMetrics(name: string) { +function rowWithoutMetrics(id: string, name: string) { return { + id, name, uptime: null, averageCpuUsagePercent: null, @@ -154,7 +170,7 @@ function collectMetricValues(rows: MetricsExplorerRow[]) { }; } -function unpackMetrics(row: MetricsExplorerRow): Omit { +function unpackMetrics(row: MetricsExplorerRow): Omit { return { uptime: row[metricByField['kubernetes.pod.start_time']] as number | null, averageCpuUsagePercent: row[metricByField['kubernetes.pod.cpu.usage.node.pct']] as diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/components/metrics_node_details_link.tsx b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/components/metrics_node_details_link.tsx index 93c591d3e5ec8..c2b26d4f9ec7f 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/components/metrics_node_details_link.tsx +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/components/metrics_node_details_link.tsx @@ -17,12 +17,14 @@ type ExtractStrict = Extract; interface MetricsNodeDetailsLinkProps { id: string; + label: string; nodeType: ExtractStrict; timerange: Pick; } export const MetricsNodeDetailsLink = ({ id, + label, nodeType, timerange, }: MetricsNodeDetailsLinkProps) => { @@ -35,5 +37,5 @@ export const MetricsNodeDetailsLink = ({ }) ); - return {id}; + return {label}; }; diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/index.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/index.ts index b3e04f1122998..06599df168690 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/index.ts +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export { metricsToApiOptions } from './metrics_to_api_options'; -export type { MetricsMap } from './metrics_to_api_options'; +export { metricsToApiOptions, createMetricByFieldLookup } from './metrics_to_api_options'; +export type { MetricsMap, MetricsQueryOptions } from './metrics_to_api_options'; export { useInfrastructureNodeMetrics } from './use_infrastructure_node_metrics'; export type { SortState } from './use_infrastructure_node_metrics'; diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/metrics_to_api_options.test.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/metrics_to_api_options.test.ts index da4ccc45ebf7d..79402b8813e08 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/metrics_to_api_options.test.ts +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/metrics_to_api_options.test.ts @@ -5,44 +5,62 @@ * 2.0. */ -import type { MetricsMap } from './metrics_to_api_options'; -import { metricsToApiOptions } from './metrics_to_api_options'; +import type { MetricsExplorerOptions } from '../../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; +import { + createMetricByFieldLookup, + MetricsQueryOptions, + metricsToApiOptions, +} from './metrics_to_api_options'; describe('metricsToApiOptions', () => { type TestNodeTypeMetricsField = 'test.node.type.field1' | 'test.node.type.field2'; - const testMetricsMapField1First: MetricsMap = { - 'test.node.type.field1': { - aggregation: 'max', - field: 'test.node.type.field1', + const testMetricsMapField1First: MetricsQueryOptions = { + sourceFilter: { + term: { + 'event.module': 'test', + }, }, - 'test.node.type.field2': { - aggregation: 'avg', - field: 'test.node.type.field2', + groupByField: 'test.node.type.groupingField', + metricsMap: { + 'test.node.type.field1': { + aggregation: 'max', + field: 'test.node.type.field1', + }, + 'test.node.type.field2': { + aggregation: 'avg', + field: 'test.node.type.field2', + }, }, }; - const testMetricsMapField1Second: MetricsMap = { - 'test.node.type.field2': { - aggregation: 'avg', - field: 'test.node.type.field2', + const testMetricsMapField1Second: MetricsQueryOptions = { + sourceFilter: { + term: { + 'event.module': 'test', + }, }, - 'test.node.type.field1': { - aggregation: 'max', - field: 'test.node.type.field1', + groupByField: 'test.node.type.groupingField', + metricsMap: { + 'test.node.type.field2': { + aggregation: 'avg', + field: 'test.node.type.field2', + }, + 'test.node.type.field1': { + aggregation: 'max', + field: 'test.node.type.field1', + }, }, }; const fields = ['test.node.type.field1', 'test.node.type.field2']; it('should join the grouping field with the metrics in the APIs expected format', () => { - const { options } = metricsToApiOptions( - testMetricsMapField1First, - 'test.node.type.groupingField' - ); + const { options } = metricsToApiOptions(testMetricsMapField1First); expect(options).toEqual({ aggregation: 'avg', groupBy: 'test.node.type.groupingField', + filterQuery: JSON.stringify({ bool: { filter: [{ term: { 'event.module': 'test' } }] } }), metrics: [ { field: 'test.node.type.field1', @@ -53,28 +71,21 @@ describe('metricsToApiOptions', () => { aggregation: 'avg', }, ], - }); + } as MetricsExplorerOptions); }); it('should provide a mapping object that allows consumer to ignore metric definition order', () => { - const field1First = metricsToApiOptions( - testMetricsMapField1First, - 'test.node.type.groupingField' - ); + const metricByFieldFirst = createMetricByFieldLookup(testMetricsMapField1First.metricsMap); - assertListContentIsEqual(Object.keys(field1First.metricByField), fields); - expect(field1First.metricByField).toEqual({ + assertListContentIsEqual(Object.keys(metricByFieldFirst), fields); + expect(metricByFieldFirst).toEqual({ 'test.node.type.field1': 'metric_0', 'test.node.type.field2': 'metric_1', }); - const field1Second = metricsToApiOptions( - testMetricsMapField1Second, - 'test.node.type.groupingField' - ); + const metricByFieldSecond = createMetricByFieldLookup(testMetricsMapField1Second.metricsMap); - assertListContentIsEqual(Object.keys(field1Second.metricByField), fields); - expect(field1Second.metricByField).toEqual({ + expect(metricByFieldSecond).toEqual({ 'test.node.type.field1': 'metric_1', 'test.node.type.field2': 'metric_0', }); diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/metrics_to_api_options.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/metrics_to_api_options.ts index 23d6383a303da..33d89f8d3c31f 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/metrics_to_api_options.ts +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/metrics_to_api_options.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { ObjectValues } from '../../../../../common/utility_types'; import type { MetricsExplorerOptions, MetricsExplorerOptionsMetric, @@ -40,6 +42,15 @@ If the endpoint where to change its return format to: Then this code would no longer be needed. */ +// MetricsQueryOptions wraps the basic things needed to build the query for metrics +// sourceFilter is used to define filter to get only relevant docs that contain metrics info +// e.g: { 'source.module': 'system' } +export interface MetricsQueryOptions { + sourceFilter: QueryDslQueryContainer; + groupByField: string | string[]; + metricsMap: MetricsMap; +} + // The input to this generic type is a (union) string type that defines all the fields we want to // request metrics for. This input type serves as something like a "source of truth" for which // fields are being used. The resulting MetricsMap and metricByField helper ensures a type safe @@ -57,27 +68,43 @@ export interface NodeMetricsExplorerOptionsMetric field: Field; } -export function metricsToApiOptions(metricsMap: MetricsMap, groupBy: string) { - const metrics = Object.values(metricsMap) as Array>; +export function metricsToApiOptions( + metricsQueryOptions: MetricsQueryOptions, + filterClauseDsl?: QueryDslQueryContainer +) { + const metrics = Object.values(metricsQueryOptions.metricsMap) as Array< + NodeMetricsExplorerOptionsMetric + >; const options: MetricsExplorerOptions = { aggregation: 'avg', - groupBy, + groupBy: metricsQueryOptions.groupByField, metrics, + filterQuery: JSON.stringify( + buildFilterClause(metricsQueryOptions.sourceFilter, filterClauseDsl) + ), }; - const metricByField = createFieldLookup(Object.keys(metricsMap) as T[], metrics); - return { options, - metricByField, }; } -function createFieldLookup( - fields: T[], - metrics: Array> -) { +function buildFilterClause( + sourceFilter: QueryDslQueryContainer, + filterClauseDsl?: QueryDslQueryContainer +): QueryDslQueryContainer { + return { + bool: { + filter: !!filterClauseDsl ? [sourceFilter, filterClauseDsl] : [sourceFilter], + }, + }; +} + +export function createMetricByFieldLookup(metricMap: MetricsMap) { + const fields = Object.keys(metricMap) as Array; + const metrics = Object.values(metricMap) as ObjectValues; + const setMetricIndexToField = (acc: Record, field: T) => { return { ...acc, diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/use_infrastructure_node_metrics.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/use_infrastructure_node_metrics.ts index 374685a374f24..93ddcc8bf4aa8 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/use_infrastructure_node_metrics.ts +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/hooks/use_infrastructure_node_metrics.ts @@ -6,10 +6,10 @@ */ import { parse } from '@kbn/datemath'; -import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { useEffect, useMemo, useState } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { + MetricsExplorerRequestBody, MetricsExplorerResponse, MetricsExplorerSeries, } from '../../../../../common/http_api/metrics_explorer'; @@ -28,7 +28,6 @@ export interface SortState { interface UseInfrastructureNodeMetricsOptions { metricsExplorerOptions: MetricsExplorerOptions; timerange: Pick; - filterClauseDsl?: QueryDslQueryContainer; transform: (series: MetricsExplorerSeries) => T; sortState: SortState; currentPageIndex: number; @@ -48,14 +47,7 @@ const nullData: MetricsExplorerResponse = { export const useInfrastructureNodeMetrics = ( options: UseInfrastructureNodeMetricsOptions ) => { - const { - metricsExplorerOptions, - timerange, - filterClauseDsl, - transform, - sortState, - currentPageIndex, - } = options; + const { metricsExplorerOptions, timerange, transform, sortState, currentPageIndex } = options; const [transformedNodes, setTransformedNodes] = useState([]); const fetch = useKibanaHttpFetch(); @@ -69,12 +61,12 @@ export const useInfrastructureNodeMetrics = ( return Promise.resolve(nullData); } - const request = { + const request: MetricsExplorerRequestBody = { metrics: metricsExplorerOptions.metrics, groupBy: metricsExplorerOptions.groupBy, limit: NODE_COUNT_LIMIT, indexPattern: source.configuration.metricAlias, - filterQuery: JSON.stringify(filterClauseDsl), + filterQuery: metricsExplorerOptions.filterQuery, timerange: timerangeWithInterval, }; @@ -93,7 +85,7 @@ export const useInfrastructureNodeMetrics = ( }, cancelPreviousOn: 'creation', }, - [source, metricsExplorerOptions, timerangeWithInterval, filterClauseDsl] + [source, metricsExplorerOptions, timerangeWithInterval] ); const isLoadingNodes = promiseState === 'pending' || promiseState === 'uninitialized'; diff --git a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/index.ts b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/index.ts index 8c74b28764d35..25f78647fa026 100644 --- a/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/index.ts +++ b/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables/shared/index.ts @@ -6,8 +6,12 @@ */ export { MetricsNodeDetailsLink, NumberCell, StepwisePagination, UptimeCell } from './components'; -export { metricsToApiOptions, useInfrastructureNodeMetrics } from './hooks'; -export type { MetricsMap, SortState } from './hooks'; +export { + metricsToApiOptions, + useInfrastructureNodeMetrics, + createMetricByFieldLookup, +} from './hooks'; +export type { MetricsMap, SortState, MetricsQueryOptions } from './hooks'; export type { IntegratedNodeMetricsTableProps, SourceProviderProps, diff --git a/x-pack/plugins/kubernetes_security/common/constants.ts b/x-pack/plugins/kubernetes_security/common/constants.ts index c88d30d7712ed..52f99f76ddab8 100644 --- a/x-pack/plugins/kubernetes_security/common/constants.ts +++ b/x-pack/plugins/kubernetes_security/common/constants.ts @@ -14,3 +14,13 @@ export const AGGREGATE_PAGE_SIZE = 10; // so, bucket sort can only page through what we request at the top level agg, which means there is a ceiling to how many aggs we can page through. // we should also test this approach at scale. export const AGGREGATE_MAX_BUCKETS = 2000; + +// react-query caching keys +export const QUERY_KEY_PERCENT_WIDGET = 'kubernetesSecurityPercentWidget'; + +export const DEFAULT_QUERY = '{"bool":{"must":[],"filter":[],"should":[],"must_not":[]}}'; + +// ECS fields +export const ENTRY_LEADER_INTERACTIVE = 'process.entry_leader.interactive'; +export const ENTRY_LEADER_USER_ID = 'process.entry_leader.user.id'; +export const ENTRY_LEADER_ENTITY_ID = 'process.entry_leader.entity_id'; diff --git a/x-pack/plugins/kubernetes_security/common/types/aggregate/index.ts b/x-pack/plugins/kubernetes_security/common/types/aggregate/index.ts new file mode 100644 index 0000000000000..70747aa8e878d --- /dev/null +++ b/x-pack/plugins/kubernetes_security/common/types/aggregate/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface AggregateResult { + key: string | number; + key_as_string?: string; + doc_count: number; + count_by_aggs: { + value: number; + }; +} diff --git a/x-pack/plugins/kubernetes_security/kibana.json b/x-pack/plugins/kubernetes_security/kibana.json index c54efd9db8d4c..9d0bb40a89890 100644 --- a/x-pack/plugins/kubernetes_security/kibana.json +++ b/x-pack/plugins/kubernetes_security/kibana.json @@ -12,7 +12,9 @@ "ruleRegistry", "sessionView" ], - "requiredBundles": [], + "requiredBundles": [ + "kibanaReact" + ], "server": true, "ui": true } diff --git a/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/index.test.tsx b/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/index.test.tsx index fa379cf23d7e1..08b6354abf201 100644 --- a/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/index.test.tsx +++ b/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/index.test.tsx @@ -8,14 +8,19 @@ import React from 'react'; // eslint-disable-next-line @kbn/eslint/module_migration import { MemoryRouterProps } from 'react-router'; -import { render, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { KubernetesSecurityRoutes } from '.'; +import { createAppRootMockRenderer } from '../../test'; jest.mock('../kubernetes_widget', () => ({ KubernetesWidget: () =>
{'Mock kubernetes widget'}
, })); +jest.mock('../percent_widget', () => ({ + PercentWidget: () =>
{'Mock percent widget'}
, +})); + const renderWithRouter = ( initialEntries: MemoryRouterProps['initialEntries'] = ['/kubernetes'] ) => { @@ -40,9 +45,17 @@ const renderWithRouter = ( }, }; }); - return render( + const mockedContext = createAppRootMockRenderer(); + return mockedContext.render( - {'Mock filters'}
} /> + {'Mock filters'}
} + globalFilter={{ + filterQuery: '{"bool":{"must":[],"filter":[],"should":[],"must_not":[]}}', + startDate: '2022-03-08T18:52:15.532Z', + endDate: '2022-06-09T17:52:15.532Z', + }} + /> ); }; @@ -51,5 +64,6 @@ describe('Kubernetes security routes', () => { it('navigates to the kubernetes page', () => { renderWithRouter(); expect(screen.getAllByText('Mock kubernetes widget')).toHaveLength(3); + expect(screen.getAllByText('Mock percent widget')).toHaveLength(2); }); }); diff --git a/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/index.tsx b/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/index.tsx index 1abf39a9c45fb..f2949dd0e4ab1 100644 --- a/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/index.tsx +++ b/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/index.tsx @@ -5,41 +5,64 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { Route, Switch } from 'react-router-dom'; import { EuiBadge, EuiFlexGroup, EuiFlexItem, + EuiIconTip, EuiLoadingContent, EuiSpacer, + EuiText, EuiTextColor, } from '@elastic/eui'; -import { CSSObject } from '@emotion/react'; -import { KUBERNETES_PATH } from '../../../common/constants'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { + KUBERNETES_PATH, + ENTRY_LEADER_INTERACTIVE, + ENTRY_LEADER_USER_ID, + ENTRY_LEADER_ENTITY_ID, +} from '../../../common/constants'; import { KubernetesWidget } from '../kubernetes_widget'; +import { PercentWidget } from '../percent_widget'; import { KubernetesSecurityDeps } from '../../types'; +import { AggregateResult } from '../../../common/types/aggregate'; +import { useStyles } from './styles'; -const widgetBadge: CSSObject = { - position: 'absolute', - bottom: '16px', - left: '16px', - width: 'calc(100% - 32px)', - fontSize: '12px', - lineHeight: '18px', - padding: '4px 8px', - display: 'flex', -}; +const KubernetesSecurityRoutesComponent = ({ + filter, + indexPattern, + globalFilter, +}: KubernetesSecurityDeps) => { + const styles = useStyles(); -const treeViewContainer: CSSObject = { - position: 'relative', - border: '1px solid #D3DAE6', - borderRadius: '6px', - padding: '16px', - height: '500px', -}; + const onReduceInteractiveAggs = useCallback( + (result: AggregateResult[]): Record => + result.reduce((groupedByKeyValue, aggregate) => { + groupedByKeyValue[aggregate.key_as_string || (aggregate.key.toString() as string)] = + aggregate.count_by_aggs.value; + return groupedByKeyValue; + }, {} as Record), + [] + ); + + const onReduceRootAggs = useCallback( + (result: AggregateResult[]): Record => + result.reduce((groupedByKeyValue, aggregate) => { + if (aggregate.key === '0') { + groupedByKeyValue[aggregate.key] = aggregate.count_by_aggs.value; + } else { + groupedByKeyValue.nonRoot = + (groupedByKeyValue.nonRoot || 0) + aggregate.count_by_aggs.value; + } + return groupedByKeyValue; + }, {} as Record), + [] + ); -const KubernetesSecurityRoutesComponent = ({ filter }: KubernetesSecurityDeps) => { return ( @@ -58,7 +81,7 @@ const KubernetesSecurityRoutesComponent = ({ filter }: KubernetesSecurityDeps) = href="#" target="blank" css={{ - ...widgetBadge, + ...styles.widgetBadge, '.euiBadge__content': { width: '100%', '.euiBadge__text': { @@ -77,7 +100,7 @@ const KubernetesSecurityRoutesComponent = ({ filter }: KubernetesSecurityDeps) = - + 1000 {' live'} @@ -89,7 +112,98 @@ const KubernetesSecurityRoutesComponent = ({ filter }: KubernetesSecurityDeps) = -
+ + + + + + + + } + /> + + } + widgetKey="sessionsPercentage" + indexPattern={indexPattern} + globalFilter={globalFilter} + dataValueMap={{ + true: { + name: i18n.translate('xpack.kubernetesSecurity.sessionsChart.interactive', { + defaultMessage: 'Interactive', + }), + fieldName: ENTRY_LEADER_INTERACTIVE, + color: euiThemeVars.euiColorVis0, + }, + false: { + name: i18n.translate('xpack.kubernetesSecurity.sessionsChart.nonInteractive', { + defaultMessage: 'Non-interactive', + }), + fieldName: ENTRY_LEADER_INTERACTIVE, + color: euiThemeVars.euiColorVis1, + }, + }} + groupedBy={ENTRY_LEADER_INTERACTIVE} + countBy={ENTRY_LEADER_ENTITY_ID} + onReduce={onReduceInteractiveAggs} + /> + + + + + + + + } + /> + + } + widgetKey="rootLoginPercentage" + indexPattern={indexPattern} + globalFilter={globalFilter} + dataValueMap={{ + '0': { + name: i18n.translate('xpack.kubernetesSecurity.userLoginChart.root', { + defaultMessage: 'Root', + }), + fieldName: ENTRY_LEADER_USER_ID, + color: euiThemeVars.euiColorVis2, + }, + nonRoot: { + name: i18n.translate('xpack.kubernetesSecurity.userLoginChart.nonRoot', { + defaultMessage: 'Non-root', + }), + fieldName: ENTRY_LEADER_USER_ID, + color: euiThemeVars.euiColorVis3, + shouldHideFilter: true, + }, + }} + groupedBy={ENTRY_LEADER_USER_ID} + countBy={ENTRY_LEADER_ENTITY_ID} + onReduce={onReduceRootAggs} + /> + + +
diff --git a/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/styles.ts b/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/styles.ts new file mode 100644 index 0000000000000..a6725007fb5f7 --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/components/kubernetes_security_routes/styles.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import { CSSObject } from '@emotion/react'; +import { useEuiTheme } from '../../hooks'; + +export const useStyles = () => { + const { euiTheme } = useEuiTheme(); + + const cached = useMemo(() => { + const { size, font } = euiTheme; + + const widgetBadge: CSSObject = { + position: 'absolute', + bottom: size.base, + left: size.base, + width: `calc(100% - ${size.xl})`, + fontSize: size.m, + lineHeight: '18px', + padding: `${size.xs} ${size.s}`, + display: 'flex', + }; + + const treeViewContainer: CSSObject = { + position: 'relative', + border: euiTheme.border.thin, + borderRadius: euiTheme.border.radius.medium, + padding: size.base, + height: '500px', + }; + + const percentageWidgets: CSSObject = { + marginBottom: size.l, + }; + + const percentageChartTitle: CSSObject = { + marginRight: size.xs, + display: 'inline', + fontWeight: font.weight.bold, + }; + + return { + widgetBadge, + treeViewContainer, + percentageWidgets, + percentageChartTitle, + }; + }, [euiTheme]); + + return cached; +}; diff --git a/x-pack/plugins/kubernetes_security/public/components/percent_widget/hooks.ts b/x-pack/plugins/kubernetes_security/public/components/percent_widget/hooks.ts new file mode 100644 index 0000000000000..decdb0c6e27a9 --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/components/percent_widget/hooks.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 { useQuery } from 'react-query'; +import { CoreStart } from '@kbn/core/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { QUERY_KEY_PERCENT_WIDGET, AGGREGATE_ROUTE } from '../../../common/constants'; +import { AggregateResult } from '../../../common/types/aggregate'; + +export const useFetchPercentWidgetData = ( + onReduce: (result: AggregateResult[]) => Record, + filterQuery: string, + widgetKey: string, + groupBy: string, + countBy?: string, + index?: string +) => { + const { http } = useKibana().services; + const cachingKeys = [QUERY_KEY_PERCENT_WIDGET, widgetKey, filterQuery, groupBy, countBy, index]; + const query = useQuery( + cachingKeys, + async (): Promise> => { + const res = await http.get(AGGREGATE_ROUTE, { + query: { + query: filterQuery, + groupBy, + countBy, + page: 0, + index, + }, + }); + + return onReduce(res); + }, + { + refetchOnWindowFocus: false, + refetchOnMount: false, + refetchOnReconnect: false, + } + ); + + return query; +}; diff --git a/x-pack/plugins/kubernetes_security/public/components/percent_widget/index.test.tsx b/x-pack/plugins/kubernetes_security/public/components/percent_widget/index.test.tsx new file mode 100644 index 0000000000000..234256e2d7dd4 --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/components/percent_widget/index.test.tsx @@ -0,0 +1,120 @@ +/* + * 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 { ENTRY_LEADER_INTERACTIVE } from '../../../common/constants'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; +import { GlobalFilter } from '../../types'; +import { PercentWidget, LOADING_TEST_ID, PERCENT_DATA_TEST_ID } from '.'; +import { useFetchPercentWidgetData } from './hooks'; + +const MOCK_DATA: Record = { + false: 47, + true: 1, +}; +const TITLE = 'Percent Widget Title'; +const GLOBAL_FILTER: GlobalFilter = { + endDate: '2022-06-15T14:15:25.777Z', + filterQuery: '{"bool":{"must":[],"filter":[],"should":[],"must_not":[]}}', + startDate: '2022-05-15T14:15:25.777Z', +}; +const DATA_VALUE_MAP = { + true: { + name: 'Interactive', + fieldName: ENTRY_LEADER_INTERACTIVE, + color: 'red', + }, + false: { + name: 'Non-interactive', + fieldName: ENTRY_LEADER_INTERACTIVE, + color: 'blue', + }, +}; + +jest.mock('../../hooks/use_filter', () => ({ + useSetFilter: () => ({ + getFilterForValueButton: jest.fn(), + getFilterOutValueButton: jest.fn(), + filterManager: {}, + }), +})); + +jest.mock('./hooks'); +const mockUseFetchData = useFetchPercentWidgetData as jest.Mock; + +describe('PercentWidget component', () => { + let renderResult: ReturnType; + const mockedContext = createAppRootMockRenderer(); + const render: () => ReturnType = () => + (renderResult = mockedContext.render( + + )); + + describe('When PercentWidget is mounted', () => { + describe('with data', () => { + beforeEach(() => { + mockUseFetchData.mockImplementation(() => ({ + data: MOCK_DATA, + isLoading: false, + })); + }); + + it('should show title', async () => { + render(); + + expect(renderResult.getByText(TITLE)).toBeVisible(); + }); + it('should show data value names and value', async () => { + render(); + + expect(renderResult.getByText('Interactive')).toBeVisible(); + expect(renderResult.getByText('Non-interactive')).toBeVisible(); + expect(renderResult.queryByTestId(LOADING_TEST_ID)).toBeNull(); + expect(renderResult.getByText(47)).toBeVisible(); + expect(renderResult.getByText(1)).toBeVisible(); + }); + it('should show same number of data items as the number of records provided in dataValueMap', async () => { + render(); + + expect(renderResult.getAllByTestId(PERCENT_DATA_TEST_ID)).toHaveLength(2); + }); + }); + + describe('without data ', () => { + it('should show data value names and zeros as values when loading', async () => { + mockUseFetchData.mockImplementation(() => ({ + data: undefined, + isLoading: true, + })); + render(); + + expect(renderResult.getByText('Interactive')).toBeVisible(); + expect(renderResult.getByText('Non-interactive')).toBeVisible(); + expect(renderResult.getByTestId(LOADING_TEST_ID)).toBeVisible(); + expect(renderResult.getAllByText(0)).toHaveLength(2); + }); + it('should show zeros as values if no data returned', async () => { + mockUseFetchData.mockImplementation(() => ({ + data: undefined, + isLoading: false, + })); + render(); + + expect(renderResult.getByText('Interactive')).toBeVisible(); + expect(renderResult.getByText('Non-interactive')).toBeVisible(); + expect(renderResult.getAllByText(0)).toHaveLength(2); + }); + }); + }); +}); diff --git a/x-pack/plugins/kubernetes_security/public/components/percent_widget/index.tsx b/x-pack/plugins/kubernetes_security/public/components/percent_widget/index.tsx new file mode 100644 index 0000000000000..a5eac7bca92b3 --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/components/percent_widget/index.tsx @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ReactNode, useMemo, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiText } from '@elastic/eui'; +import { useStyles } from './styles'; +import type { IndexPattern, GlobalFilter } from '../../types'; +import { useSetFilter } from '../../hooks'; +import { addTimerangeToQuery } from '../../utils/add_timerange_to_query'; +import { AggregateResult } from '../../../common/types/aggregate'; +import { useFetchPercentWidgetData } from './hooks'; + +export const LOADING_TEST_ID = 'kubernetesSecurity:percent-widget-loading'; +export const PERCENT_DATA_TEST_ID = 'kubernetesSecurity:percentage-widget-data'; + +export interface PercenWidgetDataValueMap { + name: string; + fieldName: string; + color: string; + shouldHideFilter?: boolean; +} + +export interface PercentWidgetDeps { + title: ReactNode; + dataValueMap: Record; + widgetKey: string; + indexPattern?: IndexPattern; + globalFilter: GlobalFilter; + groupedBy: string; + countBy?: string; + onReduce: (result: AggregateResult[]) => Record; +} + +interface FilterButtons { + filterForButtons: ReactNode[]; + filterOutButtons: ReactNode[]; +} + +export const PercentWidget = ({ + title, + dataValueMap, + widgetKey, + indexPattern, + globalFilter, + groupedBy, + countBy, + onReduce, +}: PercentWidgetDeps) => { + const [hoveredFilter, setHoveredFilter] = useState(null); + const styles = useStyles(); + + const filterQueryWithTimeRange = useMemo(() => { + return addTimerangeToQuery( + globalFilter.filterQuery, + globalFilter.startDate, + globalFilter.endDate + ); + }, [globalFilter.filterQuery, globalFilter.startDate, globalFilter.endDate]); + + const { data, isLoading } = useFetchPercentWidgetData( + onReduce, + filterQueryWithTimeRange, + widgetKey, + groupedBy, + countBy, + indexPattern?.title + ); + + const { getFilterForValueButton, getFilterOutValueButton, filterManager } = useSetFilter(); + const dataValueSum = useMemo( + () => (data ? Object.keys(data).reduce((sumSoFar, current) => sumSoFar + data[current], 0) : 0), + [data] + ); + const filterButtons = useMemo(() => { + const result: FilterButtons = { + filterForButtons: [], + filterOutButtons: [], + }; + Object.keys(dataValueMap).forEach((groupedByValue) => { + if (!dataValueMap[groupedByValue].shouldHideFilter) { + result.filterForButtons.push( + getFilterForValueButton({ + field: dataValueMap[groupedByValue].fieldName, + filterManager, + size: 'xs', + onClick: () => {}, + onFilterAdded: () => {}, + ownFocus: false, + showTooltip: true, + value: [groupedByValue], + }) + ); + result.filterOutButtons.push( + getFilterOutValueButton({ + field: dataValueMap[groupedByValue].fieldName, + filterManager, + size: 'xs', + onClick: () => {}, + onFilterAdded: () => {}, + ownFocus: false, + showTooltip: true, + value: [groupedByValue], + }) + ); + } + }); + + return result; + }, [dataValueMap, filterManager, getFilterForValueButton, getFilterOutValueButton]); + + return ( +
+ {isLoading && ( + + )} +
{title}
+ + {Object.keys(dataValueMap).map((groupedByValue, idx) => { + const value = data?.[groupedByValue] || 0; + return ( + setHoveredFilter(idx)} + onMouseLeave={() => setHoveredFilter(null)} + data-test-subj={PERCENT_DATA_TEST_ID} + > + + {dataValueMap[groupedByValue].name} + {hoveredFilter === idx && ( +
+ {filterButtons.filterForButtons[idx]} + {filterButtons.filterOutButtons[idx]} +
+ )} + {value} +
+
+
+
+ + ); + })} + +
+ ); +}; diff --git a/x-pack/plugins/kubernetes_security/public/components/percent_widget/styles.ts b/x-pack/plugins/kubernetes_security/public/components/percent_widget/styles.ts new file mode 100644 index 0000000000000..5e90d7c946f92 --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/components/percent_widget/styles.ts @@ -0,0 +1,77 @@ +/* + * 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 { useMemo } from 'react'; +import { CSSObject } from '@emotion/react'; +import { useEuiTheme } from '../../hooks'; + +export const useStyles = () => { + const { euiTheme } = useEuiTheme(); + + const cached = useMemo(() => { + const { size, colors, font, border } = euiTheme; + + const container: CSSObject = { + padding: size.base, + border: euiTheme.border.thin, + borderRadius: euiTheme.border.radius.medium, + overflow: 'auto', + position: 'relative', + }; + + const title: CSSObject = { + marginBottom: size.m, + }; + + const dataInfo: CSSObject = { + marginBottom: size.xs, + display: 'flex', + alignItems: 'center', + height: '18px', + }; + + const dataValue: CSSObject = { + fontWeight: font.weight.semiBold, + marginLeft: 'auto', + }; + + const filters: CSSObject = { + marginLeft: size.s, + }; + + const percentageBackground: CSSObject = { + position: 'relative', + backgroundColor: colors.lightShade, + height: size.xs, + borderRadius: border.radius.small, + }; + + const percentageBar: CSSObject = { + position: 'absolute', + height: size.xs, + borderRadius: border.radius.small, + }; + + const loadingSpinner: CSSObject = { + alignItems: 'center', + margin: `${size.xs} auto ${size.xl} auto`, + }; + + return { + container, + title, + dataInfo, + dataValue, + filters, + percentageBackground, + percentageBar, + loadingSpinner, + }; + }, [euiTheme]); + + return cached; +}; diff --git a/x-pack/plugins/kubernetes_security/public/hooks/index.ts b/x-pack/plugins/kubernetes_security/public/hooks/index.ts new file mode 100644 index 0000000000000..1f63ff5b670e5 --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/hooks/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { useEuiTheme } from './use_eui_theme'; +export { useSetFilter } from './use_filter'; diff --git a/x-pack/plugins/kubernetes_security/public/hooks/use_eui_theme.ts b/x-pack/plugins/kubernetes_security/public/hooks/use_eui_theme.ts new file mode 100644 index 0000000000000..02f7dd479d2ac --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/hooks/use_eui_theme.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 { shade, useEuiTheme as useEuiThemeHook } from '@elastic/eui'; +import { euiLightVars, euiDarkVars } from '@kbn/ui-theme'; +import { useMemo } from 'react'; + +type EuiThemeProps = Parameters; +type ExtraEuiVars = { + // eslint-disable-next-line @typescript-eslint/naming-convention + euiColorVis6_asText: string; + buttonsBackgroundNormalDefaultPrimary: string; +}; +type EuiVars = typeof euiLightVars & ExtraEuiVars; +type EuiThemeReturn = ReturnType & { euiVars: EuiVars }; + +// Not all Eui Tokens were fully migrated to @elastic/eui/useEuiTheme yet, so +// this hook overrides the default useEuiTheme hook to provide a custom hook that +// allows the use the euiVars tokens from the euiLightVars and euiDarkVars +export const useEuiTheme = (...props: EuiThemeProps): EuiThemeReturn => { + const euiThemeHook = useEuiThemeHook(...props); + + const euiVars = useMemo(() => { + const themeVars = euiThemeHook.colorMode === 'DARK' ? euiDarkVars : euiLightVars; + + const extraEuiVars: ExtraEuiVars = { + // eslint-disable-next-line @typescript-eslint/naming-convention + euiColorVis6_asText: shade(themeVars.euiColorVis6, 0.335), + buttonsBackgroundNormalDefaultPrimary: '#006DE4', + }; + + return { + ...themeVars, + ...extraEuiVars, + }; + }, [euiThemeHook.colorMode]); + + return { + ...euiThemeHook, + euiVars, + }; +}; diff --git a/x-pack/plugins/kubernetes_security/public/hooks/use_filter.ts b/x-pack/plugins/kubernetes_security/public/hooks/use_filter.ts new file mode 100644 index 0000000000000..b12839bc332e2 --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/hooks/use_filter.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 { useMemo } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { CoreStart } from '@kbn/core/public'; +import type { StartPlugins } from '../types'; + +export const useSetFilter = () => { + const { data, timelines } = useKibana().services; + const { getFilterForValueButton, getFilterOutValueButton } = timelines.getHoverActions(); + + const filterManager = useMemo(() => data.query.filterManager, [data.query.filterManager]); + + return { + getFilterForValueButton, + getFilterOutValueButton, + filterManager, + }; +}; diff --git a/x-pack/plugins/kubernetes_security/public/test/index.tsx b/x-pack/plugins/kubernetes_security/public/test/index.tsx new file mode 100644 index 0000000000000..6174925b6003c --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/test/index.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, ReactNode, useMemo } from 'react'; +import { createMemoryHistory, MemoryHistory } from 'history'; +import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react'; +import { QueryClient, QueryClientProvider, setLogger } from 'react-query'; +import { Router } from 'react-router-dom'; +import { History } from 'history'; +import useObservable from 'react-use/lib/useObservable'; +import { I18nProvider } from '@kbn/i18n-react'; +import { CoreStart } from '@kbn/core/public'; +import { coreMock } from '@kbn/core/public/mocks'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; + +type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; + +// hide react-query output in console +setLogger({ + error: () => {}, + // eslint-disable-next-line no-console + log: console.log, + // eslint-disable-next-line no-console + warn: console.warn, +}); + +/** + * Mocked app root context renderer + */ +export interface AppContextTestRender { + history: ReturnType; + coreStart: ReturnType; + /** + * A wrapper around `AppRootContext` component. Uses the mocked modules as input to the + * `AppRootContext` + */ + AppWrapper: React.FC; + /** + * Renders the given UI within the created `AppWrapper` providing the given UI a mocked + * endpoint runtime context environment + */ + render: UiRender; +} + +const createCoreStartMock = ( + history: MemoryHistory +): ReturnType => { + const coreStart = coreMock.createStart({ basePath: '/mock' }); + + // Mock the certain APP Ids returned by `application.getUrlForApp()` + coreStart.application.getUrlForApp.mockImplementation((appId) => { + switch (appId) { + case 'sessionView': + return '/app/sessionView'; + default: + return `${appId} not mocked!`; + } + }); + + coreStart.application.navigateToUrl.mockImplementation((url) => { + history.push(url.replace('/app/sessionView', '')); + return Promise.resolve(); + }); + + return coreStart; +}; + +const AppRootProvider = memo<{ + history: History; + coreStart: CoreStart; + children: ReactNode | ReactNode[]; +}>(({ history, coreStart: { http, notifications, uiSettings, application }, children }) => { + const isDarkMode = useObservable(uiSettings.get$('theme:darkMode')); + const services = useMemo( + () => ({ http, notifications, application }), + [application, http, notifications] + ); + return ( + + + + {children} + + + + ); +}); + +AppRootProvider.displayName = 'AppRootProvider'; + +/** + * Creates a mocked app context custom renderer that can be used to render + * component that depend upon the application's surrounding context providers. + * Factory also returns the content that was used to create the custom renderer, allowing + * for further customization. + */ + +export const createAppRootMockRenderer = (): AppContextTestRender => { + const history = createMemoryHistory(); + const coreStart = createCoreStartMock(history); + + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // turns retries off + retry: false, + // prevent jest did not exit errors + cacheTime: Infinity, + }, + }, + }); + + const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => ( + + {children} + + ); + + const render: UiRender = (ui, options = {}) => { + return reactRender(ui, { + wrapper: AppWrapper, + ...options, + }); + }; + + return { + history, + coreStart, + AppWrapper, + render, + }; +}; diff --git a/x-pack/plugins/kubernetes_security/public/types.ts b/x-pack/plugins/kubernetes_security/public/types.ts index 65a25868a0655..d6313b25bf011 100644 --- a/x-pack/plugins/kubernetes_security/public/types.ts +++ b/x-pack/plugins/kubernetes_security/public/types.ts @@ -6,11 +6,34 @@ */ import React from 'react'; import { CoreStart } from '@kbn/core/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { FieldSpec } from '@kbn/data-plugin/common'; +import type { TimelinesUIStart } from '@kbn/timelines-plugin/public'; +import type { SessionViewStart } from '@kbn/session-view-plugin/public'; -export type KubernetesSecurityServices = CoreStart; +export interface StartPlugins { + data: DataPublicPluginStart; + timelines: TimelinesUIStart; + sessionView: SessionViewStart; +} + +export type KubernetesSecurityServices = CoreStart & StartPlugins; + +export interface IndexPattern { + fields: FieldSpec[]; + title: string; +} + +export interface GlobalFilter { + filterQuery?: string; + startDate: string; + endDate: string; +} export interface KubernetesSecurityDeps { filter: React.ReactNode; + indexPattern?: IndexPattern; + globalFilter: GlobalFilter; } export interface KubernetesSecurityStart { diff --git a/x-pack/plugins/kubernetes_security/public/utils/add_timerange_to_query.test.ts b/x-pack/plugins/kubernetes_security/public/utils/add_timerange_to_query.test.ts new file mode 100644 index 0000000000000..20e5e36cb3951 --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/utils/add_timerange_to_query.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DEFAULT_QUERY } from '../../common/constants'; +import { addTimerangeToQuery } from './add_timerange_to_query'; + +const TEST_QUERY = + '{"bool":{"must":[],"filter":[{"bool":{"should":[{"match":{"process.entry_leader.same_as_process":true}}],"minimum_should_match":1}}],"should":[],"must_not":[]}}'; +const TEST_INVALID_QUERY = '{"bool":{"must":['; +const TEST_EMPTY_STRING = ''; +const TEST_DATE = '2022-06-09T22:36:46.628Z'; +const VALID_RESULT = + '{"bool":{"must":[],"filter":[{"bool":{"should":[{"match":{"process.entry_leader.same_as_process":true}}],"minimum_should_match":1}},{"range":{"@timestamp":{"gte":"2022-06-09T22:36:46.628Z","lte":"2022-06-09T22:36:46.628Z"}}}],"should":[],"must_not":[]}}'; + +describe('addTimerangeToQuery(query, startDate, endDate)', () => { + it('works for valid query, startDate, and endDate', () => { + expect(addTimerangeToQuery(TEST_QUERY, TEST_DATE, TEST_DATE)).toEqual(VALID_RESULT); + }); + it('works with missing filter in bool', () => { + expect(addTimerangeToQuery('{"bool":{}}', TEST_DATE, TEST_DATE)).toEqual( + '{"bool":{"filter":[{"range":{"@timestamp":{"gte":"2022-06-09T22:36:46.628Z","lte":"2022-06-09T22:36:46.628Z"}}}]}}' + ); + }); + it('returns default query with invalid JSON query', () => { + expect(addTimerangeToQuery(TEST_INVALID_QUERY, TEST_DATE, TEST_DATE)).toEqual(DEFAULT_QUERY); + expect(addTimerangeToQuery(TEST_EMPTY_STRING, TEST_DATE, TEST_DATE)).toEqual(DEFAULT_QUERY); + expect(addTimerangeToQuery('{}', TEST_DATE, TEST_DATE)).toEqual(DEFAULT_QUERY); + }); + it('returns default query with invalid startDate or endDate', () => { + expect(addTimerangeToQuery(TEST_QUERY, TEST_EMPTY_STRING, TEST_DATE)).toEqual(DEFAULT_QUERY); + expect(addTimerangeToQuery(TEST_QUERY, TEST_DATE, TEST_EMPTY_STRING)).toEqual(DEFAULT_QUERY); + }); +}); diff --git a/x-pack/plugins/kubernetes_security/public/utils/add_timerange_to_query.ts b/x-pack/plugins/kubernetes_security/public/utils/add_timerange_to_query.ts new file mode 100644 index 0000000000000..0eb1239435483 --- /dev/null +++ b/x-pack/plugins/kubernetes_security/public/utils/add_timerange_to_query.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DEFAULT_QUERY } from '../../common/constants'; + +/** + * Add startDate and endDate filter for '@timestamp' field into query. + * + * Used by frontend components + * + * @param {String | undefined} query Example: '{"bool":{"must":[],"filter":[],"should":[],"must_not":[]}}' + * @param {String} startDate Example: '2022-06-08T18:52:15.532Z' + * @param {String} endDate Example: '2022-06-09T17:52:15.532Z' + * @return {String} Add startDate and endDate as a '@timestamp' range filter in query and return. + * If startDate or endDate is invalid Date string, or that query is not + * in the right format, return a default query. + */ + +export const addTimerangeToQuery = ( + query: string | undefined, + startDate: string, + endDate: string +) => { + if (!(query && !isNaN(Date.parse(startDate)) && !isNaN(Date.parse(endDate)))) { + return DEFAULT_QUERY; + } + + try { + const parsedQuery = JSON.parse(query); + if (!parsedQuery.bool) { + throw new Error("Field 'bool' does not exist in query."); + } + + const range = { + range: { + '@timestamp': { + gte: startDate, + lte: endDate, + }, + }, + }; + if (parsedQuery.bool.filter) { + parsedQuery.bool.filter = [...parsedQuery.bool.filter, range]; + } else { + parsedQuery.bool.filter = [range]; + } + + return JSON.stringify(parsedQuery); + } catch { + return DEFAULT_QUERY; + } +}; diff --git a/x-pack/plugins/kubernetes_security/server/routes/aggregate.ts b/x-pack/plugins/kubernetes_security/server/routes/aggregate.ts index 8f90a8ee8ba50..252b20a458a78 100644 --- a/x-pack/plugins/kubernetes_security/server/routes/aggregate.ts +++ b/x-pack/plugins/kubernetes_security/server/routes/aggregate.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { SortCombinations } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { schema } from '@kbn/config-schema'; import type { ElasticsearchClient } from '@kbn/core/server'; import { IRouter } from '@kbn/core/server'; @@ -14,6 +15,10 @@ import { AGGREGATE_MAX_BUCKETS, } from '../../common/constants'; +// sort by values +const ASC = 'asc'; +const DESC = 'desc'; + export const registerAggregateRoute = (router: IRouter) => { router.get( { @@ -21,18 +26,20 @@ export const registerAggregateRoute = (router: IRouter) => { validate: { query: schema.object({ query: schema.string(), + countBy: schema.maybe(schema.string()), groupBy: schema.string(), page: schema.number(), index: schema.maybe(schema.string()), + sortByCount: schema.maybe(schema.string()), }), }, }, async (context, request, response) => { const client = (await context.core).elasticsearch.client.asCurrentUser; - const { query, groupBy, page, index } = request.query; + const { query, countBy, sortByCount, groupBy, page, index } = request.query; try { - const body = await doSearch(client, query, groupBy, page, index); + const body = await doSearch(client, query, groupBy, page, index, countBy, sortByCount); return response.ok({ body }); } catch (err) { @@ -47,10 +54,27 @@ export const doSearch = async ( query: string, groupBy: string, page: number, // zero based - index?: string + index?: string, + countBy?: string, + sortByCount?: string ) => { const queryDSL = JSON.parse(query); + const countByAggs = countBy + ? { + count_by_aggs: { + cardinality: { + field: countBy, + }, + }, + } + : undefined; + + let sort: SortCombinations = { _key: { order: ASC } }; + if (sortByCount === ASC || sortByCount === DESC) { + sort = { 'count_by_aggs.value': { order: sortByCount } }; + } + const search = await client.search({ index: [index || PROCESS_EVENTS_INDEX], body: { @@ -63,9 +87,10 @@ export const doSearch = async ( size: AGGREGATE_MAX_BUCKETS, }, aggs: { + ...countByAggs, bucket_sort: { bucket_sort: { - sort: [{ _key: { order: 'asc' } }], // defaulting to alphabetic sort + sort: [sort], // defaulting to alphabetic sort size: AGGREGATE_PAGE_SIZE, from: AGGREGATE_PAGE_SIZE * page, }, diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx index 54321d5005f47..94a55a393814c 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx @@ -89,6 +89,63 @@ describe('datatable cell renderer', () => { expect(cell.find('.lnsTableCell--right').exists()).toBeTruthy(); }); + it('does not set multiline class for regular height tables', () => { + const cell = mountWithIntl( + + {}} + isExpandable={false} + isDetails={false} + isExpanded={false} + /> + + ); + expect(cell.find('.lnsTableCell--multiline').exists()).toBeFalsy(); + }); + + it('set multiline class for auto height tables', () => { + const MultiLineCellRenderer = createGridCell( + { + a: { convert: (x) => `formatted ${x}` } as FieldFormat, + }, + { columns: [], sortingColumnId: '', sortingDirection: 'none' }, + DataContext, + { get: jest.fn() } as unknown as IUiSettingsClient, + true + ); + const cell = mountWithIntl( + + {}} + isExpandable={false} + isDetails={false} + isExpanded={false} + /> + + ); + expect(cell.find('.lnsTableCell--multiline').exists()).toBeTruthy(); + }); + describe('dynamic coloring', () => { const paletteRegistry = chartPluginMock.createPaletteRegistry(); const customPalette = paletteRegistry.get('custom'); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx index 1bf58ae30a13e..e43c08aec6375 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx @@ -8,6 +8,7 @@ import React, { useContext, useEffect } from 'react'; import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; import type { IUiSettingsClient } from '@kbn/core/public'; +import classNames from 'classnames'; import type { FormatFactory } from '../../../common'; import { getOriginalId } from '../../../common/expressions'; import type { ColumnConfig } from '../../../common/expressions'; @@ -18,7 +19,8 @@ export const createGridCell = ( formatters: Record>, columnConfig: ColumnConfig, DataContext: React.Context, - uiSettings: IUiSettingsClient + uiSettings: IUiSettingsClient, + fitRowToContent?: boolean ) => { // Changing theme requires a full reload of the page, so we can cache here const IS_DARK_THEME = uiSettings.get('theme:darkMode'); @@ -74,7 +76,10 @@ export const createGridCell = ( */ dangerouslySetInnerHTML={{ __html: content }} // eslint-disable-line react/no-danger data-test-subj="lnsTableCellContent" - className={`lnsTableCell--${currentAlignment}`} + className={classNames({ + 'lnsTableCell--multiline': fitRowToContent, + [`lnsTableCell--${currentAlignment}`]: true, + })} /> ); }; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss index f8cb27cf1d66c..0f0ed53a10021 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss @@ -2,6 +2,10 @@ height: 100%; } +.lnsTableCell--multiline { + white-space: pre-wrap; +} + .lnsTableCell--left { text-align: left; } diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index e9be1a9d74f00..28f4b4dbe9f2b 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -337,8 +337,15 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ]); const renderCellValue = useMemo( - () => createGridCell(formatters, columnConfig, DataContext, props.uiSettings), - [formatters, columnConfig, props.uiSettings] + () => + createGridCell( + formatters, + columnConfig, + DataContext, + props.uiSettings, + props.args.fitRowToContent + ), + [formatters, columnConfig, props.uiSettings, props.args.fitRowToContent] ); const columnVisibility = useMemo( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index abd6da25c52ea..c97e5232af898 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -58,10 +58,10 @@ import { selectIsFullscreenDatasource, selectSearchSessionId, selectActiveDatasourceId, - selectActiveData, selectDatasourceStates, selectChangesApplied, applyChanges, + selectStagedActiveData, } from '../../state_management'; import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from './config_panel/dimension_container'; @@ -191,7 +191,7 @@ export function SuggestionPanel({ }: SuggestionPanelProps) { const dispatchLens = useLensDispatch(); const activeDatasourceId = useLensSelector(selectActiveDatasourceId); - const activeData = useLensSelector(selectActiveData); + const activeData = useLensSelector(selectStagedActiveData); const datasourceStates = useLensSelector(selectDatasourceStates); const existsStagedPreview = useLensSelector((state) => Boolean(state.lens.stagedPreview)); const currentVisualization = useLensSelector(selectCurrentVisualization); @@ -300,7 +300,7 @@ export function SuggestionPanel({ activeDatasourceId, datasourceMap, visualizationMap, - frame.activeData, + activeData, ]); const context: ExecutionContextSearch = useLensSelector(selectExecutionContextSearch); diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index df728e2bf8501..7ea8053caa588 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -376,6 +376,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { : state.stagedPreview || { datasourceStates: state.datasourceStates, visualization: state.visualization, + activeData: state.activeData, }, }; }, diff --git a/x-pack/plugins/lens/public/state_management/selectors.ts b/x-pack/plugins/lens/public/state_management/selectors.ts index 57cb43452232a..9217c66863c5c 100644 --- a/x-pack/plugins/lens/public/state_management/selectors.ts +++ b/x-pack/plugins/lens/public/state_management/selectors.ts @@ -19,6 +19,8 @@ export const selectFilters = (state: LensState) => state.lens.filters; export const selectResolvedDateRange = (state: LensState) => state.lens.resolvedDateRange; export const selectVisualization = (state: LensState) => state.lens.visualization; export const selectStagedPreview = (state: LensState) => state.lens.stagedPreview; +export const selectStagedActiveData = (state: LensState) => + state.lens.stagedPreview?.activeData || state.lens.activeData; export const selectAutoApplyEnabled = (state: LensState) => !state.lens.autoApplyDisabled; export const selectChangesApplied = (state: LensState) => !state.lens.autoApplyDisabled || Boolean(state.lens.changesApplied); diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index efba6312e8ba6..c11215d4a9f8e 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -29,6 +29,7 @@ export type DatasourceStates = Record { const fetcher = jest .fn() .mockImplementationOnce(async () => { - await delay(100); + await delay(2); return firstLicense; }) .mockImplementationOnce(async () => { - await delay(100); + await delay(2); return secondLicense; }); diff --git a/x-pack/plugins/licensing/common/license_update.ts b/x-pack/plugins/licensing/common/license_update.ts index 227886879e4e5..b674f4a066ce3 100644 --- a/x-pack/plugins/licensing/common/license_update.ts +++ b/x-pack/plugins/licensing/common/license_update.ts @@ -5,11 +5,21 @@ * 2.0. */ -import { ConnectableObservable, Observable, Subject, from, merge, firstValueFrom } from 'rxjs'; +import { type Observable, Subject, merge, firstValueFrom } from 'rxjs'; -import { filter, map, pairwise, exhaustMap, publishReplay, share, takeUntil } from 'rxjs/operators'; +import { + filter, + map, + pairwise, + exhaustMap, + share, + shareReplay, + takeUntil, + finalize, + startWith, +} from 'rxjs/operators'; import { hasLicenseInfoChanged } from './has_license_info_changed'; -import { ILicense } from './types'; +import type { ILicense } from './types'; export function createLicenseUpdate( triggerRefresh$: Observable, @@ -18,26 +28,36 @@ export function createLicenseUpdate( initialValues?: ILicense ) { const manuallyRefresh$ = new Subject(); - const fetched$ = merge(triggerRefresh$, manuallyRefresh$).pipe(exhaustMap(fetcher), share()); - const cached$ = fetched$.pipe( + const fetched$ = merge(triggerRefresh$, manuallyRefresh$).pipe( takeUntil(stop$), - publishReplay(1) - // have to cast manually as pipe operator cannot return ConnectableObservable - // https://github.com/ReactiveX/rxjs/issues/2972 - ) as ConnectableObservable; - - const cachedSubscription = cached$.connect(); - stop$.subscribe({ complete: () => cachedSubscription.unsubscribe() }); + exhaustMap(fetcher), + share() + ); - const initialValues$ = initialValues ? from([undefined, initialValues]) : from([undefined]); + // provide a first, empty license, so that we can compare in the filter below + const startWithArgs = initialValues ? [undefined, initialValues] : [undefined]; - const license$: Observable = merge(initialValues$, cached$).pipe( + const license$: Observable = fetched$.pipe( + shareReplay(1), + startWith(...startWithArgs), pairwise(), filter(([previous, next]) => hasLicenseInfoChanged(previous, next!)), map(([, next]) => next!) ); + // start periodic license fetch right away + const licenseSub = license$.subscribe(); + + stop$ + .pipe( + finalize(() => { + manuallyRefresh$.complete(); + licenseSub.unsubscribe(); + }) + ) + .subscribe(); + return { license$, refreshManually() { diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts index aaeeb4e058008..0d21cd689bf46 100644 --- a/x-pack/plugins/licensing/server/plugin.ts +++ b/x-pack/plugins/licensing/server/plugin.ts @@ -5,15 +5,16 @@ * 2.0. */ -import { Observable, Subject, Subscription, timer } from 'rxjs'; +import type { Observable, Subject, Subscription } from 'rxjs'; +import { ReplaySubject, timer } from 'rxjs'; import moment from 'moment'; import { createHash } from 'crypto'; import stringify from 'json-stable-stringify'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { MaybePromise } from '@kbn/utility-types'; +import type { MaybePromise } from '@kbn/utility-types'; import { isPromise } from '@kbn/std'; -import { +import type { CoreSetup, Logger, Plugin, @@ -22,22 +23,22 @@ import { } from '@kbn/core/server'; import { registerAnalyticsContextProvider } from '../common/register_analytics_context_provider'; -import { +import type { ILicense, PublicLicense, PublicFeatures, LicenseType, LicenseStatus, } from '../common/types'; -import { LicensingPluginSetup, LicensingPluginStart } from './types'; +import type { LicensingPluginSetup, LicensingPluginStart } from './types'; import { License } from '../common/license'; import { createLicenseUpdate } from '../common/license_update'; -import { ElasticsearchError } from './types'; +import type { ElasticsearchError } from './types'; import { registerRoutes } from './routes'; import { FeatureUsageService } from './services'; -import { LicenseConfigType } from './licensing_config'; +import type { LicenseConfigType } from './licensing_config'; import { createRouteHandlerContext } from './licensing_route_handler_context'; import { createOnPreResponseHandler } from './on_pre_response_handler'; import { getPluginStatus$ } from './plugin_status'; @@ -94,7 +95,7 @@ function sign({ * current Kibana instance. */ export class LicensingPlugin implements Plugin { - private stop$ = new Subject(); + private stop$: Subject = new ReplaySubject(1); private readonly logger: Logger; private readonly config: LicenseConfigType; private loggingSubscription?: Subscription; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx index 524556e12a9af..c35ad5bacf371 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx @@ -108,7 +108,7 @@ export const Page: FC<{ /> ) : null} {jobIdToUse !== undefined && ( - + >; + setAnalyticsId: (update: AnalyticsSelectorIds) => void; jobsOnly?: boolean; setIsIdSelectorFlyoutVisible: React.Dispatch>; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/page.tsx index 78561d11dcd1b..cae309ebcb761 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/page.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC, useState, useEffect } from 'react'; +import React, { FC, useState, useEffect, useCallback } from 'react'; import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -27,16 +27,28 @@ import { AnalyticsEmptyPrompt } from '../analytics_management/components/empty_p export const Page: FC = () => { const [globalState, setGlobalState] = useUrlState('_g'); - const mapJobId = globalState?.ml?.jobId; - const mapModelId = globalState?.ml?.modelId; + const jobId = globalState?.ml?.jobId; + const modelId = globalState?.ml?.modelId; const [isLoading, setIsLoading] = useState(false); const [isIdSelectorFlyoutVisible, setIsIdSelectorFlyoutVisible] = useState( - !mapJobId && !mapModelId + !jobId && !modelId ); const [jobsExist, setJobsExist] = useState(true); const { refresh } = useRefreshAnalyticsList({ isLoading: setIsLoading }); - const [analyticsId, setAnalyticsId] = useState(); + + const setAnalyticsId = useCallback( + (analyticsId: AnalyticsSelectorIds) => { + setGlobalState({ + ml: { + ...(analyticsId.job_id && !analyticsId.model_id ? { jobId: analyticsId.job_id } : {}), + ...(analyticsId.model_id ? { modelId: analyticsId.model_id } : {}), + }, + }); + }, + [setGlobalState] + ); + const { services: { docLinks }, } = useMlKibana(); @@ -59,20 +71,6 @@ export const Page: FC = () => { checkJobsExist(); }, []); - useEffect( - function updateUrl() { - if (analyticsId !== undefined) { - setGlobalState({ - ml: { - ...(analyticsId.job_id && !analyticsId.model_id ? { jobId: analyticsId.job_id } : {}), - ...(analyticsId.model_id ? { modelId: analyticsId.model_id } : {}), - }, - }); - } - }, - [analyticsId?.job_id, analyticsId?.model_id] - ); - const getEmptyState = () => { if (jobsExist === false) { return ; @@ -95,9 +93,6 @@ export const Page: FC = () => { ); }; - const jobId = mapJobId ?? analyticsId?.job_id; - const modelId = mapModelId ?? analyticsId?.model_id; - return ( <> { /> ) : null} - {jobId !== undefined ? ( - + {jobId !== undefined && modelId === undefined ? ( + { /> ) : null} - {modelId !== undefined ? ( + {modelId !== undefined && jobId === undefined ? ( { - {jobId ?? modelId ? ( + {jobId || modelId ? ( { + _id: string; + _index: string; + _source: T & { [ALERT_UUID]: string }; +} + +export type GenericAlert830 = AlertWithCommonFields800; + +// This is the type of the final generated alert including base fields, common fields +// added by the alertWithPersistence function, and arbitrary fields copied from source documents +export type DetectionAlert830 = GenericAlert830 | EqlShellAlert800 | EqlBuildingBlockAlert800; + +export interface EqlShellFields830 extends BaseFields830 { + [ALERT_GROUP_ID]: string; + [ALERT_UUID]: string; +} + +export interface EqlBuildingBlockFields830 extends BaseFields830 { + [ALERT_GROUP_ID]: string; + [ALERT_GROUP_INDEX]: number; + [ALERT_BUILDING_BLOCK_TYPE]: 'default'; +} diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/index.ts index 51b5c505b817a..15b80f441e77c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/index.ts @@ -6,23 +6,25 @@ */ import type { - Ancestor800, - BaseFields800, - DetectionAlert800, - WrappedFields800, - EqlBuildingBlockFields800, - EqlShellFields800, -} from './8.0.0'; + EqlBuildingBlockFields830, + EqlShellFields830, + WrappedFields830, + DetectionAlert830, + BaseFields830, + Ancestor830, +} from './8.3.0'; + +import type { DetectionAlert800 } from './8.0.0'; // When new Alert schemas are created for new Kibana versions, add the DetectionAlert type from the new version // here, e.g. `export type DetectionAlert = DetectionAlert800 | DetectionAlert820` if a new schema is created in 8.2.0 -export type DetectionAlert = DetectionAlert800; +export type DetectionAlert = DetectionAlert800 | DetectionAlert830; export type { - Ancestor800 as AncestorLatest, - BaseFields800 as BaseFieldsLatest, - DetectionAlert800 as DetectionAlertLatest, - WrappedFields800 as WrappedFieldsLatest, - EqlBuildingBlockFields800 as EqlBuildingBlockFieldsLatest, - EqlShellFields800 as EqlShellFieldsLatest, + Ancestor830 as AncestorLatest, + BaseFields830 as BaseFieldsLatest, + DetectionAlert830 as DetectionAlertLatest, + WrappedFields830 as WrappedFieldsLatest, + EqlBuildingBlockFields830 as EqlBuildingBlockFieldsLatest, + EqlShellFields830 as EqlShellFieldsLatest, }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index a9edba9e6f41c..74fc8bbbc9331 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -123,6 +123,12 @@ export type IdOrUndefined = t.TypeOf; export const index = t.array(t.string); export type Index = t.TypeOf; +export const data_view_id = t.string; +export type DataViewId = t.TypeOf; + +export const dataViewIdOrUndefined = t.union([data_view_id, t.undefined]); +export type DataViewIdOrUndefined = t.TypeOf; + export const indexOrUndefined = t.union([index, t.undefined]); export type IndexOrUndefined = t.TypeOf; @@ -462,14 +468,17 @@ const bulkActionEditPayloadTags = t.type({ export type BulkActionEditPayloadTags = t.TypeOf; -const bulkActionEditPayloadIndexPatterns = t.type({ - type: t.union([ - t.literal(BulkActionEditType.add_index_patterns), - t.literal(BulkActionEditType.delete_index_patterns), - t.literal(BulkActionEditType.set_index_patterns), - ]), - value: index, -}); +const bulkActionEditPayloadIndexPatterns = t.intersection([ + t.type({ + type: t.union([ + t.literal(BulkActionEditType.add_index_patterns), + t.literal(BulkActionEditType.delete_index_patterns), + t.literal(BulkActionEditType.set_index_patterns), + ]), + value: index, + }), + t.exact(t.partial({ overwriteDataViews: t.boolean })), +]); export type BulkActionEditPayloadIndexPatterns = t.TypeOf< typeof bulkActionEditPayloadIndexPatterns diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts index 9341860a7a012..72c48a139ca1d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts @@ -50,6 +50,7 @@ import { anomaly_threshold, filters, index, + data_view_id, saved_id, timeline_id, timeline_title, @@ -114,6 +115,7 @@ export const addPrepackagedRulesSchema = t.intersection([ filters, // defaults to undefined if not set during decode from: DefaultFromString, // defaults to "now-6m" if not set during decode index, // defaults to undefined if not set during decode + data_view_id, // defaults to undefined if not set during decode interval: DefaultIntervalString, // defaults to "5m" if not set during decode query, // defaults to undefined if not set during decode language, // defaults to undefined if not set during decode diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts index 07c2a2d93c3a4..d3efcaf0f5df5 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts @@ -1773,4 +1773,124 @@ describe('import rules schema', () => { expect(message.schema).toEqual(expected); }); }); + + describe('data_view_id', () => { + test('Defined data_view_id and empty index does validate', () => { + const payload: ImportRulesSchema = { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + type: 'query', + query: 'some query', + data_view_id: 'logs-*', + index: [], + interval: '5m', + }; + + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + type: 'query', + query: 'some query', + index: [], + data_view_id: 'logs-*', + interval: '5m', + references: [], + actions: [], + enabled: true, + false_positives: [], + max_signals: DEFAULT_MAX_SIGNALS, + tags: [], + threat: [], + throttle: null, + version: 1, + exceptions_list: [], + immutable: false, + }; + expect(message.schema).toEqual(expected); + }); + + // Both can be defined, but if a data_view_id is defined, rule will use that one + test('Defined data_view_id and index does validate', () => { + const payload: ImportRulesSchema = { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + type: 'query', + query: 'some query', + data_view_id: 'logs-*', + index: ['auditbeat-*'], + interval: '5m', + }; + + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'low', + type: 'query', + query: 'some query', + index: ['auditbeat-*'], + data_view_id: 'logs-*', + interval: '5m', + references: [], + actions: [], + enabled: true, + false_positives: [], + max_signals: DEFAULT_MAX_SIGNALS, + tags: [], + threat: [], + throttle: null, + version: 1, + exceptions_list: [], + immutable: false, + }; + expect(message.schema).toEqual(expected); + }); + + test('data_view_id cannot be a number', () => { + const payload: Omit & { data_view_id: number } = { + ...getImportRulesSchemaMock(), + data_view_id: 5, + }; + + const decoded = importRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "5" supplied to "data_view_id"', + ]); + expect(message.schema).toEqual({}); + }); + }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts index e684cc30c17a5..6e419e97775e7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts @@ -53,6 +53,7 @@ import { filters, RuleId, index, + data_view_id, output_index, saved_id, timeline_id, @@ -123,6 +124,7 @@ export const importRulesSchema = t.intersection([ filters, // defaults to undefined if not set during decode from: DefaultFromString, // defaults to "now-6m" if not set during decode index, // defaults to undefined if not set during decode + data_view_id, // defaults to undefined if not set during decode immutable: OnlyFalseAllowed, // defaults to "false" if not set during decode interval: DefaultIntervalString, // defaults to "5m" if not set during decode query, // defaults to undefined if not set during decode diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts index f37a4147ff559..80a267ab6c2da 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts @@ -38,6 +38,7 @@ import { anomaly_threshold, filters, index, + data_view_id, output_index, saved_id, timeline_id, @@ -89,6 +90,7 @@ export const patchRulesSchema = t.exact( from, rule_id, index, + data_view_id, interval, query, language, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts index 68371bca04eeb..994f8dc643425 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts @@ -29,6 +29,18 @@ export const getCreateRulesSchemaMock = (ruleId = 'rule-1'): QueryCreateSchema = rule_id: ruleId, }); +export const getCreateRulesSchemaMockWithDataView = (ruleId = 'rule-1'): QueryCreateSchema => ({ + data_view_id: 'logs-*', + description: 'Detecting root and admin users', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + severity: 'high', + type: 'query', + risk_score: 55, + language: 'kuery', + rule_id: ruleId, +}); + export const getCreateSavedQueryRulesSchemaMock = (ruleId = 'rule-1'): SavedQueryCreateSchema => ({ description: 'Detecting root and admin users', name: 'Query with a rule id', @@ -56,7 +68,7 @@ export const getCreateThreatMatchRulesSchemaMock = ( language: 'kuery', rule_id: ruleId, threat_query: '*:*', - threat_index: ['list-index'], + threat_index: ['auditbeat-*'], threat_indicator_path: DEFAULT_INDICATOR_SOURCE_PATH, interval: '5m', from: 'now-6m', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts index 4000b8af4829d..4342a37620d78 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts @@ -14,6 +14,8 @@ import { getCreateThreatMatchRulesSchemaMock, getCreateRulesSchemaMock, getCreateThresholdRulesSchemaMock, + getCreateRulesSchemaMockWithDataView, + getCreateMachineLearningRulesSchemaMock, } from './rule_schemas.mock'; import { getListArrayMock } from '../types/lists.mock'; @@ -1199,4 +1201,89 @@ describe('create rules schema', () => { expect(message.schema).toEqual({}); }); }); + + describe('data_view_id', () => { + test('validates when "data_view_id" and index are defined', () => { + const payload = { ...getCreateRulesSchemaMockWithDataView(), index: ['auditbeat-*'] }; + const decoded = createRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('"data_view_id" cannot be a number', () => { + const payload: Omit & { data_view_id: number } = { + ...getCreateRulesSchemaMockWithDataView(), + data_view_id: 5, + }; + + const decoded = createRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "5" supplied to "data_view_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "query" with "data_view_id" defined', () => { + const payload = getCreateRulesSchemaMockWithDataView(); + + const decoded = createRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getCreateRulesSchemaMockWithDataView(); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should validate a type of "saved_query" with "data_view_id" defined', () => { + const payload = { ...getCreateSavedQueryRulesSchemaMock(), data_view_id: 'logs-*' }; + + const decoded = createRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = { ...getCreateSavedQueryRulesSchemaMock(), data_view_id: 'logs-*' }; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should validate a type of "threat_match" with "data_view_id" defined', () => { + const payload = { ...getCreateThreatMatchRulesSchemaMock(), data_view_id: 'logs-*' }; + + const decoded = createRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = { ...getCreateThreatMatchRulesSchemaMock(), data_view_id: 'logs-*' }; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should validate a type of "threshold" with "data_view_id" defined', () => { + const payload = { ...getCreateThresholdRulesSchemaMock(), data_view_id: 'logs-*' }; + + const decoded = createRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = { ...getCreateThresholdRulesSchemaMock(), data_view_id: 'logs-*' }; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "machine_learning" with "data_view_id" defined', () => { + const payload = { ...getCreateMachineLearningRulesSchemaMock(), data_view_id: 'logs-*' }; + + const decoded = createRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(message.schema).toEqual({}); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "data_view_id"']); + }); + }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts index 970032ee051da..33127dc434d82 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts @@ -32,6 +32,7 @@ import { version } from '@kbn/securitysolution-io-ts-types'; import { id, index, + data_view_id, filters, timestamp_field, event_category_override, @@ -214,6 +215,7 @@ const eqlRuleParams = { }, optional: { index, + data_view_id, filters, timestamp_field, event_category_override, @@ -238,6 +240,7 @@ const threatMatchRuleParams = { }, optional: { index, + data_view_id, filters, saved_id, threat_filters, @@ -263,6 +266,7 @@ const queryRuleParams = { }, optional: { index, + data_view_id, filters, saved_id, }, @@ -288,6 +292,7 @@ const savedQueryRuleParams = { // Having language, query, and filters possibly defined adds more code confusion and probably user confusion // if the saved object gets deleted for some reason index, + data_view_id, query, filters, }, @@ -311,6 +316,7 @@ const thresholdRuleParams = { }, optional: { index, + data_view_id, filters, saved_id, }, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index eeaab6dc50021..1e532e29fcc3e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -152,7 +152,7 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial { test('should return two fields for a rule of type "query"', () => { const fields = addQueryFields({ type: 'query' }); - expect(fields.length).toEqual(2); + expect(fields.length).toEqual(3); }); test('should return two fields for a rule of type "threshold"', () => { const fields = addQueryFields({ type: 'threshold' }); - expect(fields.length).toEqual(2); + expect(fields.length).toEqual(3); }); test('should return two fields for a rule of type "saved_query"', () => { const fields = addQueryFields({ type: 'saved_query' }); - expect(fields.length).toEqual(2); + expect(fields.length).toEqual(3); }); test('should return two fields for a rule of type "threat_match"', () => { const fields = addQueryFields({ type: 'threat_match' }); - expect(fields.length).toEqual(2); + expect(fields.length).toEqual(3); }); }); @@ -777,7 +777,7 @@ describe('rules_schema', () => { test('should return nine (9) fields for a rule of type "threat_match"', () => { const fields = addThreatMatchFields({ type: 'threat_match' }); - expect(fields.length).toEqual(9); + expect(fields.length).toEqual(10); }); }); @@ -790,7 +790,78 @@ describe('rules_schema', () => { test('should return 3 fields for a rule of type "eql"', () => { const fields = addEqlFields({ type: 'eql' }); - expect(fields.length).toEqual(5); + expect(fields.length).toEqual(6); + }); + }); + + describe('data_view_id', () => { + test('it should validate a type of "query" with "data_view_id" defined', () => { + const payload = { ...getRulesSchemaMock(), data_view_id: 'logs-*' }; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = { ...getRulesSchemaMock(), data_view_id: 'logs-*' }; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should validate a type of "saved_query" with "data_view_id" defined', () => { + const payload = getRulesSchemaMock(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + payload.data_view_id = 'logs-*'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getRulesSchemaMock(); + + expected.type = 'saved_query'; + expected.saved_id = 'save id 123'; + expected.data_view_id = 'logs-*'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should validate a type of "eql" with "data_view_id" defined', () => { + const payload = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' }; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' }; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should validate a type of "threat_match" with "data_view_id" defined', () => { + const payload = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' }; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' }; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "machine_learning" with "data_view_id" defined', () => { + const payload = { ...getRulesMlSchemaMock(), data_view_id: 'logs-*' }; + + const dependents = getDependents(payload); + const decoded = dependents.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "data_view_id"']); + expect(message.schema).toEqual({}); }); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts index b02235a1bc018..b48ef59f132e8 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts @@ -39,6 +39,7 @@ import { isMlRule } from '../../../machine_learning/helpers'; import { isThresholdRule } from '../../utils'; import { anomaly_threshold, + data_view_id, description, enabled, timestamp_field, @@ -128,6 +129,9 @@ export type RequiredRulesSchema = t.TypeOf; * check_type_dependents file for whichever REST flow it is going through. */ export const dependentRulesSchema = t.partial({ + // All but ML + data_view_id, + // query fields language, query, @@ -243,6 +247,7 @@ export const addQueryFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixe return [ t.exact(t.type({ query: dependentRulesSchema.props.query })), t.exact(t.type({ language: dependentRulesSchema.props.language })), + t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), ]; } else { return []; @@ -267,6 +272,7 @@ export const addThresholdFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t. return [ t.exact(t.type({ threshold: dependentRulesSchema.props.threshold })), t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })), + t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), ]; } else { return []; @@ -283,6 +289,7 @@ export const addEqlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[ t.exact(t.partial({ tiebreaker_field: dependentRulesSchema.props.tiebreaker_field })), t.exact(t.type({ query: dependentRulesSchema.props.query })), t.exact(t.type({ language: dependentRulesSchema.props.language })), + t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), ]; } else { return []; @@ -292,6 +299,7 @@ export const addEqlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[ export const addThreatMatchFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { if (typeAndTimelineOnly.type === 'threat_match') { return [ + t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), t.exact(t.type({ threat_query: dependentRulesSchema.props.threat_query })), t.exact(t.type({ threat_index: dependentRulesSchema.props.threat_index })), t.exact(t.type({ threat_mapping: dependentRulesSchema.props.threat_mapping })), diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts index 52cd4e79e6a6b..b26bb85a263a1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts @@ -11,6 +11,7 @@ import { isThreatMatchRule, normalizeMachineLearningJobIds, normalizeThresholdField, + isMlRule, } from './utils'; import { hasLargeValueList } from '@kbn/securitysolution-list-utils'; @@ -124,6 +125,16 @@ describe('#hasNestedEntry', () => { }); }); +describe('isMlRule', () => { + test('it returns true if a ML rule', () => { + expect(isMlRule('machine_learning')).toEqual(true); + }); + + test('it returns false if not a Ml rule', () => { + expect(isMlRule('query')).toEqual(false); + }); +}); + describe('#hasEqlSequenceQuery', () => { describe('when a non-sequence query is passed', () => { const query = 'process where process.name == "regsvr32.exe"'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index de5a05edc2f8a..5d81e1af7ea4a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -37,12 +37,14 @@ export const hasEqlSequenceQuery = (ruleQuery: string | undefined): boolean => { return false; }; +// these functions should be typeguards and accept an entire rule. export const isEqlRule = (ruleType: Type | undefined): boolean => ruleType === 'eql'; export const isThresholdRule = (ruleType: Type | undefined): boolean => ruleType === 'threshold'; export const isQueryRule = (ruleType: Type | undefined): boolean => ruleType === 'query' || ruleType === 'saved_query'; export const isThreatMatchRule = (ruleType: Type | undefined): boolean => ruleType === 'threat_match'; +export const isMlRule = (ruleType: Type | undefined): boolean => ruleType === 'machine_learning'; export const normalizeThresholdField = ( thresholdField: string | string[] | null | undefined diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts index 47e78cdab4a53..60cab431a5444 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts @@ -10,7 +10,7 @@ import uuid from 'uuid'; import { EndpointActionListRequestSchema, HostIsolationRequestSchema, - KillProcessRequestSchema, + KillOrSuspendProcessRequestSchema, } from './actions'; describe('actions schemas', () => { @@ -190,7 +190,7 @@ describe('actions schemas', () => { }); }); - describe('KillProcessRequestSchema', () => { + describe('KillOrSuspendProcessRequestSchema', () => { it('should require at least 1 Endpoint ID', () => { expect(() => { HostIsolationRequestSchema.body.validate({}); @@ -199,7 +199,7 @@ describe('actions schemas', () => { it('should accept pid', () => { expect(() => { - KillProcessRequestSchema.body.validate({ + KillOrSuspendProcessRequestSchema.body.validate({ endpoint_ids: ['ABC-XYZ-000'], parameters: { pid: 1234, @@ -210,7 +210,7 @@ describe('actions schemas', () => { it('should accept entity_id', () => { expect(() => { - KillProcessRequestSchema.body.validate({ + KillOrSuspendProcessRequestSchema.body.validate({ endpoint_ids: ['ABC-XYZ-000'], parameters: { entity_id: 5678, @@ -221,7 +221,7 @@ describe('actions schemas', () => { it('should reject pid and entity_id together', () => { expect(() => { - KillProcessRequestSchema.body.validate({ + KillOrSuspendProcessRequestSchema.body.validate({ endpoint_ids: ['ABC-XYZ-000'], parameters: { pid: 1234, @@ -233,7 +233,7 @@ describe('actions schemas', () => { it('should reject if no pid or entity_id', () => { expect(() => { - KillProcessRequestSchema.body.validate({ + KillOrSuspendProcessRequestSchema.body.validate({ endpoint_ids: ['ABC-XYZ-000'], comment: 'a user comment', parameters: {}, @@ -243,7 +243,7 @@ describe('actions schemas', () => { it('should accept a comment', () => { expect(() => { - KillProcessRequestSchema.body.validate({ + KillOrSuspendProcessRequestSchema.body.validate({ endpoint_ids: ['ABC-XYZ-000'], comment: 'a user comment', parameters: { diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts index 944b21b9b910d..c4dfa7a5b434c 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts @@ -22,7 +22,7 @@ export const HostIsolationRequestSchema = { body: schema.object({ ...BaseActionRequestSchema }), }; -export const KillProcessRequestSchema = { +export const KillOrSuspendProcessRequestSchema = { body: schema.object({ ...BaseActionRequestSchema, parameters: schema.oneOf([ @@ -34,7 +34,7 @@ export const KillProcessRequestSchema = { export const ResponseActionBodySchema = schema.oneOf([ HostIsolationRequestSchema.body, - KillProcessRequestSchema.body, + KillOrSuspendProcessRequestSchema.body, ]); export const EndpointActionLogRequestSchema = { diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts index b9fe201a1cddf..0389ac8e216ae 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts @@ -29,6 +29,7 @@ describe('Endpoint Authz service', () => { ['canIsolateHost'], ['canUnIsolateHost'], ['canKillProcess'], + ['canSuspendProcess'], ])('should set `%s` to `true`', (authProperty) => { expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles)[authProperty]).toBe( true @@ -51,6 +52,14 @@ describe('Endpoint Authz service', () => { ); }); + it('should set `canSuspendProcess` to false if not proper license', () => { + licenseService.isPlatinumPlus.mockReturnValue(false); + + expect( + calculateEndpointAuthz(licenseService, fleetAuthz, userRoles).canSuspendProcess + ).toBe(false); + }); + it('should set `canUnIsolateHost` to true even if not proper license', () => { licenseService.isPlatinumPlus.mockReturnValue(false); @@ -72,6 +81,7 @@ describe('Endpoint Authz service', () => { ['canIsolateHost'], ['canUnIsolateHost'], ['canKillProcess'], + ['canSuspendProcess'], ])('should set `%s` to `false`', (authProperty) => { expect(calculateEndpointAuthz(licenseService, fleetAuthz, userRoles)[authProperty]).toBe( false @@ -97,6 +107,7 @@ describe('Endpoint Authz service', () => { canUnIsolateHost: true, canCreateArtifactsByPolicy: false, canKillProcess: false, + canSuspendProcess: false, }); }); }); diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts index 7c515cf1a3595..5acf3e5df1975 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts @@ -33,6 +33,7 @@ export const calculateEndpointAuthz = ( canIsolateHost: isPlatinumPlusLicense && hasAllAccessToFleet, canUnIsolateHost: hasAllAccessToFleet, canKillProcess: hasAllAccessToFleet && isPlatinumPlusLicense, + canSuspendProcess: hasAllAccessToFleet && isPlatinumPlusLicense, }; }; @@ -44,5 +45,6 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => { canIsolateHost: false, canUnIsolateHost: true, canKillProcess: false, + canSuspendProcess: false, }; }; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 38d5bdca028b8..2dd82d7609de4 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -14,7 +14,7 @@ import { export type ISOLATION_ACTIONS = 'isolate' | 'unisolate'; -export type ResponseActions = ISOLATION_ACTIONS | 'kill-process'; +export type ResponseActions = ISOLATION_ACTIONS | 'kill-process' | 'suspend-process'; export const ActivityLogItemTypes = { ACTION: 'action' as const, @@ -76,19 +76,23 @@ export interface LogsEndpointActionResponse { error?: EcsError; } -interface KillProcessWithPid { +interface ResponseActionParametersWithPid { pid: number; entity_id?: never; } -interface KillProcessWithEntityId { +interface ResponseActionParametersWithEntityId { pid?: never; entity_id: number; } -export type KillProcessParameters = KillProcessWithPid | KillProcessWithEntityId; +export type ResponseActionParametersWithPidOrEntityId = + | ResponseActionParametersWithPid + | ResponseActionParametersWithEntityId; -export type EndpointActionDataParameterTypes = undefined | KillProcessParameters; +export type EndpointActionDataParameterTypes = + | undefined + | ResponseActionParametersWithPidOrEntityId; export interface EndpointActionData { command: ResponseActions; @@ -194,7 +198,7 @@ export interface HostIsolationResponse { } export interface ResponseActionApiResponse { - action?: string; + action?: string; // only if command is isolate or release data: ActionDetails; } diff --git a/x-pack/plugins/security_solution/common/endpoint/types/authz.ts b/x-pack/plugins/security_solution/common/endpoint/types/authz.ts index 3b07bc5e9b162..3f7a50537177f 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/authz.ts @@ -22,6 +22,8 @@ export interface EndpointAuthz { canUnIsolateHost: boolean; /** If user has permissions to kill process on hosts */ canKillProcess: boolean; + /** If user has permissions to suspend process on hosts */ + canSuspendProcess: boolean; } export type EndpointAuthzKeyList = Array; diff --git a/x-pack/plugins/security_solution/common/field_maps/field_names.ts b/x-pack/plugins/security_solution/common/field_maps/field_names.ts index 164f87b4e7b5c..29ed0f5985ec5 100644 --- a/x-pack/plugins/security_solution/common/field_maps/field_names.ts +++ b/x-pack/plugins/security_solution/common/field_maps/field_names.ts @@ -38,3 +38,4 @@ export const ALERT_RULE_THROTTLE = `${ALERT_RULE_NAMESPACE}.throttle` as const; export const ALERT_RULE_TIMELINE_ID = `${ALERT_RULE_NAMESPACE}.timeline_id` as const; export const ALERT_RULE_TIMELINE_TITLE = `${ALERT_RULE_NAMESPACE}.timeline_title` as const; export const ALERT_RULE_TIMESTAMP_OVERRIDE = `${ALERT_RULE_NAMESPACE}.timestamp_override` as const; +export const ALERT_RULE_INDICES = `${ALERT_RULE_NAMESPACE}.indices` as const; diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index a9134e5b124eb..c7726ac40e83d 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -39,6 +39,8 @@ export const FALSE_POSITIVES_DETAILS = 'False positive examples'; export const INDEX_PATTERNS_DETAILS = 'Index patterns'; +export const DATA_VIEW_DETAILS = 'Data View'; + export const INDICATOR_INDEX_PATTERNS = 'Indicator index patterns'; export const INDICATOR_INDEX_QUERY = 'Indicator index query'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index ed69f1d18d5e6..23602c6493da3 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -255,6 +255,7 @@ export const fillDefineCustomRuleWithImportedQueryAndContinue = ( cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click(); cy.get(TIMELINE(rule.timeline.id)).click(); cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery); + cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); cy.get(CUSTOM_QUERY_INPUT).should('not.exist'); @@ -316,6 +317,7 @@ export const fillDefineEqlRuleAndContinue = (rule: CustomRule) => { if (rule.customQuery == null) { throw new TypeError('The rule custom query should never be undefined or null '); } + cy.get(RULES_CREATION_FORM).find(EQL_QUERY_INPUT).should('exist'); cy.get(RULES_CREATION_FORM).find(EQL_QUERY_INPUT).should('be.visible'); cy.get(RULES_CREATION_FORM).find(EQL_QUERY_INPUT).type(rule.customQuery); @@ -394,6 +396,7 @@ export const fillIndexAndIndicatorIndexPattern = ( indicatorIndex?: string[] ) => { getIndexPatternClearButton().click(); + getIndicatorIndex().type(`${indexPattern}{enter}`); getIndicatorIndicatorIndex().type(`{backspace}{enter}${indicatorIndex}{enter}`); }; @@ -442,7 +445,9 @@ export const getAboutContinueButton = () => cy.get(ABOUT_CONTINUE_BTN); export const getDefineContinueButton = () => cy.get(DEFINE_CONTINUE_BUTTON); /** Returns the indicator index pattern */ -export const getIndicatorIndex = () => cy.get(THREAT_MATCH_INDICATOR_INDEX).eq(0); +export const getIndicatorIndex = () => { + return cy.get(THREAT_MATCH_INDICATOR_INDEX).eq(0); +}; /** Returns the indicator's indicator index */ export const getIndicatorIndicatorIndex = () => diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts index 267988462cfb8..7fda21016205a 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts @@ -19,6 +19,7 @@ import { import { ALERTS_TAB, BACK_TO_RULES, + DATA_VIEW_DETAILS, EXCEPTIONS_TAB, FIELDS_BROWSER_BTN, REFRESH_BUTTON, @@ -71,7 +72,7 @@ export const openExceptionFlyoutFromRuleSettings = () => { export const addsExceptionFromRuleSettings = (exception: Exception) => { openExceptionFlyoutFromRuleSettings(); - cy.get(FIELD_INPUT).type(`${exception.field}{enter}`); + cy.get(FIELD_INPUT).type(`${exception.field}{downArrow}{enter}`); cy.get(OPERATOR_INPUT).type(`${exception.operator}{enter}`); exception.values.forEach((value) => { cy.get(VALUES_INPUT).type(`${value}{enter}`); @@ -121,3 +122,9 @@ export const hasIndexPatterns = (indexPatterns: string) => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns); }); }; + +export const doesNotHaveDataView = () => { + cy.get(DEFINITION_DETAILS).within(() => { + cy.get(DETAILS_TITLE).within(() => cy.get(DATA_VIEW_DETAILS).should('not.exist')); + }); +}; diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx index 54ffea7edf6d6..853c4b634d348 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx @@ -10,7 +10,6 @@ import React from 'react'; import { KibanaPageTemplateProps } from '@kbn/shared-ux-components'; import { AppLeaveHandler } from '@kbn/core/public'; -import { useShowTimeline } from '../../../../common/utils/timeline/use_show_timeline'; import { TimelineId } from '../../../../../common/types/timeline'; import { AutoSaveWarningMsg } from '../../../../timelines/components/timeline/auto_save_warning'; import { Flyout } from '../../../../timelines/components/flyout'; @@ -20,15 +19,13 @@ export const BOTTOM_BAR_CLASSNAME = 'timeline-bottom-bar'; export const SecuritySolutionBottomBar = React.memo( ({ onAppLeave }: { onAppLeave: (handler: AppLeaveHandler) => void }) => { - const [showTimeline] = useShowTimeline(); - useResolveRedirect(); - return showTimeline ? ( + return ( <> - ) : null; + ); } ); diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.test.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.test.tsx index 005e671bb48c7..24a42dbd6ee03 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.test.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.test.tsx @@ -11,9 +11,10 @@ import React from 'react'; import { TestProviders } from '../../../common/mock'; import { SecuritySolutionTemplateWrapper } from '.'; +const mockUseShowTimeline = jest.fn((): [boolean] => [false]); jest.mock('../../../common/utils/timeline/use_show_timeline', () => ({ ...jest.requireActual('../../../common/utils/timeline/use_show_timeline'), - useShowTimeline: () => [true], + useShowTimeline: () => mockUseShowTimeline(), })); jest.mock('./bottom_bar', () => ({ @@ -21,25 +22,6 @@ jest.mock('./bottom_bar', () => ({ SecuritySolutionBottomBar: () =>
{'Bottom Bar'}
, })); -const mockSiemUserCanCrud = jest.fn(); -jest.mock('../../../common/lib/kibana', () => { - const original = jest.requireActual('../../../common/lib/kibana'); - - return { - ...original, - useKibana: () => ({ - services: { - ...original.useKibana().services, - application: { - capabilities: { - siem: mockSiemUserCanCrud(), - }, - }, - }, - }), - }; -}); - jest.mock('../../../common/components/navigation/use_security_solution_navigation', () => { return { useSecuritySolutionNavigation: () => ({ @@ -82,8 +64,8 @@ describe('SecuritySolutionTemplateWrapper', () => { jest.clearAllMocks(); }); - it('Should render to the page with bottom bar if user has SIEM show', async () => { - mockSiemUserCanCrud.mockReturnValue({ show: true }); + it('Should render with bottom bar when user allowed', async () => { + mockUseShowTimeline.mockReturnValue([true]); const { getByText } = renderComponent(); await waitFor(() => { @@ -92,8 +74,8 @@ describe('SecuritySolutionTemplateWrapper', () => { }); }); - it('Should not show bottom bar if user does not have SIEM show', async () => { - mockSiemUserCanCrud.mockReturnValue({ show: false }); + it('Should not show bottom bar when user not allowed', async () => { + mockUseShowTimeline.mockReturnValue([false]); const { getByText, queryByText } = renderComponent(); diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx index 8d7d9daad550d..6888a75da2006 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx @@ -23,7 +23,6 @@ import { } from './bottom_bar'; import { useShowTimeline } from '../../../common/utils/timeline/use_show_timeline'; import { gutterTimeline } from '../../../common/lib/helpers'; -import { useKibana } from '../../../common/lib/kibana'; import { useShowPagesWithEmptyView } from '../../../common/utils/empty_view/use_show_pages_with_empty_view'; import { useIsPolicySettingsBarVisible } from '../../../management/pages/policy/view/policy_hooks'; import { useIsGroupedNavigationEnabled } from '../../../common/components/navigation/helpers'; @@ -91,7 +90,6 @@ export const SecuritySolutionTemplateWrapper: React.FC + isTimelineBottomBarVisible && } paddingSize="none" solutionNav={solutionNav} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx index 2e7763c169d0d..3e6da12efe8e8 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { waitFor, render } from '@testing-library/react'; +import { waitFor, render, act } from '@testing-library/react'; import { AlertSummaryView } from './alert_summary_view'; import { mockAlertDetailsData } from './__mocks__'; @@ -44,59 +44,69 @@ describe('AlertSummaryView', () => { }, }); }); - test('render correct items', () => { - const { getByTestId } = render( - - - - ); - expect(getByTestId('summary-view')).toBeInTheDocument(); + test('render correct items', async () => { + await act(async () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('summary-view')).toBeInTheDocument(); + }); }); - test('it renders the action cell by default', () => { - const { getAllByTestId } = render( - - - - ); - expect(getAllByTestId('hover-actions-filter-for').length).toBeGreaterThan(0); + test('it renders the action cell by default', async () => { + await act(async () => { + const { getAllByTestId } = render( + + + + ); + expect(getAllByTestId('hover-actions-filter-for').length).toBeGreaterThan(0); + }); }); - test('Renders the correct global fields', () => { - const { getByText } = render( - - - - ); - - [ - 'host.name', - 'user.name', - i18n.RULE_TYPE, - 'query', - i18n.SOURCE_EVENT_ID, - i18n.SESSION_ID, - ].forEach((fieldId) => { - expect(getByText(fieldId)); + test('Renders the correct global fields', async () => { + await act(async () => { + const { getByText } = render( + + + + ); + + [ + 'host.name', + 'user.name', + i18n.RULE_TYPE, + 'query', + i18n.SOURCE_EVENT_ID, + i18n.SESSION_ID, + ].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('it does NOT render the action cell for the active timeline', () => { - const { queryAllByTestId } = render( - - - - ); - expect(queryAllByTestId('hover-actions-filter-for').length).toEqual(0); + test('it does NOT render the action cell for the active timeline', async () => { + await act(async () => { + const { queryAllByTestId } = render( + + + + ); + expect(queryAllByTestId('hover-actions-filter-for').length).toEqual(0); + }); }); - test('it does NOT render the action cell when readOnly is passed', () => { - const { queryAllByTestId } = render( - - - - ); - expect(queryAllByTestId('hover-actions-filter-for').length).toEqual(0); + test('it does NOT render the action cell when readOnly is passed', async () => { + await act(async () => { + const { queryAllByTestId } = render( + + + + ); + expect(queryAllByTestId('hover-actions-filter-for').length).toEqual(0); + }); }); test("render no investigation guide if it doesn't exist", async () => { @@ -105,16 +115,18 @@ describe('AlertSummaryView', () => { note: null, }, }); - const { queryByTestId } = render( - - - - ); - await waitFor(() => { - expect(queryByTestId('summary-view-guide')).not.toBeInTheDocument(); + await act(async () => { + const { queryByTestId } = render( + + + + ); + await waitFor(() => { + expect(queryByTestId('summary-view-guide')).not.toBeInTheDocument(); + }); }); }); - test('Network event renders the correct summary rows', () => { + test('Network event renders the correct summary rows', async () => { const renderProps = { ...props, data: mockAlertDetailsData.map((item) => { @@ -128,25 +140,27 @@ describe('AlertSummaryView', () => { return item; }) as TimelineEventsDetailsItem[], }; - const { getByText } = render( - - - - ); - - [ - 'host.name', - 'user.name', - 'destination.address', - 'source.address', - 'source.port', - 'process.name', - ].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + + [ + 'host.name', + 'user.name', + 'destination.address', + 'source.address', + 'source.port', + 'process.name', + ].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('DNS network event renders the correct summary rows', () => { + test('DNS network event renders the correct summary rows', async () => { const renderProps = { ...props, data: [ @@ -168,18 +182,20 @@ describe('AlertSummaryView', () => { } as TimelineEventsDetailsItem, ], }; - const { getByText } = render( - - - - ); - - ['dns.question.name', 'process.name'].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + + ['dns.question.name', 'process.name'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('Memory event code renders additional summary rows', () => { + test('Memory event code renders additional summary rows', async () => { const renderProps = { ...props, data: mockAlertDetailsData.map((item) => { @@ -193,16 +209,18 @@ describe('AlertSummaryView', () => { return item; }) as TimelineEventsDetailsItem[], }; - const { getByText } = render( - - - - ); - ['host.name', 'user.name', 'Target.process.executable'].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + ['host.name', 'user.name', 'Target.process.executable'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('Behavior event code renders additional summary rows', () => { + test('Behavior event code renders additional summary rows', async () => { const actualRuleDescription = 'The actual rule description'; const renderProps = { ...props, @@ -232,17 +250,19 @@ describe('AlertSummaryView', () => { }, ] as TimelineEventsDetailsItem[], }; - const { getByText } = render( - - - - ); - ['host.name', 'user.name', 'process.name', actualRuleDescription].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + ['host.name', 'user.name', 'process.name', actualRuleDescription].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('Malware event category shows file fields', () => { + test('Malware event category shows file fields', async () => { const enhancedData = [ ...mockAlertDetailsData.map((item) => { if (item.category === 'event' && item.field === 'event.category') { @@ -265,17 +285,19 @@ describe('AlertSummaryView', () => { ...props, data: enhancedData, }; - const { getByText } = render( - - - - ); - ['host.name', 'user.name', 'file.name', 'file.hash.sha256'].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + ['host.name', 'user.name', 'file.name', 'file.hash.sha256'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('Ransomware event code shows correct fields', () => { + test('Ransomware event code shows correct fields', async () => { const enhancedData = [ ...mockAlertDetailsData.map((item) => { if (item.category === 'event' && item.field === 'event.code') { @@ -298,17 +320,19 @@ describe('AlertSummaryView', () => { ...props, data: enhancedData, }; - const { getByText } = render( - - - - ); - ['process.hash.sha256', 'Ransomware.feature'].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + ['process.hash.sha256', 'Ransomware.feature'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('Machine learning events show correct fields', () => { + test('Machine learning events show correct fields', async () => { const enhancedData = [ ...mockAlertDetailsData.map((item) => { if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') { @@ -331,17 +355,21 @@ describe('AlertSummaryView', () => { ...props, data: enhancedData, }; - const { getByText } = render( - - - - ); - ['i_am_the_ml_job_id', 'kibana.alert.rule.parameters.anomaly_threshold'].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + ['i_am_the_ml_job_id', 'kibana.alert.rule.parameters.anomaly_threshold'].forEach( + (fieldId) => { + expect(getByText(fieldId)); + } + ); }); }); - test('[legacy] Machine learning events show correct fields', () => { + test('[legacy] Machine learning events show correct fields', async () => { const enhancedData = [ ...mockAlertDetailsData.map((item) => { if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') { @@ -364,17 +392,19 @@ describe('AlertSummaryView', () => { ...props, data: enhancedData, }; - const { getByText } = render( - - - - ); - ['i_am_the_ml_job_id', 'signal.rule.anomaly_threshold'].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + ['i_am_the_ml_job_id', 'signal.rule.anomaly_threshold'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('Threat match events show correct fields', () => { + test('Threat match events show correct fields', async () => { const enhancedData = [ ...mockAlertDetailsData.map((item) => { if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') { @@ -401,17 +431,19 @@ describe('AlertSummaryView', () => { ...props, data: enhancedData, }; - const { getByText } = render( - - - - ); - ['threat_index*', '*query*'].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + ['threat_index*', '*query*'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('[legacy] Threat match events show correct fields', () => { + test('[legacy] Threat match events show correct fields', async () => { const enhancedData = [ ...mockAlertDetailsData.map((item) => { if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') { @@ -438,17 +470,19 @@ describe('AlertSummaryView', () => { ...props, data: enhancedData, }; - const { getByText } = render( - - - - ); - ['threat_index*', '*query*'].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + ['threat_index*', '*query*'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('Ransomware event code resolves fields from the source event', () => { + test('Ransomware event code resolves fields from the source event', async () => { const renderProps = { ...props, data: mockAlertDetailsData.map((item) => { @@ -469,17 +503,19 @@ describe('AlertSummaryView', () => { return item; }) as TimelineEventsDetailsItem[], }; - const { getByText } = render( - - - - ); - ['host.name', 'user.name', 'process.name'].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + ['host.name', 'user.name', 'process.name'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('Threshold events have special fields', () => { + test('Threshold events have special fields', async () => { const enhancedData = [ ...mockAlertDetailsData.map((item) => { if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') { @@ -526,24 +562,26 @@ describe('AlertSummaryView', () => { ...props, data: enhancedData, }; - const { getByText } = render( - - - - ); - - [ - 'Threshold Count', - 'host.name [threshold]', - 'host.id [threshold]', - 'Threshold Cardinality', - 'count(host.name) >= 9001', - ].forEach((fieldId) => { - expect(getByText(fieldId)); + await act(async () => { + const { getByText } = render( + + + + ); + + [ + 'Threshold Count', + 'host.name [threshold]', + 'host.id [threshold]', + 'Threshold Cardinality', + 'count(host.name) >= 9001', + ].forEach((fieldId) => { + expect(getByText(fieldId)); + }); }); }); - test('Threshold fields are not shown when data is malformated', () => { + test('Threshold fields are not shown when data is malformated', async () => { const enhancedData = [ ...mockAlertDetailsData.map((item) => { if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') { @@ -592,27 +630,29 @@ describe('AlertSummaryView', () => { ...props, data: enhancedData, }; - const { getByText } = render( - - - - ); - - ['Threshold Count'].forEach((fieldId) => { - expect(getByText(fieldId)); - }); - - [ - 'host.name [threshold]', - 'host.id [threshold]', - 'Threshold Cardinality', - 'count(host.name) >= 9001', - ].forEach((fieldText) => { - expect(() => getByText(fieldText)).toThrow(); + await act(async () => { + const { getByText } = render( + + + + ); + + ['Threshold Count'].forEach((fieldId) => { + expect(getByText(fieldId)); + }); + + [ + 'host.name [threshold]', + 'host.id [threshold]', + 'Threshold Cardinality', + 'count(host.name) >= 9001', + ].forEach((fieldText) => { + expect(() => getByText(fieldText)).toThrow(); + }); }); }); - test('Threshold fields are not shown when data is partially missing', () => { + test('Threshold fields are not shown when data is partially missing', async () => { const enhancedData = [ ...mockAlertDetailsData.map((item) => { if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') { @@ -642,21 +682,23 @@ describe('AlertSummaryView', () => { ...props, data: enhancedData, }; - const { getByText } = render( - - - - ); - - // The `value` fields are missing here, so the enriched field info cannot be calculated correctly - ['host.id [threshold]', 'Threshold Cardinality', 'count(host.name) >= 9001'].forEach( - (fieldText) => { - expect(() => getByText(fieldText)).toThrow(); - } - ); + await act(async () => { + const { getByText } = render( + + + + ); + + // The `value` fields are missing here, so the enriched field info cannot be calculated correctly + ['host.id [threshold]', 'Threshold Cardinality', 'count(host.name) >= 9001'].forEach( + (fieldText) => { + expect(() => getByText(fieldText)).toThrow(); + } + ); + }); }); - test("doesn't render empty fields", () => { + test("doesn't render empty fields", async () => { const renderProps = { ...props, data: mockAlertDetailsData.map((item) => { @@ -672,12 +714,14 @@ describe('AlertSummaryView', () => { }) as TimelineEventsDetailsItem[], }; - const { queryByTestId } = render( - - - - ); + await act(async () => { + const { queryByTestId } = render( + + + + ); - expect(queryByTestId('event-field-kibana.alert.rule.name')).not.toBeInTheDocument(); + expect(queryByTestId('event-field-kibana.alert.rule.name')).not.toBeInTheDocument(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/related_cases.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/related_cases.test.tsx index 9aa301d4aedd0..c76f6f42a9e11 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/related_cases.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/related_cases.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -45,13 +45,11 @@ describe('Related Cases', () => { (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ read: false, }); - act(() => { - render( - - - - ); - }); + render( + + + + ); expect(screen.queryByText('cases')).toBeNull(); }); @@ -66,13 +64,11 @@ describe('Related Cases', () => { describe('When related cases are unable to be retrieved', () => { test('should show 0 related cases when there are none', async () => { mockGetRelatedCases.mockReturnValue([]); - act(() => { - render( - - - - ); - }); + render( + + + + ); expect(await screen.findByText('0 cases.')).toBeInTheDocument(); }); @@ -81,14 +77,11 @@ describe('Related Cases', () => { describe('When 1 related case is retrieved', () => { test('should show 1 related case', async () => { mockGetRelatedCases.mockReturnValue([{ id: '789', title: 'Test Case' }]); - act(() => { - render( - - - - ); - }); - + render( + + + + ); expect(await screen.findByText('1 case:')).toBeInTheDocument(); expect(await screen.findByTestId('case-details-link')).toHaveTextContent('Test Case'); }); @@ -100,14 +93,11 @@ describe('Related Cases', () => { { id: '789', title: 'Test Case 1' }, { id: '456', title: 'Test Case 2' }, ]); - act(() => { - render( - - - - ); - }); - + render( + + + + ); expect(await screen.findByText('2 cases:')).toBeInTheDocument(); const cases = await screen.findAllByTestId('case-details-link'); expect(cases).toHaveLength(2); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_flyout/index.test.tsx index 623ab9b282480..fe41893e25c3d 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_flyout/index.test.tsx @@ -6,17 +6,15 @@ */ import React from 'react'; -import { ThemeProvider } from 'styled-components'; import { mount, ReactWrapper } from 'enzyme'; -import { waitFor } from '@testing-library/react'; +import { act, waitFor } from '@testing-library/react'; import { AddExceptionFlyout } from '.'; -import { useCurrentUser } from '../../../lib/kibana'; import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public'; import { useAsync } from '@kbn/securitysolution-hook-utils'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; import { useFetchIndex } from '../../../containers/source'; -import { stubIndexPattern } from '@kbn/data-plugin/common/stubs'; +import { createIndexPatternFieldStub, stubIndexPattern } from '@kbn/data-plugin/common/stubs'; import { useAddOrUpdateException } from '../use_add_exception'; import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; @@ -24,24 +22,14 @@ import * as helpers from '../helpers'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; import type { EntriesArray, ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { TestProviders } from '../../../mock'; + import { getRulesEqlSchemaMock, getRulesSchemaMock, } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; import { useRuleAsync } from '../../../../detections/containers/detection_engine/rules/use_rule_async'; import { AlertData } from '../types'; -import { getMockTheme } from '../../../lib/kibana/kibana_react.mock'; - -const mockTheme = getMockTheme({ - eui: { - euiBreakpoints: { - l: '1200px', - }, - paddingSizes: { - m: '10px', - }, - }, -}); jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../lib/kibana'); @@ -68,7 +56,6 @@ const mockUseFetchOrCreateRuleExceptionList = useFetchOrCreateRuleExceptionList >; const mockUseSignalIndex = useSignalIndex as jest.Mock>>; const mockUseFetchIndex = useFetchIndex as jest.Mock; -const mockUseCurrentUser = useCurrentUser as jest.Mock>>; const mockUseRuleAsync = useRuleAsync as jest.Mock; describe('When the add exception modal is opened', () => { @@ -104,7 +91,6 @@ describe('When the add exception modal is opened', () => { indexPatterns: stubIndexPattern, }, ]); - mockUseCurrentUser.mockReturnValue({ username: 'test-username' }); mockUseRuleAsync.mockImplementation(() => ({ rule: getRulesSchemaMock(), })); @@ -126,7 +112,7 @@ describe('When the add exception modal is opened', () => { }, ]); wrapper = mount( - + { onCancel={jest.fn()} onConfirm={jest.fn()} /> - + ); }); it('should show the loading spinner', () => { @@ -147,7 +133,7 @@ describe('When the add exception modal is opened', () => { let wrapper: ReactWrapper; beforeEach(async () => { wrapper = mount( - + { onCancel={jest.fn()} onConfirm={jest.fn()} /> - + ); const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; await waitFor(() => callProps.onChange({ exceptionItems: [] })); @@ -191,7 +177,7 @@ describe('When the add exception modal is opened', () => { file: { path: 'test/path' }, }; wrapper = mount( - + { onConfirm={jest.fn()} alertData={alertDataMock} /> - + ); const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; await waitFor(() => @@ -251,7 +237,7 @@ describe('When the add exception modal is opened', () => { file: { path: 'test/path' }, }; wrapper = mount( - + { onConfirm={jest.fn()} alertData={alertDataMock} /> - + ); const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; await waitFor(() => @@ -312,7 +298,7 @@ describe('When the add exception modal is opened', () => { file: { path: 'test/path' }, }; wrapper = mount( - + { onConfirm={jest.fn()} alertData={alertDataMock} /> - + ); const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; await waitFor(() => @@ -359,24 +345,26 @@ describe('When the add exception modal is opened', () => { describe('when there is bulk-closeable alert data passed to an endpoint list exception', () => { let wrapper: ReactWrapper; + const stubbed = [ + { name: 'file.path.caseless', type: 'string', aggregatable: true, searchable: true }, + { name: 'subject_name', type: 'string', aggregatable: true, searchable: true }, + { name: 'trusted', type: 'string', aggregatable: true, searchable: true }, + { name: 'file.hash.sha256', type: 'string', aggregatable: true, searchable: true }, + { name: 'event.code', type: 'string', aggregatable: true, searchable: true }, + ].map((item) => createIndexPatternFieldStub({ spec: item })); let callProps: { onChange: (props: { exceptionItems: ExceptionListItemSchema[] }) => void; exceptionListItems: ExceptionListItemSchema[]; }; beforeEach(async () => { + const stubbedIndexPattern = stubIndexPattern; // Mocks the index patterns to contain the pre-populated endpoint fields so that the exception qualifies as bulk closable mockUseFetchIndex.mockImplementation(() => [ false, { indexPatterns: { - ...stubIndexPattern, - fields: [ - { name: 'file.path.caseless', type: 'string' }, - { name: 'subject_name', type: 'string' }, - { name: 'trusted', type: 'string' }, - { name: 'file.hash.sha256', type: 'string' }, - { name: 'event.code', type: 'string' }, - ], + ...stubbedIndexPattern, + fields: stubbed, }, }, ]); @@ -385,25 +373,28 @@ describe('When the add exception modal is opened', () => { _id: 'test-id', file: { path: 'test/path' }, }; - wrapper = mount( - - - - ); - callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }) - ); + await act(async () => { + wrapper = mount( + + + + ); + }); + await waitFor(() => { + callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; + + return callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }); + }); }); - it('has the add exception button enabled', () => { + it('has the add exception button enabled', async () => { expect( wrapper.find('button[data-test-subj="add-exception-confirm-button"]').getDOMNode() ).not.toBeDisabled(); @@ -456,7 +447,7 @@ describe('When the add exception modal is opened', () => { test('when there are exception builder errors submit button is disabled', async () => { const wrapper = mount( - + { onCancel={jest.fn()} onConfirm={jest.fn()} /> - + ); const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; await waitFor(() => callProps.onChange({ exceptionItems: [], errorExists: true })); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_flyout/index.tsx index 0e063a05bab0c..ceaebb04e7be0 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_flyout/index.tsx @@ -35,6 +35,7 @@ import type { } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionsBuilderExceptionItem } from '@kbn/securitysolution-list-utils'; import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public'; +import { DataViewBase } from '@kbn/es-query'; import { hasEqlSequenceQuery, isEqlRule, @@ -72,6 +73,7 @@ export interface AddExceptionFlyoutProps { ruleId: string; exceptionListType: ExceptionListType; ruleIndices: string[]; + dataViewId?: string; alertData?: AlertData; /** * The components that use this may or may not define `alertData` @@ -127,6 +129,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ ruleName, ruleId, ruleIndices, + dataViewId, exceptionListType, alertData, isAlertDataLoading, @@ -135,7 +138,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ onRuleChange, alertStatus, }: AddExceptionFlyoutProps) { - const { http, unifiedSearch } = useKibana().services; + const { http, unifiedSearch, data } = useKibana().services; const [errorsExist, setErrorExists] = useState(false); const [comment, setComment] = useState(''); const { rule: maybeRule, loading: isRuleLoading } = useRuleAsync(ruleId); @@ -166,7 +169,21 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ } }, [jobs, ruleIndices]); - const [isIndexPatternLoading, { indexPatterns }] = useFetchIndex(memoRuleIndices); + const [isIndexPatternLoading, { indexPatterns: indexIndexPatterns }] = + useFetchIndex(memoRuleIndices); + + const [indexPattern, setIndexPattern] = useState(indexIndexPatterns); + + useEffect(() => { + const fetchSingleDataView = async () => { + if (dataViewId != null && dataViewId !== '') { + const dv = await data.dataViews.get(dataViewId); + setIndexPattern(dv); + } + }; + + fetchSingleDataView(); + }, [data.dataViews, dataViewId, setIndexPattern]); const handleBuilderOnChange = useCallback( ({ @@ -513,7 +530,8 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ listNamespaceType: ruleExceptionList.namespace_type, listTypeSpecificIndexPatternFilter: filterIndexPatterns, ruleName, - indexPatterns, + indexPatterns: + dataViewId != null && dataViewId !== '' ? indexPattern : indexIndexPatterns, isOrDisabled: isExceptionBuilderFormDisabled, isAndDisabled: isExceptionBuilderFormDisabled, isNestedDisabled: isExceptionBuilderFormDisabled, diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_flyout/index.tsx index 0097f2d02cb3e..78803b787f142 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_flyout/index.tsx @@ -32,6 +32,8 @@ import type { CreateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public'; +import { DataViewBase } from '@kbn/es-query'; + import { hasEqlSequenceQuery, isEqlRule, @@ -63,6 +65,7 @@ interface EditExceptionFlyoutProps { ruleName: string; ruleId: string; ruleIndices: string[]; + dataViewId?: string; exceptionItem: ExceptionListItemSchema; exceptionListType: ExceptionListType; onCancel: () => void; @@ -106,13 +109,14 @@ export const EditExceptionFlyout = memo(function EditExceptionFlyout({ ruleName, ruleId, ruleIndices, + dataViewId, exceptionItem, exceptionListType, onCancel, onConfirm, onRuleChange, }: EditExceptionFlyoutProps) { - const { http, unifiedSearch } = useKibana().services; + const { http, unifiedSearch, data } = useKibana().services; const [comment, setComment] = useState(''); const [errorsExist, setErrorExists] = useState(false); const { rule: maybeRule, loading: isRuleLoading } = useRuleAsync(ruleId); @@ -143,7 +147,20 @@ export const EditExceptionFlyout = memo(function EditExceptionFlyout({ } }, [jobs, ruleIndices]); - const [isIndexPatternLoading, { indexPatterns }] = useFetchIndex(memoRuleIndices); + const [isIndexPatternLoading, { indexPatterns: indexIndexPatterns }] = + useFetchIndex(memoRuleIndices); + const [indexPattern, setIndexPattern] = useState(indexIndexPatterns); + + useEffect(() => { + const fetchSingleDataView = async () => { + if (dataViewId != null && dataViewId !== '') { + const dv = await data.dataViews.get(dataViewId); + setIndexPattern(dv); + } + }; + + fetchSingleDataView(); + }, [data.dataViews, dataViewId, setIndexPattern]); const handleExceptionUpdateError = useCallback( (error: Error, statusCode: number | null, message: string | null) => { @@ -374,7 +391,7 @@ export const EditExceptionFlyout = memo(function EditExceptionFlyout({ dataTestSubj: 'edit-exception-builder', idAria: 'edit-exception-builder', onChange: handleBuilderOnChange, - indexPatterns, + indexPatterns: indexPattern, })} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx index 21c041df34136..63093c06a9450 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx @@ -56,6 +56,7 @@ interface ExceptionsViewerProps { ruleId: string; ruleName: string; ruleIndices: string[]; + dataViewId?: string; exceptionListsMeta: ExceptionListIdentifiers[]; availableListTypes: ExceptionListTypeEnum[]; commentsAccordionId: string; @@ -66,6 +67,7 @@ const ExceptionsViewerComponent = ({ ruleId, ruleName, ruleIndices, + dataViewId, exceptionListsMeta, availableListTypes, commentsAccordionId, @@ -341,6 +343,7 @@ const ExceptionsViewerComponent = ({ ruleName={ruleName} ruleId={ruleId} ruleIndices={ruleIndices} + dataViewId={dataViewId} exceptionListType={exceptionListTypeToEdit} exceptionItem={exceptionToEdit} onCancel={handleOnCancelExceptionModal} @@ -353,6 +356,7 @@ const ExceptionsViewerComponent = ({ ({ }), })); +const mockUseShowTimeline = jest.fn((): [boolean] => [false]); +jest.mock('../../../utils/timeline/use_show_timeline', () => ({ + useShowTimeline: () => mockUseShowTimeline(), +})); +const mockUseIsPolicySettingsBarVisible = jest.fn((): boolean => false); +jest.mock('../../../../management/pages/policy/view/policy_hooks', () => ({ + useIsPolicySettingsBarVisible: () => mockUseIsPolicySettingsBarVisible(), +})); + const renderNav = () => render(, { wrapper: TestProviders, @@ -178,4 +188,39 @@ describe('SecuritySideNav', () => { }) ); }); + + describe('bottom offset', () => { + it('should render with bottom offset when timeline bar visible', () => { + mockUseIsPolicySettingsBarVisible.mockReturnValueOnce(false); + mockUseShowTimeline.mockReturnValueOnce([true]); + renderNav(); + expect(mockSolutionGroupedNav).toHaveBeenCalledWith( + expect.objectContaining({ + bottomOffset: bottomNavOffset, + }) + ); + }); + + it('should render with bottom offset when policy settings bar visible', () => { + mockUseShowTimeline.mockReturnValueOnce([false]); + mockUseIsPolicySettingsBarVisible.mockReturnValueOnce(true); + renderNav(); + expect(mockSolutionGroupedNav).toHaveBeenCalledWith( + expect.objectContaining({ + bottomOffset: bottomNavOffset, + }) + ); + }); + + it('should not render with bottom offset when not needed', () => { + mockUseShowTimeline.mockReturnValueOnce([false]); + mockUseIsPolicySettingsBarVisible.mockReturnValueOnce(false); + renderNav(); + expect(mockSolutionGroupedNav).toHaveBeenCalledWith( + expect.not.objectContaining({ + bottomOffset: bottomNavOffset, + }) + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx index dc00396b4e209..8db77bae34a80 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx @@ -16,6 +16,9 @@ import { SolutionGroupedNav } from '../solution_grouped_nav'; import { CustomSideNavItem, DefaultSideNavItem, SideNavItem } from '../solution_grouped_nav/types'; import { NavLinkItem } from '../types'; import { EuiIconLaunch } from './icons/launch'; +import { useShowTimeline } from '../../../utils/timeline/use_show_timeline'; +import { useIsPolicySettingsBarVisible } from '../../../../management/pages/policy/view/policy_hooks'; +import { bottomNavOffset } from '../../../lib/helpers'; const isFooterNavItem = (id: SecurityPageName) => id === SecurityPageName.landing || id === SecurityPageName.administration; @@ -142,9 +145,21 @@ export const SecuritySideNav: React.FC = () => { const [items, footerItems] = useSideNavItems(); const selectedId = useSelectedId(); + const isPolicySettingsVisible = useIsPolicySettingsBarVisible(); + const [isTimelineBottomBarVisible] = useShowTimeline(); + const bottomOffset = + isTimelineBottomBarVisible || isPolicySettingsVisible ? bottomNavOffset : undefined; + if (items.length === 0 && footerItems.length === 0) { return ; } - return ; + return ( + + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.test.tsx index 5f5fd14605643..8ef060fb3ba90 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.test.tsx @@ -12,11 +12,6 @@ import { TestProviders } from '../../../mock'; import { SolutionGroupedNav, SolutionGroupedNavProps } from './solution_grouped_nav'; import { SideNavItem } from './types'; -const mockUseShowTimeline = jest.fn((): [boolean] => [false]); -jest.mock('../../../utils/timeline/use_show_timeline', () => ({ - useShowTimeline: () => mockUseShowTimeline(), -})); - const mockItems: SideNavItem[] = [ { id: SecurityPageName.dashboardsLanding, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.tsx index 073723b80f518..8aecccc521416 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.tsx @@ -25,6 +25,7 @@ export interface SolutionGroupedNavProps { items: SideNavItem[]; selectedId: string; footerItems?: SideNavItem[]; + bottomOffset?: string; } export interface SolutionNavItemsProps { items: SideNavItem[]; @@ -52,6 +53,7 @@ export const SolutionGroupedNavComponent: React.FC = ({ items, selectedId, footerItems = [], + bottomOffset, }) => { const isMobileSize = useIsWithinBreakpoints(['xs', 's']); @@ -108,9 +110,10 @@ export const SolutionGroupedNavComponent: React.FC = ({ items={panelItems} title={title} categories={categories} + bottomOffset={bottomOffset} /> ); - }, [activePanelNavId, navItemsById, onClosePanelNav, onOutsidePanelClick]); + }, [activePanelNavId, bottomOffset, navItemsById, onClosePanelNav, onOutsidePanelClick]); return ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.styles.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.styles.tsx index 069c146ca0719..c237043e5c4c2 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.styles.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.styles.tsx @@ -7,7 +7,7 @@ import { EuiPanel } from '@elastic/eui'; import styled from 'styled-components'; -export const EuiPanelStyled = styled(EuiPanel)<{ $hasBottomBar: boolean }>` +export const EuiPanelStyled = styled(EuiPanel)<{ $bottomOffset?: string }>` position: fixed; top: 95px; left: 247px; @@ -16,11 +16,11 @@ export const EuiPanelStyled = styled(EuiPanel)<{ $hasBottomBar: boolean }>` height: inherit; // If the bottom bar is visible add padding to the navigation - ${({ $hasBottomBar, theme }) => - $hasBottomBar && + ${({ $bottomOffset, theme }) => + $bottomOffset != null && ` height: inherit; - bottom: 51px; + bottom: ${$bottomOffset}; box-shadow: // left -${theme.eui.euiSizeS} 0 ${theme.eui.euiSizeS} -${theme.eui.euiSizeS} rgb(0 0 0 / 15%), diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.test.tsx index 8215d9c0b9f40..3162df787af1c 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.test.tsx @@ -11,11 +11,16 @@ import { SecurityPageName } from '../../../../app/types'; import { TestProviders } from '../../../mock'; import { SolutionNavPanel, SolutionNavPanelProps } from './solution_grouped_nav_panel'; import { DefaultSideNavItem } from './types'; +import { bottomNavOffset } from '../../../lib/helpers'; -const mockUseShowTimeline = jest.fn((): [boolean] => [false]); -jest.mock('../../../utils/timeline/use_show_timeline', () => ({ - useShowTimeline: () => mockUseShowTimeline(), -})); +const mockUseIsWithinBreakpoints = jest.fn(() => true); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + useIsWithinBreakpoints: () => mockUseIsWithinBreakpoints(), + }; +}); const mockItems: DefaultSideNavItem[] = [ { @@ -100,6 +105,22 @@ describe('SolutionGroupedNav', () => { }); }); + describe('bottom offset', () => { + it('should add bottom offset', () => { + mockUseIsWithinBreakpoints.mockReturnValueOnce(true); + const result = renderNavPanel({ bottomOffset: bottomNavOffset }); + + expect(result.getByTestId('groupedNavPanel')).toHaveStyle({ bottom: bottomNavOffset }); + }); + + it('should not add bottom offset if not large screen', () => { + mockUseIsWithinBreakpoints.mockReturnValueOnce(false); + const result = renderNavPanel({ bottomOffset: bottomNavOffset }); + + expect(result.getByTestId('groupedNavPanel')).not.toHaveStyle({ bottom: bottomNavOffset }); + }); + }); + describe('close', () => { it('should call onClose callback if link clicked', () => { const result = renderNavPanel(); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx index a418f666d2782..4c0ccc6116703 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx @@ -24,7 +24,6 @@ import { } from '@elastic/eui'; import classNames from 'classnames'; import { EuiPanelStyled } from './solution_grouped_nav_panel.styles'; -import { useShowTimeline } from '../../../utils/timeline/use_show_timeline'; import type { DefaultSideNavItem } from './types'; import type { LinkCategories } from '../../../links/types'; @@ -34,6 +33,7 @@ export interface SolutionNavPanelProps { title: string; items: DefaultSideNavItem[]; categories?: LinkCategories; + bottomOffset?: string; } export interface SolutionNavPanelCategoriesProps { categories: LinkCategories; @@ -58,12 +58,14 @@ const SolutionNavPanelComponent: React.FC = ({ title, categories, items, + bottomOffset, }) => { - const [hasTimelineBar] = useShowTimeline(); const isLargerBreakpoint = useIsWithinBreakpoints(['l', 'xl']); - const isTimelineVisible = hasTimelineBar && isLargerBreakpoint; const panelClasses = classNames('eui-yScroll'); + // Only larger breakpoint needs to add bottom offset, other sizes should have full height + const bottomOffsetLargerBreakpoint = isLargerBreakpoint ? bottomOffset : undefined; + // ESC key closes PanelNav const onKeyDown = useCallback( (ev: KeyboardEvent) => { @@ -82,8 +84,8 @@ const SolutionNavPanelComponent: React.FC = ({ { // FLAKY: https://github.com/elastic/kibana/issues/132659 describe.skip('#onQuerySubmit', () => { test(' is the only reference that changed when filterQuery props get updated', async () => { - const wrapper = await getWrapper( - - ); - const searchBarProps = wrapper.find(SearchBar).props(); - const onChangedQueryRef = searchBarProps.onQueryChange; - const onSubmitQueryRef = searchBarProps.onQuerySubmit; - const onSavedQueryRef = searchBarProps.onSavedQueryUpdated; + await act(async () => { + const wrapper = await getWrapper( + + ); + const searchBarProps = wrapper.find(SearchBar).props(); + const onChangedQueryRef = searchBarProps.onQueryChange; + const onSubmitQueryRef = searchBarProps.onQuerySubmit; + const onSavedQueryRef = searchBarProps.onSavedQueryUpdated; - wrapper.setProps({ filterQuery: { expression: 'new: one', kind: 'kuery' } }); - wrapper.update(); + wrapper.setProps({ filterQuery: { expression: 'new: one', kind: 'kuery' } }); + wrapper.update(); - expect(onSubmitQueryRef).not.toEqual(wrapper.find(SearchBar).props().onQuerySubmit); - expect(onChangedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onQueryChange); - expect(onSavedQueryRef).toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated); + expect(onSubmitQueryRef).not.toEqual(wrapper.find(SearchBar).props().onQuerySubmit); + expect(onChangedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onQueryChange); + expect(onSavedQueryRef).toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated); + }); }); test(' is only reference that changed when timelineId props get updated', async () => { @@ -278,68 +280,74 @@ describe('QueryBar ', () => { }); test('is only reference that changed when dataProviders props get updated', async () => { - const wrapper = await getWrapper( - - ); - const searchBarProps = wrapper.find(SearchBar).props(); - const onChangedQueryRef = searchBarProps.onQueryChange; - const onSubmitQueryRef = searchBarProps.onQuerySubmit; - const onSavedQueryRef = searchBarProps.onSavedQueryUpdated; - wrapper.setProps({ onSavedQuery: jest.fn() }); - wrapper.update(); + await act(async () => { + const wrapper = await getWrapper( + + ); + const searchBarProps = wrapper.find(SearchBar).props(); + const onChangedQueryRef = searchBarProps.onQueryChange; + const onSubmitQueryRef = searchBarProps.onQuerySubmit; + const onSavedQueryRef = searchBarProps.onSavedQueryUpdated; + wrapper.setProps({ onSavedQuery: jest.fn() }); + wrapper.update(); - expect(onSavedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated); - expect(onChangedQueryRef).toEqual(wrapper.find(SearchBar).props().onQueryChange); - expect(onSubmitQueryRef).toEqual(wrapper.find(SearchBar).props().onQuerySubmit); + expect(onSavedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated); + expect(onChangedQueryRef).toEqual(wrapper.find(SearchBar).props().onQueryChange); + expect(onSubmitQueryRef).toEqual(wrapper.find(SearchBar).props().onQuerySubmit); + }); }); }); describe('SavedQueryManagementComponent state', () => { test('popover should remain open when "Save current query" button was clicked', async () => { - const wrapper = await getWrapper( - - ); - const isSavedQueryPopoverOpen = () => - wrapper.find('EuiPopover[data-test-subj="queryBarMenuPopover"]').prop('isOpen'); + await act(async () => { + const wrapper = await getWrapper( + + ); + const isSavedQueryPopoverOpen = () => + wrapper.find('EuiPopover[data-test-subj="queryBarMenuPopover"]').prop('isOpen'); - expect(isSavedQueryPopoverOpen()).toBeFalsy(); + expect(isSavedQueryPopoverOpen()).toBeFalsy(); - wrapper.find('button[data-test-subj="showQueryBarMenu"]').simulate('click'); + wrapper.find('button[data-test-subj="showQueryBarMenu"]').simulate('click'); - await waitFor(() => { - expect(isSavedQueryPopoverOpen()).toBeTruthy(); - }); - wrapper.find('button[data-test-subj="saved-query-management-save-button"]').simulate('click'); + await waitFor(() => { + expect(isSavedQueryPopoverOpen()).toBeTruthy(); + }); + wrapper + .find('button[data-test-subj="saved-query-management-save-button"]') + .simulate('click'); - await waitFor(() => { - expect(isSavedQueryPopoverOpen()).toBeTruthy(); + await waitFor(() => { + expect(isSavedQueryPopoverOpen()).toBeTruthy(); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts index e29ebad739960..68a20d82e95ab 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts @@ -12,6 +12,8 @@ import { EqlSearchStrategyResponse, EQL_SEARCH_STRATEGY, } from '@kbn/data-plugin/common'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + import { getValidationErrors, isErrorResponse, @@ -19,22 +21,27 @@ import { } from '../../../../common/search_strategy/eql'; interface Params { - index: string[]; + dataViewTitle: string; query: string; data: DataPublicPluginStart; signal: AbortSignal; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } export const validateEql = async ({ data, - index, + dataViewTitle, query, signal, + runtimeMappings, }: Params): Promise<{ valid: boolean; errors: string[] }> => { const { rawResponse: response } = await firstValueFrom( data.search.search( { - params: { index: index.join(), body: { query, size: 0 } }, + params: { + index: dataViewTitle, + body: { query, runtime_mappings: runtimeMappings, size: 0 }, + }, options: { ignore: [400] }, }, { diff --git a/x-pack/plugins/security_solution/public/common/lib/helpers/index.tsx b/x-pack/plugins/security_solution/public/common/lib/helpers/index.tsx index 42417df1b3677..e233d397b1f70 100644 --- a/x-pack/plugins/security_solution/public/common/lib/helpers/index.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/helpers/index.tsx @@ -30,3 +30,4 @@ export type ValueOf = T[keyof T]; */ export const gutterTimeline = '70px'; // Michael: Temporary until timeline is moved. +export const bottomNavOffset = '51px'; diff --git a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx index 9968785e884fa..f19e98020fce8 100644 --- a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx @@ -29,12 +29,33 @@ const mockUseSourcererDataView = jest.fn( jest.mock('../../containers/sourcerer', () => ({ useSourcererDataView: () => mockUseSourcererDataView(), })); -const mockedUseIsGroupedNavigationEnabled = jest.fn(); +const mockedUseIsGroupedNavigationEnabled = jest.fn(); jest.mock('../../components/navigation/helpers', () => ({ useIsGroupedNavigationEnabled: () => mockedUseIsGroupedNavigationEnabled(), })); +const mockSiemUserCanRead = jest.fn(() => true); +jest.mock('../../lib/kibana', () => { + const original = jest.requireActual('../../lib/kibana'); + + return { + ...original, + useKibana: () => ({ + services: { + ...original.useKibana().services, + application: { + capabilities: { + siem: { + show: mockSiemUserCanRead(), + }, + }, + }, + }, + }), + }; +}); + describe('use show timeline', () => { beforeAll(() => { // initialize all App links before running test @@ -157,4 +178,18 @@ describe('use show timeline', () => { expect(result.current).toEqual([false]); }); }); + + describe('Security solution capabilities', () => { + it('should show timeline when user has read capabilities', () => { + mockSiemUserCanRead.mockReturnValueOnce(true); + const { result } = renderHook(() => useShowTimeline()); + expect(result.current).toEqual([true]); + }); + + it('should not show timeline when user does not have read capabilities', () => { + mockSiemUserCanRead.mockReturnValueOnce(false); + const { result } = renderHook(() => useShowTimeline()); + expect(result.current).toEqual([false]); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx index 041e36f52a3db..25d175f46aad3 100644 --- a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx @@ -5,13 +5,14 @@ * 2.0. */ -import { useState, useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { matchPath, useLocation } from 'react-router-dom'; import { getLinksWithHiddenTimeline } from '../../links'; import { useIsGroupedNavigationEnabled } from '../../components/navigation/helpers'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useSourcererDataView } from '../../containers/sourcerer'; +import { useKibana } from '../../lib/kibana'; const DEPRECATED_HIDDEN_TIMELINE_ROUTES: readonly string[] = [ `/cases/configure`, @@ -23,33 +24,36 @@ const DEPRECATED_HIDDEN_TIMELINE_ROUTES: readonly string[] = [ '/manage', ]; -const isTimelineHidden = (currentPath: string, isGroupedNavigationEnabled: boolean): boolean => { +const isTimelinePathVisible = ( + currentPath: string, + isGroupedNavigationEnabled: boolean +): boolean => { const groupLinksWithHiddenTimelinePaths = getLinksWithHiddenTimeline().map((l) => l.path); const hiddenTimelineRoutes = isGroupedNavigationEnabled ? groupLinksWithHiddenTimelinePaths : DEPRECATED_HIDDEN_TIMELINE_ROUTES; - return !!hiddenTimelineRoutes.find((route) => matchPath(currentPath, route)); + return !hiddenTimelineRoutes.find((route) => matchPath(currentPath, route)); }; export const useShowTimeline = () => { const isGroupedNavigationEnabled = useIsGroupedNavigationEnabled(); const { pathname } = useLocation(); const { indicesExist, dataViewId } = useSourcererDataView(SourcererScopeName.timeline); + const userHasSecuritySolutionVisible = useKibana().services.application.capabilities.siem.show; - const [isTimelinePath, setIsTimelinePath] = useState( - !isTimelineHidden(pathname, isGroupedNavigationEnabled) + const isTimelineAllowed = useMemo( + () => userHasSecuritySolutionVisible && (indicesExist || dataViewId === null), + [indicesExist, dataViewId, userHasSecuritySolutionVisible] ); - useEffect(() => { - setIsTimelinePath(!isTimelineHidden(pathname, isGroupedNavigationEnabled)); - }, [pathname, isGroupedNavigationEnabled]); - - const showTimeline = useMemo( - () => isTimelinePath && (dataViewId === null || indicesExist), - [isTimelinePath, indicesExist, dataViewId] - ); + const showTimeline = useMemo(() => { + if (!isTimelineAllowed) { + return false; + } + return isTimelinePathVisible(pathname, isGroupedNavigationEnabled); + }, [isTimelineAllowed, pathname, isGroupedNavigationEnabled]); return [showTimeline]; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/data_view_selector/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/data_view_selector/index.test.tsx new file mode 100644 index 0000000000000..8e547084cbd13 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/data_view_selector/index.test.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { DataViewSelector } from '.'; +import { useFormFieldMock } from '../../../../common/mock'; + +jest.mock('../../../../common/lib/kibana'); + +describe('data_view_selector', () => { + it('renders correctly', () => { + const Component = () => { + const field = useFormFieldMock({ value: '' }); + // @ts-expect-error TODO: FIX THIS + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('[data-test-subj="pick-rule-data-source"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/data_view_selector/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/data_view_selector/index.tsx new file mode 100644 index 0000000000000..c4c11d1d549ed --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/data_view_selector/index.tsx @@ -0,0 +1,145 @@ +/* + * 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, { useMemo, useState, useEffect } from 'react'; + +import { + EuiCallOut, + EuiComboBox, + EuiComboBoxOptionOption, + EuiFormRow, + EuiSpacer, +} from '@elastic/eui'; + +import { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataViewBase } from '@kbn/es-query'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports'; +import * as i18n from './translations'; +import { useKibana } from '../../../../common/lib/kibana'; +import { DefineStepRule } from '../../../pages/detection_engine/rules/types'; + +interface DataViewSelectorProps { + kibanaDataViews: { [x: string]: DataViewListItem }; + field: FieldHook; + setIndexPattern: (indexPattern: DataViewBase) => void; +} + +export const DataViewSelector = ({ + kibanaDataViews, + field, + setIndexPattern, +}: DataViewSelectorProps) => { + const { data } = useKibana().services; + + let isInvalid; + let errorMessage; + let dataViewId: string | null | undefined; + if (field != null) { + const fieldAndError = getFieldValidityAndErrorMessage(field); + isInvalid = fieldAndError.isInvalid; + errorMessage = fieldAndError.errorMessage; + dataViewId = field.value; + } + + const kibanaDataViewsDefined = useMemo( + () => kibanaDataViews != null && Object.keys(kibanaDataViews).length > 0, + [kibanaDataViews] + ); + + // Most likely case here is that a data view of an existing rule was deleted + // and can no longer be found + const selectedDataViewNotFound = useMemo( + () => + dataViewId != null && + dataViewId !== '' && + kibanaDataViewsDefined && + !Object.hasOwn(kibanaDataViews, dataViewId), + [kibanaDataViewsDefined, dataViewId, kibanaDataViews] + ); + const [selectedOption, setSelectedOption] = useState>>( + !selectedDataViewNotFound && dataViewId != null && dataViewId !== '' + ? [{ id: kibanaDataViews[dataViewId].id, label: kibanaDataViews[dataViewId].title }] + : [] + ); + + const [selectedDataView, setSelectedDataView] = useState(); + + // TODO: optimize this, pass down array of data view ids + // at the same time we grab the data views in the top level form component + const dataViewOptions = useMemo(() => { + return kibanaDataViewsDefined + ? Object.values(kibanaDataViews).map((dv) => ({ + label: dv.title, + id: dv.id, + })) + : []; + }, [kibanaDataViewsDefined, kibanaDataViews]); + + useEffect(() => { + const fetchSingleDataView = async () => { + if (selectedDataView != null) { + const dv = await data.dataViews.get(selectedDataView.id); + setIndexPattern(dv); + } + }; + + fetchSingleDataView(); + }, [data.dataViews, selectedDataView, setIndexPattern]); + + const onChangeDataViews = (options: Array>) => { + const selectedDataViewOption = options; + + setSelectedOption(selectedDataViewOption ?? []); + + if ( + selectedDataViewOption != null && + selectedDataViewOption.length > 0 && + selectedDataViewOption[0].id != null + ) { + setSelectedDataView(kibanaDataViews[selectedDataViewOption[0].id]); + field?.setValue(selectedDataViewOption[0].id); + } else { + setSelectedDataView(undefined); + field?.setValue(undefined); + } + }; + + return ( + <> + {selectedDataViewNotFound && dataViewId != null && ( + <> + +

{i18n.DATA_VIEW_NOT_FOUND_WARNING_DESCRIPTION(dataViewId)}

+
+ + + )} + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/data_view_selector/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/data_view_selector/translations.tsx new file mode 100644 index 0000000000000..a52ba9c4f7dd7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/data_view_selector/translations.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const PICK_INDEX_PATTERNS = i18n.translate( + 'xpack.securitySolution.detectionEngine.stepDefineRule.pickDataView', + { + defaultMessage: 'Select a Data View', + } +); + +export const DATA_VIEW_NOT_FOUND_WARNING_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.stepDefineRule.dataViewNotFoundLabel', + { + defaultMessage: 'Selected data view not found', + } +); + +export const DATA_VIEW_NOT_FOUND_WARNING_DESCRIPTION = (dataView: string) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.stepDefineRule.dataViewNotFoundDescription', + { + values: { dataView }, + defaultMessage: + 'Your data view of "id": "{dataView}" was not found. It could be that it has since been deleted.', + } + ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index df5fcb8d96ba7..ba112a596b653 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -238,6 +238,8 @@ export const getDescriptionItem = ( } else if (field === 'threatMapping') { const threatMap: ThreatMapping = get(field, data); return buildThreatMappingDescription(label, threatMap); + } else if (field === 'dataViewId') { + return []; } else if (Array.isArray(get(field, data)) && field !== 'threatMapping') { const values: string[] = get(field, data); return buildStringArrayDescription(label, field, values); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts index 249d26e991894..6de4a8ced764f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts @@ -57,7 +57,7 @@ export const eqlValidator = async ( const [{ value, formData }] = args; const { query: queryValue } = value as FieldValueQueryBar; const query = queryValue.query as string; - const { index, ruleType } = formData as DefineStepRule; + const { dataViewId, index, ruleType } = formData as DefineStepRule; const needsValidation = (ruleType === undefined && !isEmpty(query)) || (isEqlRule(ruleType) && !isEmpty(query)); @@ -67,8 +67,17 @@ export const eqlValidator = async ( try { const { data } = KibanaServices.get(); + let dataViewTitle = index?.join(); + let runtimeMappings = {}; + if (dataViewId != null) { + const dataView = await data.dataViews.get(dataViewId); + + dataViewTitle = dataView.title; + runtimeMappings = dataView.getRuntimeMappings(); + } + const signal = new AbortController().signal; - const response = await validateEql({ data, query, signal, index }); + const response = await validateEql({ data, query, signal, dataViewTitle, runtimeMappings }); if (response?.valid === false) { return { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.test.ts index 8621201a399e7..b3ceb363ba18f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.test.ts @@ -70,6 +70,7 @@ describe('query_preview/helpers', () => { isQueryBarValid: true, isThreatQueryBarValid: true, index: [], + dataViewId: undefined, threatIndex: ['threat-*'], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -86,6 +87,7 @@ describe('query_preview/helpers', () => { isQueryBarValid: false, isThreatQueryBarValid: true, index: ['test-*'], + dataViewId: undefined, threatIndex: ['threat-*'], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -102,6 +104,7 @@ describe('query_preview/helpers', () => { isQueryBarValid: true, isThreatQueryBarValid: false, index: ['test-*'], + dataViewId: undefined, threatIndex: ['threat-*'], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -118,6 +121,7 @@ describe('query_preview/helpers', () => { isQueryBarValid: true, isThreatQueryBarValid: true, index: ['test-*'], + dataViewId: undefined, threatIndex: [], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -134,6 +138,7 @@ describe('query_preview/helpers', () => { isQueryBarValid: true, isThreatQueryBarValid: true, index: ['test-*'], + dataViewId: undefined, threatIndex: ['threat-*'], threatMapping: [], machineLearningJobId: ['test-ml-job-id'], @@ -148,6 +153,7 @@ describe('query_preview/helpers', () => { isQueryBarValid: true, isThreatQueryBarValid: true, index: ['test-*'], + dataViewId: undefined, threatIndex: ['threat-*'], threatMapping: [], machineLearningJobId: [], @@ -162,6 +168,7 @@ describe('query_preview/helpers', () => { isQueryBarValid: true, isThreatQueryBarValid: true, index: ['test-*'], + dataViewId: undefined, threatIndex: ['threat-*'], threatMapping: [], machineLearningJobId: [], @@ -176,6 +183,7 @@ describe('query_preview/helpers', () => { isQueryBarValid: true, isThreatQueryBarValid: true, index: ['test-*'], + dataViewId: undefined, threatIndex: ['threat-*'], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -192,6 +200,7 @@ describe('query_preview/helpers', () => { isQueryBarValid: true, isThreatQueryBarValid: true, index: ['test-*'], + dataViewId: undefined, threatIndex: ['threat-*'], threatMapping: [], machineLearningJobId: [], diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.ts index a2c4d2b14cdc9..29587298b454e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.ts @@ -206,6 +206,7 @@ export const getIsRulePreviewDisabled = ({ isQueryBarValid, isThreatQueryBarValid, index, + dataViewId, threatIndex, threatMapping, machineLearningJobId, @@ -215,12 +216,14 @@ export const getIsRulePreviewDisabled = ({ isQueryBarValid: boolean; isThreatQueryBarValid: boolean; index: string[]; + dataViewId: string | undefined; threatIndex: string[]; threatMapping: ThreatMapping; machineLearningJobId: string[]; queryBar: FieldValueQueryBar; }) => { - if (!isQueryBarValid || index.length === 0) return true; + if (!isQueryBarValid || ((index == null || index.length === 0) && dataViewId == null)) + return true; if (ruleType === 'threat_match') { if (!isThreatQueryBarValid || !threatIndex.length || !threatMapping) return true; if ( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx index 20ed47fffda2f..8a4bd38fea039 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx @@ -43,6 +43,7 @@ export interface RulePreviewProps { index: string[]; isDisabled: boolean; query: FieldValueQueryBar; + dataViewId?: string; ruleType: Type; threatIndex: string[]; threatMapping: ThreatMapping; @@ -65,6 +66,7 @@ const defaultTimeRange: Unit = 'h'; const RulePreviewComponent: React.FC = ({ index, + dataViewId, isDisabled, query, ruleType, @@ -108,6 +110,7 @@ const RulePreviewComponent: React.FC = ({ } = usePreviewRoute({ index, isDisabled, + dataViewId, query, threatIndex, threatQuery, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx index d65fd91e6b891..881e86d27923b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx @@ -37,7 +37,7 @@ export const usePreviewHistogram = ({ config: getEsQueryConfig(uiSettings), indexPattern: { fields: [], - title: index.join(), + title: index == null ? '' : index.join(), }, queries: [{ query: `kibana.alert.rule.uuid:${previewId}`, language: 'kuery' }], filters: [], diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx index fde527e49a46f..b055597cacffe 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx @@ -18,6 +18,7 @@ import { EqlOptionsSelected } from '../../../../../common/search_strategy'; interface PreviewRouteParams { isDisabled: boolean; index: string[]; + dataViewId?: string; threatIndex: string[]; query: FieldValueQueryBar; threatQuery: FieldValueQueryBar; @@ -32,6 +33,7 @@ interface PreviewRouteParams { export const usePreviewRoute = ({ index, + dataViewId, isDisabled, query, threatIndex, @@ -91,6 +93,7 @@ export const usePreviewRoute = ({ setRule( formatPreviewRule({ index, + dataViewId, query, ruleType, threatIndex, @@ -106,6 +109,7 @@ export const usePreviewRoute = ({ } }, [ index, + dataViewId, isRequestTriggered, query, rule, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx index 02600b80c3b66..cbd5c070198ec 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx @@ -31,6 +31,7 @@ const mockTheme = getMockTheme({ }, }); +jest.mock('../../../../common/lib/kibana'); jest.mock('../../../../common/containers/source'); jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index 5f5b636d6afe1..b8cc1077001a2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -9,6 +9,7 @@ import { EuiAccordion, EuiFlexItem, EuiSpacer, EuiFormRow } from '@elastic/eui'; import React, { FC, memo, useCallback, useEffect, useState, useMemo } from 'react'; import styled from 'styled-components'; +import { DataViewBase } from '@kbn/es-query'; import { RuleStepProps, RuleStep, @@ -42,6 +43,7 @@ import { AutocompleteField } from '../autocomplete_field'; import { useFetchIndex } from '../../../../common/containers/source'; import { isThreatMatchRule } from '../../../../../common/detection_engine/utils'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; +import { useKibana } from '../../../../common/lib/kibana'; const CommonUseField = getUseField({ component: Field }); @@ -73,6 +75,8 @@ const StepAboutRuleComponent: FC = ({ onSubmit, setForm, }) => { + const { data } = useKibana().services; + const isThreatMatchRuleValue = useMemo( () => isThreatMatchRule(defineRuleData?.ruleType), [defineRuleData?.ruleType] @@ -96,7 +100,38 @@ const StepAboutRuleComponent: FC = ({ ); const [severityValue, setSeverityValue] = useState(initialState.severity.value); - const [indexPatternLoading, { indexPatterns }] = useFetchIndex(defineRuleData?.index ?? []); + + /** + * 1. if not null, fetch data view from id saved on rule form + * 2. Create a state to set the indexPattern to be used + * 3. useEffect if indexIndexPattern is updated and dataView from rule form is empty + */ + + const [indexPatternLoading, { indexPatterns: indexIndexPattern }] = useFetchIndex( + defineRuleData?.index ?? [] + ); + + const [indexPattern, setIndexPattern] = useState(indexIndexPattern); + + useEffect(() => { + if ( + defineRuleData?.index != null && + (defineRuleData?.dataViewId === '' || defineRuleData?.dataViewId == null) + ) { + setIndexPattern(indexIndexPattern); + } + }, [defineRuleData?.dataViewId, defineRuleData?.index, indexIndexPattern]); + + useEffect(() => { + const fetchSingleDataView = async () => { + if (defineRuleData?.dataViewId != null && defineRuleData?.dataViewId !== '') { + const dv = await data.dataViews.get(defineRuleData?.dataViewId); + setIndexPattern(dv); + } + }; + + fetchSingleDataView(); + }, [data.dataViews, defineRuleData, indexIndexPattern, setIndexPattern]); const { form } = useForm({ defaultValue: initialState, @@ -190,7 +225,7 @@ const StepAboutRuleComponent: FC = ({ idAria: 'detectionEngineStepAboutRuleSeverityField', isDisabled: isLoading || indexPatternLoading, options: severityOptions, - indices: indexPatterns, + indices: indexPattern, }} />
@@ -203,7 +238,7 @@ const StepAboutRuleComponent: FC = ({ dataTestSubj: 'detectionEngineStepAboutRuleRiskScore', idAria: 'detectionEngineStepAboutRuleRiskScore', isDisabled: isLoading || indexPatternLoading, - indices: indexPatterns, + indices: indexPattern, }} /> @@ -345,7 +380,7 @@ const StepAboutRuleComponent: FC = ({ dataTestSubj: 'detectionEngineStepAboutRuleRuleNameOverride', fieldType: 'string', idAria: 'detectionEngineStepAboutRuleRuleNameOverride', - indices: indexPatterns, + indices: indexPattern, isDisabled: isLoading || indexPatternLoading, placeholder: '', }} @@ -358,7 +393,7 @@ const StepAboutRuleComponent: FC = ({ dataTestSubj: 'detectionEngineStepAboutRuleTimestampOverride', fieldType: 'date', idAria: 'detectionEngineStepAboutRuleTimestampOverride', - indices: indexPatterns, + indices: indexPattern, isDisabled: isLoading || indexPatternLoading, placeholder: '', }} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.test.tsx index 6c19a49c7f491..2236e8513deec 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.test.tsx @@ -12,6 +12,29 @@ import { StepDefineRule, aggregatableFields } from '.'; import mockBrowserFields from './mock_browser_fields.json'; jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../common/hooks/use_selector', () => { + const actual = jest.requireActual('../../../../common/hooks/use_selector'); + return { + ...actual, + useDeepEqualSelector: () => ({ + kibanaDataViews: [{ id: 'world' }], + sourcererScope: 'my-selected-dataview-id', + }), + }; +}); +jest.mock('../../../../common/containers/sourcerer', () => { + const actual = jest.requireActual('../../../../common/containers/sourcerer'); + return { + ...actual, + useSourcererDataView: jest + .fn() + .mockReturnValue({ indexPattern: ['fakeindex'], loading: false }), + }; +}); +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '/alerts' }) }; +}); test('aggregatableFields', function () { expect(aggregatableFields(mockBrowserFields)).toMatchSnapshot(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 20a34c0b75a3a..40d76b43b5c0f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -5,13 +5,26 @@ * 2.0. */ -import { EuiButtonEmpty, EuiFormRow, EuiSpacer } from '@elastic/eui'; -import React, { FC, memo, useCallback, useMemo, useState, useEffect } from 'react'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + EuiButtonGroup, + EuiButtonGroupOptionProps, + EuiText, +} from '@elastic/eui'; +import React, { FC, memo, useCallback, useState, useEffect, useMemo } from 'react'; + import styled from 'styled-components'; +import { i18n as i18nCore } from '@kbn/i18n'; import { isEqual, isEmpty } from 'lodash'; import { FieldSpec } from '@kbn/data-views-plugin/common'; import usePrevious from 'react-use/lib/usePrevious'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; +import { FormattedMessage } from '@kbn/i18n-react'; import { DEFAULT_INDEX_KEY, DEFAULT_THREAT_INDEX_KEY, @@ -56,13 +69,29 @@ import { isThresholdRule, } from '../../../../../common/detection_engine/utils'; import { EqlQueryBar } from '../eql_query_bar'; +import { DataViewSelector } from '../data_view_selector'; import { ThreatMatchInput } from '../threatmatch_input'; import { BrowserField, BrowserFields, useFetchIndex } from '../../../../common/containers/source'; import { RulePreview } from '../rule_preview'; import { getIsRulePreviewDisabled } from '../rule_preview/helpers'; +import { DocLink } from '../../../../common/components/links_to_docs/doc_link'; + +const DATA_VIEW_SELECT_ID = 'dataView'; +const INDEX_PATTERN_SELECT_ID = 'indexPatterns'; const CommonUseField = getUseField({ component: Field }); +const StyledButtonGroup = styled(EuiButtonGroup)` + display: flex; + justify-content: right; + .euiButtonGroupButton { + padding-right: ${(props) => props.theme.eui.paddingSizes.l}; + } +`; + +const StyledFlexGroup = styled(EuiFlexGroup)` + margin-bottom: -21px; +`; interface StepDefineRuleProps extends RuleStepProps { defaultValues?: DefineStepRule; } @@ -146,11 +175,13 @@ const StepDefineRuleComponent: FC = ({ isUpdateView = false, onSubmit, setForm, + kibanaDataViews, }) => { const mlCapabilities = useMlCapabilities(); const [openTimelineSearch, setOpenTimelineSearch] = useState(false); const [indexModified, setIndexModified] = useState(false); const [threatIndexModified, setThreatIndexModified] = useState(false); + const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); const [threatIndicesConfig] = useUiSetting$(DEFAULT_THREAT_INDEX_KEY); const initialState = defaultValues ?? { @@ -158,17 +189,20 @@ const StepDefineRuleComponent: FC = ({ index: indicesConfig, threatIndex: threatIndicesConfig, }; + const { form } = useForm({ defaultValue: initialState, options: { stripEmptyFields: false }, schema, }); + const { getFields, getFormData, reset, submit } = form; const [ { index: formIndex, ruleType: formRuleType, queryBar: formQuery, + dataViewId: formDataViewId, threatIndex: formThreatIndex, threatQueryBar: formThreatQuery, threshold: formThreshold, @@ -183,6 +217,7 @@ const StepDefineRuleComponent: FC = ({ 'ruleType', 'queryBar', 'threshold', + 'dataViewId', 'threshold.field', 'threshold.value', 'threshold.cardinality.field', @@ -197,16 +232,43 @@ const StepDefineRuleComponent: FC = ({ const [isQueryBarValid, setIsQueryBarValid] = useState(false); const [isThreatQueryBarValid, setIsThreatQueryBarValid] = useState(false); const index = formIndex || initialState.index; + const dataView = formDataViewId || initialState.dataViewId; const threatIndex = formThreatIndex || initialState.threatIndex; const machineLearningJobId = formMachineLearningJobId ?? initialState.machineLearningJobId; const anomalyThreshold = formAnomalyThreshold ?? initialState.anomalyThreshold; const ruleType = formRuleType || initialState.ruleType; + + // if 'index' is selected, use these browser fields + // otherwise use the dataview browserfields const previousRuleType = usePrevious(ruleType); - const [indexPatternsLoading, { browserFields, indexPatterns }] = useFetchIndex(index); - const fields: Readonly = aggregatableFields(browserFields); const [optionsSelected, setOptionsSelected] = useState( defaultValues?.eqlOptions || {} ); + const [initIsIndexPatternLoading, { browserFields, indexPatterns: initIndexPattern }] = + useFetchIndex(index, false); + const [indexPattern, setIndexPattern] = useState(initIndexPattern); + const [isIndexPatternLoading, setIsIndexPatternLoading] = useState(initIsIndexPatternLoading); + const [dataSourceRadioIdSelected, setDataSourceRadioIdSelected] = useState( + dataView == null || dataView === '' ? INDEX_PATTERN_SELECT_ID : DATA_VIEW_SELECT_ID + ); + + useEffect(() => { + if (dataSourceRadioIdSelected === INDEX_PATTERN_SELECT_ID) { + setIndexPattern(initIndexPattern); + } + }, [initIndexPattern, dataSourceRadioIdSelected]); + + // Callback for when user toggles between Data Views and Index Patterns + const onChangeDataSource = (optionId: string) => { + setDataSourceRadioIdSelected(optionId); + }; + + const [aggFields, setAggregatableFields] = useState([]); + + useEffect(() => { + const { fields } = indexPattern; + setAggregatableFields(fields); + }, [indexPattern]); const [ threatIndexPatternsLoading, @@ -331,21 +393,26 @@ const StepDefineRuleComponent: FC = ({ const ThresholdInputChildren = useCallback( ({ thresholdField, thresholdValue, thresholdCardinalityField, thresholdCardinalityValue }) => ( ), - [fields] + [aggFields] ); + const SourcererFlex = styled(EuiFlexItem)` + align-items: flex-end; + `; + + SourcererFlex.displayName = 'SourcererFlex'; const ThreatMatchInputChildren = useCallback( ({ threatMapping }) => ( = ({ ), [ handleResetThreatIndices, - indexPatterns, + indexPattern, threatBrowserFields, threatIndexModified, threatIndexPatterns, @@ -364,6 +431,166 @@ const StepDefineRuleComponent: FC = ({ ] ); + const dataViewIndexPatternToggleButtonOptions: EuiButtonGroupOptionProps[] = useMemo( + () => [ + { + id: INDEX_PATTERN_SELECT_ID, + label: i18nCore.translate( + 'xpack.securitySolution.ruleDefine.indexTypeSelect.indexPattern', + { + defaultMessage: 'Index Patterns', + } + ), + iconType: + dataSourceRadioIdSelected === INDEX_PATTERN_SELECT_ID ? 'checkInCircleFilled' : 'empty', + 'data-test-subj': `rule-index-toggle-${INDEX_PATTERN_SELECT_ID}`, + }, + { + id: DATA_VIEW_SELECT_ID, + label: i18nCore.translate('xpack.securitySolution.ruleDefine.indexTypeSelect.dataView', { + defaultMessage: 'Data View', + }), + iconType: + dataSourceRadioIdSelected === DATA_VIEW_SELECT_ID ? 'checkInCircleFilled' : 'empty', + 'data-test-subj': `rule-index-toggle-${DATA_VIEW_SELECT_ID}`, + }, + ], + [dataSourceRadioIdSelected] + ); + + const DataViewSelectorMemo = useMemo(() => { + return ( + + ); + }, [kibanaDataViews]); + const DataSource = useMemo(() => { + return ( + + + + + + + + + + + + + + + + + + + + + {dataSourceRadioIdSelected === DATA_VIEW_SELECT_ID ? ( + DataViewSelectorMemo + ) : ( + + {i18n.RESET_DEFAULT_INDEX} + + ) : null, + }} + componentProps={{ + idAria: 'detectionEngineStepDefineRuleIndices', + 'data-test-subj': 'detectionEngineStepDefineRuleIndices', + euiFieldProps: { + fullWidth: true, + placeholder: '', + }, + }} + /> + )} + + + + ); + }, [ + dataSourceRadioIdSelected, + dataViewIndexPatternToggleButtonOptions, + DataViewSelectorMemo, + indexModified, + handleResetIndices, + ]); + + const QueryBarMemo = useMemo( + () => ( + + {i18n.IMPORT_TIMELINE_QUERY} + + ), + }} + component={QueryBarDefineRule} + componentProps={{ + browserFields, + // docValueFields, + // runtimeMappings, + idAria: 'detectionEngineStepDefineRuleQueryBar', + indexPattern, + isDisabled: isLoading, + isLoading: isIndexPatternLoading, + dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', + openTimelineSearch, + onValidityChange: setIsQueryBarValid, + onCloseTimelineSearch: handleCloseTimelineSearch, + }} + /> + ), + [ + browserFields, + handleCloseTimelineSearch, + handleOpenTimelineSearch, + indexPattern, + isIndexPatternLoading, + isLoading, + openTimelineSearch, + ] + ); const onOptionsChange = useCallback((field: FieldsEqlOptions, value: string | undefined) => { setOptionsSelected((prevOptions) => ({ ...prevOptions, @@ -373,31 +600,31 @@ const StepDefineRuleComponent: FC = ({ const optionsData = useMemo( () => - isEmpty(indexPatterns.fields) + isEmpty(indexPattern.fields) ? { keywordFields: [], dateFields: [], nonDateFields: [], } : { - keywordFields: (indexPatterns.fields as FieldSpec[]) + keywordFields: (indexPattern.fields as FieldSpec[]) .filter((f) => f.esTypes?.includes('keyword')) .map((f) => ({ label: f.name })), - dateFields: indexPatterns.fields + dateFields: indexPattern.fields .filter((f) => f.type === 'date') .map((f) => ({ label: f.name })), - nonDateFields: indexPatterns.fields + nonDateFields: indexPattern.fields .filter((f) => f.type !== 'date') .map((f) => ({ label: f.name })), }, - [indexPatterns] + [indexPattern] ); return isReadOnlyView ? ( @@ -418,25 +645,9 @@ const StepDefineRuleComponent: FC = ({ /> <> - - {i18n.RESET_DEFAULT_INDEX} - - ) : null, - }} - componentProps={{ - idAria: 'detectionEngineStepDefineRuleIndices', - 'data-test-subj': 'detectionEngineStepDefineRuleIndices', - euiFieldProps: { - fullWidth: true, - placeholder: '', - }, - }} - /> + + {DataSource} + {isEqlRule(ruleType) ? ( = ({ onValidityChange: setIsQueryBarValid, idAria: 'detectionEngineStepDefineRuleEqlQueryBar', isDisabled: isLoading, - indexPattern: indexPatterns, + isLoading: isIndexPatternLoading, + indexPattern, showFilterBar: true, - isLoading: indexPatternsLoading, + // isLoading: indexPatternsLoading, dataTestSubj: 'detectionEngineStepDefineRuleEqlQueryBar', }} config={{ @@ -461,34 +673,7 @@ const StepDefineRuleComponent: FC = ({ }} /> ) : ( - - {i18n.IMPORT_TIMELINE_QUERY} - - ), - }} - component={QueryBarDefineRule} - componentProps={{ - browserFields, - idAria: 'detectionEngineStepDefineRuleQueryBar', - indexPattern: indexPatterns, - isDisabled: isLoading, - isLoading: indexPatternsLoading, - dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', - openTimelineSearch, - onValidityChange: setIsQueryBarValid, - onCloseTimelineSearch: handleCloseTimelineSearch, - }} - /> + QueryBarMemo )} @@ -566,11 +751,13 @@ const StepDefineRuleComponent: FC = ({ = ({ ); }; - export const StepDefineRule = memo(StepDefineRuleComponent); export function aggregatableFields(browserFields: BrowserFields): BrowserFields { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx index eb6a71ac8a5ee..c863eb8cf1eab 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx @@ -59,9 +59,9 @@ export const schema: FormSchema = { ...args: Parameters ): ReturnType> | undefined => { const [{ formData }] = args; - const needsValidation = !isMlRule(formData.ruleType); + const skipValidation = isMlRule(formData.ruleType) || formData.dataViewId != null; - if (!needsValidation) { + if (skipValidation) { return; } @@ -77,6 +77,48 @@ export const schema: FormSchema = { }, ], }, + dataViewTitle: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.dataViewSelector', + { + defaultMessage: 'Data View', + } + ), + validations: [], + }, + dataViewId: { + fieldsToValidateOnChange: ['dataViewId'], + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ path, formData }] = args; + // the dropdown defaults the dataViewId to an empty string somehow on render.. + // need to figure this out. + const notEmptyDataViewId = formData.dataViewId != null && formData.dataViewId !== ''; + const skipValidation = + isMlRule(formData.ruleType) || + ((formData.index != null || notEmptyDataViewId) && + !(formData.index != null && notEmptyDataViewId)); + + if (skipValidation) { + return; + } + + return { + path, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.dataViewSelectorFieldRequired', + { + defaultMessage: 'Please select an available Data View or Index Pattern.', + } + ), + }; + }, + }, + ], + }, eqlOptions: {}, queryBar: { validations: [ diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx index 77c88918abf9c..91efeff024831 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx @@ -9,8 +9,7 @@ import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; -import { BrowserFields } from '../../../../common/containers/source'; -import { getCategorizedFieldNames } from '../../../../timelines/components/edit_data_provider/helpers'; +import { DataViewFieldBase } from '@kbn/es-query'; import { FieldHook, Field } from '../../../../shared_imports'; import { THRESHOLD_FIELD_PLACEHOLDER } from './translations'; @@ -30,7 +29,7 @@ interface ThresholdInputProps { thresholdValue: FieldHook; thresholdCardinalityField: FieldHook; thresholdCardinalityValue: FieldHook; - browserFields: BrowserFields; + browserFields: DataViewFieldBase[]; } const OperatorWrapper = styled(EuiFlexItem)` @@ -53,7 +52,7 @@ const ThresholdInputComponent: React.FC = ({ () => ({ fullWidth: true, noSuggestions: false, - options: getCategorizedFieldNames(browserFields), + options: browserFields.map((field) => ({ label: field.name })), placeholder: THRESHOLD_FIELD_PLACEHOLDER, onCreateOption: undefined, style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, @@ -64,7 +63,7 @@ const ThresholdInputComponent: React.FC = ({ () => ({ fullWidth: true, noSuggestions: false, - options: getCategorizedFieldNames(browserFields), + options: browserFields.map((field) => ({ label: field.name })), placeholder: THRESHOLD_FIELD_PLACEHOLDER, onCreateOption: undefined, style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts index 6aad67f91920d..630e8804d31e5 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts @@ -29,6 +29,7 @@ import { building_block_type, license, rule_name_override, + data_view_id, timestamp_override, timestamp_field, event_category_override, @@ -133,6 +134,7 @@ export const RuleSchema = t.intersection([ anomaly_threshold: t.number, filters: t.array(t.unknown), index: t.array(t.string), + data_view_id, language: t.string, license, meta: MetaRule, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts index 006ab332dc174..f67b34a7149d4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -193,6 +193,7 @@ export const mockDefineStepRule = (): DefineStepRule => ({ anomalyThreshold: 50, machineLearningJobId: [], index: ['filebeat-'], + dataViewId: undefined, queryBar: mockQueryBar, threatQueryBar: mockQueryBar, requiredFields: [], diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/index_patterns_form.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/index_patterns_form.tsx index 04cdbb5d88aa9..fa027cb2e4f75 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/index_patterns_form.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/index_patterns_form.tsx @@ -40,6 +40,7 @@ type IndexPatternsEditActions = interface IndexPatternsFormData { index: string[]; overwrite: boolean; + overwriteDataViews: boolean; } const schema: FormSchema = { @@ -58,9 +59,17 @@ const schema: FormSchema = { type: FIELD_TYPES.CHECKBOX, label: i18n.BULK_EDIT_FLYOUT_FORM_ADD_INDEX_PATTERNS_OVERWRITE_LABEL, }, + overwriteDataViews: { + type: FIELD_TYPES.CHECKBOX, + label: i18n.BULK_EDIT_FLYOUT_FORM_DATA_VIEWS_OVERWRITE_LABEL, + }, }; -const initialFormData: IndexPatternsFormData = { index: [], overwrite: false }; +const initialFormData: IndexPatternsFormData = { + index: [], + overwrite: false, + overwriteDataViews: false, +}; const getFormConfig = (editAction: IndexPatternsEditActions) => editAction === BulkActionEditType.add_index_patterns @@ -95,7 +104,10 @@ const IndexPatternsFormComponent = ({ const { indexHelpText, indexLabel, formTitle } = getFormConfig(editAction); - const [{ overwrite }] = useFormData({ form, watch: ['overwrite'] }); + const [{ overwrite, overwriteDataViews }] = useFormData({ + form, + watch: ['overwrite', 'overwriteDataViews'], + }); const { uiSettings } = useKibana().services; const defaultPatterns = uiSettings.get(DEFAULT_INDEX_KEY); @@ -108,6 +120,7 @@ const IndexPatternsFormComponent = ({ const payload = { value: data.index, type: data.overwrite ? BulkActionEditType.set_index_patterns : editAction, + overwriteDataViews: data.overwriteDataViews, }; onConfirm(payload); @@ -139,7 +152,7 @@ const IndexPatternsFormComponent = ({ /> )} {overwrite && ( - + )} + + {overwriteDataViews && ( + + + + + + )} ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 214ec2373c949..13d3af58d1e99 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -89,6 +89,7 @@ export interface RuleFields { machineLearningJobId: unknown; queryBar: unknown; index: unknown; + dataViewId?: unknown; ruleType: unknown; threshold?: unknown; threatIndex?: unknown; @@ -350,6 +351,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep return { ...baseFields, ...typeFields, + data_view_id: ruleFields.dataViewId, }; }; @@ -476,6 +478,7 @@ export const formatRule = ( export const formatPreviewRule = ({ index, + dataViewId, query, threatIndex, threatQuery, @@ -488,6 +491,7 @@ export const formatPreviewRule = ({ eqlOptions, }: { index: string[]; + dataViewId?: string; threatIndex: string[]; query: FieldValueQueryBar; threatQuery: FieldValueQueryBar; @@ -502,6 +506,7 @@ export const formatPreviewRule = ({ const defineStepData = { ...stepDefineDefaultValue, index, + dataViewId, queryBar: query, ruleType, threatIndex, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx index 42ba9fafd3ea2..a6ff44f6d6f2a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx @@ -13,9 +13,10 @@ import { EuiSpacer, EuiFlexGroup, } from '@elastic/eui'; -import React, { useCallback, useRef, useState, useMemo } from 'react'; +import React, { useCallback, useRef, useState, useMemo, useEffect } from 'react'; import styled from 'styled-components'; +import { DataViewListItem } from '@kbn/data-views-plugin/common'; import { useCreateRule } from '../../../../containers/detection_engine/rules'; import { CreateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; @@ -93,6 +94,7 @@ const CreateRulePageComponent: React.FC = () => { const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = useListsConfig(); const { navigateToApp } = useKibana().services.application; + const { data: dataServices } = useKibana().services; const loading = userInfoLoading || listsConfigLoading; const [, dispatchToaster] = useStateToaster(); const [activeStep, setActiveStep] = useState(RuleStep.defineRule); @@ -136,6 +138,22 @@ const CreateRulePageComponent: React.FC = () => { const ruleType = stepsData.current[RuleStep.defineRule].data?.ruleType; const ruleName = stepsData.current[RuleStep.aboutRule].data?.name; const actionMessageParams = useMemo(() => getActionMessageParams(ruleType), [ruleType]); + const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); + + useEffect(() => { + const fetchDataViews = async () => { + const dataViewsRefs = await dataServices.dataViews.getIdsWithTitle(); + const dataViewIdIndexPatternMap = dataViewsRefs.reduce( + (acc, item) => ({ + ...acc, + [item.id]: item, + }), + {} + ); + setDataViewOptions(dataViewIdIndexPatternMap); + }; + fetchDataViews(); + }, [dataServices.dataViews]); const handleAccordionToggle = useCallback( (step: RuleStep, isOpen: boolean) => @@ -333,6 +351,7 @@ const CreateRulePageComponent: React.FC = () => { isLoading={isLoading || loading} setForm={setFormHook} onSubmit={submitStepDefineRule} + kibanaDataViews={dataViewOptions} descriptionColumns="singleSplit" // We need a key to make this component remount when edit/view mode is toggled // https://github.com/elastic/kibana/pull/132834#discussion_r881705566 diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx index 8212bebabb5c3..5f110a43eb8b1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx @@ -26,8 +26,6 @@ import { useSourcererDataView } from '../../../../../common/containers/sourcerer import { useParams } from 'react-router-dom'; import { mockHistory, Router } from '../../../../../common/mock/router'; -import { useKibana } from '../../../../../common/lib/kibana'; - import { fillEmptySeverityMappings } from '../helpers'; // Test will fail because we will to need to mock some core services to make the test work @@ -64,7 +62,15 @@ jest.mock('../../../../containers/detection_engine/rules/use_rule_with_fallback' useRuleWithFallback: jest.fn(), }; }); -jest.mock('../../../../../common/containers/sourcerer'); +jest.mock('../../../../../common/containers/sourcerer', () => { + const actual = jest.requireActual('../../../../../common/containers/sourcerer'); + return { + ...actual, + useSourcererDataView: jest + .fn() + .mockReturnValue({ indexPattern: ['fakeindex'], loading: false }), + }; +}); jest.mock('../../../../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ from: '2020-07-07T08:20:18.966Z', @@ -80,13 +86,55 @@ jest.mock('react-router-dom', () => { ...originalModule, useParams: jest.fn(), useHistory: jest.fn(), + useLocation: jest.fn().mockReturnValue({ pathname: '/alerts' }), }; }); -jest.mock('../../../../../common/lib/kibana'); - const mockRedirectLegacyUrl = jest.fn(); const mockGetLegacyUrlConflict = jest.fn(); +jest.mock('../../../../../common/lib/kibana', () => { + const originalModule = jest.requireActual('../../../../../common/lib/kibana'); + return { + ...originalModule, + useKibana: () => ({ + services: { + storage: { + get: jest.fn().mockReturnValue(true), + }, + application: { + getUrlForApp: (appId: string, options?: { path?: string }) => + `/app/${appId}${options?.path}`, + navigateToApp: jest.fn(), + capabilities: { + actions: { + delete: true, + save: true, + show: true, + }, + }, + }, + data: { + dataViews: { + getIdsWithTitle: () => [], + }, + search: { + search: () => ({ + subscribe: () => ({ + unsubscribe: jest.fn(), + }), + }), + }, + }, + spaces: { + ui: { + components: { getLegacyUrlConflict: mockGetLegacyUrlConflict }, + redirectLegacyUrl: mockRedirectLegacyUrl, + }, + }, + }, + }), + }; +}); const state: State = { ...mockGlobalState, @@ -143,16 +191,6 @@ describe('RuleDetailsPageComponent', () => { async function setup() { mockRedirectLegacyUrl.mockReset(); mockGetLegacyUrlConflict.mockReset(); - const useKibanaMock = useKibana as jest.Mocked; - - // eslint-disable-next-line react-hooks/rules-of-hooks - useKibanaMock().services.spaces = { - ui: { - // @ts-expect-error - components: { getLegacyUrlConflict: mockGetLegacyUrlConflict }, - redirectLegacyUrl: mockRedirectLegacyUrl, - }, - }; } it('renders correctly with no outcome property on rule', async () => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index cc8872b901f44..9ac9442ec170f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -34,6 +34,7 @@ import { import { Dispatch } from 'redux'; import { isTab } from '@kbn/timelines-plugin/public'; +import { DataViewListItem } from '@kbn/data-views-plugin/common'; import { useDeepEqualSelector, useShallowEqualSelector, @@ -173,7 +174,16 @@ const RuleDetailsPageComponent: React.FC = ({ clearEventsLoading, clearSelected, }) => { - const { navigateToApp } = useKibana().services.application; + const { + data, + application: { + navigateToApp, + capabilities: { actions }, + }, + timelines: timelinesUi, + spaces: spacesApi, + } = useKibana().services; + const dispatch = useDispatch(); const containerElement = useRef(null); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); @@ -241,24 +251,43 @@ const RuleDetailsPageComponent: React.FC = ({ defineRuleData: null, scheduleRuleData: null, }; + const [dataViewTitle, setDataViewTitle] = useState(); + useEffect(() => { + const fetchDataViewTitle = async () => { + if (defineRuleData?.dataViewId != null && defineRuleData?.dataViewId !== '') { + const dataView = await data.dataViews.get(defineRuleData?.dataViewId); + setDataViewTitle(dataView.title); + } + }; + fetchDataViewTitle(); + }, [data.dataViews, defineRuleData?.dataViewId]); const [showBuildingBlockAlerts, setShowBuildingBlockAlerts] = useState(false); const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false); const mlCapabilities = useMlCapabilities(); const { formatUrl } = useFormatUrl(SecurityPageName.rules); const { globalFullScreen } = useGlobalFullScreen(); const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); + useEffect(() => { + const fetchDataViews = async () => { + const dataViewsRefs = await data.dataViews.getIdsWithTitle(); + if (dataViewsRefs.length > 0) { + const dataViewIdIndexPatternMap = dataViewsRefs.reduce( + (acc, item) => ({ + ...acc, + [item.id]: item, + }), + {} + ); + setDataViewOptions(dataViewIdIndexPatternMap); + } + }; + fetchDataViews(); + }, [data.dataViews]); // TODO: Refactor license check + hasMlAdminPermissions to common check const hasMlPermissions = hasMlLicense(mlCapabilities) && hasMlAdminPermissions(mlCapabilities); - const { - services: { - application: { - capabilities: { actions }, - }, - timelines: timelinesUi, - spaces: spacesApi, - }, - } = useKibana(); + const hasActionsPrivileges = useMemo(() => { if (rule?.actions != null && rule?.actions.length > 0 && isBoolean(actions.show)) { return actions.show; @@ -450,7 +479,6 @@ const RuleDetailsPageComponent: React.FC = ({ ), [isExistingRule, ruleDetailTab, setRuleDetailTab, pageTabs] ); - const ruleIndices = useMemo(() => rule?.index ?? DEFAULT_INDEX_PATTERN, [rule?.index]); const lastExecution = rule?.execution_summary?.last_execution; const lastExecutionStatus = lastExecution?.status; @@ -734,7 +762,8 @@ const RuleDetailsPageComponent: React.FC = ({ descriptionColumns="singleSplit" isReadOnlyView={true} isLoading={false} - defaultValues={defineRuleData} + defaultValues={{ dataViewTitle, ...defineRuleData }} + kibanaDataViews={dataViewOptions} /> )} @@ -814,7 +843,8 @@ const RuleDetailsPageComponent: React.FC = ({ = ({ - ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx index 6734636853a08..a563ab85be336 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx @@ -18,6 +18,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React, { FC, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; +import { DataViewListItem } from '@kbn/data-views-plugin/common'; import { UpdateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; import { useRule, useUpdateRule } from '../../../../containers/detection_engine/rules'; import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; @@ -75,6 +76,7 @@ const EditRulePageComponent: FC = () => { ] = useUserData(); const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = useListsConfig(); + const { data: dataServices } = useKibana().services; const { navigateToApp } = useKibana().services.application; const { detailName: ruleId } = useParams<{ detailName: string | undefined }>(); @@ -103,6 +105,22 @@ const EditRulePageComponent: FC = () => { return stepData.data != null && !stepIsValid(stepData); }); const [{ isLoading, isSaved }, setRule] = useUpdateRule(); + const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); + + useEffect(() => { + const fetchDataViews = async () => { + const dataViewsRefs = await dataServices.dataViews.getIdsWithTitle(); + const dataViewIdIndexPatternMap = dataViewsRefs.reduce( + (acc, item) => ({ + ...acc, + [item.id]: item, + }), + {} + ); + setDataViewOptions(dataViewIdIndexPatternMap); + }; + fetchDataViews(); + }, [dataServices.dataViews]); const actionMessageParams = useMemo(() => getActionMessageParams(rule?.type), [rule?.type]); const setFormHook = useCallback( (step: K, hook: RuleStepsFormHooks[K]) => { @@ -138,6 +156,7 @@ const EditRulePageComponent: FC = () => { isUpdateView defaultValues={defineStep.data} setForm={setFormHook} + kibanaDataViews={dataViewOptions} /> )} @@ -226,6 +245,7 @@ const EditRulePageComponent: FC = () => { scheduleStep.data, actionsStep.data, actionMessageParams, + dataViewOptions, ] ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index 93e17efd38d5f..c568d49fe7867 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -83,6 +83,7 @@ export const getDefineStepsData = (rule: Rule): DefineStepRule => ({ anomalyThreshold: rule.anomaly_threshold ?? 50, machineLearningJobId: rule.machine_learning_job_id ?? [], index: rule.index ?? [], + dataViewId: rule.data_view_id, threatIndex: rule.threat_index ?? [], threatQueryBar: { query: { query: rule.threat_query ?? '', language: rule.threat_language ?? '' }, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 6eae85a64438b..7a9c02d7ed8f9 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -312,6 +312,13 @@ export const BULK_EDIT_FLYOUT_FORM_ADD_INDEX_PATTERNS_OVERWRITE_LABEL = i18n.tra } ); +export const BULK_EDIT_FLYOUT_FORM_DATA_VIEWS_OVERWRITE_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.dataViewsOverwriteCheckboxLabel', + { + defaultMessage: 'Apply changes to rules configured with data views', + } +); + export const BULK_EDIT_FLYOUT_FORM_DELETE_INDEX_PATTERNS_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.deleteIndexPatternsComboboxLabel', { @@ -1066,7 +1073,7 @@ export const RULES_BULK_EDIT_SUCCESS_DESCRIPTION = (rulesCount: number) => { values: { rulesCount }, defaultMessage: - "You've successfully updated {rulesCount, plural, =1 {# rule} other {# rules}}.", + "You've successfully updated {rulesCount, plural, =1 {# rule} other {# rules}}. If you did not select to apply changes to rules using Kibana data views, those rules were not updated and will continue using data views.", } ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index 3ec6b51fa6329..119a43efab894 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -6,6 +6,7 @@ */ import type { List } from '@kbn/securitysolution-io-ts-list-types'; + import { RiskScoreMapping, ThreatIndex, @@ -17,6 +18,8 @@ import { } from '@kbn/securitysolution-io-ts-alerting-types'; import type { Filter } from '@kbn/es-query'; import { RuleAction } from '@kbn/alerting-plugin/common'; +import { DataViewListItem } from '@kbn/data-views-plugin/common'; + import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { FieldValueQueryBar } from '../../../components/rules/query_bar'; import { FieldValueTimeline } from '../../../components/rules/pick_timeline'; @@ -89,6 +92,7 @@ export interface RuleStepProps { onSubmit?: () => void; resizeParentContainer?: (height: number) => void; setForm?: (step: K, hook: RuleStepsFormHooks[K]) => void; + kibanaDataViews?: { [x: string]: DataViewListItem }; } export interface AboutStepRule { @@ -128,11 +132,17 @@ export interface AboutStepRiskScore { isMappingChecked: boolean; } +/** + * add / update data source types to show XOR relationship between 'index' and 'dataViewId' fields + * Maybe something with io-ts? + */ export interface DefineStepRule { anomalyThreshold: number; index: string[]; machineLearningJobId: string[]; queryBar: FieldValueQueryBar; + dataViewId?: string; + dataViewTitle?: string; relatedIntegrations: RelatedIntegrationArray; requiredFields: RequiredFieldArray; ruleType: Type; @@ -164,6 +174,7 @@ export interface DefineStepRuleJson { machine_learning_job_id?: string[]; saved_id?: string; query?: string; + data_view_id?: string; language?: string; threshold?: { field: string[]; diff --git a/x-pack/plugins/security_solution/public/kubernetes/pages/index.tsx b/x-pack/plugins/security_solution/public/kubernetes/pages/index.tsx index 6cb28c7a59ce2..59ba4fa340e21 100644 --- a/x-pack/plugins/security_solution/public/kubernetes/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/kubernetes/pages/index.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; +import { getEsQueryConfig } from '@kbn/data-plugin/common'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useKibana } from '../../common/lib/kibana'; import { SecurityPageName } from '../../../common/constants'; @@ -13,17 +14,52 @@ import { SpyRoute } from '../../common/utils/route/spy_routes'; import { FiltersGlobal } from '../../common/components/filters_global'; import { SiemSearchBar } from '../../common/components/search_bar'; import { showGlobalFilters } from '../../timelines/components/timeline/helpers'; +import { inputsSelectors } from '../../common/store'; import { useGlobalFullScreen } from '../../common/containers/use_full_screen'; import { useSourcererDataView } from '../../common/containers/sourcerer'; +import { useGlobalTime } from '../../common/containers/use_global_time'; +import { useDeepEqualSelector } from '../../common/hooks/use_selector'; +import { convertToBuildEsQuery } from '../../common/lib/keury'; +import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; export const KubernetesContainer = React.memo(() => { - const { kubernetesSecurity } = useKibana().services; + const { kubernetesSecurity, uiSettings } = useKibana().services; const { globalFullScreen } = useGlobalFullScreen(); const { indexPattern, // runtimeMappings, // loading: isLoadingIndexPattern, } = useSourcererDataView(); + const { from, to } = useGlobalTime(); + + const getGlobalFiltersQuerySelector = useMemo( + () => inputsSelectors.globalFiltersQuerySelector(), + [] + ); + const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); + const query = useDeepEqualSelector(getGlobalQuerySelector); + const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + + const [filterQuery, kqlError] = useMemo( + () => + convertToBuildEsQuery({ + config: getEsQueryConfig(uiSettings), + indexPattern, + queries: [query], + filters, + }), + [filters, indexPattern, uiSettings, query] + ); + + useInvalidFilterQuery({ + id: 'kubernetesQuery', + filterQuery, + kqlError, + query, + startDate: from, + endDate: to, + }); + return ( {kubernetesSecurity.getKubernetesPage({ @@ -32,6 +68,12 @@ export const KubernetesContainer = React.memo(() => { ), + indexPattern, + globalFilter: { + filterQuery, + startDate: from, + endDate: to, + }, })} diff --git a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response.tsx b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response.tsx index b468dce0a5b48..027e8e541d89b 100644 --- a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response.tsx +++ b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response.tsx @@ -8,7 +8,9 @@ import React, { memo, useCallback } from 'react'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n-react'; +import { DocLinksStart } from '@kbn/core/public'; import { EuiHealth, EuiText, EuiTreeView, EuiNotificationBadge } from '@elastic/eui'; +import { useKibana } from '../../../common/lib/kibana'; import { HostPolicyResponseActionStatus, HostPolicyResponseAppliedAction, @@ -17,7 +19,7 @@ import { ImmutableArray, ImmutableObject, } from '../../../../common/endpoint/types'; -import { formatResponse } from './policy_response_friendly_names'; +import { formatResponse, PolicyResponseActionFormatter } from './policy_response_friendly_names'; import { PolicyResponseActionItem } from './policy_response_action_item'; // Most of them are needed in order to display large react nodes (PolicyResponseActionItem) in child levels. @@ -59,6 +61,7 @@ export const PolicyResponse = memo( policyResponseActions, policyResponseAttentionCount, }: PolicyResponseProps) => { + const { docLinks } = useKibana().services; const getEntryIcon = useCallback( (status: HostPolicyResponseActionStatus, unsuccessCounts: number) => status === HostPolicyResponseActionStatus.success ? ( @@ -88,6 +91,12 @@ export const PolicyResponse = memo( (currentAction) => currentAction.name === actionKey ) as ImmutableObject; + const policyResponseActionFormatter = new PolicyResponseActionFormatter( + action || {}, + docLinks.links.securitySolution.policyResponseTroubleshooting[ + action.name as keyof DocLinksStart['links']['securitySolution']['policyResponseTroubleshooting'] + ] + ); return { label: ( - {formatResponse(actionKey)} + {policyResponseActionFormatter.title} ), id: actionKey, @@ -116,11 +125,7 @@ export const PolicyResponse = memo( { label: ( {}} // TODO + policyResponseActionFormatter={policyResponseActionFormatter} /> ), id: `action_message_${actionKey}`, @@ -135,7 +140,11 @@ export const PolicyResponse = memo( }; }); }, - [getEntryIcon, policyResponseActions] + [ + docLinks.links.securitySolution.policyResponseTroubleshooting, + getEntryIcon, + policyResponseActions, + ] ); const getResponseConfigs = useCallback( diff --git a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_action_item.tsx b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_action_item.tsx index 9f9fdb48ede15..65529f96fff2c 100644 --- a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_action_item.tsx +++ b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_action_item.tsx @@ -7,8 +7,8 @@ import React, { memo } from 'react'; import styled from 'styled-components'; -import { EuiButton, EuiCallOut, EuiText, EuiSpacer } from '@elastic/eui'; -import { HostPolicyResponseActionStatus } from '../../../../common/endpoint/types'; +import { EuiLink, EuiCallOut, EuiText } from '@elastic/eui'; +import { PolicyResponseActionFormatter } from './policy_response_friendly_names'; const StyledEuiCallout = styled(EuiCallOut)` padding: ${({ theme }) => theme.eui.paddingSizes.s}; @@ -19,39 +19,36 @@ const StyledEuiCallout = styled(EuiCallOut)` `; interface PolicyResponseActionItemProps { - status: HostPolicyResponseActionStatus; - actionTitle: string; - actionMessage: string; - actionButtonLabel?: string; - actionButtonOnClick?: () => void; + policyResponseActionFormatter: PolicyResponseActionFormatter; } /** * A policy response action item */ export const PolicyResponseActionItem = memo( - ({ - status, - actionTitle, - actionMessage, - actionButtonLabel, - actionButtonOnClick, - }: PolicyResponseActionItemProps) => { - return status !== HostPolicyResponseActionStatus.success && - status !== HostPolicyResponseActionStatus.unsupported ? ( - + ({ policyResponseActionFormatter }: PolicyResponseActionItemProps) => { + return policyResponseActionFormatter.hasError ? ( + - {actionMessage} + {policyResponseActionFormatter.errorDescription} + {policyResponseActionFormatter.linkText && policyResponseActionFormatter.linkUrl && ( + + {policyResponseActionFormatter.linkText} + + )} - - {actionButtonLabel && actionButtonOnClick && ( - - {actionButtonLabel} - - )} ) : ( - {actionMessage} + {policyResponseActionFormatter.description || policyResponseActionFormatter.title} ); } diff --git a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_friendly_names.ts b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_friendly_names.ts index 3551d00c50c73..2abc1efd406ec 100644 --- a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_friendly_names.ts +++ b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_friendly_names.ts @@ -6,270 +6,418 @@ */ import { i18n } from '@kbn/i18n'; +import { + HostPolicyResponseActionStatus, + HostPolicyResponseAppliedAction, + ImmutableObject, +} from '../../../../common/endpoint/types'; -const policyResponses: Array<[string, string]> = [ - [ - 'configure_dns_events', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_dns_events', { - defaultMessage: 'Configure DNS Events', - }), - ], - [ - 'configure_elasticsearch_connection', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.configure_elasticsearch_connection', - { defaultMessage: 'Configure Elasticsearch Connection' } - ), - ], - [ - 'configure_file_events', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_file_events', { - defaultMessage: 'Configure File Events', - }), - ], - [ - 'configure_imageload_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.configure_imageload_events', - { defaultMessage: 'Configure Image Load Events' } - ), - ], - [ - 'configure_kernel', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_kernel', { - defaultMessage: 'Configure Kernel', - }), - ], - [ - 'configure_logging', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_logging', { - defaultMessage: 'Configure Logging', - }), - ], - [ - 'configure_malware', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_malware', { - defaultMessage: 'Configure Malware', - }), - ], - [ - 'configure_network_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.configure_network_events', - { defaultMessage: 'Configure Network Events' } - ), - ], - [ - 'configure_process_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.configure_process_events', - { defaultMessage: 'Configure Process Events' } - ), - ], - [ - 'configure_registry_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.configure_registry_events', - { defaultMessage: 'Configure Registry Events' } - ), - ], - [ - 'configure_security_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.configure_security_events', - { defaultMessage: 'Configure Security Events' } - ), - ], - [ - 'connect_kernel', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.connect_kernel', { - defaultMessage: 'Connect Kernel', - }), - ], - [ - 'detect_async_image_load_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.detect_async_image_load_events', - { defaultMessage: 'Detect Async Image Load Events' } - ), - ], - [ - 'detect_file_open_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.detect_file_open_events', - { defaultMessage: 'Detect File Open Events' } - ), - ], - [ - 'detect_file_write_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.detect_file_write_events', - { defaultMessage: 'Detect File Write Events' } - ), - ], - [ - 'detect_network_events', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.detect_network_events', { - defaultMessage: 'Detect Network Events', - }), - ], - [ - 'detect_process_events', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.detect_process_events', { - defaultMessage: 'Detect Process Events', - }), - ], - [ - 'detect_registry_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.detect_registry_events', - { defaultMessage: 'Detect Registry Events' } - ), - ], - [ - 'detect_sync_image_load_events', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.detect_sync_image_load_events', - { defaultMessage: 'Detect Sync Image Load Events' } - ), - ], - [ - 'download_global_artifacts', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.download_global_artifacts', - { defaultMessage: 'Download Global Artifacts' } - ), - ], - [ - 'download_user_artifacts', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.download_user_artifacts', - { defaultMessage: 'Download User Artifacts' } - ), - ], - [ - 'load_config', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.load_config', { - defaultMessage: 'Load Config', - }), - ], - [ - 'load_malware_model', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.load_malware_model', { - defaultMessage: 'Load Malware Model', - }), - ], - [ - 'read_elasticsearch_config', - i18n.translate( - 'xpack.securitySolution.endpoint.details.policyResponse.read_elasticsearch_config', - { defaultMessage: 'Read Elasticsearch Config' } - ), - ], - [ - 'read_events_config', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_events_config', { - defaultMessage: 'Read Events Config', - }), - ], - [ - 'read_kernel_config', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_kernel_config', { - defaultMessage: 'Read Kernel Config', - }), - ], - [ - 'read_logging_config', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_logging_config', { - defaultMessage: 'Read Logging Config', - }), - ], - [ - 'read_malware_config', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_malware_config', { - defaultMessage: 'Read Malware Config', - }), - ], - [ - 'workflow', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.workflow', { - defaultMessage: 'Workflow', - }), - ], -]; +type PolicyResponseSections = + | 'logging' + | 'streaming' + | 'malware' + | 'events' + | 'memory_protection' + | 'behavior_protection'; -const responseMap = new Map(policyResponses); - -// Additional values used in the Policy Response UI -responseMap.set( - 'success', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.success', { - defaultMessage: 'Success', - }) -); -responseMap.set( - 'warning', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.warning', { - defaultMessage: 'Warning', - }) -); -responseMap.set( - 'failure', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.failed', { - defaultMessage: 'Failed', - }) -); -responseMap.set( - 'logging', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.logging', { - defaultMessage: 'Logging', - }) -); -responseMap.set( - 'streaming', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.streaming', { - defaultMessage: 'Streaming', - }) -); -responseMap.set( - 'malware', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.malware', { - defaultMessage: 'Malware', - }) -); -responseMap.set( - 'events', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.events', { - defaultMessage: 'Events', - }) -); -responseMap.set( - 'memory_protection', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.memory_protection', { - defaultMessage: 'Memory Threat', - }) -); -responseMap.set( - 'behavior_protection', - i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.behavior_protection', { - defaultMessage: 'Malicious Behavior', - }) +const policyResponseSections = Object.freeze( + new Map([ + [ + 'logging', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.logging', { + defaultMessage: 'Logging', + }), + ], + [ + 'streaming', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.streaming', { + defaultMessage: 'Streaming', + }), + ], + [ + 'malware', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.malware', { + defaultMessage: 'Malware', + }), + ], + [ + 'events', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.events', { + defaultMessage: 'Events', + }), + ], + [ + 'memory_protection', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.memory_protection', { + defaultMessage: 'Memory Threat', + }), + ], + [ + 'behavior_protection', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.behavior_protection', { + defaultMessage: 'Malicious Behavior', + }), + ], + ]) ); /** * Maps a server provided value to corresponding i18n'd string. */ -export function formatResponse(responseString: string) { - if (responseMap.has(responseString)) { - return responseMap.get(responseString); +export function formatResponse(responseString: PolicyResponseSections | string) { + if (policyResponseSections.has(responseString)) { + return policyResponseSections.get(responseString); } // Its possible for the UI to receive an Action name that it does not yet have a translation, // thus we generate a label for it here by making it more user fiendly - responseMap.set( + policyResponseSections.set( responseString, responseString.replace(/_/g, ' ').replace(/\b(\w)/g, (m) => m.toUpperCase()) ); - return responseMap.get(responseString); + return policyResponseSections.get(responseString); +} + +type PolicyResponseAction = + | 'configure_dns_events' + | 'configure_dns_events' + | 'configure_elasticsearch_connection' + | 'configure_file_events' + | 'configure_imageload_events' + | 'configure_kernel' + | 'configure_logging' + | 'configure_malware' + | 'configure_network_events' + | 'configure_process_events' + | 'configure_registry_events' + | 'configure_security_events' + | 'connect_kernel' + | 'detect_async_image_load_events' + | 'detect_file_open_events' + | 'detect_file_write_events' + | 'detect_network_events' + | 'detect_process_events' + | 'detect_registry_events' + | 'detect_sync_image_load_events' + | 'download_global_artifacts' + | 'download_user_artifacts' + | 'load_config' + | 'load_malware_model' + | 'read_elasticsearch_config' + | 'read_events_config' + | 'read_kernel_config' + | 'read_logging_config' + | 'read_malware_config' + | 'workflow' + | 'full_disk_access'; + +const policyResponseTitles = Object.freeze( + new Map([ + [ + 'configure_dns_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.configure_dns_events', + { + defaultMessage: 'Configure DNS Events', + } + ), + ], + [ + 'configure_elasticsearch_connection', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.configure_elasticsearch_connection', + { defaultMessage: 'Configure Elasticsearch Connection' } + ), + ], + [ + 'configure_file_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.configure_file_events', + { + defaultMessage: 'Configure File Events', + } + ), + ], + [ + 'configure_imageload_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.configure_imageload_events', + { defaultMessage: 'Configure Image Load Events' } + ), + ], + [ + 'configure_kernel', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_kernel', { + defaultMessage: 'Configure Kernel', + }), + ], + [ + 'configure_logging', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_logging', { + defaultMessage: 'Configure Logging', + }), + ], + [ + 'configure_malware', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_malware', { + defaultMessage: 'Configure Malware', + }), + ], + [ + 'configure_network_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.configure_network_events', + { defaultMessage: 'Configure Network Events' } + ), + ], + [ + 'configure_process_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.configure_process_events', + { defaultMessage: 'Configure Process Events' } + ), + ], + [ + 'configure_registry_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.configure_registry_events', + { defaultMessage: 'Configure Registry Events' } + ), + ], + [ + 'configure_security_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.configure_security_events', + { defaultMessage: 'Configure Security Events' } + ), + ], + [ + 'connect_kernel', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.connect_kernel', { + defaultMessage: 'Connect Kernel', + }), + ], + [ + 'detect_async_image_load_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.detect_async_image_load_events', + { defaultMessage: 'Detect Async Image Load Events' } + ), + ], + [ + 'detect_file_open_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.detect_file_open_events', + { defaultMessage: 'Detect File Open Events' } + ), + ], + [ + 'detect_file_write_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.detect_file_write_events', + { defaultMessage: 'Detect File Write Events' } + ), + ], + [ + 'detect_network_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.detect_network_events', + { + defaultMessage: 'Detect Network Events', + } + ), + ], + [ + 'detect_process_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.detect_process_events', + { + defaultMessage: 'Detect Process Events', + } + ), + ], + [ + 'detect_registry_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.detect_registry_events', + { defaultMessage: 'Detect Registry Events' } + ), + ], + [ + 'detect_sync_image_load_events', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.detect_sync_image_load_events', + { defaultMessage: 'Detect Sync Image Load Events' } + ), + ], + [ + 'download_global_artifacts', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.download_global_artifacts', + { defaultMessage: 'Download Global Artifacts' } + ), + ], + [ + 'download_user_artifacts', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.download_user_artifacts', + { defaultMessage: 'Download User Artifacts' } + ), + ], + [ + 'load_config', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.load_config', { + defaultMessage: 'Load Config', + }), + ], + [ + 'load_malware_model', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.load_malware_model', { + defaultMessage: 'Load Malware Model', + }), + ], + [ + 'read_elasticsearch_config', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.read_elasticsearch_config', + { defaultMessage: 'Read Elasticsearch Config' } + ), + ], + [ + 'read_events_config', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_events_config', { + defaultMessage: 'Read Events Config', + }), + ], + [ + 'read_kernel_config', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_kernel_config', { + defaultMessage: 'Read Kernel Config', + }), + ], + [ + 'read_logging_config', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_logging_config', { + defaultMessage: 'Read Logging Config', + }), + ], + [ + 'read_malware_config', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_malware_config', { + defaultMessage: 'Read Malware Config', + }), + ], + [ + 'workflow', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.workflow', { + defaultMessage: 'Workflow', + }), + ], + [ + 'full_disk_access', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.full_disk_access', { + defaultMessage: 'Full Disk Access', + }), + ], + ]) +); + +type PolicyResponseStatus = `${HostPolicyResponseActionStatus}`; + +const policyResponseStatuses = Object.freeze( + new Map([ + [ + 'success', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.success', { + defaultMessage: 'Success', + }), + ], + [ + 'warning', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.warning', { + defaultMessage: 'Warning', + }), + ], + [ + 'failure', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.failed', { + defaultMessage: 'Failed', + }), + ], + [ + 'unsupported', + i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.unsupported', { + defaultMessage: 'Unsupported', + }), + ], + ]) +); + +const descriptions = Object.freeze( + new Map | string, string>([ + [ + 'full_disk_access', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.description.full_disk_access', + { + defaultMessage: 'You must enable full disk access for Elastic Endpoint on your machine. ', + } + ), + ], + ]) +); + +const linkTexts = Object.freeze( + new Map | string, string>([ + [ + 'full_disk_access', + i18n.translate( + 'xpack.securitySolution.endpoint.details.policyResponse.link.text.full_disk_access', + { + defaultMessage: 'Learn more.', + } + ), + ], + ]) +); + +/** + * An array with errors we want to bubble up in policy response + */ +const GENERIC_ACTION_ERRORS: readonly string[] = Object.freeze(['full_disk_access']); + +export class PolicyResponseActionFormatter { + public key: string; + public title: string; + public description: string; + public hasError: boolean; + public errorTitle: string; + public errorDescription?: string; + public status?: string; + public linkText?: string; + public linkUrl?: string; + + constructor( + policyResponseAppliedAction: ImmutableObject, + link?: string + ) { + this.key = policyResponseAppliedAction.name; + this.title = + policyResponseTitles.get(this.key) ?? + this.key.replace(/_/g, ' ').replace(/\b(\w)/g, (m) => m.toUpperCase()); + this.hasError = + policyResponseAppliedAction.status === 'failure' || + policyResponseAppliedAction.status === 'warning'; + this.description = descriptions.get(this.key) || policyResponseAppliedAction.message; + this.errorDescription = descriptions.get(this.key) || policyResponseAppliedAction.message; + this.errorTitle = this.errorDescription ? this.title : policyResponseAppliedAction.name; + this.status = policyResponseStatuses.get(policyResponseAppliedAction.status); + this.linkText = linkTexts.get(this.key); + this.linkUrl = link; + } + + public isGeneric(): boolean { + return GENERIC_ACTION_ERRORS.includes(this.key); + } } diff --git a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx index 1b772f203a0fd..aa1b33e24d8fe 100644 --- a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx @@ -219,4 +219,47 @@ describe('when on the policy response', () => { const component = await renderOpenedTree(); expect(component.getByText('A New Unknown Action')).not.toBeNull(); }); + + it('should not display error callout if status success', async () => { + const policyResponse = createPolicyResponse(); + policyResponse.Endpoint.policy.applied.actions.forEach( + (action) => (action.status = HostPolicyResponseActionStatus.success) + ); + runMock(policyResponse); + const component = await renderOpenedTree(); + expect(component.queryAllByTestId('endpointPolicyResponseErrorCallOut')).toHaveLength(0); + }); + + describe('error callout', () => { + let policyResponse: HostPolicyResponse; + + beforeEach(() => { + policyResponse = createPolicyResponse(HostPolicyResponseActionStatus.failure); + runMock(policyResponse); + }); + + it('should not display link if type is NOT mapped', async () => { + const component = await renderOpenedTree(); + const calloutLink = component.queryByTestId('endpointPolicyResponseErrorCallOutLink'); + expect(calloutLink).toBeNull(); + }); + + it('should display link if type is mapped', async () => { + const action = { + name: 'full_disk_access', + message: + 'You must enable full disk access for Elastic Endpoint on your machine. See our troubleshooting documentation for more information', + status: HostPolicyResponseActionStatus.failure, + }; + + policyResponse.Endpoint.policy.applied.actions.push(action); + policyResponse.Endpoint.policy.applied.response.configurations.malware.concerned_actions.push( + 'full_disk_access' + ); + + const component = await renderOpenedTree(); + const calloutLinks = component.queryAllByTestId('endpointPolicyResponseErrorCallOutLink'); + expect(calloutLinks.length).toEqual(2); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.tsx b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.tsx index 3f30fc5dbb148..d9538e96fc099 100644 --- a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.tsx @@ -4,14 +4,18 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { memo, useEffect, useState } from 'react'; +import React, { memo, useEffect, useState, useMemo } from 'react'; import { EuiEmptyPrompt, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { DocLinksStart } from '@kbn/core/public'; +import { useKibana } from '../../../common/lib/kibana'; import type { HostPolicyResponse } from '../../../../common/endpoint/types'; import { PreferenceFormattedDateFromPrimitive } from '../../../common/components/formatted_date'; import { useGetEndpointPolicyResponse } from '../../hooks/endpoint/use_get_endpoint_policy_response'; import { PolicyResponse } from './policy_response'; import { getFailedOrWarningActionCountFromPolicyResponse } from '../../pages/endpoint_hosts/store/utils'; +import { PolicyResponseActionItem } from './policy_response_action_item'; +import { PolicyResponseActionFormatter } from './policy_response_friendly_names'; export interface PolicyResponseWrapperProps { endpointId: string; @@ -23,6 +27,8 @@ export const PolicyResponseWrapper = memo( ({ endpointId, showRevisionMessage = true, onShowNeedsAttentionBadge }) => { const { data, isLoading, isFetching, isError } = useGetEndpointPolicyResponse(endpointId); + const { docLinks } = useKibana().services; + const [policyResponseConfig, setPolicyResponseConfig] = useState(); const [policyResponseActions, setPolicyResponseActions] = @@ -58,6 +64,34 @@ export const PolicyResponseWrapper = memo( } }, [policyResponseAttentionCount, onShowNeedsAttentionBadge]); + const genericErrors = useMemo(() => { + if (!policyResponseConfig && !policyResponseActions) { + return []; + } + + return policyResponseActions?.reduce( + (acc, currentAction) => { + const policyResponseActionFormatter = new PolicyResponseActionFormatter( + currentAction, + docLinks.links.securitySolution.policyResponseTroubleshooting[ + currentAction.name as keyof DocLinksStart['links']['securitySolution']['policyResponseTroubleshooting'] + ] + ); + + if (policyResponseActionFormatter.isGeneric() && policyResponseActionFormatter.hasError) { + acc.push(policyResponseActionFormatter); + } + + return acc; + }, + [] + ); + }, [ + docLinks.links.securitySolution.policyResponseTroubleshooting, + policyResponseActions, + policyResponseConfig, + ]); + return ( <> {showRevisionMessage && ( @@ -91,11 +125,20 @@ export const PolicyResponseWrapper = memo( )} {isLoading && } {policyResponseConfig !== undefined && policyResponseActions !== undefined && ( - + <> + + + {genericErrors?.map((genericActionError) => ( + + + + + ))} + )} ); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index cc04517d87bbd..3a91ee35269be 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -39,6 +39,7 @@ import { metadataTransformPrefix, ENDPOINT_ACTIONS_INDEX, KILL_PROCESS_ROUTE, + SUSPEND_PROCESS_ROUTE, } from '../../../../common/endpoint/constants'; import { ActionDetails, @@ -369,6 +370,17 @@ describe('Response actions', () => { expect(actionDoc.data.command).toEqual('kill-process'); }); + it('sends the suspend-process command payload from the suspend process route', async () => { + const ctx = await callRoute(SUSPEND_PROCESS_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + }); + const actionDoc: EndpointAction = ( + ctx.core.elasticsearch.client.asInternalUser.index.mock + .calls[0][0] as estypes.IndexRequest + ).body!; + expect(actionDoc.data.command).toEqual('suspend-process'); + }); + describe('With endpoint data streams', () => { it('handles unisolation', async () => { const ctx = await callRoute( @@ -454,6 +466,35 @@ describe('Response actions', () => { expect(responseBody.action).toBeUndefined(); }); + it('handles suspend-process', async () => { + const parameters = { entity_id: 1234 }; + const ctx = await callRoute( + SUSPEND_PROCESS_ROUTE, + { + body: { endpoint_ids: ['XYZ'], parameters }, + }, + { endpointDsExists: true } + ); + const indexDoc = ctx.core.elasticsearch.client.asInternalUser.index; + const actionDocs: [ + { index: string; body?: LogsEndpointAction }, + { index: string; body?: EndpointAction } + ] = [ + indexDoc.mock.calls[0][0] as estypes.IndexRequest, + indexDoc.mock.calls[1][0] as estypes.IndexRequest, + ]; + + expect(actionDocs[0].index).toEqual(ENDPOINT_ACTIONS_INDEX); + expect(actionDocs[1].index).toEqual(AGENT_ACTIONS_INDEX); + expect(actionDocs[0].body!.EndpointActions.data.command).toEqual('suspend-process'); + expect(actionDocs[1].body!.data.command).toEqual('suspend-process'); + expect(actionDocs[1].body!.data.parameters).toEqual(parameters); + + expect(mockResponse.ok).toBeCalled(); + const responseBody = mockResponse.ok.mock.calls[0][0]?.body as ResponseActionApiResponse; + expect(responseBody.action).toBeUndefined(); + }); + it('handles errors', async () => { const ErrMessage = 'Uh oh!'; await callRoute( diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts index 91bec0ad66101..5f7ad42127f7c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts @@ -16,7 +16,7 @@ import { CommentType } from '@kbn/cases-plugin/common'; import { HostIsolationRequestSchema, - KillProcessRequestSchema, + KillOrSuspendProcessRequestSchema, ResponseActionBodySchema, } from '../../../../common/endpoint/schema/actions'; import { APP_ID } from '../../../../common/constants'; @@ -27,6 +27,7 @@ import { ENDPOINT_ACTION_RESPONSES_DS, failedFleetActionErrorCode, KILL_PROCESS_ROUTE, + SUSPEND_PROCESS_ROUTE, } from '../../../../common/endpoint/constants'; import type { EndpointAction, @@ -36,7 +37,7 @@ import type { LogsEndpointAction, LogsEndpointActionResponse, ResponseActions, - KillProcessParameters, + ResponseActionParametersWithPidOrEntityId, } from '../../../../common/endpoint/types'; import type { SecuritySolutionPluginRouter, @@ -83,13 +84,32 @@ export function registerResponseActionRoutes( router.post( { path: KILL_PROCESS_ROUTE, - validate: KillProcessRequestSchema, + validate: KillOrSuspendProcessRequestSchema, options: { authRequired: true, tags: ['access:securitySolution'] }, }, withEndpointAuthz( { all: ['canKillProcess'] }, logger, - responseActionRequestHandler(endpointContext, 'kill-process') + responseActionRequestHandler( + endpointContext, + 'kill-process' + ) + ) + ); + + router.post( + { + path: SUSPEND_PROCESS_ROUTE, + validate: KillOrSuspendProcessRequestSchema, + options: { authRequired: true, tags: ['access:securitySolution'] }, + }, + withEndpointAuthz( + { all: ['canSuspendProcess'] }, + logger, + responseActionRequestHandler( + endpointContext, + 'suspend-process' + ) ) ); } @@ -98,6 +118,7 @@ const commandToFeatureKeyMap = new Map([ ['isolate', 'HOST_ISOLATION'], ['unisolate', 'HOST_ISOLATION'], ['kill-process', 'KILL_PROCESS'], + ['suspend-process', 'SUSPEND_PROCESS'], ]); const returnActionIdCommands: ResponseActions[] = ['isolate', 'unisolate']; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts b/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts index fa03aaf039e50..4171bb803fe65 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts @@ -19,6 +19,7 @@ const FEATURES = { MEMORY_THREAT_PROTECTION: 'Memory threat protection', BEHAVIOR_PROTECTION: 'Behavior protection', KILL_PROCESS: 'Kill process', + SUSPEND_PROCESS: 'Suspend process', } as const; export type FeatureKeys = keyof typeof FEATURES; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts index 04e8f2130e88f..fa9b5cab1bfd7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts @@ -39,6 +39,7 @@ describe('schedule_notification_actions', () => { riskScore: 80, riskScoreMapping: [], ruleNameOverride: undefined, + dataViewId: undefined, outputIndex: 'output-1', severity: 'high', severityMapping: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts index 72ddb96301c47..cd672af86032b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts @@ -52,6 +52,7 @@ describe('schedule_throttle_notification_actions', () => { severityMapping: [], threat: [], timestampOverride: undefined, + dataViewId: undefined, to: 'now', type: 'query', references: ['http://www.example.com'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 37d1bc445a585..8cb03c7790733 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -90,6 +90,7 @@ export const patchRulesBulkRoute = ( rule_id: ruleId, id, index, + data_view_id: dataViewId, interval, max_signals: maxSignals, risk_score: riskScore, @@ -169,6 +170,7 @@ export const patchRulesBulkRoute = ( meta, filters, index, + dataViewId, interval, maxSignals, riskScore, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts index 47a2b38691d08..d704f5678f53d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -69,6 +69,7 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP rule_id: ruleId, id, index, + data_view_id: dataViewId, interval, max_signals: maxSignals, risk_score: riskScore, @@ -157,6 +158,7 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP filters, rule: migratedRule, index, + dataViewId, interval, maxSignals, riskScore, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts index e5442175cd72f..dd98137b3b2e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts @@ -117,6 +117,7 @@ export const importRules = async ({ filters: filtersRest, rule_id: ruleId, index, + data_view_id: dataViewId, interval, max_signals: maxSignals, related_integrations: relatedIntegrations, @@ -196,6 +197,7 @@ export const importRules = async ({ filters, ruleId, index, + dataViewId, interval, maxSignals, name, @@ -260,6 +262,7 @@ export const importRules = async ({ filters, rule: migratedRule, index, + dataViewId, interval, maxSignals, relatedIntegrations, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index 4501c81a82aaf..e19a178c54eec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -11,6 +11,8 @@ import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils'; import agent from 'elastic-apm-node'; import { createPersistenceRuleTypeWrapper } from '@kbn/rule-registry-plugin/server'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + import { buildRuleMessageFactory } from './factories/build_rule_message_factory'; import { checkPrivilegesFromEsClient, @@ -36,10 +38,19 @@ import { scheduleThrottledNotificationActions } from '../notifications/schedule_ import aadFieldConversion from '../routes/index/signal_aad_mapping.json'; import { extractReferences, injectReferences } from '../signals/saved_object_references'; import { withSecuritySpan } from '../../../utils/with_security_span'; +import { getInputIndex } from '../signals/get_input_output_index'; /* eslint-disable complexity */ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = - ({ lists, logger, config, ruleDataClient, eventLogService, ruleExecutionLoggerFactory }) => + ({ + lists, + logger, + config, + ruleDataClient, + eventLogService, + ruleExecutionLoggerFactory, + version, + }) => (type) => { const { alertIgnoreFields: ignoreFields, alertMergeStrategy: mergeStrategy } = config; const persistenceRuleType = createPersistenceRuleTypeWrapper({ ruleDataClient, logger }); @@ -67,6 +78,9 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = rule, } = options; let runState = state; + let hasError = false; + let inputIndex: string[] = []; + let runtimeMappings: estypes.MappingRuntimeFields | undefined; const { from, maxSignals, meta, ruleId, timestampOverride, to } = params; const { alertWithPersistence, @@ -130,17 +144,47 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = id: alertId, }; + /** + * Data Views Logic + * Use of data views is supported for all rules other than ML. + * Rules can define both a data view and index pattern, but on execution: + * - Data view is used if it is defined + * - Rule exits early if data view defined is not found (ie: it's been deleted) + * - If no data view defined, falls to using existing index logic + */ + if (!isMachineLearningParams(params)) { + try { + const { index, runtimeMappings: dataViewRuntimeMappings } = await getInputIndex({ + index: params.index, + services, + version, + logger, + ruleId: params.ruleId, + dataViewId: params.dataViewId, + }); + + inputIndex = index ?? []; + runtimeMappings = dataViewRuntimeMappings; + } catch (exc) { + const errorMessage = buildRuleMessage(`Check for indices to search failed ${exc}`); + logger.error(errorMessage); + await ruleExecutionLogger.logStatusChange({ + newStatus: RuleExecutionStatus.failed, + message: errorMessage, + }); + + return result.state; + } + } + // check if rule has permissions to access given index pattern // move this collection of lines into a function in utils // so that we can use it in create rules route, bulk, etc. try { if (!isMachineLearningParams(params)) { - const index = params.index; const hasTimestampOverride = !!timestampOverride; - const inputIndices = params.index ?? []; - - const privileges = await checkPrivilegesFromEsClient(esClient, inputIndices); + const privileges = await checkPrivilegesFromEsClient(esClient, inputIndex); wroteWarningStatus = await hasReadIndexPrivileges({ privileges, @@ -154,19 +198,21 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const timestampFieldCaps = await withSecuritySpan('fieldCaps', () => services.scopedClusterClient.asCurrentUser.fieldCaps( { - index, + index: inputIndex, fields: hasTimestampOverride ? ['@timestamp', timestampOverride] : ['@timestamp'], include_unmapped: true, + runtime_mappings: runtimeMappings, }, { meta: true } ) ); + wroteWarningStatus = await hasTimestampFields({ timestampField: hasTimestampOverride ? timestampOverride : '@timestamp', timestampFieldCapsResponse: timestampFieldCaps, - inputIndices, + inputIndices: inputIndex, ruleExecutionLogger, logger, buildRuleMessage, @@ -182,7 +228,6 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = }); wroteWarningStatus = true; } - let hasError = false; const { tuples, remainingGap } = getRuleRangeTuples({ logger, previousStartedAt, @@ -236,6 +281,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = mergeStrategy, completeRule, spaceId, + indicesToQuery: inputIndex, }); const wrapSequences = wrapSequencesFactory({ @@ -244,6 +290,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = mergeStrategy, completeRule, spaceId, + indicesToQuery: inputIndex, }); for (const tuple of tuples) { @@ -252,6 +299,8 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = services, state: runState, runOpts: { + inputIndex, + runtimeMappings, buildRuleMessage, bulkCreate, exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts index b856eaa8b9686..0f42fab352cfc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts @@ -61,12 +61,23 @@ export const createEqlAlertType = ( producer: SERVER_APP_ID, async executor(execOptions) { const { - runOpts: { bulkCreate, exceptionItems, completeRule, tuple, wrapHits, wrapSequences }, + runOpts: { + inputIndex, + runtimeMappings, + bulkCreate, + exceptionItems, + completeRule, + tuple, + wrapHits, + wrapSequences, + }, services, state, } = execOptions; const result = await eqlExecutor({ + inputIndex, + runtimeMappings, bulkCreate, exceptionItems, experimentalFeatures, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts index 083f495366480..725ec2958d339 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts @@ -39,6 +39,7 @@ import { ALERT_DEPTH, ALERT_ORIGINAL_EVENT, ALERT_BUILDING_BLOCK_TYPE, + ALERT_RULE_INDICES, } from '../../../../../../common/field_maps/field_names'; import { getCompleteRuleMock, getQueryRuleParams } from '../../../schemas/rule_schemas.mock'; @@ -59,7 +60,13 @@ describe('buildAlert', () => { const completeRule = getCompleteRuleMock(getQueryRuleParams()); const reason = 'alert reasonable reason'; const alert = { - ...buildAlert([doc], completeRule, SPACE_ID, reason), + ...buildAlert( + [doc], + completeRule, + SPACE_ID, + reason, + completeRule.ruleParams.index as string[] + ), ...additionalAlertFields(doc), }; const timestamp = alert[TIMESTAMP]; @@ -151,6 +158,7 @@ describe('buildAlert', () => { query: 'user.name: root or user.name: admin', filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }], }, + [ALERT_RULE_INDICES]: completeRule.ruleParams.index, ...flattenWithPrefix(ALERT_RULE_NAMESPACE, { actions: [], author: ['Elastic'], @@ -232,7 +240,13 @@ describe('buildAlert', () => { const completeRule = getCompleteRuleMock(getQueryRuleParams()); const reason = 'alert reasonable reason'; const alert = { - ...buildAlert([doc], completeRule, SPACE_ID, reason), + ...buildAlert( + [doc], + completeRule, + SPACE_ID, + reason, + completeRule.ruleParams.index as string[] + ), ...additionalAlertFields(doc), }; const timestamp = alert[TIMESTAMP]; @@ -251,6 +265,7 @@ describe('buildAlert', () => { }, ], [ALERT_ORIGINAL_TIME]: '2020-04-20T21:27:45.000Z', + [ALERT_RULE_INDICES]: completeRule.ruleParams.index, ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { action: 'socket_opened', dataset: 'socket', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts index 8492ec6cd2c26..dc111cf229a5c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts @@ -60,6 +60,7 @@ import { ALERT_ORIGINAL_EVENT, ALERT_BUILDING_BLOCK_TYPE, ALERT_RULE_ACTIONS, + ALERT_RULE_INDICES, ALERT_RULE_THROTTLE, ALERT_RULE_TIMELINE_ID, ALERT_RULE_TIMELINE_TITLE, @@ -130,12 +131,14 @@ export const buildAncestors = (doc: SimpleHit): AncestorLatest[] => { * @param rule The rule that is generating the new alert. * @param spaceId The space ID in which the rule was executed. * @param reason Human readable string summarizing alert. + * @param indicesToQuery Array of index patterns searched by the rule. */ export const buildAlert = ( docs: SimpleHit[], completeRule: CompleteRule, spaceId: string | null | undefined, reason: string, + indicesToQuery: string[], overrides?: { nameOverride: string; severityOverride: string; @@ -204,6 +207,7 @@ export const buildAlert = ( [ALERT_RULE_FROM]: params.from, [ALERT_RULE_IMMUTABLE]: params.immutable, [ALERT_RULE_INTERVAL]: schedule.interval, + [ALERT_RULE_INDICES]: indicesToQuery, [ALERT_RULE_LICENSE]: params.license, [ALERT_RULE_MAX_SIGNALS]: params.maxSignals, [ALERT_RULE_NAME]: overrides?.nameOverride ?? name, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts index 29ca39442f033..7b3e2dc1359da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts @@ -58,7 +58,8 @@ describe('buildAlert', () => { completeRule, 'allFields', SPACE_ID, - jest.fn() + jest.fn(), + completeRule.ruleParams.index as string[] ); expect(alertGroup.length).toEqual(3); expect(alertGroup[0]).toEqual( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts index 445f6944a1835..f20d45bd20631 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts @@ -42,7 +42,8 @@ export const buildAlertGroupFromSequence = ( completeRule: CompleteRule, mergeStrategy: ConfigType['alertMergeStrategy'], spaceId: string | null | undefined, - buildReasonMessage: BuildReasonMessage + buildReasonMessage: BuildReasonMessage, + indicesToQuery: string[] ): Array> => { const ancestors: Ancestor[] = sequence.events.flatMap((event) => buildAncestors(event)); if (ancestors.some((ancestor) => ancestor?.rule === completeRule.alertId)) { @@ -54,7 +55,16 @@ export const buildAlertGroupFromSequence = ( let baseAlerts: BaseFieldsLatest[] = []; try { baseAlerts = sequence.events.map((event) => - buildBulkBody(spaceId, completeRule, event, mergeStrategy, [], false, buildReasonMessage) + buildBulkBody( + spaceId, + completeRule, + event, + mergeStrategy, + [], + false, + buildReasonMessage, + indicesToQuery + ) ); } catch (error) { logger.error(error); @@ -78,7 +88,13 @@ export const buildAlertGroupFromSequence = ( // Now that we have an array of building blocks for the events in the sequence, // we can build the signal that links the building blocks together // and also insert the group id (which is also the "shell" signal _id) in each building block - const shellAlert = buildAlertRoot(wrappedBaseFields, completeRule, spaceId, buildReasonMessage); + const shellAlert = buildAlertRoot( + wrappedBaseFields, + completeRule, + spaceId, + buildReasonMessage, + indicesToQuery + ); const sequenceAlert: WrappedFieldsLatest = { _id: shellAlert[ALERT_UUID], _index: '', @@ -105,7 +121,8 @@ export const buildAlertRoot = ( wrappedBuildingBlocks: Array>, completeRule: CompleteRule, spaceId: string | null | undefined, - buildReasonMessage: BuildReasonMessage + buildReasonMessage: BuildReasonMessage, + indicesToQuery: string[] ): EqlShellFieldsLatest => { const mergedAlerts = objectArrayIntersection(wrappedBuildingBlocks.map((alert) => alert._source)); const reason = buildReasonMessage({ @@ -113,7 +130,7 @@ export const buildAlertRoot = ( severity: completeRule.ruleParams.severity, mergedDoc: mergedAlerts as SignalSourceHit, }); - const doc = buildAlert(wrappedBuildingBlocks, completeRule, spaceId, reason); + const doc = buildAlert(wrappedBuildingBlocks, completeRule, spaceId, reason, indicesToQuery); const alertId = generateAlertId(doc); return { ...mergedAlerts, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts index a73471ec83ada..a808ac4f91046 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts @@ -49,7 +49,8 @@ export const buildBulkBody = ( mergeStrategy: ConfigType['alertMergeStrategy'], ignoreFields: ConfigType['alertIgnoreFields'], applyOverrides: boolean, - buildReasonMessage: BuildReasonMessage + buildReasonMessage: BuildReasonMessage, + indicesToQuery: string[] ): BaseFieldsLatest => { const mergedDoc = getMergeStrategy(mergeStrategy)({ doc, ignoreFields }); const eventFields = buildEventTypeAlert(mergedDoc); @@ -85,7 +86,7 @@ export const buildBulkBody = ( return { ...filteredSource, ...eventFields, - ...buildAlert([mergedDoc], completeRule, spaceId, reason, overrides), + ...buildAlert([mergedDoc], completeRule, spaceId, reason, indicesToQuery, overrides), ...additionalAlertFields({ ...mergedDoc, _source: { ...mergedDoc._source, ...eventFields } }), }; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts index 2c5c9cca02080..4ac595e4ff921 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts @@ -25,11 +25,13 @@ export const wrapHitsFactory = ignoreFields, mergeStrategy, spaceId, + indicesToQuery, }: { completeRule: CompleteRule; ignoreFields: ConfigType['alertIgnoreFields']; mergeStrategy: ConfigType['alertMergeStrategy']; spaceId: string | null | undefined; + indicesToQuery: string[]; }) => ( events: Array>, @@ -53,7 +55,8 @@ export const wrapHitsFactory = mergeStrategy, ignoreFields, true, - buildReasonMessage + buildReasonMessage, + indicesToQuery ), [ALERT_UUID]: id, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts index 5bea6e346f438..fb95bcf4cea64 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts @@ -23,12 +23,14 @@ export const wrapSequencesFactory = ignoreFields, mergeStrategy, spaceId, + indicesToQuery, }: { logger: Logger; completeRule: CompleteRule; ignoreFields: ConfigType['alertIgnoreFields']; mergeStrategy: ConfigType['alertMergeStrategy']; spaceId: string | null | undefined; + indicesToQuery: string[]; }): WrapSequences => (sequences, buildReasonMessage) => sequences.reduce( @@ -40,7 +42,8 @@ export const wrapSequencesFactory = completeRule, mergeStrategy, spaceId, - buildReasonMessage + buildReasonMessage, + indicesToQuery ), ], [] diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts index a8c6d8adf881a..579fc947fa51e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts @@ -63,6 +63,8 @@ export const createIndicatorMatchAlertType = ( async executor(execOptions) { const { runOpts: { + inputIndex, + runtimeMappings, buildRuleMessage, bulkCreate, exceptionItems, @@ -77,6 +79,8 @@ export const createIndicatorMatchAlertType = ( } = execOptions; const result = await threatMatchExecutor({ + inputIndex, + runtimeMappings, buildRuleMessage, bulkCreate, exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts index 78741552bb7e7..526c686961f12 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts @@ -41,6 +41,7 @@ describe('Custom Query Alerts', () => { ruleDataClient, eventLogService, ruleExecutionLoggerFactory: () => ruleExecutionLogMock.forExecutors.create(), + version: '8.3', }); const eventsTelemetry = createMockTelemetryEventsSender(true); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts index 9ac86e7de518d..c9626edc19d12 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts @@ -62,6 +62,8 @@ export const createQueryAlertType = ( async executor(execOptions) { const { runOpts: { + inputIndex, + runtimeMappings, buildRuleMessage, bulkCreate, exceptionItems, @@ -89,6 +91,8 @@ export const createQueryAlertType = ( tuple, version, wrapHits, + inputIndex, + runtimeMappings, }); return { ...result, state }; }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts index fbf1c65fa81f1..896ea445134e0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts @@ -66,6 +66,8 @@ export const createSavedQueryAlertType = ( async executor(execOptions) { const { runOpts: { + inputIndex, + runtimeMappings, buildRuleMessage, bulkCreate, exceptionItems, @@ -80,6 +82,8 @@ export const createSavedQueryAlertType = ( } = execOptions; const result = await queryExecutor({ + inputIndex, + runtimeMappings, buildRuleMessage, bulkCreate, exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts index 317f80c8ee948..6906e91706f02 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts @@ -71,6 +71,8 @@ export const createThresholdAlertType = ( tuple, wrapHits, ruleDataReader, + inputIndex, + runtimeMappings, }, services, startedAt, @@ -91,6 +93,8 @@ export const createThresholdAlertType = ( version, wrapHits, ruleDataReader, + inputIndex, + runtimeMappings, }); return result; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index 62dc5b3134205..e32c193262476 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -9,6 +9,7 @@ import { Moment } from 'moment'; import { Logger } from '@kbn/logging'; import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { RuleExecutorOptions, RuleType } from '@kbn/alerting-plugin/server'; import { @@ -66,6 +67,8 @@ export interface RunOpts { wrapHits: WrapHits; wrapSequences: WrapSequences; ruleDataReader: IRuleDataReader; + inputIndex: string[]; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } export type SecurityAlertType< @@ -98,6 +101,7 @@ export interface CreateSecurityRuleTypeWrapperProps { ruleDataClient: IRuleDataClient; eventLogService: IEventLogService; ruleExecutionLoggerFactory: RuleExecutionLogForExecutorsFactory; + version: string; } export type CreateSecurityRuleTypeWrapper = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_action_edit.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_action_edit.test.ts new file mode 100644 index 0000000000000..84e7db189aa37 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_action_edit.test.ts @@ -0,0 +1,249 @@ +/* + * 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 { + addItemsToArray, + deleteItemsFromArray, + applyBulkActionEditToRule, +} from './bulk_action_edit'; +import { BulkActionEditType } from '../../../../common/detection_engine/schemas/common/schemas'; +import { RuleAlertType } from './types'; +describe('bulk_action_edit', () => { + describe('addItemsToArray', () => { + test('should add single item to array', () => { + expect(addItemsToArray(['a', 'b', 'c'], ['d'])).toEqual(['a', 'b', 'c', 'd']); + }); + + test('should add multiple items to array', () => { + expect(addItemsToArray(['a', 'b', 'c'], ['d', 'e'])).toEqual(['a', 'b', 'c', 'd', 'e']); + }); + + test('should not allow to add duplicated items', () => { + expect(addItemsToArray(['a', 'c'], ['b', 'c'])).toEqual(['a', 'c', 'b']); + }); + }); + + describe('deleteItemsFromArray', () => { + test('should remove single item from array', () => { + expect(deleteItemsFromArray(['a', 'b', 'c'], ['c'])).toEqual(['a', 'b']); + }); + + test('should remove multiple items from array', () => { + expect(deleteItemsFromArray(['a', 'b', 'c'], ['b', 'c'])).toEqual(['a']); + }); + + test('should return array unchanged if items to remove absent in array', () => { + expect(deleteItemsFromArray(['a', 'c'], ['x', 'z'])).toEqual(['a', 'c']); + }); + }); + + describe('applyBulkActionEditToRule', () => { + const getRuleMock = (params = {}) => ({ + tags: ['tag1', 'tag2'], + params: { type: 'query', index: ['initial-index-*'], ...params }, + }); + describe('tags', () => { + test('should add new tags to rule', () => { + const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, { + type: BulkActionEditType.add_tags, + value: ['new_tag'], + }); + expect(editedRule.tags).toEqual(['tag1', 'tag2', 'new_tag']); + }); + test('should remove tag from rule', () => { + const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, { + type: BulkActionEditType.delete_tags, + value: ['tag1'], + }); + expect(editedRule.tags).toEqual(['tag2']); + }); + + test('should rewrite tags in rule', () => { + const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, { + type: BulkActionEditType.set_tags, + value: ['tag_r_1', 'tag_r_2'], + }); + expect(editedRule.tags).toEqual(['tag_r_1', 'tag_r_2']); + }); + }); + + describe('index_patterns', () => { + test('should add new index pattern to rule', () => { + const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, { + type: BulkActionEditType.add_index_patterns, + value: ['my-index-*'], + }); + expect(editedRule.params).toHaveProperty('index', ['initial-index-*', 'my-index-*']); + }); + test('should remove index pattern from rule', () => { + const editedRule = applyBulkActionEditToRule( + { params: { index: ['initial-index-*', 'index-2-*'] } } as RuleAlertType, + { + type: BulkActionEditType.delete_index_patterns, + value: ['index-2-*'], + } + ); + expect(editedRule.params).toHaveProperty('index', ['initial-index-*']); + }); + + test('should rewrite index pattern in rule', () => { + const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, { + type: BulkActionEditType.set_index_patterns, + value: ['index'], + }); + expect(editedRule.params).toHaveProperty('index', ['index']); + }); + + test('should throw error on adding index pattern if rule is of machine learning type', () => { + expect(() => + applyBulkActionEditToRule({ params: { type: 'machine_learning' } } as RuleAlertType, { + type: BulkActionEditType.add_index_patterns, + value: ['my-index-*'], + }) + ).toThrow( + "Index patterns can't be added. Machine learning rule doesn't have index patterns property" + ); + }); + + test('should throw error on deleting index pattern if rule is of machine learning type', () => { + expect(() => + applyBulkActionEditToRule({ params: { type: 'machine_learning' } } as RuleAlertType, { + type: BulkActionEditType.delete_index_patterns, + value: ['my-index-*'], + }) + ).toThrow( + "Index patterns can't be deleted. Machine learning rule doesn't have index patterns property" + ); + }); + + test('should throw error on overwriting index pattern if rule is of machine learning type', () => { + expect(() => + applyBulkActionEditToRule({ params: { type: 'machine_learning' } } as RuleAlertType, { + type: BulkActionEditType.set_index_patterns, + value: ['my-index-*'], + }) + ).toThrow( + "Index patterns can't be overwritten. Machine learning rule doesn't have index patterns property" + ); + }); + + test('should throw error if all index patterns are deleted', () => { + expect(() => + applyBulkActionEditToRule({ params: { index: ['my-index-*'] } } as RuleAlertType, { + type: BulkActionEditType.delete_index_patterns, + value: ['my-index-*'], + }) + ).toThrow("Can't delete all index patterns. At least one index pattern must be left"); + }); + + test('should throw error if all index patterns are rewritten with empty list', () => { + expect(() => + applyBulkActionEditToRule({ params: { index: ['my-index-*'] } } as RuleAlertType, { + type: BulkActionEditType.set_index_patterns, + value: [], + }) + ).toThrow("Index patterns can't be overwritten with empty list"); + }); + + describe('overwriteDataViews', () => { + describe('overwriteDataViews is true', () => { + test('should add new index pattern to rule', () => { + const editedRule = applyBulkActionEditToRule( + getRuleMock({ dataViewId: 'logs-*', index: [] }) as RuleAlertType, + { + type: BulkActionEditType.add_index_patterns, + value: ['my-index-*'], + overwriteDataViews: true, + } + ); + expect(editedRule.params).toHaveProperty('index', ['my-index-*']); + expect(editedRule.params).toHaveProperty('dataViewId', undefined); + }); + + test('should remove index pattern from rule', () => { + const editedRule = applyBulkActionEditToRule( + getRuleMock({ dataViewId: 'logs-*', index: ['index', 'index-2-*'] }) as RuleAlertType, + { + type: BulkActionEditType.delete_index_patterns, + value: ['index-2-*'], + overwriteDataViews: true, + } + ); + expect(editedRule.params).toHaveProperty('index', ['index']); + expect(editedRule.params).toHaveProperty('dataViewId', undefined); + }); + + test('should rewrite index pattern in rule', () => { + const editedRule = applyBulkActionEditToRule( + getRuleMock({ dataViewId: 'logs-*', index: ['index', 'index-1'] }) as RuleAlertType, + { + type: BulkActionEditType.set_index_patterns, + value: ['index'], + overwriteDataViews: true, + } + ); + expect(editedRule.params).toHaveProperty('index', ['index']); + expect(editedRule.params).toHaveProperty('dataViewId', undefined); + }); + }); + + describe('overwriteDataViews is false', () => { + test('should NOT remove dataViewId from rule if adding index pattern', () => { + const editedRule = applyBulkActionEditToRule( + getRuleMock({ dataViewId: 'logs-*', index: [] }) as RuleAlertType, + { + type: BulkActionEditType.add_index_patterns, + value: ['my-index-*'], + overwriteDataViews: false, + } + ); + expect(editedRule.params).toHaveProperty('dataViewId', 'logs-*'); + }); + + test('should NOT remove dataViewId from rule if deleting index pattern', () => { + const editedRule = applyBulkActionEditToRule( + getRuleMock({ dataViewId: 'logs-*', index: ['index', 'index-2-*'] }) as RuleAlertType, + { + type: BulkActionEditType.delete_index_patterns, + value: ['index-2-*'], + overwriteDataViews: false, + } + ); + expect(editedRule.params).toHaveProperty('dataViewId', 'logs-*'); + }); + + test('should NOT remove dataViewId from rule if setting index pattern', () => { + const editedRule = applyBulkActionEditToRule( + getRuleMock({ dataViewId: 'logs-*', index: [] }) as RuleAlertType, + { + type: BulkActionEditType.set_index_patterns, + value: ['index'], + overwriteDataViews: false, + } + ); + expect(editedRule.params).toHaveProperty('dataViewId', 'logs-*'); + }); + }); + }); + }); + + describe('timeline', () => { + test('should set timeline', () => { + const editedRule = applyBulkActionEditToRule(getRuleMock() as RuleAlertType, { + type: BulkActionEditType.set_timeline, + value: { + timeline_id: '91832785-286d-4ebe-b884-1a208d111a70', + timeline_title: 'Test timeline', + }, + }); + + expect(editedRule.params.timelineId).toBe('91832785-286d-4ebe-b884-1a208d111a70'); + expect(editedRule.params.timelineTitle).toBe('Test timeline'); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_action_edit.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_action_edit.ts new file mode 100644 index 0000000000000..dd87a572dcef8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_action_edit.ts @@ -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 { RuleAlertType } from './types'; + +import { + BulkActionEditPayload, + BulkActionEditType, +} from '../../../../common/detection_engine/schemas/common/schemas'; + +import { invariant } from '../../../../common/utils/invariant'; +import { isMachineLearningParams } from '../signals/utils'; + +export const addItemsToArray = (arr: T[], items: T[]): T[] => + Array.from(new Set([...arr, ...items])); + +export const deleteItemsFromArray = (arr: T[], items: T[]): T[] => { + const itemsSet = new Set(items); + return arr.filter((item) => !itemsSet.has(item)); +}; + +export const applyBulkActionEditToRule = ( + existingRule: RuleAlertType, + action: BulkActionEditPayload +): RuleAlertType => { + const rule = { ...existingRule, params: { ...existingRule.params } }; + + switch (action.type) { + // tags actions + case BulkActionEditType.add_tags: + rule.tags = addItemsToArray(rule.tags ?? [], action.value); + break; + + case BulkActionEditType.delete_tags: + rule.tags = deleteItemsFromArray(rule.tags ?? [], action.value); + break; + + case BulkActionEditType.set_tags: + rule.tags = action.value; + break; + + // index_patterns actions + // index pattern is not present in machine learning rule type, so we throw error on it + case BulkActionEditType.add_index_patterns: + invariant( + rule.params.type !== 'machine_learning', + "Index patterns can't be added. Machine learning rule doesn't have index patterns property" + ); + + if (!isMachineLearningParams(rule.params) && action.overwriteDataViews) { + rule.params.dataViewId = undefined; + } + rule.params.index = addItemsToArray(rule.params.index ?? [], action.value); + + break; + + case BulkActionEditType.delete_index_patterns: + invariant( + rule.params.type !== 'machine_learning', + "Index patterns can't be deleted. Machine learning rule doesn't have index patterns property" + ); + + if (!isMachineLearningParams(rule.params) && action.overwriteDataViews) { + rule.params.dataViewId = undefined; + } + rule.params.index = deleteItemsFromArray(rule.params.index ?? [], action.value); + + invariant( + rule.params.index.length !== 0, + "Can't delete all index patterns. At least one index pattern must be left" + ); + + break; + + case BulkActionEditType.set_index_patterns: + invariant( + rule.params.type !== 'machine_learning', + "Index patterns can't be overwritten. Machine learning rule doesn't have index patterns property" + ); + invariant(action.value.length !== 0, "Index patterns can't be overwritten with empty list"); + + if (!isMachineLearningParams(rule.params) && action.overwriteDataViews) { + rule.params.dataViewId = undefined; + } + rule.params.index = action.value; + + break; + + // timeline actions + case BulkActionEditType.set_timeline: + const timelineId = action.value.timeline_id.trim() || undefined; + const timelineTitle = timelineId ? action.value.timeline_title : undefined; + + rule.params.timelineId = timelineId; + rule.params.timelineTitle = timelineTitle; + break; + } + + return rule; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts index 788f832eec82c..00a8b381b05d7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts @@ -32,6 +32,7 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ ruleId: 'rule-1', immutable: false, index: ['index-123'], + dataViewId: undefined, interval: '5m', maxSignals: 100, relatedIntegrations: undefined, @@ -90,6 +91,7 @@ export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ ruleId: 'rule-1', immutable: false, index: ['index-123'], + dataViewId: undefined, interval: '5m', maxSignals: 100, relatedIntegrations: undefined, @@ -141,6 +143,7 @@ export const getCreateThreatMatchRulesOptionsMock = (): CreateRulesOptions => ({ from: 'now-1m', immutable: false, index: ['*'], + dataViewId: undefined, interval: '5m', itemsPerSearch: undefined, language: 'kuery', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index ef6299d68ac4c..9e32ab79f2d07 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -46,6 +46,7 @@ export const createRules = async ({ ruleId, immutable, index, + dataViewId, interval, maxSignals, relatedIntegrations, @@ -97,6 +98,7 @@ export const createRules = async ({ description, ruleId, index, + dataViewId, timestampField, eventCategoryOverride, tiebreakerField, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts index cab22e136f529..20f028fb1f703 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts @@ -58,6 +58,7 @@ describe('duplicateRule', () => { timelineTitle: undefined, ruleNameOverride: undefined, timestampOverride: undefined, + dataViewId: undefined, }, schedule: { interval: '5m', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts index 2de6028b53517..7ca213817cf0c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -39,6 +39,7 @@ export const installPrepackagedRules = ( filters: filtersObject, rule_id: ruleId, index, + data_view_id: dataViewId, interval, max_signals: maxSignals, related_integrations: relatedIntegrations, @@ -100,6 +101,7 @@ export const installPrepackagedRules = ( filters, ruleId, index, + dataViewId, interval, maxSignals, relatedIntegrations, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index 49f8bef7faaff..5fc98e8cd30fc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -54,6 +54,7 @@ export const patchRules = async ({ filters, from, index, + dataViewId, interval, maxSignals, relatedIntegrations, @@ -113,6 +114,7 @@ export const patchRules = async ({ filters, from, index, + dataViewId, interval, maxSignals, relatedIntegrations, @@ -170,6 +172,7 @@ export const patchRules = async ({ meta, filters, index, + dataViewId, maxSignals, relatedIntegrations, requiredFields, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index 268972e287bcb..3d5b8849fc6b1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -95,6 +95,7 @@ import { EventCategoryOverrideOrUndefined, TiebreakerFieldOrUndefined, NamespaceOrUndefined, + DataViewIdOrUndefined, RelatedIntegrationArray, RequiredFieldArray, SetupGuide, @@ -165,6 +166,7 @@ export interface CreateRulesOptions { ruleId: RuleId; immutable: Immutable; index: IndexOrUndefined; + dataViewId: DataViewIdOrUndefined; interval: Interval; license: LicenseOrUndefined; maxSignals: MaxSignals; @@ -234,6 +236,7 @@ interface PatchRulesFieldsOptions { machineLearningJobId: MachineLearningJobIdOrUndefined; filters: PartialFilter[]; index: IndexOrUndefined; + dataViewId: DataViewIdOrUndefined; interval: IntervalOrUndefined; license: LicenseOrUndefined; maxSignals: MaxSignalsOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts index b10d08ef35c84..f64383d36e11e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -83,6 +83,7 @@ export const createPromises = ( filters: filtersObject, rule_id: ruleId, index, + data_view_id: dataViewId, interval, max_signals: maxSignals, related_integrations: relatedIntegrations, @@ -174,6 +175,7 @@ export const createPromises = ( filters, ruleId, index, + dataViewId, interval, maxSignals, relatedIntegrations, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index b2f4c5ce3ede8..e59b135b636e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -111,6 +111,7 @@ describe('utils', () => { calculateVersion(true, 1, { author: [], buildingBlockType: undefined, + dataViewId: undefined, description: 'some description change', timestampField: undefined, eventCategoryOverride: undefined, @@ -168,6 +169,7 @@ describe('utils', () => { calculateVersion(true, 2, { author: [], buildingBlockType: undefined, + dataViewId: undefined, description: 'some description change', timestampField: undefined, eventCategoryOverride: undefined, @@ -225,6 +227,7 @@ describe('utils', () => { calculateVersion(false, 1, { author: [], buildingBlockType: undefined, + dataViewId: undefined, description: 'some description change', timestampField: undefined, eventCategoryOverride: undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index cbafe771b819c..35060927f3762 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -58,6 +58,7 @@ import { EventCategoryOverrideOrUndefined, TiebreakerFieldOrUndefined, NamespaceOrUndefined, + DataViewIdOrUndefined, RelatedIntegrationArray, RequiredFieldArray, SetupGuide, @@ -112,6 +113,7 @@ export interface UpdateProperties { machineLearningJobId: MachineLearningJobIdOrUndefined; filters: PartialFilter[] | undefined; index: IndexOrUndefined; + dataViewId: DataViewIdOrUndefined; interval: IntervalOrUndefined; maxSignals: MaxSignalsOrUndefined; relatedIntegrations: RelatedIntegrationArray | undefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts index cb55a8dbe25a9..b615e705b556b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts @@ -56,6 +56,7 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif type: params.type, language: params.language, index: params.index, + dataViewId: params.data_view_id, query: params.query, filters: params.filters, timestampField: params.timestamp_field, @@ -68,6 +69,7 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif type: params.type, language: params.language ?? 'kuery', index: params.index, + dataViewId: params.data_view_id, query: params.query, filters: params.filters, savedId: params.saved_id, @@ -86,6 +88,7 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif type: params.type, language: params.language ?? 'kuery', index: params.index, + dataViewId: params.data_view_id, query: params.query ?? '', filters: params.filters, savedId: params.saved_id, @@ -99,6 +102,7 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif query: params.query, filters: params.filters, savedId: params.saved_id, + dataViewId: params.data_view_id, }; } case 'threshold': { @@ -106,6 +110,7 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif type: params.type, language: params.language ?? 'kuery', index: params.index, + dataViewId: params.data_view_id, query: params.query, filters: params.filters, savedId: params.saved_id, @@ -184,6 +189,7 @@ export const typeSpecificCamelToSnake = (params: TypeSpecificRuleParams): Respon type: params.type, language: params.language, index: params.index, + data_view_id: params.dataViewId, query: params.query, filters: params.filters, timestamp_field: params.timestampField, @@ -196,6 +202,7 @@ export const typeSpecificCamelToSnake = (params: TypeSpecificRuleParams): Respon type: params.type, language: params.language, index: params.index, + data_view_id: params.dataViewId, query: params.query, filters: params.filters, saved_id: params.savedId, @@ -214,6 +221,7 @@ export const typeSpecificCamelToSnake = (params: TypeSpecificRuleParams): Respon type: params.type, language: params.language, index: params.index, + data_view_id: params.dataViewId, query: params.query, filters: params.filters, saved_id: params.savedId, @@ -234,6 +242,7 @@ export const typeSpecificCamelToSnake = (params: TypeSpecificRuleParams): Respon type: params.type, language: params.language, index: params.index, + data_view_id: params.dataViewId, query: params.query, filters: params.filters, saved_id: params.savedId, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts index 011bd31b0b929..2580364329407 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts @@ -63,6 +63,7 @@ export const getThresholdRuleParams = (): ThresholdRuleParams => { type: 'threshold', language: 'kuery', index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + dataViewId: undefined, query: 'user.name: root or user.name: admin', filters: undefined, savedId: undefined, @@ -89,6 +90,7 @@ export const getEqlRuleParams = (): EqlRuleParams => { filters: undefined, timestampField: undefined, eventCategoryOverride: undefined, + dataViewId: undefined, tiebreakerField: undefined, }; }; @@ -109,6 +111,7 @@ export const getQueryRuleParams = (): QueryRuleParams => { language: 'kuery', query: 'user.name: root or user.name: admin', index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + dataViewId: undefined, filters: [ { query: { @@ -129,6 +132,7 @@ export const getThreatRuleParams = (): ThreatRuleParams => { language: 'kuery', query: '*:*', index: ['some-index'], + dataViewId: undefined, filters: undefined, savedId: undefined, threatQuery: 'threat-query', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts index c5624c4f59022..fa4d912368d33 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts @@ -48,6 +48,7 @@ import { false_positives, rule_id, immutable, + dataViewIdOrUndefined, indexOrUndefined, licenseOrUndefined, output_index, @@ -81,6 +82,7 @@ import { import { SERVER_APP_ID } from '../../../../common/constants'; const nonEqlLanguages = t.keyof({ kuery: null, lucene: null }); + export const baseRuleParams = t.exact( t.type({ author, @@ -125,6 +127,7 @@ const eqlSpecificRuleParams = t.type({ filters: filtersOrUndefined, timestampField: timestampFieldOrUndefined, eventCategoryOverride: eventCategoryOverrideOrUndefined, + dataViewId: dataViewIdOrUndefined, tiebreakerField: tiebreakerFieldOrUndefined, }); export const eqlRuleParams = t.intersection([baseRuleParams, eqlSpecificRuleParams]); @@ -145,6 +148,7 @@ const threatSpecificRuleParams = t.type({ threatIndicatorPath: threatIndicatorPathOrUndefined, concurrentSearches: concurrentSearchesOrUndefined, itemsPerSearch: itemsPerSearchOrUndefined, + dataViewId: dataViewIdOrUndefined, }); export const threatRuleParams = t.intersection([baseRuleParams, threatSpecificRuleParams]); export type ThreatRuleParams = t.TypeOf; @@ -157,6 +161,7 @@ const querySpecificRuleParams = t.exact( query, filters: filtersOrUndefined, savedId: savedIdOrUndefined, + dataViewId: dataViewIdOrUndefined, }) ); export const queryRuleParams = t.intersection([baseRuleParams, querySpecificRuleParams]); @@ -168,6 +173,7 @@ const savedQuerySpecificRuleParams = t.type({ // if the saved object gets deleted for some reason language: nonEqlLanguages, index: indexOrUndefined, + dataViewId: dataViewIdOrUndefined, query: queryOrUndefined, filters: filtersOrUndefined, savedId: saved_id, @@ -183,6 +189,7 @@ const thresholdSpecificRuleParams = t.type({ filters: filtersOrUndefined, savedId: savedIdOrUndefined, threshold: thresholdNormalized, + dataViewId: dataViewIdOrUndefined, }); export const thresholdRuleParams = t.intersection([baseRuleParams, thresholdSpecificRuleParams]); export type ThresholdRuleParams = t.TypeOf; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 03074b9560553..f17e0ea9d2b48 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -78,6 +78,7 @@ import { ALERT_RULE_TIMELINE_ID, ALERT_RULE_TIMELINE_TITLE, ALERT_RULE_TIMESTAMP_OVERRIDE, + ALERT_RULE_INDICES, } from '../../../../../common/field_maps/field_names'; import { SERVER_APP_ID } from '../../../../../common/constants'; @@ -299,6 +300,7 @@ export const sampleAlertDocAADNoSortId = ( [ALERT_RULE_FROM]: 'now-6m', [ALERT_RULE_IMMUTABLE]: false, [ALERT_RULE_INTERVAL]: '5m', + [ALERT_RULE_INDICES]: ['auditbeat-*'], [ALERT_RULE_LICENSE]: 'Elastic License', [ALERT_RULE_MAX_SIGNALS]: 10000, [ALERT_RULE_NAME]: 'rule-name', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts index a068d239209ff..915d675d5644c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts @@ -27,6 +27,7 @@ describe('create_signals', () => { size: 100, searchAfterSortIds: undefined, timestampOverride: undefined, + runtimeMappings: undefined, }); expect(query).toEqual({ allow_no_indices: true, @@ -84,6 +85,7 @@ describe('create_signals', () => { size: 100, searchAfterSortIds: undefined, timestampOverride: 'event.ingested', + runtimeMappings: undefined, }); expect(query).toEqual({ allow_no_indices: true, @@ -183,6 +185,7 @@ describe('create_signals', () => { size: 100, searchAfterSortIds: [fakeSortId], timestampOverride: undefined, + runtimeMappings: undefined, }); expect(query).toEqual({ allow_no_indices: true, @@ -241,6 +244,7 @@ describe('create_signals', () => { size: 100, searchAfterSortIds: [fakeSortIdNumber], timestampOverride: undefined, + runtimeMappings: undefined, }); expect(query).toEqual({ allow_no_indices: true, @@ -298,6 +302,7 @@ describe('create_signals', () => { size: 100, searchAfterSortIds: undefined, timestampOverride: undefined, + runtimeMappings: undefined, }); expect(query).toEqual({ allow_no_indices: true, @@ -362,6 +367,7 @@ describe('create_signals', () => { size: 100, searchAfterSortIds: undefined, timestampOverride: undefined, + runtimeMappings: undefined, }); expect(query).toEqual({ allow_no_indices: true, @@ -427,6 +433,7 @@ describe('create_signals', () => { searchAfterSortIds: undefined, timestampOverride: undefined, trackTotalHits: false, + runtimeMappings: undefined, }); expect(query.track_total_hits).toEqual(false); }); @@ -442,6 +449,7 @@ describe('create_signals', () => { timestampOverride: undefined, sortOrder: 'desc', trackTotalHits: false, + runtimeMappings: undefined, }); expect(query.body.sort[0]).toEqual({ '@timestamp': { @@ -461,6 +469,7 @@ describe('create_signals', () => { searchAfterSortIds: undefined, timestampOverride: 'event.ingested', sortOrder: 'desc', + runtimeMappings: undefined, }); expect(query.body.sort[0]).toEqual({ 'event.ingested': { @@ -487,6 +496,7 @@ describe('create_signals', () => { undefined, undefined, [], + undefined, undefined ); expect(request).toEqual({ @@ -495,6 +505,7 @@ describe('create_signals', () => { body: { size: 100, query: 'process where true', + runtime_mappings: undefined, filter: { bool: { filter: [ @@ -535,7 +546,9 @@ describe('create_signals', () => { undefined, 'event.ingested', [], - 'event.other_category' + undefined, + 'event.other_category', + undefined ); expect(request).toEqual({ allow_no_indices: true, @@ -544,6 +557,7 @@ describe('create_signals', () => { event_category_field: 'event.other_category', size: 100, query: 'process where true', + runtime_mappings: undefined, filter: { bool: { filter: [ @@ -619,6 +633,7 @@ describe('create_signals', () => { undefined, undefined, [getExceptionListItemSchemaMock()], + undefined, undefined ); expect(request).toEqual({ @@ -627,6 +642,7 @@ describe('create_signals', () => { body: { size: 100, query: 'process where true', + runtime_mappings: undefined, filter: { bool: { filter: [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts index 640ba18f3806b..adb395eb49945 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts @@ -19,6 +19,7 @@ interface BuildEventsSearchQuery { from: string; to: string; filter: estypes.QueryDslQueryContainer; + runtimeMappings: estypes.MappingRuntimeFields | undefined; size: number; sortOrder?: estypes.SortOrder; searchAfterSortIds: estypes.SortResults | undefined; @@ -97,6 +98,7 @@ export const buildEventsSearchQuery = ({ to, filter, size, + runtimeMappings, searchAfterSortIds, sortOrder, timestampOverride, @@ -132,6 +134,7 @@ export const buildEventsSearchQuery = ({ const searchQuery = { allow_no_indices: true, + runtime_mappings: runtimeMappings, index, size, ignore_unavailable: true, @@ -180,6 +183,7 @@ export const buildEqlSearchRequest = ( filters: FiltersOrUndefined, timestampOverride: TimestampOverrideOrUndefined, exceptionLists: ExceptionListItemSchema[], + runtimeMappings: estypes.MappingRuntimeFields | undefined, eventCategoryOverride?: string, timestampField?: string, tiebreakerField?: string @@ -214,6 +218,7 @@ export const buildEqlSearchRequest = ( filter: requestFilter, }, }, + runtime_mappings: runtimeMappings, timestamp_field: timestampField, event_category_field: eventCategoryOverride, tiebreaker_field: tiebreakerField, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts index 67054b10793cc..182c10e13970c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts @@ -16,6 +16,7 @@ import { getIndexVersion } from '../../routes/index/get_index_version'; import { SIGNALS_TEMPLATE_VERSION } from '../../routes/index/get_signals_template'; import { allowedExperimentalValues } from '../../../../../common/experimental_features'; import { EqlRuleParams } from '../../schemas/rule_schemas'; +import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; jest.mock('../../routes/index/get_index_version'); @@ -47,6 +48,8 @@ describe('eql_executor', () => { it('should set a warning when exception list for eql rule contains value list exceptions', async () => { const exceptionItems = [getExceptionListItemSchemaMock({ entries: [getEntryListMock()] })]; const response = await eqlExecutor({ + inputIndex: DEFAULT_INDEX_PATTERN, + runtimeMappings: {}, completeRule: eqlCompleteRule, tuple, exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts index d6d894a31e387..c78fc6bbb2451 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts @@ -13,9 +13,10 @@ import { AlertInstanceState, RuleExecutorServices, } from '@kbn/alerting-plugin/server'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + import { buildEqlSearchRequest } from '../build_events_query'; import { hasLargeValueItem } from '../../../../../common/detection_engine/utils'; -import { getInputIndex } from '../get_input_output_index'; import { BulkCreate, @@ -36,6 +37,8 @@ import { } from '../../../../../common/detection_engine/schemas/alerts'; export const eqlExecutor = async ({ + inputIndex, + runtimeMappings, completeRule, tuple, exceptionItems, @@ -47,6 +50,8 @@ export const eqlExecutor = async ({ wrapHits, wrapSequences, }: { + inputIndex: string[]; + runtimeMappings: estypes.MappingRuntimeFields | undefined; completeRule: CompleteRule; tuple: RuleRangeTuple; exceptionItems: ExceptionListItemSchema[]; @@ -69,13 +74,6 @@ export const eqlExecutor = async ({ result.warning = true; } - const inputIndex = await getInputIndex({ - experimentalFeatures, - services, - version, - index: ruleParams.index, - }); - const request = buildEqlSearchRequest( ruleParams.query, inputIndex, @@ -85,6 +83,7 @@ export const eqlExecutor = async ({ ruleParams.filters, ruleParams.timestampOverride, exceptionItems, + runtimeMappings, ruleParams.eventCategoryOverride, ruleParams.timestampField, ruleParams.tiebreakerField diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts index c4d1348741b12..3e51dc9f117ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts @@ -13,8 +13,9 @@ import { RuleExecutorServices, } from '@kbn/alerting-plugin/server'; import { ListClient } from '@kbn/lists-plugin/server'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + import { getFilter } from '../get_filter'; -import { getInputIndex } from '../get_input_output_index'; import { searchAfterAndBulkCreate } from '../search_after_bulk_create'; import { RuleRangeTuple, BulkCreate, WrapHits } from '../types'; import { ITelemetryEventsSender } from '../../../telemetry/sender'; @@ -25,6 +26,8 @@ import { buildReasonMessageForQueryAlert } from '../reason_formatters'; import { withSecuritySpan } from '../../../../utils/with_security_span'; export const queryExecutor = async ({ + inputIndex, + runtimeMappings, completeRule, tuple, listClient, @@ -39,7 +42,9 @@ export const queryExecutor = async ({ bulkCreate, wrapHits, }: { - completeRule: CompleteRule; + inputIndex: string[]; + runtimeMappings: estypes.MappingRuntimeFields | undefined; + completeRule: CompleteRule | CompleteRule; tuple: RuleRangeTuple; listClient: ListClient; exceptionItems: ExceptionListItemSchema[]; @@ -56,13 +61,6 @@ export const queryExecutor = async ({ const ruleParams = completeRule.ruleParams; return withSecuritySpan('queryExecutor', async () => { - const inputIndex = await getInputIndex({ - experimentalFeatures, - services, - version, - index: ruleParams.index, - }); - const esFilter = await getFilter({ type: ruleParams.type, filters: ruleParams.filters, @@ -90,6 +88,7 @@ export const queryExecutor = async ({ buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings, }); }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts index e7cd27549ff4d..4955917fbabe7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts @@ -7,13 +7,14 @@ import { Logger } from '@kbn/core/server'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + import { AlertInstanceContext, AlertInstanceState, RuleExecutorServices, } from '@kbn/alerting-plugin/server'; import { ListClient } from '@kbn/lists-plugin/server'; -import { getInputIndex } from '../get_input_output_index'; import { RuleRangeTuple, BulkCreate, WrapHits } from '../types'; import { ITelemetryEventsSender } from '../../../telemetry/sender'; import { BuildRuleMessage } from '../rule_messages'; @@ -24,6 +25,8 @@ import { withSecuritySpan } from '../../../../utils/with_security_span'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; export const threatMatchExecutor = async ({ + inputIndex, + runtimeMappings, completeRule, tuple, listClient, @@ -38,6 +41,8 @@ export const threatMatchExecutor = async ({ bulkCreate, wrapHits, }: { + inputIndex: string[]; + runtimeMappings: estypes.MappingRuntimeFields | undefined; completeRule: CompleteRule; tuple: RuleRangeTuple; listClient: ListClient; @@ -55,12 +60,6 @@ export const threatMatchExecutor = async ({ const ruleParams = completeRule.ruleParams; return withSecuritySpan('threatMatchExecutor', async () => { - const inputIndex = await getInputIndex({ - experimentalFeatures, - services, - version, - index: ruleParams.index, - }); return createThreatSignals({ alertId: completeRule.alertId, buildRuleMessage, @@ -89,6 +88,7 @@ export const threatMatchExecutor = async ({ tuple, type: ruleParams.type, wrapHits, + runtimeMappings, }); }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts index 0e37cc66d9654..ae39ef56d812a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts @@ -72,6 +72,8 @@ describe('threshold_executor', () => { })), wrapHits: jest.fn(), ruleDataReader: ruleDataClientMock.getReader({ namespace: 'default' }), + runtimeMappings: {}, + inputIndex: ['auditbeat-*'], }); expect(response.warningMessages.length).toEqual(1); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts index 0a89f15ad4b01..7d7481a79bf89 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts @@ -6,6 +6,7 @@ */ import { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { Logger } from '@kbn/core/server'; @@ -19,7 +20,6 @@ import { IRuleDataReader } from '@kbn/rule-registry-plugin/server'; import { hasLargeValueItem } from '../../../../../common/detection_engine/utils'; import { CompleteRule, ThresholdRuleParams } from '../../schemas/rule_schemas'; import { getFilter } from '../get_filter'; -import { getInputIndex } from '../get_input_output_index'; import { bulkCreateThresholdSignals, findThresholdSignals, @@ -44,6 +44,8 @@ import { withSecuritySpan } from '../../../../utils/with_security_span'; import { buildThresholdSignalHistory } from '../threshold/build_signal_history'; export const thresholdExecutor = async ({ + inputIndex, + runtimeMappings, completeRule, tuple, exceptionItems, @@ -58,6 +60,8 @@ export const thresholdExecutor = async ({ wrapHits, ruleDataReader, }: { + inputIndex: string[]; + runtimeMappings: estypes.MappingRuntimeFields | undefined; completeRule: CompleteRule; tuple: RuleRangeTuple; exceptionItems: ExceptionListItemSchema[]; @@ -107,13 +111,6 @@ export const thresholdExecutor = async ({ result.warning = true; } - const inputIndex = await getInputIndex({ - experimentalFeatures, - services, - version, - index: ruleParams.index, - }); - // Eliminate dupes const bucketFilters = await getThresholdBucketFilters({ signalHistory, @@ -147,6 +144,7 @@ export const thresholdExecutor = async ({ threshold: ruleParams.threshold, timestampOverride: ruleParams.timestampOverride, buildRuleMessage, + runtimeMappings, }); // Build and index new alerts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts index 5a2150c191a4a..410be81a0ff9f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts @@ -6,12 +6,15 @@ */ import { alertsMock, RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks'; +import type { MockedLogger } from '@kbn/logging-mocks'; +import { loggerMock } from '@kbn/logging-mocks'; + import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { getInputIndex, GetInputIndex } from './get_input_output_index'; -import { allowedExperimentalValues } from '../../../../common/experimental_features'; describe('get_input_output_index', () => { let servicesMock: RuleExecutorServicesMock; + const logger: MockedLogger = loggerMock.create(); beforeAll(() => { jest.resetAllMocks(); @@ -33,102 +36,163 @@ describe('get_input_output_index', () => { services: servicesMock, version: '8.0.0', index: ['test-input-index-1'], - experimentalFeatures: { - ...allowedExperimentalValues, - }, + ruleId: 'rule_1', + logger, }; }); describe('getInputOutputIndex', () => { - test('Returns inputIndex if inputIndex is passed in', async () => { - servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ - id, - type, - references: [], - attributes: {}, - })); - const inputIndex = await getInputIndex(defaultProps); - expect(inputIndex).toEqual(['test-input-index-1']); - }); + describe('data view is not defined', () => { + test('Returns inputIndex if inputIndex is passed in', async () => { + servicesMock.savedObjectsClient.get.mockImplementation( + async (type: string, id: string) => ({ + id, + type, + references: [], + attributes: {}, + }) + ); + const inputIndex = await getInputIndex(defaultProps); + expect(inputIndex).toEqual({ index: ['test-input-index-1'], runtimeMappings: {} }); + }); - test('Returns a saved object inputIndex if passed in inputIndex is undefined', async () => { - servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ - id, - type, - references: [], - attributes: { - [DEFAULT_INDEX_KEY]: ['configured-index-1', 'configured-index-2'], - }, - })); - const inputIndex = await getInputIndex({ - ...defaultProps, - index: undefined, + test('Returns a saved object inputIndex if passed in inputIndex is undefined', async () => { + servicesMock.savedObjectsClient.get.mockImplementation( + async (type: string, id: string) => ({ + id, + type, + references: [], + attributes: { + [DEFAULT_INDEX_KEY]: ['configured-index-1', 'configured-index-2'], + }, + }) + ); + const inputIndex = await getInputIndex({ + ...defaultProps, + index: undefined, + }); + expect(inputIndex).toEqual({ + index: ['configured-index-1', 'configured-index-2'], + runtimeMappings: {}, + }); }); - expect(inputIndex).toEqual(['configured-index-1', 'configured-index-2']); - }); - test('Returns a saved object inputIndex if passed in inputIndex is null', async () => { - servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ - id, - type, - references: [], - attributes: { - [DEFAULT_INDEX_KEY]: ['configured-index-1', 'configured-index-2'], - }, - })); - const inputIndex = await getInputIndex({ - ...defaultProps, - index: null, + test('Returns a saved object inputIndex if passed in inputIndex is null', async () => { + servicesMock.savedObjectsClient.get.mockImplementation( + async (type: string, id: string) => ({ + id, + type, + references: [], + attributes: { + [DEFAULT_INDEX_KEY]: ['configured-index-1', 'configured-index-2'], + }, + }) + ); + const inputIndex = await getInputIndex({ + ...defaultProps, + index: null, + }); + expect(inputIndex).toEqual({ + index: ['configured-index-1', 'configured-index-2'], + runtimeMappings: {}, + }); }); - expect(inputIndex).toEqual(['configured-index-1', 'configured-index-2']); - }); - test('Returns a saved object inputIndex default from constants if inputIndex passed in is null and the key is also null', async () => { - servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ - id, - type, - references: [], - attributes: { - [DEFAULT_INDEX_KEY]: null, - }, - })); - const inputIndex = await getInputIndex({ - ...defaultProps, - index: null, + test('Returns a saved object inputIndex default from constants if inputIndex passed in is null and the key is also null', async () => { + servicesMock.savedObjectsClient.get.mockImplementation( + async (type: string, id: string) => ({ + id, + type, + references: [], + attributes: { + [DEFAULT_INDEX_KEY]: null, + }, + }) + ); + const inputIndex = await getInputIndex({ + ...defaultProps, + index: null, + }); + expect(inputIndex).toEqual({ index: DEFAULT_INDEX_PATTERN, runtimeMappings: {} }); + }); + + test('Returns a saved object inputIndex default from constants if inputIndex passed in is undefined and the key is also null', async () => { + servicesMock.savedObjectsClient.get.mockImplementation( + async (type: string, id: string) => ({ + id, + type, + references: [], + attributes: { + [DEFAULT_INDEX_KEY]: null, + }, + }) + ); + const inputIndex = await getInputIndex({ + ...defaultProps, + index: undefined, + }); + expect(inputIndex).toEqual({ index: DEFAULT_INDEX_PATTERN, runtimeMappings: {} }); + }); + + test('Returns a saved object inputIndex default from constants if both passed in inputIndex and configuration attributes are missing and the index is undefined', async () => { + const inputIndex = await getInputIndex({ + ...defaultProps, + index: undefined, + }); + expect(inputIndex).toEqual({ index: DEFAULT_INDEX_PATTERN, runtimeMappings: {} }); + }); + + test('Returns a saved object inputIndex default from constants if both passed in inputIndex and configuration attributes are missing and the index is null', async () => { + const inputIndex = await getInputIndex({ + ...defaultProps, + index: null, + }); + expect(inputIndex).toEqual({ index: DEFAULT_INDEX_PATTERN, runtimeMappings: {} }); }); - expect(inputIndex).toEqual(DEFAULT_INDEX_PATTERN); }); + }); - test('Returns a saved object inputIndex default from constants if inputIndex passed in is undefined and the key is also null', async () => { + describe('data view is defined', () => { + test('Returns data view indices and runtime mappings if data view is found', async () => { servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ - id, + id: '12345', type, references: [], attributes: { - [DEFAULT_INDEX_KEY]: null, + title: 'test-*,foo-*,bar-*', + runtimeFieldMap: '{"test-runtime":{"type":"keyword"}}', }, })); const inputIndex = await getInputIndex({ - ...defaultProps, - index: undefined, + services: servicesMock, + version: '8.0.0', + index: ['test-*'], + ruleId: 'rule_1', + logger, + dataViewId: '12345', }); - expect(inputIndex).toEqual(DEFAULT_INDEX_PATTERN); - }); - - test('Returns a saved object inputIndex default from constants if both passed in inputIndex and configuration attributes are missing and the index is undefined', async () => { - const inputIndex = await getInputIndex({ - ...defaultProps, - index: undefined, + expect(inputIndex).toEqual({ + index: ['test-*', 'foo-*', 'bar-*'], + runtimeMappings: { 'test-runtime': { type: 'keyword' } }, }); - expect(inputIndex).toEqual(DEFAULT_INDEX_PATTERN); }); - test('Returns a saved object inputIndex default from constants if both passed in inputIndex and configuration attributes are missing and the index is null', async () => { - const inputIndex = await getInputIndex({ - ...defaultProps, - index: null, - }); - expect(inputIndex).toEqual(DEFAULT_INDEX_PATTERN); + test('Returns error if no matching data view found', async () => { + servicesMock.savedObjectsClient.get.mockRejectedValue( + new Error('Saved object [index-pattern/12345] not found') + ); + await expect( + getInputIndex({ + services: servicesMock, + version: '8.0.0', + index: [], + dataViewId: '12345', + ruleId: 'rule_1', + logger, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Saved object [index-pattern/12345] not found"` + ); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.ts index 9a4d068dc9658..29bedb1c2a0a4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.ts @@ -10,25 +10,73 @@ import { AlertInstanceState, RuleExecutorServices, } from '@kbn/alerting-plugin/server'; +import { DataViewAttributes } from '@kbn/data-views-plugin/common'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { Logger } from '@kbn/core/server'; + import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; -import { ExperimentalFeatures } from '../../../../common/experimental_features'; import { withSecuritySpan } from '../../../utils/with_security_span'; export interface GetInputIndex { - experimentalFeatures: ExperimentalFeatures; index: string[] | null | undefined; services: RuleExecutorServices; version: string; + logger: Logger; + // the rule's rule_id + ruleId: string; + dataViewId?: string; +} + +export interface GetInputIndexReturn { + index: string[] | null; + runtimeMappings: estypes.MappingRuntimeFields | undefined; + errorToWrite?: string; + warningToWrite?: string; } export const getInputIndex = async ({ - experimentalFeatures, index, services, version, -}: GetInputIndex): Promise => { + logger, + ruleId, + dataViewId, +}: GetInputIndex): Promise => { + // If data views defined, use it + if (dataViewId != null && dataViewId !== '') { + // Check to see that the selected dataView exists + const dataView = await services.savedObjectsClient.get( + 'index-pattern', + dataViewId + ); + const indices = dataView.attributes.title.split(','); + const runtimeMappings = + dataView.attributes.runtimeFieldMap != null + ? JSON.parse(dataView.attributes.runtimeFieldMap) + : {}; + + logger.debug( + `[rule_id:${ruleId}] - Data view "${dataViewId}" found - indices to search include: ${indices}.` + ); + logger.debug( + `[rule_id:${ruleId}] - Data view "${dataViewId}" includes ${ + Object.keys(runtimeMappings).length + } mapped runtime fields.` + ); + + // if data view does exist, return it and it's runtimeMappings + return { + index: indices, + runtimeMappings, + }; + } if (index != null) { - return index; + logger.debug(`[rule_id:${ruleId}] - Indices to search include: ${index}.`); + + return { + index, + runtimeMappings: {}, + }; } else { const configuration = await withSecuritySpan('getDefaultIndex', () => services.savedObjectsClient.get<{ @@ -36,9 +84,23 @@ export const getInputIndex = async ({ }>('config', version) ); if (configuration.attributes != null && configuration.attributes[DEFAULT_INDEX_KEY] != null) { - return configuration.attributes[DEFAULT_INDEX_KEY]; + logger.debug( + `[rule_id:${ruleId}] - No index patterns defined, falling back to using configured default indices: ${configuration.attributes[DEFAULT_INDEX_KEY]}.` + ); + + return { + index: configuration.attributes[DEFAULT_INDEX_KEY], + runtimeMappings: {}, + }; } else { - return DEFAULT_INDEX_PATTERN; + logger.debug( + `[rule_id:${ruleId}] - No index patterns defined, falling back to using default indices: ${DEFAULT_INDEX_PATTERN}.` + ); + + return { + index: DEFAULT_INDEX_PATTERN, + runtimeMappings: {}, + }; } } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.test.ts new file mode 100644 index 0000000000000..534c2f591a83b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { extractDataView } from './extract_data_view'; + +describe('extract_data_view', () => { + type FuncReturn = ReturnType; + + test('it returns an empty array if "dataViewId" being null', () => { + expect(extractDataView({ dataViewId: null })).toEqual([]); + }); + + test('it returns an empty array if "dataViewId" is not defined', () => { + expect(extractDataView({ dataViewId: undefined })).toEqual([]); + }); + + test('it returns an empty array if "dataViewId" is empty string', () => { + expect(extractDataView({ dataViewId: ' ' })).toEqual([]); + }); + + test('It returns exception list transformed into a saved object references', () => { + expect(extractDataView({ dataViewId: 'logs-*' })).toEqual([ + { + id: 'logs-*', + name: 'dataViewId_0', + type: 'index-pattern', + }, + ]); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.ts new file mode 100644 index 0000000000000..c2f4118134c39 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectReference } from '@kbn/core/server'; + +import { + EqlRuleParams, + QueryRuleParams, + ThreatRuleParams, + ThresholdRuleParams, +} from '../../schemas/rule_schemas'; + +/** + * This extracts the "dataViewId" and returns it as a saved object reference. + * @param dataViewId The data view SO "id" to be returned as a saved object reference. + * @returns The saved object references from the specified data view + */ +export const extractDataView = ({ + dataViewId, +}: { + dataViewId: + | ThresholdRuleParams['dataViewId'] + | ThreatRuleParams['dataViewId'] + | QueryRuleParams['dataViewId'] + | EqlRuleParams['dataViewId'] + | undefined + | null; +}): SavedObjectReference[] => { + if (dataViewId == null || dataViewId.trim() === '') { + return []; + } else { + return [ + { + name: 'dataViewId_0', + id: dataViewId, + type: 'index-pattern', + }, + ]; + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts index 0a06b42049768..94f4a5f8d7c68 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts @@ -30,10 +30,12 @@ describe('extract_references', () => { logger = loggingSystemMock.create().get('security_solution'); }); - test('It returns params untouched and the references extracted as exception list saved object references', () => { + test('It returns params untouched and the references extracted', () => { const params: Partial = { + type: 'eql', note: 'some note', exceptionsList: mockExceptionsList(), + dataViewId: 'logs-*', }; expect( extractReferences({ @@ -48,68 +50,174 @@ describe('extract_references', () => { name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`, type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC, }, + { + id: 'logs-*', + name: 'dataViewId_0', + type: 'index-pattern', + }, ], }); }); - test('It returns params untouched and the references extracted as 2 exception list saved object references', () => { - const params: Partial = { - note: 'some note', - exceptionsList: [ - mockExceptionsList()[0], - { ...mockExceptionsList()[0], id: '456', namespace_type: 'single' }, - ], - }; - expect( - extractReferences({ - logger, + describe('exception lists', () => { + test('It returns params untouched and the references extracted as exception list saved object references', () => { + const params: Partial = { + note: 'some note', + exceptionsList: mockExceptionsList(), + }; + expect( + extractReferences({ + logger, + params: params as RuleParams, + }) + ).toEqual({ params: params as RuleParams, - }) - ).toEqual({ - params: params as RuleParams, - references: [ - { - id: '123', - name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`, - type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC, - }, - { - id: '456', - name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_1`, - type: EXCEPTION_LIST_NAMESPACE, - }, - ], + references: [ + { + id: '123', + name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`, + type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC, + }, + ], + }); }); - }); - test('It returns params untouched and the references an empty array if the exceptionsList is an empty array', () => { - const params: Partial = { - note: 'some note', - exceptionsList: [], - }; - expect( - extractReferences({ - logger, + test('It returns params untouched and the references extracted as 2 exception list saved object references', () => { + const params: Partial = { + note: 'some note', + exceptionsList: [ + mockExceptionsList()[0], + { ...mockExceptionsList()[0], id: '456', namespace_type: 'single' }, + ], + }; + expect( + extractReferences({ + logger, + params: params as RuleParams, + }) + ).toEqual({ params: params as RuleParams, - }) - ).toEqual({ - params: params as RuleParams, - references: [], + references: [ + { + id: '123', + name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`, + type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC, + }, + { + id: '456', + name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_1`, + type: EXCEPTION_LIST_NAMESPACE, + }, + ], + }); + }); + + test('It returns params untouched and the references an empty array if the exceptionsList is an empty array', () => { + const params: Partial = { + note: 'some note', + exceptionsList: [], + }; + expect( + extractReferences({ + logger, + params: params as RuleParams, + }) + ).toEqual({ + params: params as RuleParams, + references: [], + }); + }); + + test('It returns params untouched and the references an empty array if the exceptionsList is missing for any reason', () => { + const params: Partial = { + note: 'some note', + }; + expect( + extractReferences({ + logger, + params: params as RuleParams, + }) + ).toEqual({ + params: params as RuleParams, + references: [], + }); }); }); - test('It returns params untouched and the references an empty array if the exceptionsList is missing for any reason', () => { - const params: Partial = { - note: 'some note', - }; - expect( - extractReferences({ - logger, + describe('data view', () => { + test('It returns params untouched and the references extracted as data view saved object references', () => { + const params: Partial = { + type: 'eql', + note: 'some note', + exceptionsList: [], + dataViewId: 'logs-*', + }; + expect( + extractReferences({ + logger, + params: params as RuleParams, + }) + ).toEqual({ params: params as RuleParams, - }) - ).toEqual({ - params: params as RuleParams, - references: [], + references: [ + { + id: 'logs-*', + name: 'dataViewId_0', + type: 'index-pattern', + }, + ], + }); + }); + + test('It returns params untouched and the references an empty array if the data view is an empty string', () => { + const params: Partial = { + type: 'eql', + note: 'some note', + exceptionsList: [], + dataViewId: ' ', + }; + expect( + extractReferences({ + logger, + params: params as RuleParams, + }) + ).toEqual({ + params: params as RuleParams, + references: [], + }); + }); + + test('It returns params untouched and the references an empty array if the data view is null', () => { + const params: Partial = { + type: 'eql', + note: 'some note', + exceptionsList: [], + }; + expect( + extractReferences({ + logger, + params: params as RuleParams, + }) + ).toEqual({ + params: params as RuleParams, + references: [], + }); + }); + + test('It returns params untouched and the references an empty array if the data view is not defined', () => { + const params: Partial = { + note: 'some note', + exceptionsList: [], + }; + expect( + extractReferences({ + logger, + params: params as RuleParams, + }) + ).toEqual({ + params: params as RuleParams, + references: [], + }); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.ts index 533055d7cd572..58e231eff1b81 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.ts @@ -7,8 +7,13 @@ import { Logger } from '@kbn/core/server'; import { RuleParamsAndRefs } from '@kbn/alerting-plugin/server'; + import { RuleParams } from '../../schemas/rule_schemas'; + +import { isEqlParams, isQueryParams, isThresholdParams, isThreatParams } from '../utils'; + import { extractExceptionsList } from './extract_exceptions_list'; +import { extractDataView } from './extract_data_view'; /** * Extracts references and returns the saved object references. @@ -42,8 +47,23 @@ export const extractReferences = ({ logger, exceptionsList: params.exceptionsList, }); - const returnReferences = [...exceptionReferences]; + let returnReferences = [...exceptionReferences]; + // if statement is needed here because dataViewId is not on the base rule params + // much like how the index property is not on the base rule params either + if ( + isEqlParams(params) || + isQueryParams(params) || + isThresholdParams(params) || + isThreatParams(params) + ) { + returnReferences = [ + ...returnReferences, + ...extractDataView({ + dataViewId: params.dataViewId, + }), + ]; + } // Modify params if you want to remove any elements separately here. For exceptionLists, we do not remove the id and instead // keep it to both fail safe guard against manually removed saved object references or if there are migration issues and the saved object // references are removed. Also keeping it we can detect and log out a warning if the reference between it and the saved_object reference diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_data_view.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_data_view.test.ts new file mode 100644 index 0000000000000..3c1cd7ed6f865 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_data_view.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { SavedObjectReference } from '@kbn/core/server'; +import { injectDataViewReferences } from './inject_data_view'; + +describe('inject_data_view', () => { + type FuncReturn = ReturnType; + let logger = loggingSystemMock.create().get('security_solution'); + const mockSavedObjectReferences = (): SavedObjectReference[] => [ + { + id: 'logs-*', + name: 'dataViewId_0', + type: 'index-pattern', + }, + ]; + + beforeEach(() => { + logger = loggingSystemMock.create().get('security_solution'); + }); + + test('returns undefined given an empty "dataViewId" and "savedObjectReferences"', () => { + expect( + injectDataViewReferences({ + logger, + savedObjectReferences: [], + }) + ).toBeUndefined(); + }); + + test('returns undefined given undefined for "dataViewId"', () => { + expect( + injectDataViewReferences({ + logger, + savedObjectReferences: [], + }) + ).toBeUndefined(); + }); + + test('returns undefined given null for "dataViewId"', () => { + expect( + injectDataViewReferences({ + logger, + savedObjectReferences: [], + }) + ).toBeUndefined(); + }); + + test('returns undefined when given an empty array for "savedObjectReferences"', () => { + expect( + injectDataViewReferences({ + logger, + savedObjectReferences: [], + }) + ).toEqual(undefined); + }); + + test('returns parameters from the saved object if found', () => { + expect( + injectDataViewReferences({ + logger, + savedObjectReferences: mockSavedObjectReferences(), + }) + ).toEqual('logs-*'); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_data_view.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_data_view.ts new file mode 100644 index 0000000000000..6a877a99ead77 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_data_view.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger, SavedObjectReference } from '@kbn/core/server'; +import { getSavedObjectReferenceForDataView } from './utils'; + +/** + * This injects any "dataViewId" from saved object reference and returns the "dataViewId" using the saved object reference. If for + * some reason it is missing on saved object reference, we log an error about it and then take the last known good value from the "dataViewId" + * + * @param logger The kibana injected logger + * @param dataViewId The data view id to merge the saved object reference from. + * @param savedObjectReferences The saved object references which should contain an "dataViewId" + * @returns The dataViewId with the saved object reference replacing any value in the saved object's id. + */ +export const injectDataViewReferences = ({ + logger, + savedObjectReferences, +}: { + logger: Logger; + savedObjectReferences: SavedObjectReference[]; +}): string | null | undefined => { + const foundSavedObject = getSavedObjectReferenceForDataView({ + logger, + savedObjectReferences, + }); + if (foundSavedObject != null) { + const reference = foundSavedObject.id; + return reference; + } else { + return undefined; + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts index e8addc01720e6..343a476dabefc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts @@ -122,7 +122,7 @@ describe('inject_exceptions_list', () => { savedObjectReferences: [{ ...mockSavedObjectReferences()[0], name: 'other-name_0' }], }); expect(logger.error).toBeCalledWith( - 'The saved object references were not found for our exception list when we were expecting to find it. Kibana migrations might not have run correctly or someone might have removed the saved object references manually. Returning the last known good exception list id which might not work. exceptionItem with its id being returned is: {"id":"123","list_id":"456","type":"detection","namespace_type":"agnostic"}' + 'The saved object references were not found for our exception list when we were expecting to find it. Kibana migrations might not have run correctly or someone might have removed the saved object references manually. Returning the last known good exception list which might not work. Value being returned is: {"id":"123","list_id":"456","type":"detection","namespace_type":"agnostic"}' ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.ts index 9e5bb24b2c443..19513dab20791 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.ts @@ -46,7 +46,11 @@ export const injectExceptionsReferences = ({ }; return reference; } else { - logMissingSavedObjectError({ logger, exceptionItem }); + logMissingSavedObjectError({ + logger, + missingFieldValue: exceptionItem, + missingField: 'exception list', + }); return exceptionItem; } }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.ts index 3aa1e46331fb1..a4523e3e72600 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.ts @@ -7,7 +7,9 @@ import { Logger, SavedObjectReference } from '@kbn/core/server'; import { RuleParams } from '../../schemas/rule_schemas'; +import { isMachineLearningParams } from '../utils'; import { injectExceptionsReferences } from './inject_exceptions_list'; +import { injectDataViewReferences } from './inject_data_view'; /** * Injects references and returns the saved object references. @@ -42,9 +44,22 @@ export const injectReferences = ({ exceptionsList: params.exceptionsList, savedObjectReferences, }); - const ruleParamsWithSavedObjectReferences: TParams = { + + let ruleParamsWithSavedObjectReferences: TParams = { ...params, exceptionsList, }; + + if (!isMachineLearningParams(params)) { + const dataView = injectDataViewReferences({ + logger, + savedObjectReferences, + }); + ruleParamsWithSavedObjectReferences = { + ...ruleParamsWithSavedObjectReferences, + dataViewId: dataView, + }; + } + return ruleParamsWithSavedObjectReferences; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_reference_for_data_view.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_reference_for_data_view.test.ts new file mode 100644 index 0000000000000..ae39160ba93e1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_reference_for_data_view.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { SavedObjectReference } from '@kbn/core/server'; +import { getSavedObjectReferenceForDataView } from '.'; + +describe('get_saved_object_reference_for_data_view', () => { + type FuncReturn = ReturnType; + let logger = loggingSystemMock.create().get('security_solution'); + + beforeEach(() => { + logger = loggingSystemMock.create().get('security_solution'); + }); + + const mockSavedObjectReferences = (): SavedObjectReference[] => [ + { + id: 'logs-*', + name: 'dataViewId_0', + type: 'index-pattern', + }, + ]; + + test('returns reference found, given index zero', () => { + expect( + getSavedObjectReferenceForDataView({ + logger, + savedObjectReferences: mockSavedObjectReferences(), + }) + ).toEqual(mockSavedObjectReferences()[0]); + }); + + test('returns undefined, when it cannot find the reference', () => { + expect( + getSavedObjectReferenceForDataView({ + logger, + savedObjectReferences: [{ ...mockSavedObjectReferences()[0], name: 'other-name_0' }], + }) + ).toEqual(undefined); + }); + + test('returns found reference, even if the reference is mixed with other references', () => { + expect( + getSavedObjectReferenceForDataView({ + logger, + savedObjectReferences: [ + { ...mockSavedObjectReferences()[0], name: 'other-name_0' }, + mockSavedObjectReferences()[0], + ], + }) + ).toEqual(mockSavedObjectReferences()[0]); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_reference_for_data_view.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_reference_for_data_view.ts new file mode 100644 index 0000000000000..476279845210b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_reference_for_data_view.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger, SavedObjectReference } from '@kbn/core/server'; +import { getSavedObjectReference } from './get_saved_object_reference'; + +/** + * Given saved object references, this will return the data view saved object reference + * even if it is mixed in with other reference objects. This is needed since a references array can contain multiple + * types of saved objects in a single array + * @param logger The kibana injected logger + * @param savedObjectReferences The saved object references which can contain "dataViewId" mixed with other saved object types + * @returns The saved object reference if found, otherwise undefined + */ +export const getSavedObjectReferenceForDataView = ({ + logger, + savedObjectReferences, +}: { + logger: Logger; + savedObjectReferences: SavedObjectReference[]; +}): SavedObjectReference | undefined => { + return getSavedObjectReference({ + logger, + name: 'dataViewId', + index: 0, + savedObjectReferences, + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/index.ts index 3a3d559a6ed39..fc1d12e7a72ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/index.ts @@ -11,3 +11,4 @@ export * from './get_saved_object_name_pattern'; export * from './get_saved_object_reference_for_exceptions_list'; export * from './get_saved_object_reference'; export * from './log_missing_saved_object_error'; +export * from './get_saved_object_reference_for_data_view'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.test.ts index 7f96ff68636ad..26c8a9c4bdb0b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.test.ts @@ -19,7 +19,8 @@ describe('log_missing_saved_object_error', () => { test('logs expect error message', () => { logMissingSavedObjectError({ logger, - exceptionItem: { + missingField: 'exception list', + missingFieldValue: { id: '123', list_id: '456', type: 'detection', @@ -27,7 +28,7 @@ describe('log_missing_saved_object_error', () => { }, }); expect(logger.error).toBeCalledWith( - 'The saved object references were not found for our exception list when we were expecting to find it. Kibana migrations might not have run correctly or someone might have removed the saved object references manually. Returning the last known good exception list id which might not work. exceptionItem with its id being returned is: {"id":"123","list_id":"456","type":"detection","namespace_type":"agnostic"}' + 'The saved object references were not found for our exception list when we were expecting to find it. Kibana migrations might not have run correctly or someone might have removed the saved object references manually. Returning the last known good exception list which might not work. Value being returned is: {"id":"123","list_id":"456","type":"detection","namespace_type":"agnostic"}' ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.ts index 303c5b394bcc2..4a3ce53d138a8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.ts @@ -8,24 +8,30 @@ import { Logger } from '@kbn/core/server'; import { RuleParams } from '../../../schemas/rule_schemas'; +type Keys = keyof RuleParams; +type PossibleRuleParamValues = RuleParams[Keys]; + /** * This will log a warning that we are missing an object reference. * @param logger The kibana injected logger - * @param exceptionItem The exception item to log the warning out as + * @param missingFieldValue The value of the field not found in saved object references + * @param missingField The name of the field not found in saved object references */ export const logMissingSavedObjectError = ({ logger, - exceptionItem, + missingFieldValue, + missingField, }: { logger: Logger; - exceptionItem: RuleParams['exceptionsList'][0]; + missingFieldValue: PossibleRuleParamValues; + missingField: string; }): void => { logger.error( [ - 'The saved object references were not found for our exception list when we were expecting to find it. ', + `The saved object references were not found for our ${missingField} when we were expecting to find it. `, 'Kibana migrations might not have run correctly or someone might have removed the saved object references manually. ', - 'Returning the last known good exception list id which might not work. exceptionItem with its id being returned is: ', - JSON.stringify(exceptionItem), + `Returning the last known good ${missingField} which might not work. Value being returned is: `, + JSON.stringify(missingFieldValue), ].join('') ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 4f3a798a327ce..10263cbde5740 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -107,6 +107,7 @@ describe('searchAfterAndBulkCreate', () => { mergeStrategy: 'missingFields', ignoreFields: [], spaceId: 'default', + indicesToQuery: inputIndexPattern, }); }); @@ -214,6 +215,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(true); expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(5); @@ -306,6 +308,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(true); expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(4); @@ -380,6 +383,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(true); expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(2); @@ -439,6 +443,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(true); expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(2); @@ -507,6 +512,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(true); expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(2); @@ -562,6 +568,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(true); expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(1); @@ -630,6 +637,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(true); expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(1); @@ -700,6 +708,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(true); expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(2); @@ -747,6 +756,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(true); expect(createdSignalsCount).toEqual(0); @@ -793,6 +803,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(false); expect(createdSignalsCount).toEqual(0); // should not create signals if search threw error @@ -917,6 +928,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(success).toEqual(false); expect(errors).toEqual(['error on creation']); @@ -1001,6 +1013,7 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, bulkCreate, wrapHits, + runtimeMappings: undefined, }); expect(mockEnrichment).toHaveBeenCalledWith( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index 84ef95b856a5f..dbd43e5228e62 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -41,6 +41,7 @@ export const searchAfterAndBulkCreate = async ({ trackTotalHits, tuple, wrapHits, + runtimeMappings, }: SearchAfterAndBulkCreateParams): Promise => { return withSecuritySpan('searchAfterAndBulkCreate', async () => { const ruleParams = completeRule.ruleParams; @@ -61,17 +62,18 @@ export const searchAfterAndBulkCreate = async ({ errors: ['malformed date tuple'], }); } + signalsCreatedCount = 0; while (signalsCreatedCount < tuple.maxSignals) { try { let mergedSearchResults = createSearchResultReturnType(); logger.debug(buildRuleMessage(`sortIds: ${sortIds}`)); - if (hasSortId) { const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({ buildRuleMessage, searchAfterSortIds: sortIds, index: inputIndexPattern, + runtimeMappings, from: tuple.from.toISOString(), to: tuple.to.toISOString(), services, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts index 35c1c7c319c3e..d57716fa661c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts @@ -44,6 +44,7 @@ describe('singleSearchAfter', () => { filter: {}, timestampOverride: undefined, buildRuleMessage, + runtimeMappings: undefined, }); expect(searchResult).toEqual(sampleDocSearchResultsNoSortId()); }); @@ -62,6 +63,7 @@ describe('singleSearchAfter', () => { filter: {}, timestampOverride: undefined, buildRuleMessage, + runtimeMappings: undefined, }); expect(searchErrors).toEqual([]); }); @@ -112,6 +114,7 @@ describe('singleSearchAfter', () => { filter: {}, timestampOverride: undefined, buildRuleMessage, + runtimeMappings: undefined, }); expect(searchErrors).toEqual([ 'index: "index-123" reason: "some reason" type: "some type" caused by reason: "some reason" caused by type: "some type"', @@ -135,6 +138,7 @@ describe('singleSearchAfter', () => { filter: {}, timestampOverride: undefined, buildRuleMessage, + runtimeMappings: undefined, }); expect(searchResult).toEqual(sampleDocSearchResultsWithSortId()); }); @@ -155,6 +159,7 @@ describe('singleSearchAfter', () => { filter: {}, timestampOverride: undefined, buildRuleMessage, + runtimeMappings: undefined, }) ).rejects.toThrow('Fake Error'); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index 12c08b7fa9824..bd26900bfda6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -33,6 +33,7 @@ interface SingleSearchAfterParams { timestampOverride: TimestampOverrideOrUndefined; buildRuleMessage: BuildRuleMessage; trackTotalHits?: boolean; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } // utilize search_after for paging results into bulk. @@ -40,6 +41,7 @@ export const singleSearchAfter = async ({ aggregations, searchAfterSortIds, index, + runtimeMappings, from, to, services, @@ -62,6 +64,7 @@ export const singleSearchAfter = async ({ index, from, to, + runtimeMappings, filter, size: pageSize, sortOrder, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts index 4c5391d238b31..9087a29a4f7bd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts @@ -50,6 +50,7 @@ export const buildThreatEnrichment = ({ }, pitId, reassignPitId, + runtimeMappings: undefined, }); return threatResponse.hits.hits; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_event_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_event_signal.ts index 2587c76907ccb..041947f826d41 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_event_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_event_signal.ts @@ -47,6 +47,7 @@ export const createEventSignal = async ({ threatIndicatorPath, threatPitId, reassignThreatPitId, + runtimeMappings, }: CreateEventSignalOptions): Promise => { const threatFilter = buildThreatMappingFilter({ threatMapping, @@ -79,6 +80,7 @@ export const createEventSignal = async ({ }, pitId: threatPitId, reassignPitId: reassignThreatPitId, + runtimeMappings, }); const signalMatches = getSignalMatchesFromThreatList(threatListHits); @@ -139,6 +141,7 @@ export const createEventSignal = async ({ trackTotalHits: false, tuple, wrapHits, + runtimeMappings, }); logger.debug( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts index a07de583d8bab..7499c6eae3876 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts @@ -37,6 +37,7 @@ export const createThreatSignal = async ({ tuple, type, wrapHits, + runtimeMappings, }: CreateThreatSignalOptions): Promise => { const threatFilter = buildThreatMappingFilter({ threatMapping, @@ -90,6 +91,7 @@ export const createThreatSignal = async ({ trackTotalHits: false, tuple, wrapHits, + runtimeMappings, }); logger.debug( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts index f02d273585ef7..4fde025d62b19 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts @@ -51,6 +51,7 @@ export const createThreatSignals = async ({ tuple, type, wrapHits, + runtimeMappings, }: CreateThreatSignalsOptions): Promise => { const params = completeRule.ruleParams; logger.debug(buildRuleMessage('Indicator matching rule starting')); @@ -87,10 +88,10 @@ export const createThreatSignals = async ({ logger.debug(`Total event count: ${eventCount}`); - if (eventCount === 0) { - logger.debug(buildRuleMessage('Indicator matching rule has completed')); - return results; - } + // if (eventCount === 0) { + // logger.debug(buildRuleMessage('Indicator matching rule has completed')); + // return results; + // } let threatPitId: OpenPointInTimeResponse['id'] = ( await services.scopedClusterClient.asCurrentUser.openPointInTime({ @@ -195,6 +196,7 @@ export const createThreatSignals = async ({ buildRuleMessage, perPage, tuple, + runtimeMappings, }), createSignal: (slicedChunk) => @@ -229,6 +231,7 @@ export const createThreatSignals = async ({ tuple, type, wrapHits, + runtimeMappings, }), }); } else { @@ -249,6 +252,7 @@ export const createThreatSignals = async ({ threatListConfig, pitId: threatPitId, reassignPitId: reassignThreatPitId, + runtimeMappings, }), createSignal: (slicedChunk) => @@ -276,6 +280,7 @@ export const createThreatSignals = async ({ tuple, type, wrapHits, + runtimeMappings, }), }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_event_count.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_event_count.ts index ed9e8d68b8c91..1b0234509d085 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_event_count.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_event_count.ts @@ -26,6 +26,7 @@ export const getEventList = async ({ logger, tuple, timestampOverride, + runtimeMappings, }: EventsOptions): Promise> => { const calculatedPerPage = perPage ?? MAX_PER_PAGE; if (calculatedPerPage > 10000) { @@ -53,6 +54,7 @@ export const getEventList = async ({ timestampOverride, sortOrder: 'desc', trackTotalHits: false, + runtimeMappings, }); logger.debug( @@ -80,6 +82,7 @@ export const getEventCount = async ({ size: 0, timestampOverride, searchAfterSortIds: undefined, + runtimeMappings: undefined, }).body.query; const response = await esClient.count({ body: { query: eventSearchQueryBodyQuery }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts index 93e6dccdbed33..5288d05ce6b0e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts @@ -33,6 +33,7 @@ export const getThreatList = async ({ pitId, reassignPitId, perPage, + runtimeMappings, }: GetThreatListOptions): Promise> => { const calculatedPerPage = perPage ?? INDICATOR_PER_PAGE; if (calculatedPerPage > 10000) { @@ -60,6 +61,7 @@ export const getThreatList = async ({ ...threatListConfig, query: queryFilter, search_after: searchAfter, + runtime_mappings: runtimeMappings, sort: ['_shard_doc', { '@timestamp': 'asc' }], }, track_total_hits: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts index 1a9264f36b568..3ae18e33e527f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts @@ -67,6 +67,7 @@ export interface CreateThreatSignalsOptions { tuple: RuleRangeTuple; type: Type; wrapHits: WrapHits; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } export interface CreateThreatSignalOptions { @@ -93,6 +94,7 @@ export interface CreateThreatSignalOptions { tuple: RuleRangeTuple; type: Type; wrapHits: WrapHits; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } export interface CreateEventSignalOptions { @@ -127,6 +129,7 @@ export interface CreateEventSignalOptions { perPage?: number; threatPitId: OpenPointInTimeResponse['id']; reassignThreatPitId: (newPitId: OpenPointInTimeResponse['id'] | undefined) => void; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } type EntryKey = 'field' | 'value'; @@ -190,6 +193,7 @@ export interface GetThreatListOptions { threatListConfig: ThreatListConfig; pitId: OpenPointInTimeResponse['id']; reassignPitId: (newPitId: OpenPointInTimeResponse['id'] | undefined) => void; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } export interface ThreatListCountOptions { @@ -257,6 +261,7 @@ export interface EventsOptions { filters: unknown[]; timestampOverride?: string; tuple: RuleRangeTuple; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } export interface EventDoc { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.test.ts index 21fb6f5fa5cf6..e36694881040a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.test.ts @@ -45,6 +45,7 @@ describe('findThresholdSignals', () => { }, buildRuleMessage, timestampOverride: undefined, + runtimeMappings: undefined, }); expect(mockSingleSearchAfter).toHaveBeenCalledWith( expect.objectContaining({ @@ -89,6 +90,7 @@ describe('findThresholdSignals', () => { }, buildRuleMessage, timestampOverride: undefined, + runtimeMappings: undefined, }); expect(mockSingleSearchAfter).toHaveBeenCalledWith( expect.objectContaining({ @@ -132,6 +134,7 @@ describe('findThresholdSignals', () => { }, buildRuleMessage, timestampOverride: undefined, + runtimeMappings: undefined, }); expect(mockSingleSearchAfter).toHaveBeenCalledWith( expect.objectContaining({ @@ -189,6 +192,7 @@ describe('findThresholdSignals', () => { }, buildRuleMessage, timestampOverride: undefined, + runtimeMappings: undefined, }); expect(mockSingleSearchAfter).toHaveBeenCalledWith( expect.objectContaining({ @@ -260,6 +264,7 @@ describe('findThresholdSignals', () => { }, buildRuleMessage, timestampOverride: undefined, + runtimeMappings: undefined, }); expect(mockSingleSearchAfter).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts index fe7e5e2c8316e..0c6947e419690 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts @@ -7,6 +7,7 @@ import { set } from '@elastic/safer-lodash-set'; import { TIMESTAMP } from '@kbn/rule-data-utils'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { AlertInstanceContext, @@ -32,6 +33,7 @@ interface FindThresholdSignalsParams { threshold: ThresholdNormalized; buildRuleMessage: BuildRuleMessage; timestampOverride: TimestampOverrideOrUndefined; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } export const findThresholdSignals = async ({ @@ -44,6 +46,7 @@ export const findThresholdSignals = async ({ threshold, buildRuleMessage, timestampOverride, + runtimeMappings, }: FindThresholdSignalsParams): Promise<{ searchResult: SignalSearchResponse; searchDuration: string; @@ -143,5 +146,6 @@ export const findThresholdSignals = async ({ pageSize: 0, sortOrder: 'desc', buildRuleMessage, + runtimeMappings, }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 5dc19b1b257b8..83dcea4cb60e7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -31,7 +31,13 @@ import { } from '../../../../common/detection_engine/types'; import { BuildRuleMessage } from './rule_messages'; import { ITelemetryEventsSender } from '../../telemetry/sender'; -import { CompleteRule, RuleParams } from '../schemas/rule_schemas'; +import { + CompleteRule, + QueryRuleParams, + ThreatRuleParams, + RuleParams, + SavedQueryRuleParams, +} from '../schemas/rule_schemas'; import { GenericBulkCreateResponse } from '../rule_types/factories'; import { BuildReasonMessage } from './reason_formatters'; import { @@ -301,7 +307,10 @@ export interface SearchAfterAndBulkCreateParams { from: moment.Moment; maxSignals: number; }; - completeRule: CompleteRule; + completeRule: + | CompleteRule + | CompleteRule + | CompleteRule; services: RuleExecutorServices; listClient: ListClient; exceptionsList: ExceptionListItemSchema[]; @@ -318,6 +327,7 @@ export interface SearchAfterAndBulkCreateParams { wrapHits: WrapHits; trackTotalHits?: boolean; sortOrder?: estypes.SortOrder; + runtimeMappings: estypes.MappingRuntimeFields | undefined; } export interface SearchAfterAndBulkCreateReturnType { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 0dc27e0151db8..5cebe441dfe18 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -238,6 +238,7 @@ export class Plugin implements ISecuritySolutionPlugin { ruleDataClient, eventLogService, ruleExecutionLoggerFactory: ruleExecutionLogForExecutorsFactory, + version: pluginContext.env.packageInfo.version, }; const securityRuleTypeWrapper = createSecurityRuleTypeWrapper(securityRuleTypeOptions); diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts index 14b8d08d5086b..c44d7adced16a 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -349,7 +349,7 @@ export const MonitorManagementListResultCodec = t.type({ page: t.number, perPage: t.number, total: t.union([t.number, t.null]), - allTotal: t.union([t.number, t.null]), + absoluteTotal: t.union([t.number, t.null]), syncErrors: t.union([ServiceLocationErrors, t.null]), }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_inline_errors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_inline_errors.ts index aaf94f46e283a..c8dac37aebcb7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_inline_errors.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_inline_errors.ts @@ -90,7 +90,7 @@ export function useInlineErrors({ }, }, collapse: { field: 'config_id' }, - sort: [{ [sortFieldMap[sortField]]: sortOrder }], + sort: sortFieldMap[sortField] ? [{ [sortFieldMap[sortField]]: sortOrder }] : undefined, }, }, [syntheticsMonitors, lastRefresh, doFetch, sortField, sortOrder], diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts index 62176efd80fa0..ccc5d0e4d2737 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts @@ -64,9 +64,10 @@ export function useMonitorList() { error, pageState, syntheticsMonitors, + total: data?.total ?? 0, loadPage, reloadPage, isDataQueried, - allTotal: data.allTotal ?? 0, + absoluteTotal: data.absoluteTotal ?? 0, }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.test.tsx index c65d16fe0b1a5..37ddab9bb0ca7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.test.tsx @@ -52,7 +52,7 @@ describe('', () => { error: null, loading: true, data: { - allTotal: 6, + absoluteTotal: 6, perPage: 5, page: 1, total: 6, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_container.tsx index 0a9d253d287fc..86663b21a5ea9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_container.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_container.tsx @@ -18,6 +18,8 @@ export const MonitorListContainer = ({ isEnabled }: { isEnabled?: boolean }) => error, loading: monitorsLoading, syntheticsMonitors, + total, + absoluteTotal, loadPage, reloadPage, } = useMonitorList(); @@ -28,7 +30,7 @@ export const MonitorListContainer = ({ isEnabled }: { isEnabled?: boolean }) => sortOrder: pageState.sortOrder, }); - if (!isEnabled && syntheticsMonitors.length === 0) { + if (!isEnabled && absoluteTotal === 0) { return null; } @@ -37,6 +39,7 @@ export const MonitorListContainer = ({ isEnabled }: { isEnabled?: boolean }) => void; @@ -42,6 +43,7 @@ interface Props { export const MonitorList = ({ pageState: { pageIndex, pageSize, sortField, sortOrder }, syntheticsMonitors, + total, error, loading, loadPage, @@ -83,9 +85,9 @@ export const MonitorList = ({ ); const pagination = { - pageIndex: pageIndex - 1, // page index for EuiBasicTable is base 0 + pageIndex, pageSize, - totalItemCount: syntheticsMonitors.length || 0, + totalItemCount: total, pageSizeOptions: [5, 10, 25, 50, 100], }; @@ -99,7 +101,7 @@ export const MonitorList = ({ const recordRangeLabel = labels.getRecordRangeLabel({ rangeStart: pageSize * pageIndex + 1, rangeEnd: pageSize * pageIndex + pageSize, - total: syntheticsMonitors.length, + total, }); const columns = getMonitorListColumns({ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitor_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitor_page.tsx index 339abe09e3093..b09c3e19ec5c8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitor_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitor_page.tsx @@ -33,7 +33,7 @@ export const MonitorPage: React.FC = () => { syntheticsMonitors, loading: monitorsLoading, isDataQueried, - allTotal, + absoluteTotal, } = useMonitorList(); const { @@ -46,7 +46,7 @@ export const MonitorPage: React.FC = () => { const { loading: locationsLoading } = useLocations(); const showEmptyState = isEnabled !== undefined && syntheticsMonitors.length === 0; - if (isEnabled && !monitorsLoading && allTotal === 0 && isDataQueried) { + if (isEnabled && !monitorsLoading && absoluteTotal === 0 && isDataQueried) { return ; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts index 20e0981e56d73..94fecac414d90 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts @@ -22,7 +22,7 @@ export interface MonitorListState { } const initialState: MonitorListState = { - data: { page: 1, perPage: 10, total: null, monitors: [], syncErrors: [], allTotal: 0 }, + data: { page: 1, perPage: 10, total: null, monitors: [], syncErrors: [], absoluteTotal: 0 }, pageState: { pageIndex: 0, pageSize: 10, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts index a2487bc9905f5..ed932999e0939 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts @@ -72,7 +72,7 @@ export const mockState: SyntheticsAppState = { perPage: 0, page: 0, syncErrors: [], - allTotal: 0, + absoluteTotal: 0, }, error: null, loading: false, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/page_tabs.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/page_tabs.test.tsx deleted file mode 100644 index 2870006dc20be..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/page_tabs.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { PageTabs } from './page_tabs'; -import { createMemoryHistory } from 'history'; - -describe('PageTabs', () => { - it('it renders all tabs', () => { - const { getByText } = render(); - expect(getByText('Overview')).toBeInTheDocument(); - expect(getByText('Certificates')).toBeInTheDocument(); - }); - - it('it keep params while switching', () => { - const { getByTestId } = render(, { - history: createMemoryHistory({ - initialEntries: ['/settings/?g=%22%22&dateRangeStart=now-10m&dateRangeEnd=now'], - }), - }); - expect(getByTestId('uptimeSettingsToOverviewLink')).toHaveAttribute( - 'href', - '/?dateRangeStart=now-10m' - ); - expect(getByTestId('uptimeCertificatesLink')).toHaveAttribute( - 'href', - '/certificates?dateRangeStart=now-10m' - ); - }); - - it('it resets params on overview if already on overview', () => { - const { getByTestId } = render(, { - history: createMemoryHistory({ - initialEntries: ['/?g=%22%22&dateRangeStart=now-10m&dateRangeEnd=now'], - }), - }); - expect(getByTestId('uptimeSettingsToOverviewLink')).toHaveAttribute('href', '/'); - }); -}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/page_tabs.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/page_tabs.tsx deleted file mode 100644 index 9a0677faffde8..0000000000000 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/page_tabs.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useEffect, useState } from 'react'; - -import { EuiTabs, EuiTab } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useHistory, useRouteMatch } from 'react-router-dom'; -import { CERTIFICATES_ROUTE, OVERVIEW_ROUTE } from '../../../../../common/constants'; -import { useGetUrlParams } from '../../../hooks'; -import { stringifyUrlParams } from '../../../../apps/synthetics/utils/url_params/stringify_url_params'; - -const tabs = [ - { - id: OVERVIEW_ROUTE, - name: i18n.translate('xpack.synthetics.overviewPage.headerText', { - defaultMessage: 'Overview', - description: `The text that will be displayed in the app's heading when the Overview page loads.`, - }), - dataTestSubj: 'uptimeSettingsToOverviewLink', - }, - { - id: CERTIFICATES_ROUTE, - name: 'Certificates', - dataTestSubj: 'uptimeCertificatesLink', - }, -]; - -export const PageTabs = () => { - const [selectedTabId, setSelectedTabId] = useState(null); - - const history = useHistory(); - - const params = useGetUrlParams(); - - const isOverView = useRouteMatch(OVERVIEW_ROUTE); - const isCerts = useRouteMatch(CERTIFICATES_ROUTE); - - useEffect(() => { - if (isOverView?.isExact) { - setSelectedTabId(OVERVIEW_ROUTE); - } - if (isCerts) { - setSelectedTabId(CERTIFICATES_ROUTE); - } - if (!isOverView?.isExact && !isCerts) { - setSelectedTabId(null); - } - }, [isCerts, isOverView]); - - const createHrefForTab = (id: string) => { - if (selectedTabId === OVERVIEW_ROUTE && id === OVERVIEW_ROUTE) { - // If we are already on overview route and user clicks again on overview tabs, - // we will reset the filters - return history.createHref({ pathname: id }); - } - return history.createHref({ pathname: id, search: stringifyUrlParams(params, true) }); - }; - - const renderTabs = () => { - return tabs.map(({ dataTestSubj, name, id }, index) => ( - setSelectedTabId(id)} - isSelected={id === selectedTabId} - key={index} - data-test-subj={dataTestSubj} - href={createHrefForTab(id)} - > - {name} - - )); - }; - - return ( - - {renderTabs()} - - ); -}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.test.tsx index a24f8dd280626..5f331ee5f48c8 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors.test.tsx @@ -69,7 +69,14 @@ describe('useInlineErrors', function () { { error: { monitorList: null, serviceLocations: null, enablement: null }, enablement: null, - list: { monitors: [], page: 1, perPage: 10, total: null, syncErrors: null, allTotal: 0 }, + list: { + monitors: [], + page: 1, + perPage: 10, + total: null, + syncErrors: null, + absoluteTotal: 0, + }, loading: { monitorList: false, serviceLocations: false, enablement: false }, locations: [], syntheticsService: { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.test.tsx index 87fe6b5cf0910..e12dbe88d2d2b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_inline_errors_count.test.tsx @@ -67,7 +67,14 @@ describe('useInlineErrorsCount', function () { [ { error: { monitorList: null, serviceLocations: null, enablement: null }, - list: { monitors: [], page: 1, perPage: 10, total: null, syncErrors: null, allTotal: 0 }, + list: { + monitors: [], + page: 1, + perPage: 10, + total: null, + syncErrors: null, + absoluteTotal: 0, + }, enablement: null, loading: { monitorList: false, serviceLocations: false, enablement: false }, locations: [], diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.test.tsx index abd6796bff7e4..ea2d9d99d436f 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_locations.test.tsx @@ -41,7 +41,7 @@ describe('useExpViewTimeRange', function () { total: 0, monitors: [], syncErrors: null, - allTotal: 0, + absoluteTotal: 0, }, locations: [], enablement: null, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/invalid_monitors.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/invalid_monitors.tsx index 1c9430dfb6aba..2212faf66b8f1 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/invalid_monitors.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/invalid_monitors.tsx @@ -49,7 +49,7 @@ export const InvalidMonitors = ({ perPage: pageState.pageSize, total: invalidTotal ?? 0, syncErrors: null, - allTotal: 0, + absoluteTotal: 0, }, enablement: null, error: { monitorList: null, serviceLocations: null, enablement: null }, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx index 21ab49c115172..a561904be141e 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx @@ -24,7 +24,7 @@ describe('', () => { throttling: DEFAULT_THROTTLING, enablement: null, list: { - allTotal: 6, + absoluteTotal: 6, perPage: 5, page: 1, total: 6, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.test.tsx index 8d4b44096faf8..c6f326594becd 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_list.test.tsx @@ -49,7 +49,7 @@ describe('', () => { total: 6, monitors, syncErrors: null, - allTotal: 6, + absoluteTotal: 6, }, locations: [], enablement: null, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/uptime_store.mock.ts b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/uptime_store.mock.ts index c68f6b46d54f4..0e8ad3597e859 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/uptime_store.mock.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/__mocks__/uptime_store.mock.ts @@ -70,7 +70,7 @@ export const mockState: AppState = { total: null, monitors: [], syncErrors: null, - allTotal: 0, + absoluteTotal: 0, }, locations: [], loading: { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts index 9e3511e42f1fc..7a3ae1f22cdcf 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts @@ -52,7 +52,7 @@ export const initialState: MonitorManagementList = { total: null, monitors: [], syncErrors: [], - allTotal: 0, + absoluteTotal: 0, }, locations: [], enablement: null, diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts index 752569db58679..61d0694d7cfa6 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts @@ -72,6 +72,13 @@ export const syntheticsMonitor: SavedObjectsType = { tags: { type: 'keyword', }, + schedule: { + properties: { + number: { + type: 'integer', + }, + }, + }, }, }, management: { diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts index f66e78f87ee71..1bfd1f468fe05 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts @@ -92,7 +92,7 @@ export const getAllSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ type: syntheticsMonitorType, perPage, page, - sortField, + sortField: sortField === 'schedule.keyword' ? 'schedule.number' : sortField, sortOrder, searchFields: ['name', 'tags.text', 'locations.id.text', 'urls'], search: query ? `${query}*` : undefined, @@ -114,7 +114,7 @@ export const getAllSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ ...rest, monitors, perPage: perPageT, - allTotal: total, + absoluteTotal: total, syncErrors: server.syntheticsService.syncErrors, }; } @@ -125,7 +125,7 @@ export const getAllSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ ...rest, monitors, perPage: perPageT, - allTotal: rest.total, + absoluteTotal: rest.total, syncErrors: server.syntheticsService.syncErrors, }; }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 3c626effc6edc..5dc1642ac8474 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -30109,7 +30109,6 @@ "xpack.synthetics.overview.pageHeader.syntheticsCallout.content": "Uptime affiche maintenant l'aperçu du support pour les vérifications scriptées de disponibilité à plusieurs étapes. Cela signifie que vous pouvez interagir avec les éléments d'une page Web et vérifier la disponibilité d'un parcours complet (par exemple, effectuer un achat ou se connecter à un système) au lieu d'effectuer de simples petites vérifications. Veuillez cliquer ci-dessous pour en savoir plus et, si vous souhaitez être l'un des premiers à utiliser ces fonctionnalités, vous pouvez télécharger notre agent d'aperçu synthétique et visualiser vos vérifications synthétiques dans Uptime.", "xpack.synthetics.overview.pageHeader.syntheticsCallout.dismissButtonText": "Rejeter", "xpack.synthetics.overview.pageHeader.syntheticsCallout.title": "Elastic Synthetics", - "xpack.synthetics.overviewPage.headerText": "Aperçu", "xpack.synthetics.overviewPageLink.disabled.ariaLabel": "Bouton de pagination désactivé indiquant qu'aucune autre navigation ne peut être effectuée dans la liste des moniteurs.", "xpack.synthetics.overviewPageLink.next.ariaLabel": "Page de résultats suivante", "xpack.synthetics.overviewPageLink.prev.ariaLabel": "Page de résultats précédente", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ee5070b38d629..b763e0b6206aa 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -30306,7 +30306,6 @@ "xpack.synthetics.overview.pageHeader.syntheticsCallout.content": "アップタイムは、スクリプト化された複数ステップの可用性チェックのサポートをプレビューしています。つまり、単に単一のページのアップ/ダウンのチェックだけではなく、Webページの要素を操作したり、全体的な可用性を確認したりできます(購入やシステムへのサインインなど)。詳細については以下をクリックしてください。これらの機能を先駆けて使用したい場合は、プレビュー合成エージェントをダウンロードし、アップタイムでチェックを表示できます。", "xpack.synthetics.overview.pageHeader.syntheticsCallout.dismissButtonText": "閉じる", "xpack.synthetics.overview.pageHeader.syntheticsCallout.title": "Elastic Synthetics", - "xpack.synthetics.overviewPage.headerText": "概要", "xpack.synthetics.overviewPageLink.disabled.ariaLabel": "無効になったページ付けボタンです。モニターリストがこれ以上ナビゲーションできないことを示しています。", "xpack.synthetics.overviewPageLink.next.ariaLabel": "次の結果ページ", "xpack.synthetics.overviewPageLink.prev.ariaLabel": "前の結果ページ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 55e52a154a81e..38ea4b6526b23 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -30340,7 +30340,6 @@ "xpack.synthetics.overview.pageHeader.syntheticsCallout.content": "Uptime 现在正在预览对脚本化多步骤可用性检查的支持。这意味着您可以与网页元素进行交互,并检查整个过程(例如购买或登录系统)的可用性,而不仅仅是简单的单个页面启动/关闭检查。请单击下面的内容以了解详情,如果您想率先使用这些功能,则可以下载我们的预览组合代理,并在 Uptime 中查看组合检查。", "xpack.synthetics.overview.pageHeader.syntheticsCallout.dismissButtonText": "关闭", "xpack.synthetics.overview.pageHeader.syntheticsCallout.title": "Elastic Synthetics", - "xpack.synthetics.overviewPage.headerText": "概览", "xpack.synthetics.overviewPageLink.disabled.ariaLabel": "禁用的分页按钮表示在监测列表中无法进行进一步导航。", "xpack.synthetics.overviewPageLink.next.ariaLabel": "下页结果", "xpack.synthetics.overviewPageLink.prev.ariaLabel": "上页结果", diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/environment_filter/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/environment_filter/index.tsx index 8a9dcad9e1915..7a0ac99d608f5 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/environment_filter/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/environment_filter/index.tsx @@ -73,7 +73,7 @@ export function EnvironmentFilter({ }: EnvironmentFilterProps) { const history = useHistory(); const location = useLocation(); - const { environments, status = 'loading' } = useEnvironmentsFetcher({ + const { environments, loading } = useEnvironmentsFetcher({ serviceName, start, end, @@ -97,7 +97,7 @@ export function EnvironmentFilter({ onChange={(event) => { updateEnvironmentUrl(history, location, event.target.value); }} - isLoading={status === 'loading'} + isLoading={loading} style={{ minWidth }} /> ); diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts index f76de5e82586f..eda4076450c15 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts @@ -5,10 +5,28 @@ * 2.0. */ -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useEsSearch } from '@kbn/observability-plugin/public'; +import { + formatHasRumResult, + hasRumDataQuery, +} from '../../../../services/data/has_rum_data_query'; +import { useDataView } from '../local_uifilters/use_data_view'; export function useHasRumData() { - return useFetcher((callApmApi) => { - return callApmApi('GET /api/apm/observability_overview/has_rum_data', {}); - }, []); + const { dataViewTitle } = useDataView(); + const { data: response, loading } = useEsSearch( + { + index: dataViewTitle, + ...hasRumDataQuery({}), + }, + [dataViewTitle], + { + name: 'UXHasRumData', + } + ); + + return { + data: formatHasRumResult(response, dataViewTitle), + loading, + }; } diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx index 8b9b0ccdeb60e..aafaf22a9bba3 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx @@ -29,7 +29,7 @@ export function RumHome() { const PageTemplateComponent = observability.navigation.PageTemplate; - const { data: rumHasData, status } = useHasRumData(); + const { data: rumHasData, loading: isLoading } = useHasRumData(); const noDataConfig: KibanaPageTemplateProps['noDataConfig'] = !rumHasData?.hasData @@ -56,8 +56,6 @@ export function RumHome() { } : undefined; - const isLoading = status === 'loading'; - return ( diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts index 3f774375e39ea..4fd5cf01423f6 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts @@ -10,6 +10,7 @@ import { DataPublicPluginStart, isCompleteResponse, } from '@kbn/data-plugin/public'; +import { IKibanaSearchRequest } from '@kbn/data-plugin/common'; import { FetchDataParams, HasDataParams, @@ -23,49 +24,30 @@ import { DEFAULT_RANKS, } from '../../../services/data/core_web_vitals_query'; import { callApmApi } from '../../../services/rest/create_call_apm_api'; +import { + formatHasRumResult, + hasRumDataQuery, +} from '../../../services/data/has_rum_data_query'; export { createCallApmApi } from '../../../services/rest/create_call_apm_api'; -type FetchUxOverviewDateParams = FetchDataParams & { - dataStartPlugin: DataPublicPluginStart; -}; +type WithDataPlugin = T & { dataStartPlugin: DataPublicPluginStart }; async function getCoreWebVitalsResponse({ absoluteTime, serviceName, dataStartPlugin, -}: FetchUxOverviewDateParams) { +}: WithDataPlugin) { const dataView = await callApmApi('GET /internal/apm/data_view/dynamic', { signal: null, }); - return new Promise< - ESSearchResponse<{}, ReturnType> - >((resolve) => { - const search$ = dataStartPlugin.search - .search( - { - params: { - index: dataView.dynamicDataView?.title, - ...coreWebVitalsQuery( - absoluteTime.start, - absoluteTime.end, - undefined, - { - serviceName: serviceName ? [serviceName] : undefined, - } - ), - }, - }, - {} - ) - .subscribe({ - next: (result) => { - if (isCompleteResponse(result)) { - resolve(result.rawResponse as any); - search$.unsubscribe(); - } - }, - }); + return await esQuery>(dataStartPlugin, { + params: { + index: dataView.dynamicDataView?.title, + ...coreWebVitalsQuery(absoluteTime.start, absoluteTime.end, undefined, { + serviceName: serviceName ? [serviceName] : undefined, + }), + }, }); } @@ -82,7 +64,7 @@ const CORE_WEB_VITALS_DEFAULTS: UXMetrics = { }; export const fetchUxOverviewDate = async ( - params: FetchUxOverviewDateParams + params: WithDataPlugin ): Promise => { const coreWebVitalsResponse = await getCoreWebVitalsResponse(params); return { @@ -94,18 +76,42 @@ export const fetchUxOverviewDate = async ( }; export async function hasRumData( - params: HasDataParams + params: WithDataPlugin ): Promise { - return await callApmApi('GET /api/apm/observability_overview/has_rum_data', { + const dataView = await callApmApi('GET /internal/apm/data_view/dynamic', { signal: null, - params: { - query: params?.absoluteTime - ? { - start: new Date(params.absoluteTime.start).toISOString(), - end: new Date(params.absoluteTime.end).toISOString(), - uiFilters: '', - } - : undefined, - }, + }); + const esQueryResponse = await esQuery>( + params.dataStartPlugin, + { + params: { + index: dataView.dynamicDataView?.title, + ...hasRumDataQuery({ + start: params?.absoluteTime?.start, + end: params?.absoluteTime?.end, + }), + }, + } + ); + + return formatHasRumResult(esQueryResponse, dataView.dynamicDataView?.title); +} + +async function esQuery( + dataStartPlugin: DataPublicPluginStart, + query: IKibanaSearchRequest & { params: { index?: string } } +) { + return new Promise>((resolve, reject) => { + const search$ = dataStartPlugin.search.search(query).subscribe({ + next: (result) => { + if (isCompleteResponse(result)) { + resolve(result.rawResponse as any); + search$.unsubscribe(); + } + }, + error: (err) => { + reject(err); + }, + }); }); } diff --git a/x-pack/plugins/ux/public/hooks/use_environments_fetcher.tsx b/x-pack/plugins/ux/public/hooks/use_environments_fetcher.tsx index 25f73ae3e5f9d..ca110f30aa8bc 100644 --- a/x-pack/plugins/ux/public/hooks/use_environments_fetcher.tsx +++ b/x-pack/plugins/ux/public/hooks/use_environments_fetcher.tsx @@ -5,12 +5,19 @@ * 2.0. */ +import { useEsSearch } from '@kbn/observability-plugin/public'; import { useMemo } from 'react'; -import { useFetcher } from './use_fetcher'; import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '../../common/environment_filter_values'; +import { useDataView } from '../components/app/rum_dashboard/local_uifilters/use_data_view'; +import { + getEnvironments, + transformEnvironmentsResponse, +} from '../services/data/environments_query'; +import { callDateMath } from '../services/data/call_date_math'; +import { useKibanaServices } from './use_kibana_services'; function getEnvironmentOptions(environments: string[]) { const environmentOptions = environments @@ -23,8 +30,6 @@ function getEnvironmentOptions(environments: string[]) { return [ENVIRONMENT_ALL, ...environmentOptions]; } -const INITIAL_DATA = { environments: [] }; - export function useEnvironmentsFetcher({ serviceName, start, @@ -34,27 +39,34 @@ export function useEnvironmentsFetcher({ start?: string; end?: string; }) { - const { data = INITIAL_DATA, status = 'loading' } = useFetcher( - (callApmApi) => { - if (start && end) { - return callApmApi('GET /internal/apm/environments', { - params: { - query: { - start, - end, - serviceName, - }, - }, - }); - } + const { dataViewTitle } = useDataView(); + const kibana = useKibanaServices(); + const size = + // @ts-ignore defaults field should exist and contain this value + kibana.uiSettings?.defaults?.['observability:maxSuggestions']?.value ?? 100; + const { data: esQueryResponse, loading } = useEsSearch( + { + index: dataViewTitle, + ...getEnvironments({ + serviceName, + start: callDateMath(start), + end: callDateMath(end), + size, + }), }, - [start, end, serviceName] + [dataViewTitle, serviceName, start, end, size], + { name: 'UxEnvironments' } + ); + + const environments = useMemo( + () => transformEnvironmentsResponse(esQueryResponse) ?? [], + [esQueryResponse] ); const environmentOptions = useMemo( - () => getEnvironmentOptions(data.environments), - [data?.environments] + () => getEnvironmentOptions(environments), + [environments] ); - return { environments: data.environments, status, environmentOptions }; + return { environments, loading, environmentOptions }; } diff --git a/x-pack/plugins/ux/public/plugin.ts b/x-pack/plugins/ux/public/plugin.ts index aff2481a0f0b0..96e1c50637230 100644 --- a/x-pack/plugins/ux/public/plugin.ts +++ b/x-pack/plugins/ux/public/plugin.ts @@ -56,6 +56,11 @@ export interface ApmPluginStartDeps { dataViews: DataViewsPublicPluginStart; } +async function getDataStartPlugin(core: CoreSetup) { + const [_, startPlugins] = await core.getStartServices(); + return (startPlugins as ApmPluginStartDeps).data; +} + export class UxPlugin implements Plugin { constructor() {} @@ -76,14 +81,16 @@ export class UxPlugin implements Plugin { appName: 'ux', hasData: async (params?: HasDataParams) => { const dataHelper = await getUxDataHelper(); - return await dataHelper.hasRumData(params!); + const dataStartPlugin = await getDataStartPlugin(core); + return dataHelper.hasRumData({ + ...params!, + dataStartPlugin, + }); }, fetchData: async (params: FetchDataParams) => { - const [_, startPlugins] = await core.getStartServices(); - - const { data: dataStartPlugin } = startPlugins as ApmPluginStartDeps; + const dataStartPlugin = await getDataStartPlugin(core); const dataHelper = await getUxDataHelper(); - return await dataHelper.fetchUxOverviewDate({ + return dataHelper.fetchUxOverviewDate({ ...params, dataStartPlugin, }); diff --git a/x-pack/plugins/ux/public/services/data/environments_query.ts b/x-pack/plugins/ux/public/services/data/environments_query.ts new file mode 100644 index 0000000000000..56fe778d1f8d0 --- /dev/null +++ b/x-pack/plugins/ux/public/services/data/environments_query.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 { ESSearchResponse } from '@kbn/core/types/elasticsearch'; +import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types'; +import { + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_TYPE, + PROCESSOR_EVENT, +} from '../../../common/elasticsearch_fieldnames'; +import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; +import { Environment } from '../../../common/environment_rt'; + +export function transformEnvironmentsResponse( + response?: ESSearchResponse> +) { + if (!response) return response; + + const aggs = response.aggregations; + const environmentsBuckets = aggs?.environments.buckets || []; + + const environments = environmentsBuckets.map( + (environmentBucket) => environmentBucket.key as string + ); + + return environments as Environment[]; +} + +export function getEnvironments({ + serviceName, + size, + start, + end, +}: { + serviceName?: string; + size: number; + start: number; + end: number; +}) { + return { + body: { + size: 0, + query: { + bool: { + filter: [ + { + range: { + '@timestamp': { + gte: start, + lte: end, + format: 'epoch_millis', + }, + }, + }, + { + term: { + [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD, + }, + }, + { + term: { + [PROCESSOR_EVENT]: 'transaction', + }, + }, + ...(serviceName === undefined || serviceName === null + ? [] + : [{ term: { [SERVICE_NAME]: serviceName } }]), + ], + }, + }, + aggs: { + environments: { + terms: { + field: SERVICE_ENVIRONMENT, + missing: ENVIRONMENT_NOT_DEFINED.value, + size, + }, + }, + }, + }, + }; +} diff --git a/x-pack/plugins/ux/public/services/data/has_rum_data_query.ts b/x-pack/plugins/ux/public/services/data/has_rum_data_query.ts new file mode 100644 index 0000000000000..c5bd681d23c59 --- /dev/null +++ b/x-pack/plugins/ux/public/services/data/has_rum_data_query.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 { ESSearchResponse } from '@kbn/core/types/elasticsearch'; +import moment from 'moment'; +import { + SERVICE_NAME, + TRANSACTION_TYPE, + PROCESSOR_EVENT, +} from '../../../common/elasticsearch_fieldnames'; +import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types'; +import { rangeQuery } from './range_query'; + +export function formatHasRumResult( + esResult: ESSearchResponse>, + indices?: string +) { + if (!esResult) return esResult; + return { + indices, + // @ts-ignore total.value is undefined by the returned type, total is a `number` + hasData: esResult.hits.total > 0, + serviceName: + esResult.aggregations?.services?.mostTraffic?.buckets?.[0]?.key, + }; +} + +export function hasRumDataQuery({ + start = moment().subtract(24, 'h').valueOf(), + end = moment().valueOf(), +}: { + start?: number; + end?: number; +}) { + return { + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, + { term: { [PROCESSOR_EVENT]: 'transaction' } }, + ], + }, + }, + aggs: { + services: { + filter: rangeQuery(start, end)[0], + aggs: { + mostTraffic: { + terms: { + field: SERVICE_NAME, + size: 1, + }, + }, + }, + }, + }, + }, + }; +} diff --git a/x-pack/test/accessibility/apps/stack_monitoring.ts b/x-pack/test/accessibility/apps/stack_monitoring.ts new file mode 100644 index 0000000000000..4505f05eb4a8f --- /dev/null +++ b/x-pack/test/accessibility/apps/stack_monitoring.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// a11y tests for spaces, space selection and space creation and feature controls + +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'spaceSelector', 'home', 'header', 'security']); + const a11y = getService('a11y'); + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const noData = getService('monitoringNoData'); + const kibanaOverview = getService('monitoringKibanaOverview'); + const clusterOverview = getService('monitoringClusterOverview'); + + describe('Kibana Stack Monitoring a11y tests', () => { + before(async () => { + await PageObjects.common.navigateToApp('monitoring'); + await a11y.testAppSnapshot(); + }); + + it('a11y tests for no monitoring data found page', async () => { + await noData.isOnNoDataPage(); + await a11y.testAppSnapshot(); + }); + + it('a11y tests for self monitoring home page', async function () { + await noData.enableMonitoring(); + await a11y.testAppSnapshot(); + await retry.waitForWithTimeout('alert button to be visible', 30000, async () => { + return await testSubjects.isDisplayed('alerts-modal-remind-later-button'); + }); + }); + + // a11y violation caught here - if this rest is unskipped remove the test below it. + // https://github.com/elastic/kibana/issues/134139 + it.skip('a11y tests for Alerts modal remind later button', async function () { + await testSubjects.click('alerts-modal-remind-later-button'); + await a11y.testAppSnapshot(); + }); + + it('Alerts Page', async function () { + await testSubjects.click('alerts-modal-remind-later-button'); + }); + + it('a11y tests for Kibana Overview', async function () { + await clusterOverview.clickKibanaOverview(); + await kibanaOverview.isOnOverview(); + await a11y.testAppSnapshot(); + }); + + it('a11y tests for Kibana Instances Page', async function () { + await kibanaOverview.isOnOverview(); + await kibanaOverview.clickInstanceTab(); + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts index 3150925e2e49e..65a710268600d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts @@ -15,8 +15,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function createUpdateTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - // FLAKY: https://github.com/elastic/kibana/issues/132195 - describe.skip('bulkEdit', () => { + describe('bulkEdit', () => { const objectRemover = new ObjectRemover(supertest); after(() => objectRemover.removeAll()); @@ -25,7 +24,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { const { body: createdRule } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') - .send(getTestRuleData({ tags: ['default'] })); + .send(getTestRuleData({ enabled: false, tags: ['default'] })); objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting'); @@ -71,7 +70,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') - .send(getTestRuleData({ tags: [`multiple-rules-edit`] })) + .send(getTestRuleData({ enabled: false, tags: [`multiple-rules-edit`] })) .expect(200) ) ) @@ -119,11 +118,140 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }); }); + it('should bulk edit rule with schedule operation', async () => { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ enabled: false, schedule: { interval: '10m' } })); + + objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting'); + + const payload = { + ids: [createdRule.id], + operations: [ + { + operation: 'set', + field: 'schedule', + value: { interval: '1h' }, + }, + ], + }; + + const bulkEditResponse = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_bulk_edit`) + .set('kbn-xsrf', 'foo') + .send(payload); + + expect(bulkEditResponse.body.errors).to.have.length(0); + expect(bulkEditResponse.body.rules).to.have.length(1); + expect(bulkEditResponse.body.rules[0].schedule).to.eql({ interval: '1h' }); + + const { body: updatedRule } = await supertest + .get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdRule.id}`) + .set('kbn-xsrf', 'foo'); + + expect(updatedRule.schedule).to.eql({ interval: '1h' }); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdRule.id, + }); + }); + + it('should bulk edit rule with throttle operation', async () => { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ enabled: false })); + + objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting'); + + const payload = { + ids: [createdRule.id], + operations: [ + { + operation: 'set', + field: 'throttle', + value: '1h', + }, + ], + }; + + const bulkEditResponse = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_bulk_edit`) + .set('kbn-xsrf', 'foo') + .send(payload); + + expect(bulkEditResponse.body.errors).to.have.length(0); + expect(bulkEditResponse.body.rules).to.have.length(1); + expect(bulkEditResponse.body.rules[0]).property('throttle', '1h'); + + const { body: updatedRule } = await supertest + .get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdRule.id}`) + .set('kbn-xsrf', 'foo'); + + expect(updatedRule).property('throttle', '1h'); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdRule.id, + }); + }); + + it('should bulk edit rule with notifyWhen operation', async () => { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ enabled: false, throttle: '1h' })); + + objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting'); + + const payload = { + ids: [createdRule.id], + operations: [ + { + operation: 'set', + field: 'notifyWhen', + value: 'onActionGroupChange', + }, + ], + }; + + const bulkEditResponse = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_bulk_edit`) + .set('kbn-xsrf', 'foo') + .send(payload); + + expect(bulkEditResponse.body.errors).to.have.length(0); + expect(bulkEditResponse.body.rules).to.have.length(1); + expect(bulkEditResponse.body.rules[0]).property('notify_when', 'onActionGroupChange'); + + const { body: updatedRule } = await supertest + .get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdRule.id}`) + .set('kbn-xsrf', 'foo'); + + expect(updatedRule).property('notify_when', 'onActionGroupChange'); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdRule.id, + }); + }); + it(`shouldn't bulk edit rule from another space`, async () => { const { body: createdRule } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') - .send(getTestRuleData({ tags: ['default'] })); + .send(getTestRuleData({ enabled: false, tags: ['default'] })); objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting'); @@ -150,7 +278,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( - getTestRuleData({ tags: ['default'], params: { risk_score: 40, severity: 'medium' } }) + getTestRuleData({ + enabled: false, + tags: ['default'], + params: { risk_score: 40, severity: 'medium' }, + }) ); objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting'); diff --git a/x-pack/test/apm_api_integration/tests/csm/has_rum_data.spec.ts b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.spec.ts deleted file mode 100644 index 5e717739c5d04..0000000000000 --- a/x-pack/test/apm_api_integration/tests/csm/has_rum_data.spec.ts +++ /dev/null @@ -1,70 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function rumHasDataApiTests({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - - registry.when('has_rum_data without data', { config: 'trial', archives: [] }, () => { - it('returns empty list', async () => { - const start = new Date('2020-09-07T00:00:00.000Z').getTime(); - const end = new Date('2020-09-14T00:00:00.000Z').getTime() - 1; - - const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/observability_overview/has_rum_data', - params: { - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - }, - }, - }); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` - Object { - "hasData": false, - "indices": "traces-apm*,apm-*", - } - `); - }); - }); - - registry.when( - 'has RUM data with data', - { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, - () => { - it('returns that it has data and service name with most traffic', async () => { - const start = new Date('2020-09-07T00:00:00.000Z').getTime(); - const end = new Date('2020-09-16T00:00:00.000Z').getTime() - 1; - - const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/observability_overview/has_rum_data', - params: { - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - }, - }, - }); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(` - Object { - "hasData": true, - "indices": "traces-apm*,apm-*", - "serviceName": "client", - } - `); - }); - } - ); -} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts index 5382ba5fd18f4..ba8c9ea88cd17 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts @@ -379,6 +379,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.rule.version': 1, 'kibana.alert.rule.exceptions_list': [], 'kibana.alert.rule.immutable': false, + 'kibana.alert.rule.indices': ['.siem-signals-*'], 'kibana.alert.original_time': '2022-03-23T16:50:40.440Z', 'kibana.alert.original_event.agent_id_status': 'verified', 'kibana.alert.original_event.ingested': '2022-03-23T16:50:28.994Z', @@ -547,6 +548,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.rule.version': 1, 'kibana.alert.rule.exceptions_list': [], 'kibana.alert.rule.immutable': false, + 'kibana.alert.rule.indices': rule.index, 'kibana.alert.original_time': '2022-03-23T16:50:40.440Z', 'kibana.alert.original_event.agent_id_status': 'verified', 'kibana.alert.original_event.ingested': '2022-03-23T16:50:28.994Z', diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts index a952d24129894..e4fd8f11141ae 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts @@ -75,7 +75,7 @@ export default function (providerContext: FtrProviderContext) { .type('application/gzip') .send(buf) .expect(200); - expect(res.body.items.length).to.be(29); + expect(res.body.items.length).to.be(30); }); it('should install a zip archive correctly and package info should return correctly after validation', async function () { @@ -86,7 +86,7 @@ export default function (providerContext: FtrProviderContext) { .type('application/zip') .send(buf) .expect(200); - expect(res.body.items.length).to.be(29); + expect(res.body.items.length).to.be(30); }); it('should throw an error if the archive is zip but content type is gzip', async function () { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts b/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts index 834de2432d7c8..d33f9b8a922d9 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts @@ -39,7 +39,11 @@ export default function (providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xxxx') .expect(200); - const templateName = body.items[0].id; + const templateName = body.items.filter((item: any) => item.type === 'index_template')?.[0].id; + + if (!templateName) { + throw new Error('index template not found in package assets'); + } const { body: indexTemplateResponse } = await es.transport.request( { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts index 44d609c5d492e..9c257bafe87d0 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts @@ -631,6 +631,10 @@ const expectAssetsInstalled = ({ id: 'logs-all_assets.test_logs-0.1.0-pipeline2', type: 'ingest_pipeline', }, + { + id: 'metrics-all_assets.test_metrics-0.1.0', + type: 'ingest_pipeline', + }, { id: 'default', type: 'ml_model', diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index e367e76049b72..fd65a5ed4af67 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -427,6 +427,14 @@ export default function (providerContext: FtrProviderContext) { id: 'logs-all_assets.test_logs-0.2.0-pipeline1', type: 'ingest_pipeline', }, + { + id: 'logs-all_assets.test_logs2-0.2.0', + type: 'ingest_pipeline', + }, + { + id: 'metrics-all_assets.test_metrics-0.2.0', + type: 'ingest_pipeline', + }, { id: 'logs-all_assets.test_logs', type: 'index_template', diff --git a/x-pack/test/functional/es_archives/kubernetes_security/process_events/data.json b/x-pack/test/functional/es_archives/kubernetes_security/process_events/data.json index 61be25aaa7809..f8560b835ef85 100644 --- a/x-pack/test/functional/es_archives/kubernetes_security/process_events/data.json +++ b/x-pack/test/functional/es_archives/kubernetes_security/process_events/data.json @@ -8,7 +8,8 @@ "@timestamp": "2020-12-16T15:16:18.570Z", "message": "hello world 1", "orchestrator.namespace": "namespace", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -23,7 +24,8 @@ "@timestamp": "2020-12-16T15:16:18.570Z", "message": "hello world 1", "orchestrator.namespace": "namespace", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -38,7 +40,8 @@ "@timestamp": "2020-12-16T15:16:19.570Z", "message": "hello world 1", "orchestrator.namespace": "namespace02", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -53,7 +56,8 @@ "@timestamp": "2020-12-16T15:16:20.570Z", "message": "hello world security", "orchestrator.namespace": "namespace02", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "2" } } } @@ -68,7 +72,8 @@ "@timestamp": "2020-12-16T15:16:21.570Z", "message": "hello world security", "orchestrator.namespace": "namespace03", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -82,7 +87,8 @@ "@timestamp": "2020-12-16T15:16:22.570Z", "message": "hello world security", "orchestrator.namespace": "namespace03", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -96,7 +102,8 @@ "@timestamp": "2020-12-16T15:16:23.570Z", "message": "hello world security", "orchestrator.namespace": "namespace04", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -110,7 +117,8 @@ "@timestamp": "2020-12-16T15:16:24.570Z", "message": "hello world security", "orchestrator.namespace": "namespace05", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -124,7 +132,8 @@ "@timestamp": "2020-12-16T15:16:25.570Z", "message": "hello world security", "orchestrator.namespace": "namespace06", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -138,7 +147,8 @@ "@timestamp": "2020-12-16T15:16:26.570Z", "message": "hello world security", "orchestrator.namespace": "namespace07", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -152,7 +162,8 @@ "@timestamp": "2020-12-16T15:16:27.570Z", "message": "hello world security", "orchestrator.namespace": "namespace08", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -166,7 +177,8 @@ "@timestamp": "2020-12-16T15:16:28.570Z", "message": "hello world security", "orchestrator.namespace": "namespace09", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -180,7 +192,8 @@ "@timestamp": "2020-12-16T15:16:29.570Z", "message": "hello world security", "orchestrator.namespace": "namespace10", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } @@ -194,7 +207,8 @@ "@timestamp": "2020-12-16T15:16:30.570Z", "message": "hello world security", "orchestrator.namespace": "namespace11", - "container.image.name": "debian11" + "container.image.name": "debian11", + "process.entry_leader.entity_id": "1" } } } diff --git a/x-pack/test/functional/es_archives/kubernetes_security/process_events/mappings.json b/x-pack/test/functional/es_archives/kubernetes_security/process_events/mappings.json index c8440e22a211f..06a34e1295449 100644 --- a/x-pack/test/functional/es_archives/kubernetes_security/process_events/mappings.json +++ b/x-pack/test/functional/es_archives/kubernetes_security/process_events/mappings.json @@ -20,6 +20,10 @@ "container.image.name": { "type": "keyword", "ignore_above": 256 + }, + "process.entry_leader.entity_id": { + "type": "keyword", + "ignore_above": 256 } } } diff --git a/x-pack/test/functional/services/monitoring/kibana_overview.js b/x-pack/test/functional/services/monitoring/kibana_overview.js index 3549709d1eadb..ce98ab5d715fa 100644 --- a/x-pack/test/functional/services/monitoring/kibana_overview.js +++ b/x-pack/test/functional/services/monitoring/kibana_overview.js @@ -10,11 +10,16 @@ export function MonitoringKibanaOverviewProvider({ getService }) { const retry = getService('retry'); const SUBJ_OVERVIEW_PAGE = 'kibanaOverviewPage'; + const SUBJ_KBN_INSTANCES = 'kibanaInstancesPage'; return new (class KibanaOverview { async isOnOverview() { const pageId = await retry.try(() => testSubjects.find(SUBJ_OVERVIEW_PAGE)); return pageId !== null; } + + async clickInstanceTab() { + return testSubjects.click(SUBJ_KBN_INSTANCES); + } })(); } diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index 43d62ef74bf31..34302d8b517f1 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -83,6 +83,10 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv }, async loadDataAndGoToMonitorPage(dateStart: string, dateEnd: string, monitorId: string) { + const hasTour = await testSubjects.exists('syntheticsManagementTourDismiss'); + if (hasTour) { + await testSubjects.click('syntheticsManagementTourDismiss'); + } await PageObjects.timePicker.setAbsoluteRange(dateStart, dateEnd); await this.goToMonitor(monitorId); }, diff --git a/x-pack/test/kubernetes_security/basic/tests/aggregate.ts b/x-pack/test/kubernetes_security/basic/tests/aggregate.ts index b9d85299d4bc8..c1501c97e77e5 100644 --- a/x-pack/test/kubernetes_security/basic/tests/aggregate.ts +++ b/x-pack/test/kubernetes_security/basic/tests/aggregate.ts @@ -11,6 +11,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; const MOCK_INDEX = 'kubernetes-test-index'; const ORCHESTRATOR_NAMESPACE_PROPERTY = 'orchestrator.namespace'; const CONTAINER_IMAGE_NAME_PROPERTY = 'container.image.name'; +const ENTRY_LEADER_ENTITY_ID = 'process.entry_leader.entity_id'; const TIMESTAMP_PROPERTY = '@timestamp'; // eslint-disable-next-line import/no-default-export @@ -65,6 +66,44 @@ export default function aggregateTests({ getService }: FtrProviderContext) { expect(response.body[0].key).to.be('namespace11'); }); + it(`${AGGREGATE_ROUTE} return countBy value for each aggregation`, async () => { + const response = await supertest + .get(AGGREGATE_ROUTE) + .set('kbn-xsrf', 'foo') + .query({ + query: JSON.stringify({ match: { [CONTAINER_IMAGE_NAME_PROPERTY]: 'debian11' } }), + groupBy: ORCHESTRATOR_NAMESPACE_PROPERTY, + countBy: ORCHESTRATOR_NAMESPACE_PROPERTY, + page: 0, + index: MOCK_INDEX, + }); + expect(response.status).to.be(200); + expect(response.body.length).to.be(10); + + // when groupBy and countBy use the same field, count_by_aggs.value will always be 1 + response.body.forEach((agg: any) => { + expect(agg.count_by_aggs.value).to.be(1); + }); + }); + + it(`${AGGREGATE_ROUTE} return sorted aggregation by countBy field if sortByCount is true`, async () => { + const response = await supertest + .get(AGGREGATE_ROUTE) + .set('kbn-xsrf', 'foo') + .query({ + query: JSON.stringify({ match: { [CONTAINER_IMAGE_NAME_PROPERTY]: 'debian11' } }), + groupBy: ORCHESTRATOR_NAMESPACE_PROPERTY, + countBy: ENTRY_LEADER_ENTITY_ID, + page: 0, + index: MOCK_INDEX, + sortByCount: 'desc', + }); + expect(response.status).to.be(200); + expect(response.body.length).to.be(10); + expect(response.body[0].count_by_aggs.value).to.be(2); + expect(response.body[1].count_by_aggs.value).to.be(1); + }); + it(`${AGGREGATE_ROUTE} allows a range query`, async () => { const response = await supertest .get(AGGREGATE_ROUTE) diff --git a/x-pack/test/performance/services/performance.ts b/x-pack/test/performance/services/performance.ts index 35db8020309a7..3034883c93e31 100644 --- a/x-pack/test/performance/services/performance.ts +++ b/x-pack/test/performance/services/performance.ts @@ -13,12 +13,6 @@ import apm, { Span, Transaction } from 'elastic-apm-node'; import playwright, { ChromiumBrowser, Page, BrowserContext, CDPSession } from 'playwright'; import { FtrService, FtrProviderContext } from '../ftr_provider_context'; -apm.start({ - serviceName: 'functional test runner', - serverUrl: 'https://kibana-ops-e2e-perf.apm.us-central1.gcp.cloud.es.io:443', - secretToken: 'CTs9y3cvcfq13bQqsB', -}); - export interface StepCtx { page: Page; kibanaUrl: string; @@ -38,6 +32,15 @@ export class PerformanceTestingService extends FtrService { constructor(ctx: FtrProviderContext) { super(ctx); + ctx.getService('lifecycle').beforeTests.add(() => { + apm.start({ + serviceName: 'functional test runner', + serverUrl: this.config.get(`kbnTestServer.env`).ELASTIC_APM_SERVER_URL, + secretToken: this.config.get(`kbnTestServer.env`).ELASTIC_APM_SECRET_TOKEN, + globalLabels: this.config.get(`kbnTestServer.env`).ELASTIC_APM_GLOBAL_LABELS, + }); + }); + ctx.getService('lifecycle').cleanup.add(async () => { await this.shutdownBrowser(); }); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/usage/api_counters.ts b/x-pack/test/reporting_api_integration/reporting_and_security/usage/api_counters.ts index e762d2b4c5ae1..e0c918648be9e 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/usage/api_counters.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/usage/api_counters.ts @@ -17,7 +17,8 @@ export default function ({ getService }: FtrProviderContext) { const usageAPI = getService('usageAPI'); const reportingAPI = getService('reportingAPI'); - describe(`Usage Counters`, () => { + // Failing: See https://github.com/elastic/kibana/issues/134517 + describe.skip(`Usage Counters`, () => { before(async () => { await esArchiver.emptyKibanaIndex(); await reportingAPI.initEcommerce(); diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index 2f4f76de53ced..0074adb4a19d1 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -57,6 +57,15 @@ export async function SecuritySolutionConfigurableCypressTestRunner( }); } +/** + * Takes total CI jobs number(totalCiJobs) between which tests will be split and sequential number of the job(ciJobNumber). + * This helper will split file list cypress integrations into chunks, and run integrations in chunk which match ciJobNumber + * If both totalCiJobs === 1 && ciJobNumber === 1, this function will run all existing tests, without splitting them + * @param context FtrProviderContext + * @param {number} totalCiJobs - total number of jobs between which tests will be split + * @param {number} ciJobNumber - number of job + * @returns + */ export async function SecuritySolutionCypressCliTestRunnerCI( context: FtrProviderContext, totalCiJobs: number, diff --git a/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher.js b/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher.js index eca1ca4c41cd2..869d56fe90047 100644 --- a/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher.js +++ b/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher.js @@ -48,6 +48,7 @@ export default function ({ getService, getPageObjects }) { interval: `${interval}s`, }, }, + throttle_period: '15m', actions: { email_admin: { email: { diff --git a/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher_png.js b/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher_png.js index 85c00cba91fa1..d793390488596 100644 --- a/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher_png.js +++ b/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher_png.js @@ -42,6 +42,7 @@ export default ({ getService, getPageObjects }) => { interval: `${interval}s`, }, }, + throttle_period: '15m', actions: { email_admin: { email: { @@ -78,6 +79,7 @@ export default ({ getService, getPageObjects }) => { it('should successfully add a new watch for PNG Reporting', async () => { await putWatcher(watch, id, body, client, log); }); + it('should be successful and increment revision', async () => { await getWatcher(watch, id, client, log, PageObjects.common, retry.tryForTime.bind(retry)); }); diff --git a/x-pack/test/stack_functional_integration/apps/reporting/util.js b/x-pack/test/stack_functional_integration/apps/reporting/util.js index d3dc7967d1f54..9ec94c31942a4 100644 --- a/x-pack/test/stack_functional_integration/apps/reporting/util.js +++ b/x-pack/test/stack_functional_integration/apps/reporting/util.js @@ -18,11 +18,11 @@ export const putWatcher = async (watch, id, body, client, log) => { expect(putWatchResponse.body._version).to.eql('1'); }; export const getWatcher = async (watch, id, client, log, common, tryForTime) => { - await common.sleep(50000); + await common.sleep(10000); await tryForTime( 250000, async () => { - await common.sleep(25000); + await common.sleep(3000); await watcherHistory(id, client, log); @@ -31,7 +31,7 @@ export const getWatcher = async (watch, id, client, log, common, tryForTime) => expect(getWatchResponse.body._id).to.eql(id); expect(getWatchResponse.body._version).to.be.above(1); log.debug(`\n getWatchResponse.body._version: ${getWatchResponse.body._version}`); - expect(getWatchResponse.body.status.execution_state).to.eql('executed'); + expect(getWatchResponse.body.status.execution_state).to.eql('throttled'); expect(getWatchResponse.body.status.actions.email_admin.last_execution.successful).to.eql( true ); diff --git a/yarn.lock b/yarn.lock index 654b316bb062d..cd15e767aa6d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3015,10 +3015,26 @@ version "0.0.0" uid "" +"@kbn/core-analytics-browser-internal@link:bazel-bin/packages/core/analytics/core-analytics-browser-internal": + version "0.0.0" + uid "" + +"@kbn/core-analytics-browser-mocks@link:bazel-bin/packages/core/analytics/core-analytics-browser-mocks": + version "0.0.0" + uid "" + +"@kbn/core-analytics-browser@link:bazel-bin/packages/core/analytics/core-analytics-browser": + version "0.0.0" + uid "" + "@kbn/core-base-browser-internal@link:bazel-bin/packages/core/base/core-base-browser-internal": version "0.0.0" uid "" +"@kbn/core-base-browser-mocks@link:bazel-bin/packages/core/base/core-base-browser-mocks": + version "0.0.0" + uid "" + "@kbn/core-base-common-internal@link:bazel-bin/packages/core/base/core-base-common-internal": version "0.0.0" uid "" @@ -6358,10 +6374,26 @@ version "0.0.0" uid "" +"@types/kbn__core-analytics-browser-internal@link:bazel-bin/packages/core/analytics/core-analytics-browser-internal/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-analytics-browser-mocks@link:bazel-bin/packages/core/analytics/core-analytics-browser-mocks/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-analytics-browser@link:bazel-bin/packages/core/analytics/core-analytics-browser/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-base-browser-internal@link:bazel-bin/packages/core/base/core-base-browser-internal/npm_module_types": version "0.0.0" uid "" +"@types/kbn__core-base-browser-mocks@link:bazel-bin/packages/core/base/core-base-browser-mocks/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-base-browser@link:bazel-bin/packages/core/base/core-base-browser/npm_module_types": version "0.0.0" uid ""