diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index a5b27c67eaca15..0131bca3e6c507 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import actionsObj from './actions.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 116 | 0 | 116 | 7 | + ## Server ### Setup diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 5b0e69c14f58b0..241b6b82598c3f 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import advancedSettingsObj from './advanced_settings.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 23 | 0 | 22 | 1 | + ## Client ### Setup diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index a0294a02d9f109..5dce4a9a2c7b17 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import alertingObj from './alerting.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 213 | 0 | 213 | 15 | + ## Client ### Setup diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index d037dc99e1a542..e2a4ee9e7ea7ab 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import apmObj from './apm.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 37 | 0 | 37 | 30 | + ## Client ### Setup diff --git a/api_docs/apm_oss.mdx b/api_docs/apm_oss.mdx index 73707040013788..2a03249734f9c9 100644 --- a/api_docs/apm_oss.mdx +++ b/api_docs/apm_oss.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import apmOssObj from './apm_oss.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 4 | 0 | 4 | 0 | + ## Server ### Setup diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index e6b648e38cdc34..d9727cb817e26a 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import bannersObj from './banners.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 9 | 0 | 9 | 0 | + ## Common ### Interfaces diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index faf3d7eefc7f9f..217e190831313b 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import bfetchObj from './bfetch.json'; +Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. + +Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 73 | 1 | 62 | 2 | + ## Client ### Start diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 75ac78d571bc99..9d8707ab778e6b 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import canvasObj from './canvas.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 6 | 0 | 5 | 3 | + ## Client ### Setup diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 20d371cbcdc6e0..0f9cbe5364b630 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import casesObj from './cases.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 406 | 0 | 381 | 13 | + ## Client ### Start diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 1cbdb2f19a6fbb..95d0bd527e631b 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import chartsObj from './charts.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 190 | 2 | 159 | 1 | + ## Client ### Start diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 4a61ce885f1548..f9d44f9c8d08ba 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import cloudObj from './cloud.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 20 | 0 | 20 | 0 | + ## Client ### Setup diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 3514fa2697aaa0..74a7fadf7a5c37 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import consoleObj from './console.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2 | 0 | 2 | 0 | + ## Server ### Setup diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 77050dd72894a8..31889ec1042b84 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import coreObj from './core.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2273 | 148 | 1052 | 26 | + ## Client ### Classes diff --git a/api_docs/core_application.mdx b/api_docs/core_application.mdx index 76d2d95f21e05f..ce165343346322 100644 --- a/api_docs/core_application.mdx +++ b/api_docs/core_application.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import coreApplicationObj from './core_application.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2273 | 148 | 1052 | 26 | + ## Client ### Classes diff --git a/api_docs/core_chrome.mdx b/api_docs/core_chrome.mdx index a9e100b79a91ec..3fbc87ad30fffc 100644 --- a/api_docs/core_chrome.mdx +++ b/api_docs/core_chrome.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import coreChromeObj from './core_chrome.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2273 | 148 | 1052 | 26 | + ## Client ### Interfaces diff --git a/api_docs/core_http.mdx b/api_docs/core_http.mdx index ea545eb6d70def..4f15e1a1ce90c5 100644 --- a/api_docs/core_http.mdx +++ b/api_docs/core_http.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import coreHttpObj from './core_http.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2273 | 148 | 1052 | 26 | + ## Client ### Interfaces diff --git a/api_docs/core_saved_objects.mdx b/api_docs/core_saved_objects.mdx index d534853f7f29db..fd3f96e081c9e3 100644 --- a/api_docs/core_saved_objects.mdx +++ b/api_docs/core_saved_objects.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import coreSavedObjectsObj from './core_saved_objects.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2273 | 148 | 1052 | 26 | + ## Client ### Classes diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index bddd6f91796fc7..de34ce88a77be5 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dashboardObj from './dashboard.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 145 | 1 | 133 | 9 | + ## Client ### Setup diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 1ef8820befc41c..4af7f6f96cf23c 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dashboardEnhancedObj from './dashboard_enhanced.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 51 | 0 | 50 | 0 | + ## Client ### Setup diff --git a/api_docs/dashboard_mode.mdx b/api_docs/dashboard_mode.mdx index 2108754adbd5db..28b68e29bd5f26 100644 --- a/api_docs/dashboard_mode.mdx +++ b/api_docs/dashboard_mode.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dashboardModeObj from './dashboard_mode.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 11 | 0 | 11 | 0 | + ## Server ### Classes diff --git a/api_docs/data.json b/api_docs/data.json index 2f9e74584603fc..993dcec522d5e8 100644 --- a/api_docs/data.json +++ b/api_docs/data.json @@ -12344,34 +12344,6 @@ "lineNumber": 222 } }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", - "lineNumber": 11 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", - "lineNumber": 16 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx", - "lineNumber": 25 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx", - "lineNumber": 162 - } - }, { "plugin": "infra", "link": { @@ -12848,6 +12820,20 @@ "lineNumber": 11 } }, + { + "plugin": "ml", + "link": { + "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", + "lineNumber": 11 + } + }, + { + "plugin": "ml", + "link": { + "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", + "lineNumber": 16 + } + }, { "plugin": "ml", "link": { @@ -15759,20 +15745,6 @@ "lineNumber": 11 } }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx", - "lineNumber": 25 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx", - "lineNumber": 28 - } - }, { "plugin": "infra", "link": { @@ -16382,48 +16354,6 @@ "lineNumber": 57 } }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts", - "lineNumber": 14 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts", - "lineNumber": 27 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts", - "lineNumber": 213 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts", - "lineNumber": 234 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/actions.ts", - "lineNumber": 12 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/actions.ts", - "lineNumber": 17 - } - }, { "plugin": "observability", "link": { @@ -34346,34 +34276,6 @@ "lineNumber": 222 } }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", - "lineNumber": 11 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", - "lineNumber": 16 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx", - "lineNumber": 25 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx", - "lineNumber": 162 - } - }, { "plugin": "infra", "link": { @@ -34850,6 +34752,20 @@ "lineNumber": 11 } }, + { + "plugin": "ml", + "link": { + "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", + "lineNumber": 11 + } + }, + { + "plugin": "ml", + "link": { + "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", + "lineNumber": 16 + } + }, { "plugin": "ml", "link": { diff --git a/api_docs/data.mdx b/api_docs/data.mdx index f585b23a16a0c6..bd48fb09df8b30 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dataObj from './data.json'; +Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. + +Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3809 | 107 | 3257 | 75 | + ## Client ### Setup diff --git a/api_docs/data_autocomplete.mdx b/api_docs/data_autocomplete.mdx index c2231648ac905d..7d18feb2140dd4 100644 --- a/api_docs/data_autocomplete.mdx +++ b/api_docs/data_autocomplete.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dataAutocompleteObj from './data_autocomplete.json'; +Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. + +Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3809 | 107 | 3257 | 75 | + ## Client ### Interfaces diff --git a/api_docs/data_enhanced.mdx b/api_docs/data_enhanced.mdx index 07a00b908e7b26..1e6709794d3c49 100644 --- a/api_docs/data_enhanced.mdx +++ b/api_docs/data_enhanced.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dataEnhancedObj from './data_enhanced.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 16 | 0 | 16 | 2 | + ## Client ### Start diff --git a/api_docs/data_field_formats.mdx b/api_docs/data_field_formats.mdx index faeba4ac56454d..c13562eba142df 100644 --- a/api_docs/data_field_formats.mdx +++ b/api_docs/data_field_formats.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dataFieldFormatsObj from './data_field_formats.json'; +Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. + +Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3809 | 107 | 3257 | 75 | + ## Client ### Consts, variables and types diff --git a/api_docs/data_index_patterns.json b/api_docs/data_index_patterns.json index af5ddec4809768..056442ee29d668 100644 --- a/api_docs/data_index_patterns.json +++ b/api_docs/data_index_patterns.json @@ -6117,34 +6117,6 @@ "lineNumber": 222 } }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", - "lineNumber": 11 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", - "lineNumber": 16 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx", - "lineNumber": 25 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx", - "lineNumber": 162 - } - }, { "plugin": "infra", "link": { @@ -6621,6 +6593,20 @@ "lineNumber": 11 } }, + { + "plugin": "ml", + "link": { + "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", + "lineNumber": 11 + } + }, + { + "plugin": "ml", + "link": { + "path": "x-pack/plugins/ml/public/application/util/field_types_utils.ts", + "lineNumber": 16 + } + }, { "plugin": "ml", "link": { @@ -9532,20 +9518,6 @@ "lineNumber": 11 } }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx", - "lineNumber": 25 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx", - "lineNumber": 28 - } - }, { "plugin": "infra", "link": { @@ -10155,48 +10127,6 @@ "lineNumber": 57 } }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts", - "lineNumber": 14 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts", - "lineNumber": 27 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts", - "lineNumber": 213 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts", - "lineNumber": 234 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/actions.ts", - "lineNumber": 12 - } - }, - { - "plugin": "ml", - "link": { - "path": "x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/actions.ts", - "lineNumber": 17 - } - }, { "plugin": "observability", "link": { diff --git a/api_docs/data_index_patterns.mdx b/api_docs/data_index_patterns.mdx index df226467aefdce..8313619594a753 100644 --- a/api_docs/data_index_patterns.mdx +++ b/api_docs/data_index_patterns.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dataIndexPatternsObj from './data_index_patterns.json'; +Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. + +Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3809 | 107 | 3257 | 75 | + ## Server ### Functions diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index ae804971a43194..751cd14dc7c5be 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dataQueryObj from './data_query.json'; +Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. + +Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3809 | 107 | 3257 | 75 | + ## Client ### Functions diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 370bd2ffd101e8..d07be7dfc62aa2 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dataSearchObj from './data_search.json'; +Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. + +Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3809 | 107 | 3257 | 75 | + ## Client ### Functions diff --git a/api_docs/data_ui.mdx b/api_docs/data_ui.mdx index 9eedffdce76f61..c31b561bd4c803 100644 --- a/api_docs/data_ui.mdx +++ b/api_docs/data_ui.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import dataUiObj from './data_ui.json'; +Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. + +Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3809 | 107 | 3257 | 75 | + ## Client ### Functions diff --git a/api_docs/data_visualizer.json b/api_docs/data_visualizer.json index b4544a03817909..c965a0a5923ac4 100644 --- a/api_docs/data_visualizer.json +++ b/api_docs/data_visualizer.json @@ -3,7 +3,69 @@ "client": { "classes": [], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "dataVisualizer", + "id": "def-public.IndexDataVisualizerViewProps", + "type": "Interface", + "tags": [], + "label": "IndexDataVisualizerViewProps", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx", + "lineNumber": 119 + }, + "deprecated": false, + "children": [ + { + "parentPluginId": "dataVisualizer", + "id": "def-public.IndexDataVisualizerViewProps.currentIndexPattern", + "type": "Object", + "tags": [], + "label": "currentIndexPattern", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataIndexPatternsPluginApi", + "section": "def-common.IndexPattern", + "text": "IndexPattern" + } + ], + "source": { + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx", + "lineNumber": 120 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-public.IndexDataVisualizerViewProps.currentSavedSearch", + "type": "CompoundType", + "tags": [], + "label": "currentSavedSearch", + "description": [], + "signature": [ + { + "pluginId": "dataVisualizer", + "scope": "common", + "docId": "kibDataVisualizerPluginApi", + "section": "def-common.SavedSearchSavedObject", + "text": "SavedSearchSavedObject" + }, + " | null" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx", + "lineNumber": 121 + }, + "deprecated": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [], "objects": [], @@ -15,11 +77,11 @@ "label": "DataVisualizerPluginStart", "description": [], "signature": [ - "{ getFileDataVisualizerComponent: () => Promise>; getMaxBytesFormatted: () => string; }" + "{ getFileDataVisualizerComponent: () => Promise>; getIndexDataVisualizerComponent: () => Promise>; getMaxBytesFormatted: () => string; }" ], "source": { "path": "x-pack/plugins/data_visualizer/public/plugin.ts", - "lineNumber": 33 + "lineNumber": 38 }, "deprecated": false, "lifecycle": "start", @@ -46,8 +108,8 @@ "label": "DataVisualizerTableState", "description": [], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 14 + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 19 }, "deprecated": false, "children": [ @@ -59,8 +121,8 @@ "label": "pageSize", "description": [], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 15 + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 20 }, "deprecated": false }, @@ -72,8 +134,8 @@ "label": "pageIndex", "description": [], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 16 + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 21 }, "deprecated": false }, @@ -85,8 +147,8 @@ "label": "sortField", "description": [], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 17 + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 22 }, "deprecated": false }, @@ -98,8 +160,8 @@ "label": "sortDirection", "description": [], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 18 + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 23 }, "deprecated": false }, @@ -114,8 +176,8 @@ "string[]" ], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 19 + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 24 }, "deprecated": false }, @@ -130,8 +192,8 @@ "string[]" ], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 20 + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 25 }, "deprecated": false }, @@ -143,8 +205,579 @@ "label": "showDistributions", "description": [], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 21 + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 26 + }, + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.DocumentCountBuckets", + "type": "Interface", + "tags": [], + "label": "DocumentCountBuckets", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 22 + }, + "deprecated": false, + "children": [ + { + "parentPluginId": "dataVisualizer", + "id": "def-common.DocumentCountBuckets.Unnamed", + "type": "Any", + "tags": [], + "label": "Unnamed", + "description": [], + "signature": [ + "any" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 23 + }, + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.DocumentCounts", + "type": "Interface", + "tags": [], + "label": "DocumentCounts", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 26 + }, + "deprecated": false, + "children": [ + { + "parentPluginId": "dataVisualizer", + "id": "def-common.DocumentCounts.buckets", + "type": "Object", + "tags": [], + "label": "buckets", + "description": [], + "signature": [ + { + "pluginId": "dataVisualizer", + "scope": "common", + "docId": "kibDataVisualizerPluginApi", + "section": "def-common.DocumentCountBuckets", + "text": "DocumentCountBuckets" + }, + " | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 27 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.DocumentCounts.interval", + "type": "number", + "tags": [], + "label": "interval", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 28 + }, + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldRequestConfig", + "type": "Interface", + "tags": [], + "label": "FieldRequestConfig", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 16 + }, + "deprecated": false, + "children": [ + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldRequestConfig.fieldName", + "type": "string", + "tags": [], + "label": "fieldName", + "description": [], + "signature": [ + "string | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 17 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldRequestConfig.type", + "type": "CompoundType", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"number\" | \"boolean\" | \"date\" | \"keyword\" | \"text\" | \"ip\" | \"geo_point\" | \"geo_shape\" | \"unknown\"" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 18 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldRequestConfig.cardinality", + "type": "number", + "tags": [], + "label": "cardinality", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 19 + }, + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats", + "type": "Interface", + "tags": [], + "label": "FieldVisStats", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 31 + }, + "deprecated": false, + "children": [ + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.cardinality", + "type": "number", + "tags": [], + "label": "cardinality", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 32 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.count", + "type": "number", + "tags": [], + "label": "count", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 33 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.sampleCount", + "type": "number", + "tags": [], + "label": "sampleCount", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 34 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.trueCount", + "type": "number", + "tags": [], + "label": "trueCount", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 35 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.falseCount", + "type": "number", + "tags": [], + "label": "falseCount", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 36 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.earliest", + "type": "number", + "tags": [], + "label": "earliest", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 37 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.latest", + "type": "number", + "tags": [], + "label": "latest", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 38 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.documentCounts", + "type": "Object", + "tags": [], + "label": "documentCounts", + "description": [], + "signature": [ + "{ buckets?: ", + { + "pluginId": "dataVisualizer", + "scope": "common", + "docId": "kibDataVisualizerPluginApi", + "section": "def-common.DocumentCountBuckets", + "text": "DocumentCountBuckets" + }, + " | undefined; interval?: number | undefined; } | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 39 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.avg", + "type": "number", + "tags": [], + "label": "avg", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 43 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.distribution", + "type": "Object", + "tags": [], + "label": "distribution", + "description": [], + "signature": [ + "{ percentiles: ", + { + "pluginId": "dataVisualizer", + "scope": "common", + "docId": "kibDataVisualizerPluginApi", + "section": "def-common.Percentile", + "text": "Percentile" + }, + "[]; maxPercentile: number; minPercentile: 0; } | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 44 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.fieldName", + "type": "string", + "tags": [], + "label": "fieldName", + "description": [], + "signature": [ + "string | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 49 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.isTopValuesSampled", + "type": "CompoundType", + "tags": [], + "label": "isTopValuesSampled", + "description": [], + "signature": [ + "boolean | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 50 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.max", + "type": "number", + "tags": [], + "label": "max", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 51 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.median", + "type": "number", + "tags": [], + "label": "median", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 52 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.min", + "type": "number", + "tags": [], + "label": "min", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 53 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.topValues", + "type": "Array", + "tags": [], + "label": "topValues", + "description": [], + "signature": [ + "{ key: React.ReactText; doc_count: number; }[] | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 54 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.topValuesSampleSize", + "type": "number", + "tags": [], + "label": "topValuesSampleSize", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 55 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.topValuesSamplerShardSize", + "type": "number", + "tags": [], + "label": "topValuesSamplerShardSize", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 56 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.examples", + "type": "Array", + "tags": [], + "label": "examples", + "description": [], + "signature": [ + "(string | object)[] | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 57 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.timeRangeEarliest", + "type": "number", + "tags": [], + "label": "timeRangeEarliest", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 58 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.FieldVisStats.timeRangeLatest", + "type": "number", + "tags": [], + "label": "timeRangeLatest", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 59 + }, + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.Percentile", + "type": "Interface", + "tags": [], + "label": "Percentile", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 10 + }, + "deprecated": false, + "children": [ + { + "parentPluginId": "dataVisualizer", + "id": "def-common.Percentile.percent", + "type": "number", + "tags": [], + "label": "percent", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 11 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.Percentile.minValue", + "type": "number", + "tags": [], + "label": "minValue", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 12 + }, + "deprecated": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.Percentile.maxValue", + "type": "number", + "tags": [], + "label": "maxValue", + "description": [], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/field_request_config.ts", + "lineNumber": 13 }, "deprecated": false } @@ -216,8 +849,8 @@ "any[]" ], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 10 + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 17 }, "deprecated": false, "initialIsOpen": false @@ -230,11 +863,11 @@ "label": "JobFieldType", "description": [], "signature": [ - "\"number\" | \"boolean\" | \"date\" | \"text\" | \"keyword\" | \"ip\" | \"geo_point\" | \"geo_shape\" | \"unknown\"" + "\"number\" | \"boolean\" | \"date\" | \"keyword\" | \"text\" | \"ip\" | \"geo_point\" | \"geo_shape\" | \"unknown\"" ], "source": { - "path": "x-pack/plugins/data_visualizer/common/types.ts", - "lineNumber": 12 + "path": "x-pack/plugins/data_visualizer/common/types/job_field_type.ts", + "lineNumber": 9 }, "deprecated": false, "initialIsOpen": false @@ -287,6 +920,47 @@ "deprecated": false, "initialIsOpen": false }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.OMIT_FIELDS", + "type": "Array", + "tags": [], + "label": "OMIT_FIELDS", + "description": [], + "signature": [ + "string[]" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/constants.ts", + "lineNumber": 33 + }, + "deprecated": false, + "initialIsOpen": false + }, + { + "parentPluginId": "dataVisualizer", + "id": "def-common.SavedSearchSavedObject", + "type": "Type", + "tags": [], + "label": "SavedSearchSavedObject", + "description": [], + "signature": [ + { + "pluginId": "core", + "scope": "public", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-public.SimpleSavedObject", + "text": "SimpleSavedObject" + }, + "" + ], + "source": { + "path": "x-pack/plugins/data_visualizer/common/types/index.ts", + "lineNumber": 29 + }, + "deprecated": false, + "initialIsOpen": false + }, { "parentPluginId": "dataVisualizer", "id": "def-common.UI_SETTING_MAX_FILE_SIZE", @@ -325,4 +999,4 @@ } ] } -} +} \ No newline at end of file diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 07d4fc76d448a2..384fd07bd11762 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -1,29 +1,42 @@ --- -id: kibFileDataVisualizerPluginApi -slug: /kibana-dev-docs/fileDataVisualizerPluginApi -title: fileDataVisualizer +id: kibDataVisualizerPluginApi +slug: /kibana-dev-docs/dataVisualizerPluginApi +title: dataVisualizer image: https://source.unsplash.com/400x175/?github -summary: API docs for the fileDataVisualizer plugin +summary: API docs for the dataVisualizer plugin date: 2020-11-16 -tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileDataVisualizer'] +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. --- -import fileDataVisualizerObj from './file_data_visualizer.json'; +import dataVisualizerObj from './data_visualizer.json'; + + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 59 | 1 | 59 | 0 | ## Client ### Start - + + +### Interfaces + ## Common ### Objects - + ### Interfaces - + ### Consts, variables and types - + diff --git a/api_docs/deprecations.mdx b/api_docs/deprecations.mdx index b5fe8d1e31da0a..d9261b943d1708 100644 --- a/api_docs/deprecations.mdx +++ b/api_docs/deprecations.mdx @@ -1250,8 +1250,6 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [utils.d.ts#L37](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts#L37) | - | | | [edit_utils.d.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts#L8) | - | | | [edit_utils.d.ts#L11](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts#L11) | - | -| | [actions_panel.tsx#L25](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx#L25) | - | -| | [actions_panel.tsx#L28](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx#L28) | - | | | [new_job_utils.ts#L11](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts#L11) | - | | | [new_job_utils.ts#L34](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts#L34) | - | | | [exploration_query_bar.tsx#L14](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx#L14) | - | @@ -1260,12 +1258,6 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [editor.tsx#L62](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx#L62) | - | | | [custom_urls.tsx#L40](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx#L40) | - | | | [custom_urls.tsx#L57](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx#L57) | - | -| | [lens_utils.ts#L14](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts#L14) | - | -| | [lens_utils.ts#L27](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts#L27) | - | -| | [lens_utils.ts#L213](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts#L213) | - | -| | [lens_utils.ts#L234](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts#L234) | - | -| | [actions.ts#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/actions.ts#L12) | - | -| | [actions.ts#L17](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/actions.ts#L17) | - | | | [explorer_query_bar.tsx#L17](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx#L17) | - | | | [explorer_query_bar.tsx#L31](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx#L31) | - | | | [explorer_query_bar.tsx#L100](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx#L100) | - | @@ -1275,8 +1267,6 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [common.ts#L222](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/components/data_grid/common.ts#L222) | - | | | [field_types_utils.ts#L11](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.ts#L11) | - | | | [field_types_utils.ts#L16](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.ts#L16) | - | -| | [page.tsx#L25](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx#L25) | - | -| | [page.tsx#L162](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx#L162) | - | | | [field_types_utils.test.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts#L8) | - | | | [field_types_utils.test.ts#L19](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts#L19) | - | | | [field_types_utils.test.ts#L40](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts#L40) | - | @@ -1286,8 +1276,6 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [common.ts#L222](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/components/data_grid/common.ts#L222) | - | | | [field_types_utils.ts#L11](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.ts#L11) | - | | | [field_types_utils.ts#L16](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.ts#L16) | - | -| | [page.tsx#L25](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx#L25) | - | -| | [page.tsx#L162](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx#L162) | - | | | [field_types_utils.test.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts#L8) | - | | | [field_types_utils.test.ts#L19](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts#L19) | - | | | [field_types_utils.test.ts#L40](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts#L40) | - | @@ -1316,8 +1304,6 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [utils.d.ts#L37](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts#L37) | - | | | [edit_utils.d.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts#L8) | - | | | [edit_utils.d.ts#L11](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts#L11) | - | -| | [actions_panel.tsx#L25](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx#L25) | - | -| | [actions_panel.tsx#L28](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx#L28) | - | | | [new_job_utils.ts#L11](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts#L11) | - | | | [new_job_utils.ts#L34](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts#L34) | - | | | [exploration_query_bar.tsx#L14](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx#L14) | - | @@ -1326,12 +1312,6 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [editor.tsx#L62](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx#L62) | - | | | [custom_urls.tsx#L40](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx#L40) | - | | | [custom_urls.tsx#L57](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx#L57) | - | -| | [lens_utils.ts#L14](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts#L14) | - | -| | [lens_utils.ts#L27](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts#L27) | - | -| | [lens_utils.ts#L213](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts#L213) | - | -| | [lens_utils.ts#L234](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/lens_utils.ts#L234) | - | -| | [actions.ts#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/actions.ts#L12) | - | -| | [actions.ts#L17](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/action_menu/actions.ts#L17) | - | | | [explorer_query_bar.tsx#L17](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx#L17) | - | | | [explorer_query_bar.tsx#L31](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx#L31) | - | | | [explorer_query_bar.tsx#L100](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx#L100) | - | @@ -1341,8 +1321,6 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [common.ts#L222](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/components/data_grid/common.ts#L222) | - | | | [field_types_utils.ts#L11](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.ts#L11) | - | | | [field_types_utils.ts#L16](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.ts#L16) | - | -| | [page.tsx#L25](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx#L25) | - | -| | [page.tsx#L162](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx#L162) | - | | | [field_types_utils.test.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts#L8) | - | | | [field_types_utils.test.ts#L19](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts#L19) | - | | | [field_types_utils.test.ts#L40](https://github.com/elastic/kibana/tree/master/x-pack/plugins/ml/public/application/util/field_types_utils.test.ts#L40) | - | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 63abb581b1d3ec..89d67f54866f23 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import devToolsObj from './dev_tools.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 10 | 0 | 8 | 2 | + ## Client ### Setup diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index b5f6cefde7cc42..d4720530b58487 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import discoverObj from './discover.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 64 | 0 | 51 | 6 | + ## Client ### Setup diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 06037e1190c0d8..fb8842cd56bfed 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import discoverEnhancedObj from './discover_enhanced.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 39 | 0 | 37 | 2 | + ## Client ### Classes diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index eafc8543b7ce13..46a877be6ac243 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import embeddableObj from './embeddable.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 449 | 4 | 379 | 3 | + ## Client ### Setup diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 58800f0e7d3435..8680c6807bbcaa 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import embeddableEnhancedObj from './embeddable_enhanced.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 14 | 0 | 14 | 0 | + ## Client ### Setup diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 654427d83c3598..e152ec4aec9a85 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import encryptedSavedObjectsObj from './encrypted_saved_objects.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 30 | 0 | 28 | 3 | + ## Server ### Setup diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 1f9dfaae078f40..8156918dcefe49 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import enterpriseSearchObj from './enterprise_search.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2 | 0 | 2 | 0 | + ## Server ### Objects diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 2fd04e135e3fd3..e317299f0b48aa 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import esUiSharedObj from './es_ui_shared.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 88 | 4 | 86 | 1 | + ## Client ### Objects diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 6948a63cf85c93..409d6ad6d21c29 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import eventLogObj from './event_log.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 70 | 0 | 70 | 4 | + ## Server ### Setup diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index a57e91bf2a333b..e0544d866766ed 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import expressionsObj from './expressions.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 1871 | 57 | 1444 | 5 | + ## Client ### Setup diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 99776f69301592..377945d852ddd6 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import featuresObj from './features.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 215 | 0 | 97 | 2 | + ## Client ### Setup diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index aa4af1121e1183..acd0ed52e41826 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import fileUploadObj from './file_upload.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 128 | 4 | 128 | 1 | + ## Client ### Start diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 1388184f7a9480..2113069fe47e07 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import fleetObj from './fleet.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 1071 | 15 | 981 | 8 | + ## Client ### Setup diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 2e88a252eb35dd..041b9aba8ec7c8 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import globalSearchObj from './global_search.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 68 | 0 | 14 | 5 | + ## Client ### Setup diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 0a8bac237b045b..23c58467b6a986 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import homeObj from './home.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 89 | 0 | 65 | 5 | + ## Client ### Setup diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index f6aff00be5c616..a16206ad3d3610 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import indexLifecycleManagementObj from './index_lifecycle_management.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 5 | 0 | 5 | 0 | + ## Client ### Interfaces diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 0f22d279f7ecf9..9190012ac0b293 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import indexManagementObj from './index_management.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 162 | 12 | 157 | 3 | + ## Client ### Functions diff --git a/api_docs/index_pattern_field_editor.mdx b/api_docs/index_pattern_field_editor.mdx index 7c057e8706f7de..1627a18e66724a 100644 --- a/api_docs/index_pattern_field_editor.mdx +++ b/api_docs/index_pattern_field_editor.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import indexPatternFieldEditorObj from './index_pattern_field_editor.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 31 | 1 | 29 | 4 | + ## Client ### Start diff --git a/api_docs/index_pattern_management.mdx b/api_docs/index_pattern_management.mdx index b74d3a7262870a..7d82efb213ff1c 100644 --- a/api_docs/index_pattern_management.mdx +++ b/api_docs/index_pattern_management.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import indexPatternManagementObj from './index_pattern_management.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 46 | 0 | 46 | 4 | + ## Client ### Setup diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 83eb9982628644..0a13dcdfb5bcca 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import infraObj from './infra.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 23 | 0 | 20 | 4 | + ## Client ### Objects diff --git a/api_docs/ingest_pipelines.mdx b/api_docs/ingest_pipelines.mdx index eb56280c5b4ae5..25bdbceda4eadd 100644 --- a/api_docs/ingest_pipelines.mdx +++ b/api_docs/ingest_pipelines.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import ingestPipelinesObj from './ingest_pipelines.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 9 | 0 | 9 | 4 | + ## Client ### Classes diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 1fe62ba63e26e3..d69ef22ce788ad 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import inspectorObj from './inspector.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 101 | 6 | 78 | 4 | + ## Client ### Setup diff --git a/api_docs/kibana_legacy.mdx b/api_docs/kibana_legacy.mdx index 208cb8013d063b..5cd9244f0ea57f 100644 --- a/api_docs/kibana_legacy.mdx +++ b/api_docs/kibana_legacy.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import kibanaLegacyObj from './kibana_legacy.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 93 | 3 | 85 | 1 | + ## Client ### Functions diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index f4a5cf71d6a3d8..da356108593a14 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import kibanaReactObj from './kibana_react.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 245 | 1 | 216 | 4 | + ## Client ### Objects diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 9a9001af4b1f0c..a76992e0bc2b89 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import kibanaUtilsObj from './kibana_utils.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 551 | 5 | 373 | 8 | + ## Client ### Objects diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 8e9369253c2e34..8c47be1b5897ca 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import lensObj from './lens.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 172 | 0 | 161 | 16 | + ## Client ### Interfaces diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index d11b92e9bbc145..a2859cc03f8775 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import licenseApiGuardObj from './license_api_guard.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 8 | 0 | 8 | 0 | + ## Server ### Classes diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 8fde04390ebf08..b198e83acebf35 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import licenseManagementObj from './license_management.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3 | 0 | 3 | 0 | + ## Client ### Setup diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 695734b79f4934..af26b10762d273 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import licensingObj from './licensing.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 120 | 0 | 44 | 8 | + ## Client ### Setup diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 8ef77a85fb548e..47b1bde5c07822 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import listsObj from './lists.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 151 | 0 | 143 | 38 | + ## Client ### Setup diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 16e097a6158e6a..22586c7b58b494 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import managementObj from './management.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 38 | 0 | 38 | 4 | + ## Client ### Setup diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index f4f5594b193471..efa7ddb5fddf58 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import mapsObj from './maps.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 196 | 2 | 195 | 11 | + ## Client ### Start diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 5907add0c54659..0abd9d511cf90c 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import mapsEmsObj from './maps_ems.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 75 | 1 | 75 | 0 | + ## Client ### Setup diff --git a/api_docs/metrics_entities.mdx b/api_docs/metrics_entities.mdx index 19a27636511c3c..182a78cd0ddc1b 100644 --- a/api_docs/metrics_entities.mdx +++ b/api_docs/metrics_entities.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import metricsEntitiesObj from './metrics_entities.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 9 | 0 | 6 | 1 | + ## Server ### Setup diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index abb03511bbcaa7..4a9b98a3268373 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import mlObj from './ml.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 274 | 10 | 270 | 32 | + ## Client ### Start diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 4c32b1c933c930..e3dd2a0bbb6a5d 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import monitoringObj from './monitoring.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 10 | 0 | 10 | 2 | + ## Server ### Setup diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index d9dc1703ed4d86..b8b6f82162916a 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import navigationObj from './navigation.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 31 | 0 | 31 | 2 | + ## Client ### Setup diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index cb5c264e1940c7..0c3fe2bbc6640a 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import newsfeedObj from './newsfeed.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 17 | 0 | 17 | 0 | + ## Client ### Setup diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 353e65b0fa0808..6787e3f0238c87 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import observabilityObj from './observability.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 194 | 0 | 194 | 10 | + ## Client ### Setup diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 8638e916107a4a..a981f8f4b8c658 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import osqueryObj from './osquery.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 10 | 0 | 10 | 0 | + ## Client ### Setup diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 9416a74b8f42c1..3df30c54e4390b 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import presentationUtilObj from './presentation_util.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 125 | 1 | 121 | 3 | + ## Client ### Setup diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index a669a858ba6be1..166aeee82153e1 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import remoteClustersObj from './remote_clusters.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 4 | 0 | 4 | 0 | + ## Client ### Setup diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 445d38e4ec71b8..f349b223ddf97c 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import reportingObj from './reporting.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 133 | 1 | 132 | 19 | + ## Client ### Start diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 3216c8cd968952..7a7765dfe1584a 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import rollupObj from './rollup.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 20 | 0 | 20 | 0 | + ## Common ### Objects diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 964d29594d3c66..a0171607e8eebd 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import ruleRegistryObj from './rule_registry.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 43 | 0 | 43 | 6 | + ## Server ### Setup diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index a1d1b548350eec..2b9585f558ad5a 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import runtimeFieldsObj from './runtime_fields.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 24 | 0 | 19 | 2 | + ## Client ### Setup diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 01d7f9fce991fc..66579c3e3b0ca7 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import savedObjectsObj from './saved_objects.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 211 | 3 | 197 | 5 | + ## Client ### Setup diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 916288f934b575..0ee0660609f14d 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import savedObjectsManagementObj from './saved_objects_management.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 96 | 0 | 85 | 0 | + ## Client ### Setup diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 52a8b19a8300bc..fe1d90171a2c17 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import savedObjectsTaggingObj from './saved_objects_tagging.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 54 | 0 | 50 | 0 | + ## Client ### Start diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 921ee5240bfe8b..6fe8b4c6d46981 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 89 | 3 | 50 | 0 | + ## Client ### Setup diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index cdb70b22b0837c..a098e5f296d69d 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import screenshotModeObj from './screenshot_mode.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 21 | 0 | 16 | 1 | + ## Client ### Setup diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 250ceff429207b..e31fa44014c941 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import securityObj from './security.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 95 | 0 | 45 | 11 | + ## Client ### Setup diff --git a/api_docs/security_oss.mdx b/api_docs/security_oss.mdx index f8c49a56551cd3..ada97232ffe846 100644 --- a/api_docs/security_oss.mdx +++ b/api_docs/security_oss.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import securityOssObj from './security_oss.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 12 | 0 | 9 | 3 | + ## Client ### Setup diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index b38a877f7edd56..0667a98e3762bf 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import securitySolutionObj from './security_solution.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 32 | 0 | 32 | 6 | + ## Client ### Setup diff --git a/api_docs/share.mdx b/api_docs/share.mdx index e2a9da26c0fc46..58886148bd4a7b 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import shareObj from './share.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 67 | 1 | 61 | 4 | + ## Client ### Setup diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index a0fc3231baf4d9..ab2f9175987378 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import snapshotRestoreObj from './snapshot_restore.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 22 | 1 | 22 | 1 | + ## Common ### Objects diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index ccaffdd3748b55..197fe460b47d35 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import spacesObj from './spaces.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 96 | 0 | 0 | 0 | + ## Client ### Setup diff --git a/api_docs/spaces_oss.mdx b/api_docs/spaces_oss.mdx index e889a319f5f23c..b36b0b4c19aff4 100644 --- a/api_docs/spaces_oss.mdx +++ b/api_docs/spaces_oss.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import spacesOssObj from './spaces_oss.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 71 | 0 | 5 | 0 | + ## Client ### Setup diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 9fbed513491dd0..eefe0a2dc76fe7 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import stackAlertsObj from './stack_alerts.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 4 | 0 | 4 | 0 | + ## Server ### Consts, variables and types diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index b143323bf1c7a5..5c6d5b3d08ba48 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import taskManagerObj from './task_manager.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 44 | 0 | 18 | 7 | + ## Server ### Setup diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 995c9b22e268ab..f8d632c0d97082 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import telemetryObj from './telemetry.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 42 | 0 | 0 | 0 | + ## Client ### Setup diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index d112897e1c4899..749ec3ba2bd71e 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import telemetryCollectionManagerObj from './telemetry_collection_manager.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 29 | 0 | 29 | 4 | + ## Server ### Setup diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 058a9d3fcb460e..53914260b037f3 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import telemetryCollectionXpackObj from './telemetry_collection_xpack.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 1 | 0 | 1 | 0 | + ## Server ### Consts, variables and types diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 78193e3485c591..71831a66284fc5 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import telemetryManagementSectionObj from './telemetry_management_section.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 14 | 0 | 13 | 0 | + ## Client ### Setup diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index cb16e39419a438..e9d4a75e39991a 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import timelinesObj from './timelines.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 6 | 0 | 6 | 1 | + ## Client ### Setup diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 50a7a11abdf16c..91327ad1f990f3 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import triggersActionsUiObj from './triggers_actions_ui.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 237 | 1 | 228 | 19 | + ## Client ### Setup diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 9833f2a19e63ac..9045dc736bb2f8 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import uiActionsObj from './ui_actions.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 127 | 0 | 88 | 11 | + ## Client ### Setup diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 285232bf3fe0cd..3e9c9247532290 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import uiActionsEnhancedObj from './ui_actions_enhanced.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 205 | 2 | 147 | 10 | + ## Client ### Setup diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index 8d63edcd18ecad..ceb7d854a8fecc 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import uptimeObj from './uptime.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 6 | 0 | 6 | 3 | + ## Server ### Functions diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index a80a1e0e53f277..b457ca9eb331fa 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import urlForwardingObj from './url_forwarding.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 15 | 0 | 15 | 0 | + ## Client ### Classes diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 62db0389c2d5cd..197cfc72f17afb 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import usageCollectionObj from './usage_collection.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 57 | 0 | 16 | 2 | + ## Client ### Setup diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 041d01196bad9d..a904e00ef0efc5 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import visTypeTimeseriesObj from './vis_type_timeseries.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 10 | 1 | 10 | 3 | + ## Server ### Setup diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index d3834b2d5f83f8..c6d9bdc741d4af 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -11,6 +11,16 @@ warning: This document is auto-generated and is meant to be viewed inside our ex import visualizationsObj from './visualizations.json'; + + + + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 246 | 13 | 228 | 12 | + ## Client ### Setup diff --git a/docs/api/index-patterns.asciidoc b/docs/api/index-patterns.asciidoc index 79d2f164fb8c3b..b343d80b4fc15c 100644 --- a/docs/api/index-patterns.asciidoc +++ b/docs/api/index-patterns.asciidoc @@ -20,6 +20,12 @@ The following index patterns APIs are available: ** <> to set a default index pattern * Fields ** <> to change field metadata, such as `count`, `customLabel` and `format` +* Runtime fields + ** <> to retrieve a runtime field + ** <> to create a runtime field + ** <> to create or update a runtime field + ** <> to partially update an existing runtime field + ** <> to delete a runtime field include::index-patterns/get.asciidoc[] include::index-patterns/create.asciidoc[] @@ -28,3 +34,9 @@ include::index-patterns/delete.asciidoc[] include::index-patterns/default-get.asciidoc[] include::index-patterns/default-set.asciidoc[] include::index-patterns/update-fields.asciidoc[] +include::index-patterns/runtime-fields/get.asciidoc[] +include::index-patterns/runtime-fields/create.asciidoc[] +include::index-patterns/runtime-fields/upsert.asciidoc[] +include::index-patterns/runtime-fields/update.asciidoc[] +include::index-patterns/runtime-fields/delete.asciidoc[] + diff --git a/docs/api/index-patterns/create.asciidoc b/docs/api/index-patterns/create.asciidoc index 771292d6f934d5..521e25931ad49f 100644 --- a/docs/api/index-patterns/create.asciidoc +++ b/docs/api/index-patterns/create.asciidoc @@ -84,6 +84,7 @@ $ curl -X POST api/index_patterns/index_pattern "typeMeta": {}, "fieldFormats": {}, "fieldAttrs": {}, + "runtimeFieldMap": {} "allowNoIndex": "..." } } diff --git a/docs/api/index-patterns/get.asciidoc b/docs/api/index-patterns/get.asciidoc index 3f53bf0726bf14..64588e63f62ae3 100644 --- a/docs/api/index-patterns/get.asciidoc +++ b/docs/api/index-patterns/get.asciidoc @@ -58,6 +58,7 @@ The API returns an index pattern object: "typeMeta": {}, "fieldFormats": {}, "fieldAttrs": {}, + "runtimeFieldMap" {}, "allowNoIndex: "..." } } diff --git a/docs/api/index-patterns/runtime-fields/create.asciidoc b/docs/api/index-patterns/runtime-fields/create.asciidoc new file mode 100644 index 00000000000000..b0773c29e5309f --- /dev/null +++ b/docs/api/index-patterns/runtime-fields/create.asciidoc @@ -0,0 +1,61 @@ +[[index-patterns-runtime-field-api-create]] +=== Create runtime field API +++++ +Create runtime field +++++ + +experimental[] Create a runtime field + +[[index-patterns-runtime-field-create-request]] +==== Request + +`POST :/api/index_patterns/index_pattern//runtime_field` + +`POST :/s//api/index_patterns/index_pattern//runtime_field` + +[[index-patterns-runtime-field-create-params]] +==== Path parameters + +`space_id`:: +(Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +`index_pattern_id`:: +(Required, string) The ID of the index pattern. + +[[index-patterns-runtime-field-create-body]] +==== Request body + +`name`:: (Required, string) The name for a runtime field. + +`runtimeField`:: (Required, object) The runtime field definition object. + + +[[index-patterns-runtime-field-create-example]] +==== Examples + +Create a runtime field on an index pattern: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/index_patterns/index_pattern//runtime_field +{ + "name": "runtimeFoo", + "runtimeField": { + "type": "long", + "script": { + "source": "emit(doc["foo"].value)" + } + } +} +-------------------------------------------------- +// KIBANA + +The API returns created runtime field object and update index pattern object: + +[source,sh] +-------------------------------------------------- +{ + "index_pattern": {...}, + "field": {...} +} +-------------------------------------------------- diff --git a/docs/api/index-patterns/runtime-fields/delete.asciidoc b/docs/api/index-patterns/runtime-fields/delete.asciidoc new file mode 100644 index 00000000000000..840789fe1ec23f --- /dev/null +++ b/docs/api/index-patterns/runtime-fields/delete.asciidoc @@ -0,0 +1,37 @@ +[[index-patterns-runtime-field-api-delete]] +=== Delete runtime field API +++++ +Delete runtime field +++++ + +experimental[] Delete a runtime field from an index pattern. + +[[index-patterns-runtime-field-api-delete-request]] +==== Request + +`DELETE :/api/index_patterns/index_pattern//runtime_field/` + +`DELETE :/s//api/index_patterns/index_pattern//runtime_field/` + +[[index-patterns-runtime-field-api-delete-path-params]] +==== Path parameters + +`space_id`:: +(Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +`index_pattern_id`:: +(Required, string) The ID of the index pattern your want to delete a runtime field from. + +`name`:: +(Required, string) The name of the runtime field you want to delete. + + +==== Example + +Delete a runtime field from an index pattern: + +[source,sh] +-------------------------------------------------- +$ curl -X DELETE api/index_patterns/index_pattern//runtime_field/ +-------------------------------------------------- +// KIBANA diff --git a/docs/api/index-patterns/runtime-fields/get.asciidoc b/docs/api/index-patterns/runtime-fields/get.asciidoc new file mode 100644 index 00000000000000..42bd209c708bc3 --- /dev/null +++ b/docs/api/index-patterns/runtime-fields/get.asciidoc @@ -0,0 +1,52 @@ +[[index-patterns-runtime-field-api-get]] +=== Get runtime field API +++++ +Get runtime field +++++ + +experimental[] Get a runtime field + +[[index-patterns-runtime-field-get-request]] +==== Request + +`GET :/api/index_patterns/index_pattern//runtime_field/` + +`GET :/s//api/index_patterns/index_pattern//runtime_field/` + +[[index-patterns-runtime-field-get-params]] +==== Path parameters + +`space_id`:: +(Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +`index_pattern_id`:: +(Required, string) The ID of the index pattern. + +`name`:: +(Required, string) The name of the runtime field you want to retrieve. + + +[[index-patterns-runtime-field-get-example]] +==== Example + +Retrieve a runtime field named `foo` of index pattern with the `my-pattern` ID: + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/index_patterns/index_pattern/my-pattern/runtime_field/foo +-------------------------------------------------- +// KIBANA + +The API returns a runtime `field` object, and a `runtimeField` definition object: + +[source,sh] +-------------------------------------------------- +{ + "field": { + ... + }, + "runtimeField": { + ... + } +} +-------------------------------------------------- diff --git a/docs/api/index-patterns/runtime-fields/update.asciidoc b/docs/api/index-patterns/runtime-fields/update.asciidoc new file mode 100644 index 00000000000000..f34460896f7bc9 --- /dev/null +++ b/docs/api/index-patterns/runtime-fields/update.asciidoc @@ -0,0 +1,66 @@ +[[index-patterns-runtime-field-api-update]] +=== Update runtime field API +++++ +Update runtime field +++++ + +experimental[] Update an existing runtime field + +[[index-patterns-runtime-field-update-request]] +==== Request + +`POST :/api/index_patterns/index_pattern//runtime_field/` + +`POST :/s//api/index_patterns/index_pattern//runtime_field/` + +[[index-patterns-runtime-field-update-params]] +==== Path parameters + +`space_id`:: +(Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +`index_pattern_id`:: +(Required, string) The ID of the index pattern. + +`name`:: +(Required, string) The name of the runtime field you want to update. + +[[index-patterns-runtime-field-update-body]] +==== Request body + +`runtimeField`:: (Required, object) The runtime field definition object. + +You can update following fields: + +* `type` +* `script` + + + +[[index-patterns-runtime-field-update-example]] +==== Examples + +Update an existing runtime field on an index pattern: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/index_patterns/index_pattern//runtime_field/ +{ + "runtimeField": { + "script": { + "source": "emit(doc["bar"].value)" + } + } +} +-------------------------------------------------- +// KIBANA + +The API returns updated runtime field object and updated index pattern object: + +[source,sh] +-------------------------------------------------- +{ + "index_pattern": {...}, + "field": {...} +} +-------------------------------------------------- diff --git a/docs/api/index-patterns/runtime-fields/upsert.asciidoc b/docs/api/index-patterns/runtime-fields/upsert.asciidoc new file mode 100644 index 00000000000000..1b436db19c62e4 --- /dev/null +++ b/docs/api/index-patterns/runtime-fields/upsert.asciidoc @@ -0,0 +1,61 @@ +[[index-patterns-runtime-field-api-upsert]] +=== Upsert runtime field API +++++ +Upsert runtime field +++++ + +experimental[] Create or update an existing runtime field + +[[index-patterns-runtime-field-upsert-request]] +==== Request + +`PUT :/api/index_patterns/index_pattern//runtime_field` + +`PUT :/s//api/index_patterns/index_pattern//runtime_field` + +[[index-patterns-runtime-field-upsert-params]] +==== Path parameters + +`space_id`:: +(Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +`index_pattern_id`:: +(Required, string) The ID of the index pattern. + +[[index-patterns-runtime-field-upsert-body]] +==== Request body + +`name`:: (Required, string) The name for a new runtime field or a name of an existing runtime field. + +`runtimeField`:: (Required, object) The runtime field definition object. + + +[[index-patterns-runtime-field-upsert-example]] +==== Examples + +Create or update an existing runtime field on an index pattern: + +[source,sh] +-------------------------------------------------- +$ curl -X PUT api/index_patterns/index_pattern//runtime_field +{ + "name": "runtimeFoo", + "runtimeField": { + "type": "long", + "script": { + "source": "emit(doc["foo"].value)" + } + } +} +-------------------------------------------------- +// KIBANA + +The API returns created or updated runtime field object and updated index pattern object: + +[source,sh] +-------------------------------------------------- +{ + "index_pattern": {...}, + "field": {...} +} +-------------------------------------------------- diff --git a/docs/api/index-patterns/update.asciidoc b/docs/api/index-patterns/update.asciidoc index 8ed0ff89fb928c..2d5fe882d448df 100644 --- a/docs/api/index-patterns/update.asciidoc +++ b/docs/api/index-patterns/update.asciidoc @@ -93,7 +93,8 @@ $ curl -X POST api/saved_objects/index-pattern/my-pattern "fieldFormats": {}, "type": "...", "typeMeta": {}, - "fields": {} + "fields": {}, + "runtimeFieldMap": {} } } -------------------------------------------------- diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 029ee9ea4faf6c..0ae806618adc5d 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -97,6 +97,7 @@ yarn kbn watch-bazel - @kbn/securitysolution-list-utils - @kbn/securitysolution-utils - @kbn/server-http-tools +- @kbn/server-route-repository - @kbn/std - @kbn/telemetry-utils - @kbn/tinymath diff --git a/docs/developer/getting-started/sample-data.asciidoc b/docs/developer/getting-started/sample-data.asciidoc index 0d313cbabe64e4..2454c9d8a61468 100644 --- a/docs/developer/getting-started/sample-data.asciidoc +++ b/docs/developer/getting-started/sample-data.asciidoc @@ -6,7 +6,7 @@ There are a couple ways to easily get data ingested into {es}. [discrete] === Sample data packages available for one click installation -The easiest is to install one or more of our vailable sample data packages. If you have no data, you should be +The easiest is to install one or more of our available sample data packages. If you have no data, you should be prompted to install when running {kib} for the first time. You can also access and install the sample data packages by going to the home page and clicking "add sample data". @@ -27,5 +27,5 @@ Make sure to execute `node scripts/makelogs` *after* {es} is up and running! [discrete] === CSV upload -If running with a platinum or trial license, you can also use the CSV uploader provided inside the Machine learning app. -Navigate to the Data visualizer to upload your data from a file. \ No newline at end of file +You can also use the CSV uploader provided on the {kib} home page. +Navigate to **Add data** > **Upload file** to upload your data from a file. \ No newline at end of file diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.description.md b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.description.md new file mode 100644 index 00000000000000..b6bba3b5e356cd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.description.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) > [description](./kibana-plugin-core-server.pluginmanifest.description.md) + +## PluginManifest.description property + +TODO: make required once all plugins specify this. A brief description of what this plugin does and any capabilities it provides. + +Signature: + +```typescript +readonly description?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md index bd15b95d73acea..b3e20bc7ed693a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md +++ b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md @@ -21,10 +21,12 @@ Should never be used in code outside of Core but is exported for documentation p | Property | Type | Description | | --- | --- | --- | | [configPath](./kibana-plugin-core-server.pluginmanifest.configpath.md) | ConfigPath | Root used by the plugin, defaults to "id" in snake\_case format. | +| [description](./kibana-plugin-core-server.pluginmanifest.description.md) | string | TODO: make required once all plugins specify this. A brief description of what this plugin does and any capabilities it provides. | | [extraPublicDirs](./kibana-plugin-core-server.pluginmanifest.extrapublicdirs.md) | string[] | Specifies directory names that can be imported by other ui-plugins built using the same instance of the @kbn/optimizer. A temporary measure we plan to replace with better mechanisms for sharing static code between plugins | | [id](./kibana-plugin-core-server.pluginmanifest.id.md) | PluginName | Identifier of the plugin. Must be a string in camelCase. Part of a plugin public contract. Other plugins leverage it to access plugin API, navigate to the plugin, etc. | | [kibanaVersion](./kibana-plugin-core-server.pluginmanifest.kibanaversion.md) | string | The version of Kibana the plugin is compatible with, defaults to "version". | | [optionalPlugins](./kibana-plugin-core-server.pluginmanifest.optionalplugins.md) | readonly PluginName[] | An optional list of the other plugins that if installed and enabled \*\*may be\*\* leveraged by this plugin for some additional functionality but otherwise are not required for this plugin to work properly. | +| [owner](./kibana-plugin-core-server.pluginmanifest.owner.md) | {
readonly name: string;
readonly githubTeam?: string;
} | TODO: make required once all internal plugins have this specified. | | [requiredBundles](./kibana-plugin-core-server.pluginmanifest.requiredbundles.md) | readonly string[] | List of plugin ids that this plugin's UI code imports modules from that are not in requiredPlugins. | | [requiredPlugins](./kibana-plugin-core-server.pluginmanifest.requiredplugins.md) | readonly PluginName[] | An optional list of the other plugins that \*\*must be\*\* installed and enabled for this plugin to function properly. | | [server](./kibana-plugin-core-server.pluginmanifest.server.md) | boolean | Specifies whether plugin includes some server-side specific functionality. | diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.owner.md b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.owner.md new file mode 100644 index 00000000000000..a90af81aa186a4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.owner.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) > [owner](./kibana-plugin-core-server.pluginmanifest.owner.md) + +## PluginManifest.owner property + +TODO: make required once all internal plugins have this specified. + +Signature: + +```typescript +readonly owner?: { + readonly name: string; + readonly githubTeam?: string; + }; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md new file mode 100644 index 00000000000000..c0aca53255b8fd --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [getRuntimeField](./kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md) + +## IndexPattern.getRuntimeField() method + +Returns runtime field if exists + +Signature: + +```typescript +getRuntimeField(name: string): RuntimeField | null; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`RuntimeField | null` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md new file mode 100644 index 00000000000000..96dbe13a7f1978 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [hasRuntimeField](./kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md) + +## IndexPattern.hasRuntimeField() method + +Checks if runtime field exists + +Signature: + +```typescript +hasRuntimeField(name: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 53d173d39f50d0..51ca42fdce70a7 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -54,13 +54,16 @@ export declare class IndexPattern implements IIndexPattern | [getFormatterForField(field)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | | [getFormatterForFieldNoDefault(fieldname)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfieldnodefault.md) | | Get formatter for a given field name. Return undefined if none exists | | [getNonScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getnonscriptedfields.md) | | | +| [getRuntimeField(name)](./kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md) | | Returns runtime field if exists | | [getScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md) | | | | [getSourceFiltering()](./kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md) | | Get the source filtering configuration for that index. | | [getTimeField()](./kibana-plugin-plugins-data-public.indexpattern.gettimefield.md) | | | +| [hasRuntimeField(name)](./kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md) | | Checks if runtime field exists | | [isTimeBased()](./kibana-plugin-plugins-data-public.indexpattern.istimebased.md) | | | | [isTimeNanosBased()](./kibana-plugin-plugins-data-public.indexpattern.istimenanosbased.md) | | | -| [removeRuntimeField(name)](./kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate | +| [removeRuntimeField(name)](./kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate. Doesn't clear associated field attributes. | | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | +| [replaceAllRuntimeFields(newFields)](./kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md) | | Replaces all existing runtime fields with new fields | | [setFieldAttrs(fieldName, attrName, value)](./kibana-plugin-plugins-data-public.indexpattern.setfieldattrs.md) | | | | [setFieldCount(fieldName, count)](./kibana-plugin-plugins-data-public.indexpattern.setfieldcount.md) | | | | [setFieldCustomLabel(fieldName, customLabel)](./kibana-plugin-plugins-data-public.indexpattern.setfieldcustomlabel.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md index 7a5228fece782e..f2774924fc73c1 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md @@ -4,7 +4,7 @@ ## IndexPattern.removeRuntimeField() method -Remove a runtime field - removed from mapped field or removed unmapped field as appropriate +Remove a runtime field - removed from mapped field or removed unmapped field as appropriate. Doesn't clear associated field attributes. Signature: @@ -16,7 +16,7 @@ removeRuntimeField(name: string): void; | Parameter | Type | Description | | --- | --- | --- | -| name | string | | +| name | string | Field name to remove | Returns: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md new file mode 100644 index 00000000000000..076b2b38cf474a --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [replaceAllRuntimeFields](./kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md) + +## IndexPattern.replaceAllRuntimeFields() method + +Replaces all existing runtime fields with new fields + +Signature: + +```typescript +replaceAllRuntimeFields(newFields: Record): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| newFields | Record<string, RuntimeField> | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md new file mode 100644 index 00000000000000..d5dc8f966316b0 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getRuntimeField](./kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md) + +## IndexPattern.getRuntimeField() method + +Returns runtime field if exists + +Signature: + +```typescript +getRuntimeField(name: string): RuntimeField | null; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`RuntimeField | null` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md new file mode 100644 index 00000000000000..5000d5e645cbb9 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [hasRuntimeField](./kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md) + +## IndexPattern.hasRuntimeField() method + +Checks if runtime field exists + +Signature: + +```typescript +hasRuntimeField(name: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md index 97d1cd91152623..27b8a31a2582ba 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md @@ -54,13 +54,16 @@ export declare class IndexPattern implements IIndexPattern | [getFormatterForField(field)](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | | [getFormatterForFieldNoDefault(fieldname)](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfieldnodefault.md) | | Get formatter for a given field name. Return undefined if none exists | | [getNonScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md) | | | +| [getRuntimeField(name)](./kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md) | | Returns runtime field if exists | | [getScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md) | | | | [getSourceFiltering()](./kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md) | | Get the source filtering configuration for that index. | | [getTimeField()](./kibana-plugin-plugins-data-server.indexpattern.gettimefield.md) | | | +| [hasRuntimeField(name)](./kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md) | | Checks if runtime field exists | | [isTimeBased()](./kibana-plugin-plugins-data-server.indexpattern.istimebased.md) | | | | [isTimeNanosBased()](./kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md) | | | -| [removeRuntimeField(name)](./kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate | +| [removeRuntimeField(name)](./kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate. Doesn't clear associated field attributes. | | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | +| [replaceAllRuntimeFields(newFields)](./kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md) | | Replaces all existing runtime fields with new fields | | [setFieldAttrs(fieldName, attrName, value)](./kibana-plugin-plugins-data-server.indexpattern.setfieldattrs.md) | | | | [setFieldCount(fieldName, count)](./kibana-plugin-plugins-data-server.indexpattern.setfieldcount.md) | | | | [setFieldCustomLabel(fieldName, customLabel)](./kibana-plugin-plugins-data-server.indexpattern.setfieldcustomlabel.md) | | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md index da8e7e40a7fac2..ef32b80ba8502e 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md @@ -4,7 +4,7 @@ ## IndexPattern.removeRuntimeField() method -Remove a runtime field - removed from mapped field or removed unmapped field as appropriate +Remove a runtime field - removed from mapped field or removed unmapped field as appropriate. Doesn't clear associated field attributes. Signature: @@ -16,7 +16,7 @@ removeRuntimeField(name: string): void; | Parameter | Type | Description | | --- | --- | --- | -| name | string | | +| name | string | Field name to remove | Returns: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md new file mode 100644 index 00000000000000..35df871763f8a1 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [replaceAllRuntimeFields](./kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md) + +## IndexPattern.replaceAllRuntimeFields() method + +Replaces all existing runtime fields with new fields + +Signature: + +```typescript +replaceAllRuntimeFields(newFields: Record): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| newFields | Record<string, RuntimeField> | | + +Returns: + +`void` + diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index 8c0aa12ffc4c61..5e18d934863aa9 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -46,8 +46,7 @@ image::images/add-data-fleet.png[Add data using Fleet] [[upload-data-kibana]] === Upload a file -experimental[] If your data is in a CSV, JSON, or log file, you can upload it using the File -Data Visualizer. You can upload a file up to 100 MB. This value is configurable up to 1 GB in +experimental[] If your data is in a CSV, JSON, or log file, you can upload it using the {file-data-viz}. You can upload a file up to 100 MB. This value is configurable up to 1 GB in <>. To upload a file with geospatial data, refer to <>. diff --git a/docs/setup/images/add-data-fv.png b/docs/setup/images/add-data-fv.png old mode 100755 new mode 100644 index 45313d133822c1..7e253cdd0229d7 Binary files a/docs/setup/images/add-data-fv.png and b/docs/setup/images/add-data-fv.png differ diff --git a/docs/setup/images/add-data-tutorials.png b/docs/setup/images/add-data-tutorials.png index 74deedc57b42ed..782b44e383772c 100644 Binary files a/docs/setup/images/add-data-tutorials.png and b/docs/setup/images/add-data-tutorials.png differ diff --git a/docs/user/production-considerations/task-manager-health-monitoring.asciidoc b/docs/user/production-considerations/task-manager-health-monitoring.asciidoc index f64c120f61298f..d6b90a4f19e112 100644 --- a/docs/user/production-considerations/task-manager-health-monitoring.asciidoc +++ b/docs/user/production-considerations/task-manager-health-monitoring.asciidoc @@ -6,6 +6,8 @@ Health monitoring ++++ +experimental[] + The Task Manager has an internal monitoring mechanism to keep track of a variety of metrics, which can be consumed with either the health monitoring API or the {kib} server log. The health monitoring API provides a reliable endpoint that can be monitored. diff --git a/docs/user/production-considerations/task-manager-troubleshooting.asciidoc b/docs/user/production-considerations/task-manager-troubleshooting.asciidoc index 5e75aef0d9570f..c6a7b7f3d53fdc 100644 --- a/docs/user/production-considerations/task-manager-troubleshooting.asciidoc +++ b/docs/user/production-considerations/task-manager-troubleshooting.asciidoc @@ -60,6 +60,8 @@ For details on scaling Task Manager, see <>. [[task-manager-diagnosing-root-cause]] ==== Diagnose a root cause for drift +experimental[] + The following guide helps you identify a root cause for _drift_ by making sense of the output from the <> endpoint. By analyzing the different sections of the output, you can evaluate different theories that explain the drift in a deployment. diff --git a/examples/index_pattern_field_editor_example/README.md b/examples/index_pattern_field_editor_example/README.md new file mode 100644 index 00000000000000..35ae814fc10e25 --- /dev/null +++ b/examples/index_pattern_field_editor_example/README.md @@ -0,0 +1,7 @@ +## index pattern field editor example + +This example index pattern field editor app shows how to: + - Edit index pattern fields via flyout + - Delete index pattern runtime fields with modal confirm prompt + +To run this example, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/examples/index_pattern_field_editor_example/kibana.json b/examples/index_pattern_field_editor_example/kibana.json new file mode 100644 index 00000000000000..c522e6698ac3d6 --- /dev/null +++ b/examples/index_pattern_field_editor_example/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "indexPatternFieldEditorExample", + "version": "0.0.1", + "kibanaVersion": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["data", "indexPatternFieldEditor", "developerExamples"], + "optionalPlugins": [], + "requiredBundles": [] +} diff --git a/examples/index_pattern_field_editor_example/public/app.tsx b/examples/index_pattern_field_editor_example/public/app.tsx new file mode 100644 index 00000000000000..bd725759380aae --- /dev/null +++ b/examples/index_pattern_field_editor_example/public/app.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState } from 'react'; +import ReactDOM from 'react-dom'; +import { + EuiPage, + EuiPageHeader, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiButton, + EuiInMemoryTable, + EuiText, + DefaultItemAction, +} from '@elastic/eui'; +import { AppMountParameters } from '../../../src/core/public'; +import { + DataPublicPluginStart, + IndexPattern, + IndexPatternField, +} from '../../../src/plugins/data/public'; +import { IndexPatternFieldEditorStart } from '../../../src/plugins/index_pattern_field_editor/public'; + +interface Props { + indexPattern?: IndexPattern; + indexPatternFieldEditor: IndexPatternFieldEditorStart; +} + +const IndexPatternFieldEditorExample = ({ indexPattern, indexPatternFieldEditor }: Props) => { + const [fields, setFields] = useState( + indexPattern?.getNonScriptedFields() || [] + ); + const refreshFields = () => setFields(indexPattern?.getNonScriptedFields() || []); + const columns = [ + { + field: 'name', + name: 'Field name', + }, + { + name: 'Actions', + actions: [ + { + name: 'Edit', + description: 'Edit this field', + icon: 'pencil', + type: 'icon', + 'data-test-subj': 'editField', + onClick: (fld: IndexPatternField) => + indexPatternFieldEditor.openEditor({ + ctx: { indexPattern: indexPattern! }, + fieldName: fld.name, + onSave: refreshFields, + }), + }, + { + name: 'Delete', + description: 'Delete this field', + icon: 'trash', + type: 'icon', + 'data-test-subj': 'deleteField', + available: (fld) => !!fld.runtimeField, + onClick: (fld: IndexPatternField) => + indexPatternFieldEditor.openDeleteModal({ + fieldName: fld.name, + ctx: { + indexPattern: indexPattern!, + }, + onDelete: refreshFields, + }), + }, + ] as Array>, + }, + ]; + + const content = indexPattern ? ( + <> + Index pattern: {indexPattern?.title} +
+ + indexPatternFieldEditor.openEditor({ + ctx: { indexPattern: indexPattern! }, + onSave: refreshFields, + }) + } + data-test-subj="addField" + > + Add field + +
+ + items={fields} + columns={columns} + pagination={true} + hasActions={true} + sorting={{ + sort: { + field: 'name', + direction: 'asc', + }, + }} + /> + + ) : ( +

Please create an index pattern

+ ); + + return ( + + + Index pattern field editor demo + + {content} + + + + ); +}; + +interface RenderAppDependencies { + data: DataPublicPluginStart; + indexPatternFieldEditor: IndexPatternFieldEditorStart; +} + +export const renderApp = async ( + { data, indexPatternFieldEditor }: RenderAppDependencies, + { element }: AppMountParameters +) => { + const indexPattern = (await data.indexPatterns.getDefault()) || undefined; + ReactDOM.render( + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/src/plugins/index_pattern_management/public/service/environment/index.ts b/examples/index_pattern_field_editor_example/public/index.ts similarity index 74% rename from src/plugins/index_pattern_management/public/service/environment/index.ts rename to examples/index_pattern_field_editor_example/public/index.ts index ab5297ed0e14c0..cc509da31d25fb 100644 --- a/src/plugins/index_pattern_management/public/service/environment/index.ts +++ b/examples/index_pattern_field_editor_example/public/index.ts @@ -6,4 +6,6 @@ * Side Public License, v 1. */ -export { EnvironmentService, Environment, EnvironmentServiceSetup } from './environment'; +import { IndexPatternFieldEditorPlugin } from './plugin'; + +export const plugin = () => new IndexPatternFieldEditorPlugin(); diff --git a/examples/index_pattern_field_editor_example/public/plugin.tsx b/examples/index_pattern_field_editor_example/public/plugin.tsx new file mode 100644 index 00000000000000..ccbb93e3acf956 --- /dev/null +++ b/examples/index_pattern_field_editor_example/public/plugin.tsx @@ -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 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 { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public'; +import { DeveloperExamplesSetup } from '../../developer_examples/public'; +import { DataPublicPluginStart } from '../../../src/plugins/data/public'; +import { IndexPatternFieldEditorStart } from '../../../src/plugins/index_pattern_field_editor/public'; + +interface StartDeps { + data: DataPublicPluginStart; + indexPatternFieldEditor: IndexPatternFieldEditorStart; +} + +interface SetupDeps { + developerExamples: DeveloperExamplesSetup; +} + +export class IndexPatternFieldEditorPlugin implements Plugin { + public setup(core: CoreSetup, deps: SetupDeps) { + core.application.register({ + id: 'indexPatternFieldEditorExample', + title: 'Index pattern field editor example', + navLinkStatus: AppNavLinkStatus.hidden, + async mount(params: AppMountParameters) { + const [, depsStart] = await core.getStartServices(); + const { renderApp } = await import('./app'); + return renderApp(depsStart, params); + }, + }); + + deps.developerExamples.register({ + appId: 'indexPatternFieldEditorExample', + title: 'Index pattern field editor', + description: `IndexPatternFieldEditor provides a UI for editing index pattern fields directly from Kibana apps. This example plugin demonstrates integration.`, + links: [ + { + label: 'README', + href: + 'https://github.com/elastic/kibana/blob/master/src/plugins/index_pattern_field_editor/README.md', + iconType: 'logoGithub', + size: 's', + target: '_blank', + }, + ], + }); + } + + public start() {} + + public stop() {} +} diff --git a/examples/index_pattern_field_editor_example/tsconfig.json b/examples/index_pattern_field_editor_example/tsconfig.json new file mode 100644 index 00000000000000..1f6d52ed5260e4 --- /dev/null +++ b/examples/index_pattern_field_editor_example/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../typings/**/*", + ], + "exclude": [], + "references": [ + { "path": "../../src/core/tsconfig.json" }, + { "path": "../../src/plugins/kibana_react/tsconfig.json" }, + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 6f5994a8679d68..cf6bd407d53a43 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "@kbn/securitysolution-list-utils": "link:bazel-bin/packages/kbn-securitysolution-list-utils", "@kbn/securitysolution-utils": "link:bazel-bin/packages/kbn-securitysolution-utils", "@kbn/server-http-tools": "link:bazel-bin/packages/kbn-server-http-tools", - "@kbn/server-route-repository": "link:packages/kbn-server-route-repository", + "@kbn/server-route-repository": "link:bazel-bin/packages/kbn-server-route-repository", "@kbn/std": "link:bazel-bin/packages/kbn-std", "@kbn/tinymath": "link:bazel-bin/packages/kbn-tinymath", "@kbn/ui-framework": "link:packages/kbn-ui-framework", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 083ae90a031f50..3e17d471a3cac0 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -40,6 +40,7 @@ filegroup( "//packages/kbn-securitysolution-utils:build", "//packages/kbn-securitysolution-es-utils:build", "//packages/kbn-server-http-tools:build", + "//packages/kbn-server-route-repository:build", "//packages/kbn-std:build", "//packages/kbn-telemetry-tools:build", "//packages/kbn-tinymath:build", diff --git a/packages/kbn-dev-utils/src/plugins/parse_kibana_platform_plugin.ts b/packages/kbn-dev-utils/src/plugins/parse_kibana_platform_plugin.ts index 8d3fdb0f390c5e..62231f8221a957 100644 --- a/packages/kbn-dev-utils/src/plugins/parse_kibana_platform_plugin.ts +++ b/packages/kbn-dev-utils/src/plugins/parse_kibana_platform_plugin.ts @@ -29,6 +29,16 @@ interface Manifest { server: boolean; kibanaVersion: string; version: string; + // TODO: make this required. + owner?: { + // Internally, this should be a team name. + name: string; + // All internally owned plugins should have a github team specified that can be pinged in issues, or used to look up + // members who can be asked questions regarding the plugin. + githubTeam?: string; + }; + // TODO: make required. + description?: string; serviceFolders: readonly string[]; requiredPlugins: readonly string[]; optionalPlugins: readonly string[]; @@ -66,6 +76,8 @@ export function parseKibanaPlatformPlugin(manifestPath: string): KibanaPlatformP version: manifest.version, kibanaVersion: manifest.kibanaVersion || manifest.version, serviceFolders: manifest.serviceFolders || [], + owner: manifest.owner, + description: manifest.description, requiredPlugins: isValidDepsDeclaration(manifest.requiredPlugins, 'requiredPlugins'), optionalPlugins: isValidDepsDeclaration(manifest.optionalPlugins, 'optionalPlugins'), requiredBundles: isValidDepsDeclaration(manifest.requiredBundles, 'requiredBundles'), diff --git a/packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts b/packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts index 0e3d209d6398c5..55a466a430b696 100644 --- a/packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts +++ b/packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts @@ -13,20 +13,12 @@ import { REPO_ROOT, run, CiStatsReporter, createFlagError } from '@kbn/dev-utils import { Project } from 'ts-morph'; import { writePluginDocs } from './mdx/write_plugin_mdx_docs'; -import { ApiDeclaration, PluginApi, TypeKind } from './types'; +import { ApiDeclaration, ApiStats, MissingApiItemMap, PluginApi, TypeKind } from './types'; import { findPlugins } from './find_plugins'; import { pathsOutsideScopes } from './build_api_declarations/utils'; import { getPluginApiMap } from './get_plugin_api_map'; import { writeDeprecationDoc } from './mdx/write_deprecations_doc'; -export interface PluginInfo { - apiCount: number; - apiCountMissingComments: number; - id: string; - missingApiItems: string[]; - percentApiMissingComments: number; -} - function isStringArray(arr: unknown | string[]): arr is string[] { return Array.isArray(arr) && arr.every((p) => typeof p === 'string'); } @@ -95,14 +87,13 @@ export function runBuildApiDocsCli() { const id = plugin.manifest.id; const pluginApi = pluginApiMap[id]; - const apiCount = countApiForPlugin(pluginApi); - const pluginStats = collectApiStatsForPlugin(pluginApi); + const pluginStats = collectApiStatsForPlugin(pluginApi, missingApiItems); reporter.metrics([ { id, group: 'API count', - value: apiCount, + value: pluginStats.apiCount, }, { id, @@ -202,8 +193,8 @@ export function runBuildApiDocsCli() { } } - if (apiCount > 0) { - writePluginDocs(outputFolder, pluginApi, log); + if (pluginStats.apiCount > 0) { + writePluginDocs(outputFolder, { doc: pluginApi, plugin, pluginStats, log }); } writeDeprecationDoc(outputFolder, referencedDeprecations, log); }); @@ -239,27 +230,27 @@ function getTsProject(repoPath: string) { return project; } -interface ApiStats { - missingComments: ApiDeclaration[]; - isAnyType: ApiDeclaration[]; - noReferences: ApiDeclaration[]; -} - -function collectApiStatsForPlugin(doc: PluginApi): ApiStats { - const stats: ApiStats = { missingComments: [], isAnyType: [], noReferences: [] }; +function collectApiStatsForPlugin(doc: PluginApi, missingApiItems: MissingApiItemMap): ApiStats { + const stats: ApiStats = { + missingComments: [], + isAnyType: [], + noReferences: [], + apiCount: countApiForPlugin(doc), + missingExports: Object.values(missingApiItems[doc.id] ?? {}).length, + }; Object.values(doc.client).forEach((def) => { - collectStatsForApi(def, stats); + collectStatsForApi(def, stats, doc); }); Object.values(doc.server).forEach((def) => { - collectStatsForApi(def, stats); + collectStatsForApi(def, stats, doc); }); Object.values(doc.common).forEach((def) => { - collectStatsForApi(def, stats); + collectStatsForApi(def, stats, doc); }); return stats; } -function collectStatsForApi(doc: ApiDeclaration, stats: ApiStats): void { +function collectStatsForApi(doc: ApiDeclaration, stats: ApiStats, pluginApi: PluginApi): void { const missingComment = doc.description === undefined || doc.description.length === 0; if (missingComment) { stats.missingComments.push(doc); @@ -269,7 +260,7 @@ function collectStatsForApi(doc: ApiDeclaration, stats: ApiStats): void { } if (doc.children) { doc.children.forEach((child) => { - collectStatsForApi(child, stats); + collectStatsForApi(child, stats, pluginApi); }); } if (!doc.references || doc.references.length === 0) { diff --git a/packages/kbn-docs-utils/src/api_docs/mdx/types.ts b/packages/kbn-docs-utils/src/api_docs/mdx/types.ts new file mode 100644 index 00000000000000..38c25fe68f7bb2 --- /dev/null +++ b/packages/kbn-docs-utils/src/api_docs/mdx/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { KibanaPlatformPlugin, ToolingLog } from '@kbn/dev-utils'; +import { ApiStats, PluginApi } from '../types'; + +export interface WritePluginDocsOpts { + doc: PluginApi; + plugin: KibanaPlatformPlugin; + pluginStats: ApiStats; + log: ToolingLog; +} diff --git a/packages/kbn-docs-utils/src/api_docs/mdx/write_plugin_mdx_docs.ts b/packages/kbn-docs-utils/src/api_docs/mdx/write_plugin_mdx_docs.ts index 86ddf38cba4b66..557277331b099b 100644 --- a/packages/kbn-docs-utils/src/api_docs/mdx/write_plugin_mdx_docs.ts +++ b/packages/kbn-docs-utils/src/api_docs/mdx/write_plugin_mdx_docs.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { ToolingLog } from '@kbn/dev-utils'; import fs from 'fs'; import Path from 'path'; import dedent from 'dedent'; @@ -19,6 +18,7 @@ import { groupPluginApi, } from '../utils'; import { writePluginDocSplitByFolder } from './write_plugin_split_by_folder'; +import { WritePluginDocsOpts } from './types'; /** * Converts the plugin doc to mdx and writes it into the file system. If the plugin, @@ -28,12 +28,15 @@ import { writePluginDocSplitByFolder } from './write_plugin_split_by_folder'; * @param doc Contains the information of the plugin that will be written into mdx. * @param log Used for logging debug and error information. */ -export function writePluginDocs(folder: string, doc: PluginApi, log: ToolingLog): void { +export function writePluginDocs( + folder: string, + { doc, plugin, pluginStats, log }: WritePluginDocsOpts +): void { if (doc.serviceFolders) { log.debug(`Splitting plugin ${doc.id}`); - writePluginDocSplitByFolder(folder, doc, log); + writePluginDocSplitByFolder(folder, { doc, log, plugin, pluginStats }); } else { - writePluginDoc(folder, doc, log); + writePluginDoc(folder, { doc, plugin, pluginStats, log }); } } @@ -50,7 +53,10 @@ function hasPublicApi(doc: PluginApi): boolean { * @param doc Contains the information of the plugin that will be written into mdx. * @param log Used for logging debug and error information. */ -export function writePluginDoc(folder: string, doc: PluginApi, log: ToolingLog): void { +export function writePluginDoc( + folder: string, + { doc, log, plugin, pluginStats }: WritePluginDocsOpts +): void { if (!hasPublicApi(doc)) { log.debug(`${doc.id} does not have a public api. Skipping.`); return; @@ -62,6 +68,7 @@ export function writePluginDoc(folder: string, doc: PluginApi, log: ToolingLog): // Append "obj" to avoid special names in here. 'case' is one in particular that // caused issues. const json = getJsonName(fileName) + 'Obj'; + const name = plugin.manifest.owner?.name; let mdx = dedent(` --- @@ -74,9 +81,26 @@ date: 2020-11-16 tags: ['contributor', 'dev', 'apidocs', 'kibana', '${doc.id}'] 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. --- - import ${json} from './${fileName}.json'; +${plugin.manifest.description ?? ''} + +${ + plugin.manifest.owner?.githubTeam && name + ? `Contact [${name}](https://github.com/orgs/elastic/teams/${plugin.manifest.owner?.githubTeam}) for questions regarding this plugin.` + : name + ? `Contact ${name} for questions regarding this plugin.` + : '' +} + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| ${pluginStats.apiCount} | ${pluginStats.isAnyType.length} | ${ + pluginStats.missingComments.length + } | ${pluginStats.missingExports} | + `) + '\n\n'; const scopedDoc = { diff --git a/packages/kbn-docs-utils/src/api_docs/mdx/write_plugin_split_by_folder.ts b/packages/kbn-docs-utils/src/api_docs/mdx/write_plugin_split_by_folder.ts index f5d547fc03520d..1a9037df0aa253 100644 --- a/packages/kbn-docs-utils/src/api_docs/mdx/write_plugin_split_by_folder.ts +++ b/packages/kbn-docs-utils/src/api_docs/mdx/write_plugin_split_by_folder.ts @@ -6,17 +6,22 @@ * Side Public License, v 1. */ -import { ToolingLog } from '@kbn/dev-utils'; import { snakeToCamel } from '../utils'; import { PluginApi, ApiDeclaration } from '../types'; import { writePluginDoc } from './write_plugin_mdx_docs'; +import { WritePluginDocsOpts } from './types'; -export function writePluginDocSplitByFolder(folder: string, doc: PluginApi, log: ToolingLog) { +export function writePluginDocSplitByFolder( + folder: string, + { doc, plugin, pluginStats, log }: WritePluginDocsOpts +) { const apisByFolder = splitApisByFolder(doc); log.debug(`Split ${doc.id} into ${apisByFolder.length} services`); apisByFolder.forEach((docDef) => { - writePluginDoc(folder, docDef, log); + // TODO: we should probably see if we can break down these stats by service folder. As it is, they will represent stats for + // the entire plugin. + writePluginDoc(folder, { doc: docDef, plugin, pluginStats, log }); }); } diff --git a/packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts b/packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts index ff71b0efc79d1c..c5538b9da229ea 100644 --- a/packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts +++ b/packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts @@ -13,7 +13,7 @@ import { Project } from 'ts-morph'; import { ToolingLog, KibanaPlatformPlugin } from '@kbn/dev-utils'; import { writePluginDocs } from '../mdx/write_plugin_mdx_docs'; -import { ApiDeclaration, PluginApi, Reference, TextWithLinks, TypeKind } from '../types'; +import { ApiDeclaration, ApiStats, PluginApi, Reference, TextWithLinks, TypeKind } from '../types'; import { getKibanaPlatformPlugin } from './kibana_platform_plugin_mock'; import { groupPluginApi } from '../utils'; import { getPluginApiMap } from '../get_plugin_api_map'; @@ -99,11 +99,23 @@ beforeAll(() => { const plugins: KibanaPlatformPlugin[] = [pluginA, pluginB]; const { pluginApiMap } = getPluginApiMap(project, plugins, log, { collectReferences: false }); + const pluginStats: ApiStats = { + missingComments: [], + isAnyType: [], + noReferences: [], + apiCount: 3, + missingExports: 0, + }; doc = pluginApiMap.pluginA; mdxOutputFolder = Path.resolve(__dirname, 'snapshots'); - writePluginDocs(mdxOutputFolder, doc, log); - writePluginDocs(mdxOutputFolder, pluginApiMap.pluginB, log); + writePluginDocs(mdxOutputFolder, { doc, plugin: pluginA, pluginStats, log }); + writePluginDocs(mdxOutputFolder, { + doc: pluginApiMap.pluginB, + plugin: pluginB, + pluginStats, + log, + }); }); it('Setup type is extracted', () => { diff --git a/packages/kbn-docs-utils/src/api_docs/types.ts b/packages/kbn-docs-utils/src/api_docs/types.ts index 007b8c824d3c26..de53993fe30366 100644 --- a/packages/kbn-docs-utils/src/api_docs/types.ts +++ b/packages/kbn-docs-utils/src/api_docs/types.ts @@ -231,3 +231,10 @@ export interface ApiReference { export interface ReferencedDeprecations { [key: string]: Array<{ deprecatedApi: ApiDeclaration; ref: ApiReference }>; } +export interface ApiStats { + missingComments: ApiDeclaration[]; + isAnyType: ApiDeclaration[]; + noReferences: ApiDeclaration[]; + apiCount: number; + missingExports: number; +} diff --git a/packages/kbn-server-route-repository/BUILD.bazel b/packages/kbn-server-route-repository/BUILD.bazel new file mode 100644 index 00000000000000..3a146086e80bc3 --- /dev/null +++ b/packages/kbn-server-route-repository/BUILD.bazel @@ -0,0 +1,89 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-server-route-repository" +PKG_REQUIRE_NAME = "@kbn/server-route-repository" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md" +] + +SRC_DEPS = [ + "//packages/kbn-config-schema", + "//packages/kbn-io-ts-utils", + "@npm//@hapi/boom", + "@npm//fp-ts", + "@npm//io-ts", + "@npm//lodash", + "@npm//utility-types" +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/lodash", + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = DEPS + [":tsc"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-server-route-repository/package.json b/packages/kbn-server-route-repository/package.json index 4ae625d83a7003..cfeab275e19cf6 100644 --- a/packages/kbn-server-route-repository/package.json +++ b/packages/kbn-server-route-repository/package.json @@ -4,10 +4,5 @@ "types": "./target/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", - "private": true, - "scripts": { - "build": "../../node_modules/.bin/tsc", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch" - } + "private": true } diff --git a/packages/kbn-server-route-repository/tsconfig.json b/packages/kbn-server-route-repository/tsconfig.json index 8f1e72172c6759..7614a9411602ef 100644 --- a/packages/kbn-server-route-repository/tsconfig.json +++ b/packages/kbn-server-route-repository/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, + "incremental": true, "outDir": "./target", "stripInternal": false, "declaration": true, diff --git a/packages/kbn-test/src/kbn_archiver_cli.ts b/packages/kbn-test/src/kbn_archiver_cli.ts index 04581a83546686..6eb1d4bf68dadc 100644 --- a/packages/kbn-test/src/kbn_archiver_cli.ts +++ b/packages/kbn-test/src/kbn_archiver_cli.ts @@ -43,13 +43,11 @@ export function runKbnArchiverCli() { new RunWithCommands({ description: 'Import/export saved objects from archives, for testing', globalFlags: { - string: ['config', 'space', 'kibana-url', 'dir'], + string: ['config', 'space', 'kibana-url'], help: ` --space space id to operate on, defaults to the default space --config optional path to an FTR config file that will be parsed and used for defaults --kibana-url set the url that kibana can be reached at, uses the "servers.kibana" setting from --config by default - --dir directory that contains exports to be imported, or where exports will be saved, uses the "kbnArchiver.directory" - setting from --config by default `, }, async extendContext({ log, flags }) { @@ -79,23 +77,6 @@ export function runKbnArchiverCli() { ); } - let importExportDir; - if (flags.dir) { - if (typeof flags.dir !== 'string') { - throw createFlagError('expected --dir to be a string'); - } - - importExportDir = flags.dir; - } else if (config) { - importExportDir = config.get('kbnArchiver.directory'); - } - - if (!importExportDir) { - throw createFlagError( - '--config does not include a kbnArchiver.directory, specify it or include --dir flag' - ); - } - const space = flags.space; if (!(space === undefined || typeof space === 'string')) { throw createFlagError('--space must be a string'); @@ -106,7 +87,7 @@ export function runKbnArchiverCli() { kbnClient: new KbnClient({ log, url: kibanaUrl, - importExportDir, + importExportBaseDir: process.cwd(), }), }; }, diff --git a/packages/kbn-test/src/kbn_client/kbn_client.ts b/packages/kbn-test/src/kbn_client/kbn_client.ts index ac14a399918cb9..e44f5005933002 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client.ts @@ -22,7 +22,7 @@ export interface KbnClientOptions { certificateAuthorities?: Buffer[]; log: ToolingLog; uiSettingDefaults?: UiSettingValues; - importExportDir?: string; + importExportBaseDir?: string; } export class KbnClient { @@ -67,7 +67,7 @@ export class KbnClient { this.log, this.requester, this.savedObjects, - options.importExportDir + options.importExportBaseDir ); } diff --git a/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts b/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts index fe67fbb70fa3ca..5fd30929fecf68 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts @@ -8,10 +8,11 @@ import { inspect } from 'util'; import Fs from 'fs/promises'; +import { existsSync } from 'fs'; import Path from 'path'; import FormData from 'form-data'; -import { ToolingLog, isAxiosResponseError, createFailError } from '@kbn/dev-utils'; +import { ToolingLog, isAxiosResponseError, createFailError, REPO_ROOT } from '@kbn/dev-utils'; import { KbnClientRequester, uriencode, ReqOptions } from './kbn_client_requester'; import { KbnClientSavedObjects } from './kbn_client_saved_objects'; @@ -39,7 +40,7 @@ export class KbnClientImportExport { public readonly log: ToolingLog, public readonly requester: KbnClientRequester, public readonly savedObjects: KbnClientSavedObjects, - public readonly dir?: string + public readonly baseDir: string = REPO_ROOT ) {} private resolvePath(path: string) { @@ -47,18 +48,19 @@ export class KbnClientImportExport { path = `${path}.json`; } - if (!this.dir && !Path.isAbsolute(path)) { + const absolutePath = Path.resolve(this.baseDir, path); + if (!existsSync(absolutePath)) { throw new Error( - 'unable to resolve relative path to import/export without a configured dir, either path absolute path or specify --dir' + `unable to resolve path [${path}] to import/export, resolved relative to [${this.baseDir}]` ); } - return this.dir ? Path.resolve(this.dir, path) : path; + return absolutePath; } - async load(name: string, options?: { space?: string }) { - const src = this.resolvePath(name); - this.log.debug('resolved import for', name, 'to', src); + async load(path: string, options?: { space?: string }) { + const src = this.resolvePath(path); + this.log.debug('resolved import for', path, 'to', src); const objects = await parseArchive(src); this.log.info('importing', objects.length, 'saved objects', { space: options?.space }); @@ -91,8 +93,8 @@ export class KbnClientImportExport { } } - async unload(name: string, options?: { space?: string }) { - const src = this.resolvePath(name); + async unload(path: string, options?: { space?: string }) { + const src = this.resolvePath(path); this.log.debug('unloading docs from archive at', src); const objects = await parseArchive(src); @@ -110,8 +112,8 @@ export class KbnClientImportExport { this.log.success(deleted, 'saved objects deleted'); } - async save(name: string, options: { types: string[]; space?: string }) { - const dest = this.resolvePath(name); + async save(path: string, options: { types: string[]; space?: string }) { + const dest = this.resolvePath(path); this.log.debug('saving export to', dest); const resp = await this.req(options.space, { diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 36d613ec82f9e0..d4ab8f624f7111 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -32,14 +32,14 @@ export class DocLinksService { guide: `${KIBANA_DOCS}canvas.html`, }, dashboard: { - guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, - drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`, - drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html#url-drilldowns`, - urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html`, - urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html#url-template-variables`, + guide: `${KIBANA_DOCS}dashboard.html`, + drilldowns: `${KIBANA_DOCS}drilldowns.html`, + drilldownsTriggerPicker: `${KIBANA_DOCS}drilldowns.html#url-drilldowns`, + urlDrilldownTemplateSyntax: `${KIBANA_DOCS}url_templating-language.html`, + urlDrilldownVariables: `${KIBANA_DOCS}url_templating-language.html#url-template-variables`, }, discover: { - guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/discover.html`, + guide: `${KIBANA_DOCS}discover.html`, }, filebeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`, @@ -128,14 +128,14 @@ export class DocLinksService { luceneExpressions: `${ELASTICSEARCH_DOCS}modules-scripting-expression.html`, }, indexPatterns: { - introduction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index-patterns.html`, - fieldFormattersNumber: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/numeral.html`, - fieldFormattersString: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/field-formatters-string.html`, + introduction: `${KIBANA_DOCS}index-patterns.html`, + fieldFormattersNumber: `${KIBANA_DOCS}numeral.html`, + fieldFormattersString: `${KIBANA_DOCS}field-formatters-string.html`, runtimeFields: `${KIBANA_DOCS}managing-index-patterns.html#runtime-fields`, }, - addData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/connect-to-elasticsearch.html`, - kibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index.html`, - upgradeAssistant: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/upgrade-assistant.html`, + addData: `${KIBANA_DOCS}connect-to-elasticsearch.html`, + kibana: `${KIBANA_DOCS}index.html`, + upgradeAssistant: `${KIBANA_DOCS}upgrade-assistant.html`, elasticsearch: { docsBase: `${ELASTICSEARCH_DOCS}`, asyncSearch: `${ELASTICSEARCH_DOCS}async-search-intro.html`, @@ -195,23 +195,23 @@ export class DocLinksService { }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, - kueryQuerySyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kuery-query.html`, + kueryQuerySyntax: `${KIBANA_DOCS}kuery-query.html`, luceneQuerySyntax: `${ELASTICSEARCH_DOCS}query-dsl-query-string-query.html#query-string-syntax`, percolate: `${ELASTICSEARCH_DOCS}query-dsl-percolate-query.html`, queryDsl: `${ELASTICSEARCH_DOCS}query-dsl.html`, }, search: { - sessions: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/search-sessions.html`, + sessions: `${KIBANA_DOCS}search-sessions.html`, }, date: { dateMath: `${ELASTICSEARCH_DOCS}common-options.html#date-math`, dateMathIndexNames: `${ELASTICSEARCH_DOCS}date-math-index-names.html`, }, management: { - dashboardSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-dashboard-settings`, + dashboardSettings: `${KIBANA_DOCS}advanced-options.html#kibana-dashboard-settings`, indexManagement: `${ELASTICSEARCH_DOCS}index-mgmt.html`, - kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`, - visualizationSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-visualization-settings`, + kibanaSearchSettings: `${KIBANA_DOCS}advanced-options.html#kibana-search-settings`, + visualizationSettings: `${KIBANA_DOCS}advanced-options.html#kibana-visualization-settings`, }, ml: { guide: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/index.html`, @@ -242,52 +242,52 @@ export class DocLinksService { guide: `${ELASTICSEARCH_DOCS}transforms.html`, }, visualize: { - guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, - timelionDeprecation: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/timelion.html`, + guide: `${KIBANA_DOCS}dashboard.html`, + timelionDeprecation: `${KIBANA_DOCS}timelion.html`, lens: `${ELASTIC_WEBSITE_URL}what-is/kibana-lens`, - lensPanels: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/lens.html`, + lensPanels: `${KIBANA_DOCS}lens.html`, maps: `${ELASTIC_WEBSITE_URL}maps`, - vega: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/vega.html`, + vega: `${KIBANA_DOCS}vega.html`, }, observability: { guide: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/index.html`, }, alerting: { - guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alert-management.html`, - actionTypes: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/action-types.html`, - emailAction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/email-action-type.html`, - emailActionConfig: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/email-action-type.html`, - generalSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alert-action-settings-kb.html#general-alert-action-settings`, - indexAction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index-action-type.html`, - esQuery: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/rule-type-es-query.html`, - indexThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/rule-type-index-threshold.html`, - pagerDutyAction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/pagerduty-action-type.html`, - preconfiguredConnectors: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/pre-configured-connectors.html`, - preconfiguredAlertHistoryConnector: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index-action-type.html#preconfigured-connector-alert-history`, - serviceNowAction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/servicenow-action-type.html#configuring-servicenow`, - setupPrerequisites: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alerting-getting-started.html#alerting-setup-prerequisites`, - slackAction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/slack-action-type.html#configuring-slack`, - teamsAction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/teams-action-type.html#configuring-teams`, + guide: `${KIBANA_DOCS}alert-management.html`, + actionTypes: `${KIBANA_DOCS}action-types.html`, + emailAction: `${KIBANA_DOCS}email-action-type.html`, + emailActionConfig: `${KIBANA_DOCS}email-action-type.html`, + generalSettings: `${KIBANA_DOCS}alert-action-settings-kb.html#general-alert-action-settings`, + indexAction: `${KIBANA_DOCS}index-action-type.html`, + esQuery: `${KIBANA_DOCS}rule-type-es-query.html`, + indexThreshold: `${KIBANA_DOCS}rule-type-index-threshold.html`, + pagerDutyAction: `${KIBANA_DOCS}pagerduty-action-type.html`, + preconfiguredConnectors: `${KIBANA_DOCS}pre-configured-connectors.html`, + preconfiguredAlertHistoryConnector: `${KIBANA_DOCS}index-action-type.html#preconfigured-connector-alert-history`, + serviceNowAction: `${KIBANA_DOCS}servicenow-action-type.html#configuring-servicenow`, + setupPrerequisites: `${KIBANA_DOCS}alerting-getting-started.html#alerting-setup-prerequisites`, + slackAction: `${KIBANA_DOCS}slack-action-type.html#configuring-slack`, + teamsAction: `${KIBANA_DOCS}teams-action-type.html#configuring-teams`, }, maps: { - guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/maps.html`, - importGeospatialPrivileges: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/import-geospatial-data.html#import-geospatial-privileges`, + guide: `${KIBANA_DOCS}maps.html`, + importGeospatialPrivileges: `${KIBANA_DOCS}import-geospatial-data.html#import-geospatial-privileges`, }, monitoring: { - alertsKibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html`, - alertsKibanaCpuThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-cpu-threshold`, - alertsKibanaDiskThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-disk-usage-threshold`, - alertsKibanaJvmThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-jvm-memory-threshold`, - alertsKibanaMissingData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-missing-monitoring-data`, - alertsKibanaThreadpoolRejections: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-thread-pool-rejections`, - alertsKibanaCCRReadExceptions: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-ccr-read-exceptions`, - alertsKibanaLargeShardSize: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-large-shard-size`, - alertsKibanaClusterAlerts: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-cluster-alerts`, + alertsKibana: `${KIBANA_DOCS}kibana-alerts.html`, + alertsKibanaCpuThreshold: `${KIBANA_DOCS}kibana-alerts.html#kibana-alerts-cpu-threshold`, + alertsKibanaDiskThreshold: `${KIBANA_DOCS}kibana-alerts.html#kibana-alerts-disk-usage-threshold`, + alertsKibanaJvmThreshold: `${KIBANA_DOCS}kibana-alerts.html#kibana-alerts-jvm-memory-threshold`, + alertsKibanaMissingData: `${KIBANA_DOCS}kibana-alerts.html#kibana-alerts-missing-monitoring-data`, + alertsKibanaThreadpoolRejections: `${KIBANA_DOCS}kibana-alerts.html#kibana-alerts-thread-pool-rejections`, + alertsKibanaCCRReadExceptions: `${KIBANA_DOCS}kibana-alerts.html#kibana-alerts-ccr-read-exceptions`, + alertsKibanaLargeShardSize: `${KIBANA_DOCS}kibana-alerts.html#kibana-alerts-large-shard-size`, + alertsKibanaClusterAlerts: `${KIBANA_DOCS}kibana-alerts.html#kibana-alerts-cluster-alerts`, metricbeatBlog: `${ELASTIC_WEBSITE_URL}blog/external-collection-for-elastic-stack-monitoring-is-now-available-via-metricbeat`, monitorElasticsearch: `${ELASTICSEARCH_DOCS}configuring-metricbeat.html`, - monitorKibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/monitoring-metricbeat.html`, + monitorKibana: `${KIBANA_DOCS}monitoring-metricbeat.html`, monitorLogstash: `${ELASTIC_WEBSITE_URL}guide/en/logstash/${DOC_LINK_VERSION}/monitoring-with-metricbeat.html`, - troubleshootKibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/monitor-troubleshooting.html`, + troubleshootKibana: `${KIBANA_DOCS}monitor-troubleshooting.html`, }, security: { apiKeyServiceSettings: `${ELASTICSEARCH_DOCS}security-settings.html#api-key-service-settings`, @@ -295,8 +295,8 @@ export class DocLinksService { elasticsearchSettings: `${ELASTICSEARCH_DOCS}security-settings.html`, elasticsearchEnableSecurity: `${ELASTICSEARCH_DOCS}configuring-stack-security.html`, indicesPrivileges: `${ELASTICSEARCH_DOCS}security-privileges.html#privileges-list-indices`, - kibanaTLS: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/configuring-tls.html`, - kibanaPrivileges: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-privileges.html`, + kibanaTLS: `${KIBANA_DOCS}configuring-tls.html`, + kibanaPrivileges: `${KIBANA_DOCS}kibana-privileges.html`, mappingRoles: `${ELASTICSEARCH_DOCS}mapping-roles.html`, mappingRolesFieldRules: `${ELASTICSEARCH_DOCS}role-mapping-resources.html#mapping-roles-rule-field`, runAsPrivilege: `${ELASTICSEARCH_DOCS}security-privileges.html#_run_as_privilege`, @@ -305,7 +305,7 @@ export class DocLinksService { jiraAction: `${ELASTICSEARCH_DOCS}actions-jira.html`, pagerDutyAction: `${ELASTICSEARCH_DOCS}actions-pagerduty.html`, slackAction: `${ELASTICSEARCH_DOCS}actions-slack.html`, - ui: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/watcher-ui.html`, + ui: `${KIBANA_DOCS}watcher-ui.html`, }, ccs: { guide: `${ELASTICSEARCH_DOCS}modules-cross-cluster-search.html`, diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index 7ac629534ba089..b59418a67219e0 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -48,6 +48,8 @@ const KNOWN_MANIFEST_FIELDS = (() => { extraPublicDirs: true, requiredBundles: true, serviceFolders: true, + owner: true, + description: true, }; return new Set(Object.keys(manifestFields)); @@ -187,6 +189,8 @@ export async function parseManifest( ui: includesUiPlugin, server: includesServerPlugin, extraPublicDirs: manifest.extraPublicDirs, + owner: manifest.owner, + description: manifest.description, }; } diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 6b50b5e820665e..0cdc806e997ef7 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -208,6 +208,27 @@ export interface PluginManifest { * folders will cause your plugin API reference to be broken up into sub sections. */ readonly serviceFolders?: readonly string[]; + + /** + * TODO: make required once all internal plugins have this specified. + */ + readonly owner?: { + /** + * The name of the team that currently owns this plugin. + */ + readonly name: string; + /** + * All internal plugins should have a github team specified. GitHub teams can be viewed here: + * https://github.com/orgs/elastic/teams + */ + readonly githubTeam?: string; + }; + + /** + * TODO: make required once all plugins specify this. + * A brief description of what this plugin does and any capabilities it provides. + */ + readonly description?: string; } /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index acec03902bf6aa..ce13174ee19cc2 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1917,11 +1917,16 @@ export interface PluginInitializerContext { export interface PluginManifest { // Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported readonly configPath: ConfigPath; + readonly description?: string; // @deprecated readonly extraPublicDirs?: string[]; readonly id: PluginName; readonly kibanaVersion: string; readonly optionalPlugins: readonly PluginName[]; + readonly owner?: { + readonly name: string; + readonly githubTeam?: string; + }; readonly requiredBundles: readonly string[]; readonly requiredPlugins: readonly PluginName[]; readonly server: boolean; @@ -3260,9 +3265,9 @@ export const validBodyOutput: readonly ["data", "stream"]; // // src/core/server/elasticsearch/client/types.ts:94:7 - (ae-forgotten-export) The symbol "Explanation" needs to be exported by the entry point index.d.ts // src/core/server/http/router/response.ts:301:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:326:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:326:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:329:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:434:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "create" +// src/core/server/plugins/types.ts:347:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:347:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:350:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:455:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "create" ``` diff --git a/src/plugins/bfetch/kibana.json b/src/plugins/bfetch/kibana.json index 9f9f2176af671f..6d37d68ffd584f 100644 --- a/src/plugins/bfetch/kibana.json +++ b/src/plugins/bfetch/kibana.json @@ -3,5 +3,10 @@ "version": "kibana", "server": true, "ui": true, - "requiredBundles": ["kibanaUtils"] + "requiredBundles": ["kibanaUtils"], + "owner": { + "name": "App Services", + "githubTeam": "kibana-app-services" + }, + "description": "Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back." } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index c897cdbce2309a..0c3a9901f8c8ca 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -364,7 +364,6 @@ export class IndexPattern implements IIndexPattern { * @param name Field name * @param runtimeField Runtime field definition */ - addRuntimeField(name: string, runtimeField: RuntimeField) { const existingField = this.getFieldByName(name); if (existingField) { @@ -384,11 +383,41 @@ export class IndexPattern implements IIndexPattern { } /** - * Remove a runtime field - removed from mapped field or removed unmapped - * field as appropriate - * @param name Field name + * Checks if runtime field exists + * @param name + */ + hasRuntimeField(name: string): boolean { + return !!this.runtimeFieldMap[name]; + } + + /** + * Returns runtime field if exists + * @param name + */ + getRuntimeField(name: string): RuntimeField | null { + return this.runtimeFieldMap[name] ?? null; + } + + /** + * Replaces all existing runtime fields with new fields + * @param newFields */ + replaceAllRuntimeFields(newFields: Record) { + const oldRuntimeFieldNames = Object.keys(this.runtimeFieldMap); + oldRuntimeFieldNames.forEach((name) => { + this.removeRuntimeField(name); + }); + Object.entries(newFields).forEach(([name, field]) => { + this.addRuntimeField(name, field); + }); + } + + /** + * Remove a runtime field - removed from mapped field or removed unmapped + * field as appropriate. Doesn't clear associated field attributes. + * @param name - Field name to remove + */ removeRuntimeField(name: string) { const existingField = this.getFieldByName(name); if (existingField) { @@ -396,9 +425,6 @@ export class IndexPattern implements IIndexPattern { // mapped field, remove runtimeField def existingField.runtimeField = undefined; } else { - // runtimeField only - this.setFieldCustomLabel(name, null); - this.deleteFieldFormat(name); this.fields.remove(existingField); } } diff --git a/src/plugins/data/kibana.json b/src/plugins/data/kibana.json index 4e9e4c318c9575..e425d0701155bd 100644 --- a/src/plugins/data/kibana.json +++ b/src/plugins/data/kibana.json @@ -3,19 +3,14 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": [ - "bfetch", - "expressions", - "uiActions", - "share", - "inspector" - ], + "requiredPlugins": ["bfetch", "expressions", "uiActions", "share", "inspector"], "serviceFolders": ["search", "index_patterns", "query", "autocomplete", "ui", "field_formats"], "optionalPlugins": ["usageCollection"], "extraPublicDirs": ["common"], - "requiredBundles": [ - "kibanaUtils", - "kibanaReact", - "inspector" - ] + "requiredBundles": ["kibanaUtils", "kibanaReact", "inspector"], + "owner": { + "name": "App Services", + "githubTeam": "kibana-app-services" + }, + "description": "Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters." } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 5ef499840fa6de..67534577d99fcf 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1413,6 +1413,7 @@ export class IndexPattern implements IIndexPattern { typeMeta?: string | undefined; type?: string | undefined; }; + getRuntimeField(name: string): RuntimeField | null; // @deprecated (undocumented) getScriptedFields(): IndexPatternField[]; getSourceFiltering(): { @@ -1420,6 +1421,7 @@ export class IndexPattern implements IIndexPattern { }; // (undocumented) getTimeField(): IndexPatternField | undefined; + hasRuntimeField(name: string): boolean; // (undocumented) id?: string; // @deprecated (undocumented) @@ -1433,6 +1435,7 @@ export class IndexPattern implements IIndexPattern { removeRuntimeField(name: string): void; // @deprecated removeScriptedField(fieldName: string): void; + replaceAllRuntimeFields(newFields: Record): void; resetOriginalSavedObjectBody: () => void; // (undocumented) protected setFieldAttrs(fieldName: string, attrName: K, value: FieldAttrSet[K]): void; diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index 9bff590b54f1c1..d2d8cb82cf646e 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -21,6 +21,11 @@ import { registerDeleteScriptedFieldRoute } from './routes/scripted_fields/delet import { registerUpdateScriptedFieldRoute } from './routes/scripted_fields/update_scripted_field'; import type { DataPluginStart, DataPluginStartDependencies } from '../plugin'; import { registerManageDefaultIndexPatternRoutes } from './routes/default_index_pattern'; +import { registerCreateRuntimeFieldRoute } from './routes/runtime_fields/create_runtime_field'; +import { registerGetRuntimeFieldRoute } from './routes/runtime_fields/get_runtime_field'; +import { registerDeleteRuntimeFieldRoute } from './routes/runtime_fields/delete_runtime_field'; +import { registerPutRuntimeFieldRoute } from './routes/runtime_fields/put_runtime_field'; +import { registerUpdateRuntimeFieldRoute } from './routes/runtime_fields/update_runtime_field'; export function registerRoutes( http: HttpServiceSetup, @@ -55,6 +60,13 @@ export function registerRoutes( registerDeleteScriptedFieldRoute(router, getStartServices); registerUpdateScriptedFieldRoute(router, getStartServices); + // Runtime Fields API + registerCreateRuntimeFieldRoute(router, getStartServices); + registerGetRuntimeFieldRoute(router, getStartServices); + registerDeleteRuntimeFieldRoute(router, getStartServices); + registerPutRuntimeFieldRoute(router, getStartServices); + registerUpdateRuntimeFieldRoute(router, getStartServices); + router.get( { path: '/api/index_patterns/_fields_for_wildcard', diff --git a/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts b/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts index d0767334626229..7049903f84e8c2 100644 --- a/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts +++ b/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts @@ -9,7 +9,11 @@ import { schema } from '@kbn/config-schema'; import { IndexPatternSpec } from 'src/plugins/data/common'; import { handleErrors } from './util/handle_errors'; -import { fieldSpecSchema, serializedFieldFormatSchema } from './util/schemas'; +import { + fieldSpecSchema, + runtimeFieldSpecSchema, + serializedFieldFormatSchema, +} from './util/schemas'; import { IRouter, StartServicesAccessor } from '../../../../../core/server'; import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; @@ -39,6 +43,7 @@ const indexPatternSpecSchema = schema.object({ ) ), allowNoIndex: schema.maybe(schema.boolean()), + runtimeFieldMap: schema.maybe(schema.recordOf(schema.string(), runtimeFieldSpecSchema)), }); export const registerCreateIndexPatternRoute = ( @@ -66,6 +71,7 @@ export const registerCreateIndexPatternRoute = ( elasticsearchClient ); const body = req.body; + const indexPattern = await indexPatternsService.createAndSave( body.index_pattern as IndexPatternSpec, body.override, diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/create_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/create_runtime_field.ts new file mode 100644 index 00000000000000..faf6d87b6d10bf --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/create_runtime_field.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { schema } from '@kbn/config-schema'; +import { handleErrors } from '../util/handle_errors'; +import { runtimeFieldSpecSchema } from '../util/schemas'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerCreateRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.post( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + body: schema.object({ + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + runtimeField: runtimeFieldSpecSchema, + }), + }, + }, + + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const { name, runtimeField } = req.body; + + const indexPattern = await indexPatternsService.get(id); + + if (indexPattern.fields.getByName(name)) { + throw new Error(`Field [name = ${name}] already exists.`); + } + + indexPattern.addRuntimeField(name, runtimeField); + + const addedField = indexPattern.fields.getByName(name); + if (!addedField) throw new Error(`Could not create a field [name = ${name}].`); + + await indexPatternsService.updateSavedObject(indexPattern); + + const savedField = indexPattern.fields.getByName(name); + if (!savedField) throw new Error(`Could not create a field [name = ${name}].`); + + return res.ok({ + body: { + field: savedField.toSpec(), + index_pattern: indexPattern.toSpec(), + }, + }); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/delete_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/delete_runtime_field.ts new file mode 100644 index 00000000000000..58b8529d7cf5af --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/delete_runtime_field.ts @@ -0,0 +1,64 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { ErrorIndexPatternFieldNotFound } from '../../error'; +import { handleErrors } from '../util/handle_errors'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerDeleteRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.delete( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field/{name}', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + }, + }, + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const name = req.params.name; + + const indexPattern = await indexPatternsService.get(id); + const field = indexPattern.fields.getByName(name); + + if (!field) { + throw new ErrorIndexPatternFieldNotFound(id, name); + } + + if (!field.runtimeField) { + throw new Error('Only runtime fields can be deleted.'); + } + + indexPattern.removeRuntimeField(name); + + await indexPatternsService.updateSavedObject(indexPattern); + + return res.ok(); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/get_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/get_runtime_field.ts new file mode 100644 index 00000000000000..6bc2bf396c0b44 --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/get_runtime_field.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import { ErrorIndexPatternFieldNotFound } from '../../error'; +import { handleErrors } from '../util/handle_errors'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerGetRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.get( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field/{name}', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + }, + }, + + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const name = req.params.name; + + const indexPattern = await indexPatternsService.get(id); + + const field = indexPattern.fields.getByName(name); + + if (!field) { + throw new ErrorIndexPatternFieldNotFound(id, name); + } + + if (!field.runtimeField) { + throw new Error('Only runtime fields can be retrieved.'); + } + + return res.ok({ + body: { + field: field.toSpec(), + runtimeField: indexPattern.getRuntimeField(name), + }, + }); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/put_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/put_runtime_field.ts new file mode 100644 index 00000000000000..a5e92fa5a36ecb --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/put_runtime_field.ts @@ -0,0 +1,76 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { handleErrors } from '../util/handle_errors'; +import { runtimeFieldSpecSchema } from '../util/schemas'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerPutRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.put( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + body: schema.object({ + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + runtimeField: runtimeFieldSpecSchema, + }), + }, + }, + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const { name, runtimeField } = req.body; + + const indexPattern = await indexPatternsService.get(id); + + const oldFieldObject = indexPattern.fields.getByName(name); + + if (oldFieldObject && !oldFieldObject.runtimeField) { + throw new Error('Only runtime fields can be updated'); + } + + if (oldFieldObject) { + indexPattern.removeRuntimeField(name); + } + + indexPattern.addRuntimeField(name, runtimeField); + + await indexPatternsService.updateSavedObject(indexPattern); + + const fieldObject = indexPattern.fields.getByName(name); + if (!fieldObject) throw new Error(`Could not create a field [name = ${name}].`); + + return res.ok({ + body: { + field: fieldObject.toSpec(), + index_pattern: indexPattern.toSpec(), + }, + }); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/update_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/update_runtime_field.ts new file mode 100644 index 00000000000000..3f3aae46c43882 --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/update_runtime_field.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 { schema } from '@kbn/config-schema'; +import { RuntimeField } from 'src/plugins/data/common'; +import { ErrorIndexPatternFieldNotFound } from '../../error'; +import { handleErrors } from '../util/handle_errors'; +import { runtimeFieldSpec, runtimeFieldSpecTypeSchema } from '../util/schemas'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerUpdateRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.post( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field/{name}', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + body: schema.object({ + name: schema.never(), + runtimeField: schema.object({ + ...runtimeFieldSpec, + // We need to overwrite the below fields on top of `runtimeFieldSpec`, + // because some fields would be optional + type: schema.maybe(runtimeFieldSpecTypeSchema), + }), + }), + }, + }, + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const name = req.params.name; + const runtimeField = req.body.runtimeField as Partial; + + const indexPattern = await indexPatternsService.get(id); + const existingRuntimeField = indexPattern.getRuntimeField(name); + + if (!existingRuntimeField) { + throw new ErrorIndexPatternFieldNotFound(id, name); + } + + indexPattern.removeRuntimeField(name); + indexPattern.addRuntimeField(name, { + ...existingRuntimeField, + ...runtimeField, + }); + + await indexPatternsService.updateSavedObject(indexPattern); + + const fieldObject = indexPattern.fields.getByName(name); + if (!fieldObject) throw new Error(`Could not create a field [name = ${name}].`); + + return res.ok({ + body: { + field: fieldObject.toSpec(), + index_pattern: indexPattern.toSpec(), + }, + }); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts b/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts index c1509b9b848bef..1c88550c154c56 100644 --- a/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts +++ b/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts @@ -8,7 +8,11 @@ import { schema } from '@kbn/config-schema'; import { handleErrors } from './util/handle_errors'; -import { fieldSpecSchema, serializedFieldFormatSchema } from './util/schemas'; +import { + fieldSpecSchema, + runtimeFieldSpecSchema, + serializedFieldFormatSchema, +} from './util/schemas'; import { IRouter, StartServicesAccessor } from '../../../../../core/server'; import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; @@ -28,6 +32,7 @@ const indexPatternUpdateSchema = schema.object({ fieldFormats: schema.maybe(schema.recordOf(schema.string(), serializedFieldFormatSchema)), fields: schema.maybe(schema.recordOf(schema.string(), fieldSpecSchema)), allowNoIndex: schema.maybe(schema.boolean()), + runtimeFieldMap: schema.maybe(schema.recordOf(schema.string(), runtimeFieldSpecSchema)), }); export const registerUpdateIndexPatternRoute = ( @@ -78,6 +83,7 @@ export const registerUpdateIndexPatternRoute = ( type, typeMeta, fields, + runtimeFieldMap, }, } = req.body; @@ -131,6 +137,11 @@ export const registerUpdateIndexPatternRoute = ( ); } + if (runtimeFieldMap !== undefined) { + changeCount++; + indexPattern.replaceAllRuntimeFields(runtimeFieldMap); + } + if (changeCount < 1) { throw new Error('Index pattern change set is empty.'); } diff --git a/src/plugins/data/server/index_patterns/routes/util/schemas.ts b/src/plugins/data/server/index_patterns/routes/util/schemas.ts index d916423c4fc69c..79ee1ffa1ab970 100644 --- a/src/plugins/data/server/index_patterns/routes/util/schemas.ts +++ b/src/plugins/data/server/index_patterns/routes/util/schemas.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { schema } from '@kbn/config-schema'; +import { schema, Type } from '@kbn/config-schema'; +import { RUNTIME_FIELD_TYPES, RuntimeType } from '../../../../common'; export const serializedFieldFormatSchema = schema.object({ id: schema.maybe(schema.string()), @@ -52,4 +53,24 @@ export const fieldSpecSchemaFields = { shortDotsEnable: schema.maybe(schema.boolean()), }; -export const fieldSpecSchema = schema.object(fieldSpecSchemaFields); +export const fieldSpecSchema = schema.object(fieldSpecSchemaFields, { + // Allow and ignore unknowns to make fields transient. + // Because `fields` have a bunch of calculated fields + // this allows to retrieve an index pattern and then to re-create by using the retrieved payload + unknowns: 'ignore', +}); + +export const runtimeFieldSpecTypeSchema = schema.oneOf( + RUNTIME_FIELD_TYPES.map((runtimeFieldType) => schema.literal(runtimeFieldType)) as [ + Type + ] +); +export const runtimeFieldSpec = { + type: runtimeFieldSpecTypeSchema, + script: schema.maybe( + schema.object({ + source: schema.string(), + }) + ), +}; +export const runtimeFieldSpecSchema = schema.object(runtimeFieldSpec); diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index ff265ccf533015..783bd8d2fcd0e1 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -816,6 +816,7 @@ export class IndexPattern implements IIndexPattern { typeMeta?: string | undefined; type?: string | undefined; }; + getRuntimeField(name: string): RuntimeField | null; // @deprecated (undocumented) getScriptedFields(): IndexPatternField[]; getSourceFiltering(): { @@ -823,6 +824,7 @@ export class IndexPattern implements IIndexPattern { }; // (undocumented) getTimeField(): IndexPatternField | undefined; + hasRuntimeField(name: string): boolean; // (undocumented) id?: string; // @deprecated (undocumented) @@ -836,6 +838,7 @@ export class IndexPattern implements IIndexPattern { removeRuntimeField(name: string): void; // @deprecated removeScriptedField(fieldName: string): void; + replaceAllRuntimeFields(newFields: Record): void; resetOriginalSavedObjectBody: () => void; // (undocumented) protected setFieldAttrs(fieldName: string, attrName: K, value: FieldAttrSet[K]): void; diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx index ce987e2870466b..0430614d413b6b 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx @@ -346,6 +346,7 @@ export function DiscoverLayout({ verticalPosition={contentCentered ? 'center' : undefined} horizontalPosition={contentCentered ? 'center' : undefined} paddingSize="none" + hasShadow={false} className={classNames('dscPageContent', { 'dscPageContent--centered': contentCentered, })} diff --git a/src/plugins/discover/public/application/helpers/columns.test.ts b/src/plugins/discover/public/application/helpers/columns.test.ts index df3884ab98bcff..6b6125193b5f37 100644 --- a/src/plugins/discover/public/application/helpers/columns.test.ts +++ b/src/plugins/discover/public/application/helpers/columns.test.ts @@ -43,4 +43,9 @@ describe('getDisplayedColumns', () => { ] `); }); + test('returns the same instance of ["_source"] over multiple calls', async () => { + const result = getDisplayedColumns([], indexPatternWithTimefieldMock); + const result2 = getDisplayedColumns([], indexPatternWithTimefieldMock); + expect(result).toBe(result2); + }); }); diff --git a/src/plugins/discover/public/application/helpers/columns.ts b/src/plugins/discover/public/application/helpers/columns.ts index 426059060f329b..6e77717c5cf054 100644 --- a/src/plugins/discover/public/application/helpers/columns.ts +++ b/src/plugins/discover/public/application/helpers/columns.ts @@ -8,6 +8,11 @@ import { IndexPattern } from '../../../../data/common'; +// We store this outside the function as a constant, so we're not creating a new array every time +// the function is returning this. A changing array might cause the data grid to think it got +// new columns, and thus performing worse than using the same array over multiple renders. +const SOURCE_ONLY = ['_source']; + /** * Function to provide fallback when * 1) no columns are given @@ -19,5 +24,5 @@ export function getDisplayedColumns(stateColumns: string[] = [], indexPattern: I // check if all columns where removed except the configured timeField (this can't be removed) !(stateColumns.length === 1 && stateColumns[0] === indexPattern.timeFieldName) ? stateColumns - : ['_source']; + : SOURCE_ONLY; } diff --git a/src/plugins/embeddable/common/lib/migrate.ts b/src/plugins/embeddable/common/lib/migrate.ts index 0a9cd0760eb2ce..fb8ea5cf2cd84e 100644 --- a/src/plugins/embeddable/common/lib/migrate.ts +++ b/src/plugins/embeddable/common/lib/migrate.ts @@ -26,9 +26,11 @@ export const getMigrateFunction = (embeddables: CommonEmbeddableStartContract) = updatedInput.enhancements = {}; Object.keys(enhancements).forEach((key) => { if (!enhancements[key]) return; - (updatedInput.enhancements! as Record)[key] = embeddables - .getEnhancement(key) - .migrations[version](enhancements[key] as SerializableState); + const enhancementDefinition = embeddables.getEnhancement(key); + const migratedEnhancement = enhancementDefinition?.migrations?.[version] + ? enhancementDefinition.migrations[version](enhancements[key] as SerializableState) + : enhancements[key]; + (updatedInput.enhancements! as Record)[key] = migratedEnhancement; }); return updatedInput; diff --git a/src/plugins/embeddable/public/plugin.test.ts b/src/plugins/embeddable/public/plugin.test.ts index 2e7d8c73cfc6da..53302e8e6870ce 100644 --- a/src/plugins/embeddable/public/plugin.test.ts +++ b/src/plugins/embeddable/public/plugin.test.ts @@ -184,4 +184,12 @@ describe('embeddable enhancements', () => { embeddableState.enhancements.test ); }); + + test('doesnt fail if there is no migration function registered for specific version', () => { + expect(() => { + start.migrate(embeddableState, '7.10.0'); + }).not.toThrow(); + + expect(start.migrate(embeddableState, '7.10.0')).toEqual(embeddableState); + }); }); diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap index 645694371f9059..1310488c65fab8 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap @@ -59,7 +59,6 @@ exports[`EmptyState should render normally 1`] = ` } - isDisabled={false} onClick={[Function]} title={ { docLinks={docLinks} onRefresh={() => {}} navigateToApp={async () => {}} - getMlCardState={() => MlCardState.ENABLED} canSave={true} /> ); @@ -48,7 +46,6 @@ describe('EmptyState', () => { docLinks={docLinks} onRefresh={onRefreshHandler} navigateToApp={async () => {}} - getMlCardState={() => MlCardState.ENABLED} canSave={true} /> ); diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx index 438eb8a031993f..240e732752916c 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx @@ -8,7 +8,6 @@ import './empty_state.scss'; import React from 'react'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { DocLinksStart, ApplicationStart } from 'kibana/public'; import { @@ -28,60 +27,18 @@ import { } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { reactRouterNavigate } from '../../../../../../plugins/kibana_react/public'; -import { MlCardState } from '../../../types'; export const EmptyState = ({ onRefresh, navigateToApp, docLinks, - getMlCardState, canSave, }: { onRefresh: () => void; navigateToApp: ApplicationStart['navigateToApp']; docLinks: DocLinksStart; - getMlCardState: () => MlCardState; canSave: boolean; }) => { - const mlCard = ( - - navigateToApp('ml', { path: '#/filedatavisualizer' })} - className="inpEmptyState__card" - betaBadgeLabel={ - getMlCardState() === MlCardState.ENABLED - ? undefined - : i18n.translate( - 'indexPatternManagement.createIndexPattern.emptyState.basicLicenseLabel', - { - defaultMessage: 'Basic', - } - ) - } - betaBadgeTooltipContent={i18n.translate( - 'indexPatternManagement.createIndexPattern.emptyState.basicLicenseDescription', - { - defaultMessage: 'This feature requires a Basic license.', - } - )} - isDisabled={getMlCardState() === MlCardState.DISABLED} - icon={} - title={ - - } - description={ - - } - /> - - ); - const createAnyway = ( - {getMlCardState() !== MlCardState.HIDDEN ? mlCard : <>} + + navigateToApp('home', { path: '#/tutorial_directory/fileDataViz' })} + className="inpEmptyState__card" + icon={} + title={ + + } + description={ + + } + /> + { application, http, data, - getMlCardState, } = useKibana().services; const [indexPatterns, setIndexPatterns] = useState([]); const [creationOptions, setCreationOptions] = useState([]); @@ -182,7 +181,6 @@ export const IndexPatternTable = ({ canSave, history }: Props) => { onRefresh={loadSources} docLinks={docLinks} navigateToApp={application.navigateToApp} - getMlCardState={getMlCardState} canSave={canSave} /> ); diff --git a/src/plugins/index_pattern_management/public/index.ts b/src/plugins/index_pattern_management/public/index.ts index 94611705a93908..726c055d1b8c34 100644 --- a/src/plugins/index_pattern_management/public/index.ts +++ b/src/plugins/index_pattern_management/public/index.ts @@ -30,5 +30,3 @@ export { IndexPatternCreationOption, IndexPatternListConfig, } from './service'; - -export { MlCardState } from './types'; diff --git a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx index 355f529fe0f759..ec5b7c74020a5a 100644 --- a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx +++ b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx @@ -23,7 +23,7 @@ import { CreateIndexPatternWizardWithRouter, } from '../components'; import { IndexPatternManagementStartDependencies, IndexPatternManagementStart } from '../plugin'; -import { IndexPatternManagmentContext, MlCardState } from '../types'; +import { IndexPatternManagmentContext } from '../types'; const readOnlyBadge = { text: i18n.translate('indexPatternManagement.indexPatterns.badge.readOnly.text', { @@ -37,8 +37,7 @@ const readOnlyBadge = { export async function mountManagementSection( getStartServices: StartServicesAccessor, - params: ManagementAppMountParams, - getMlCardState: () => MlCardState + params: ManagementAppMountParams ) { const [ { chrome, application, uiSettings, notifications, overlays, http, docLinks }, @@ -63,7 +62,6 @@ export async function mountManagementSection( indexPatternFieldEditor, indexPatternManagementStart: indexPatternManagementStart as IndexPatternManagementStart, setBreadcrumbs: params.setBreadcrumbs, - getMlCardState, fieldFormatEditors: indexPatternFieldEditor.fieldFormatEditors, }; diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts index 3462131e50463b..6c709fb14f08d7 100644 --- a/src/plugins/index_pattern_management/public/mocks.ts +++ b/src/plugins/index_pattern_management/public/mocks.ts @@ -26,9 +26,6 @@ const createSetupContract = (): IndexPatternManagementSetup => ({ list: { addListConfig: jest.fn(), } as any, - environment: { - update: jest.fn(), - }, }); const createStartContract = (): IndexPatternManagementStart => ({ @@ -93,7 +90,6 @@ const createIndexPatternManagmentContext = (): { indexPatternFieldEditor, indexPatternManagementStart: createStartContract(), setBreadcrumbs: () => {}, - getMlCardState: () => 2, fieldFormatEditors: indexPatternFieldEditor.fieldFormatEditors, }; }; diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts index ed92172c8b91ca..e3c156927bface 100644 --- a/src/plugins/index_pattern_management/public/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -77,9 +77,7 @@ export class IndexPatternManagementPlugin mount: async (params) => { const { mountManagementSection } = await import('./management_app'); - return mountManagementSection(core.getStartServices, params, () => - this.indexPatternManagementService.environmentService.getEnvironment().ml() - ); + return mountManagementSection(core.getStartServices, params); }, }); diff --git a/src/plugins/index_pattern_management/public/service/environment/environment.mock.ts b/src/plugins/index_pattern_management/public/service/environment/environment.mock.ts deleted file mode 100644 index 1eaab2eaccc111..00000000000000 --- a/src/plugins/index_pattern_management/public/service/environment/environment.mock.ts +++ /dev/null @@ -1,34 +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 type { PublicMethodsOf } from '@kbn/utility-types'; -import { EnvironmentService, EnvironmentServiceSetup } from './environment'; -import { MlCardState } from '../../types'; - -const createSetupMock = (): jest.Mocked => { - const setup = { - update: jest.fn(), - }; - return setup; -}; - -const createMock = (): jest.Mocked> => { - const service = { - setup: jest.fn(), - getEnvironment: jest.fn(() => ({ - ml: () => MlCardState.ENABLED, - })), - }; - service.setup.mockImplementation(createSetupMock); - return service; -}; - -export const environmentServiceMock = { - createSetup: createSetupMock, - create: createMock, -}; diff --git a/src/plugins/index_pattern_management/public/service/environment/environment.test.ts b/src/plugins/index_pattern_management/public/service/environment/environment.test.ts deleted file mode 100644 index 9e571374b4784f..00000000000000 --- a/src/plugins/index_pattern_management/public/service/environment/environment.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 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 { EnvironmentService } from './environment'; -import { MlCardState } from '../../types'; - -describe('EnvironmentService', () => { - describe('setup', () => { - test('allows multiple update calls', () => { - const setup = new EnvironmentService().setup(); - expect(() => { - setup.update({ ml: () => MlCardState.ENABLED }); - }).not.toThrow(); - }); - }); - - describe('getEnvironment', () => { - test('returns default values', () => { - const service = new EnvironmentService(); - expect(service.getEnvironment().ml()).toEqual(MlCardState.DISABLED); - }); - - test('returns last state of update calls', () => { - let cardState = MlCardState.DISABLED; - const service = new EnvironmentService(); - const setup = service.setup(); - setup.update({ ml: () => cardState }); - expect(service.getEnvironment().ml()).toEqual(MlCardState.DISABLED); - cardState = MlCardState.ENABLED; - expect(service.getEnvironment().ml()).toEqual(MlCardState.ENABLED); - }); - }); -}); diff --git a/src/plugins/index_pattern_management/public/service/environment/environment.ts b/src/plugins/index_pattern_management/public/service/environment/environment.ts deleted file mode 100644 index 7bf0c1eb52068a..00000000000000 --- a/src/plugins/index_pattern_management/public/service/environment/environment.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 { MlCardState } from '../../types'; - -/** @public */ -export interface Environment { - /** - * Flag whether ml features should be advertised - */ - readonly ml: () => MlCardState; -} - -export class EnvironmentService { - private environment = { - ml: () => MlCardState.DISABLED, - }; - - public setup() { - return { - /** - * Update the environment to influence how available features are presented. - * @param update - */ - update: (update: Partial) => { - this.environment = Object.assign({}, this.environment, update); - }, - }; - } - - public getEnvironment() { - return this.environment; - } -} - -export type EnvironmentServiceSetup = ReturnType; diff --git a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts index 15be7f11892e49..f30ccfcb9f3ed7 100644 --- a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts +++ b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts @@ -9,7 +9,6 @@ import { HttpSetup } from '../../../../core/public'; import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation'; import { IndexPatternListManager, IndexPatternListConfig } from './list'; -import { EnvironmentService } from './environment'; interface SetupDependencies { httpClient: HttpSetup; } @@ -22,12 +21,10 @@ interface SetupDependencies { export class IndexPatternManagementService { indexPatternCreationManager: IndexPatternCreationManager; indexPatternListConfig: IndexPatternListManager; - environmentService: EnvironmentService; constructor() { this.indexPatternCreationManager = new IndexPatternCreationManager(); this.indexPatternListConfig = new IndexPatternListManager(); - this.environmentService = new EnvironmentService(); } public setup({ httpClient }: SetupDependencies) { @@ -40,7 +37,6 @@ export class IndexPatternManagementService { return { creation: creationManagerSetup, list: indexPatternListConfigSetup, - environment: this.environmentService.setup(), }; } diff --git a/src/plugins/index_pattern_management/public/types.ts b/src/plugins/index_pattern_management/public/types.ts index 58a138df633fd3..a61eeb99b25a57 100644 --- a/src/plugins/index_pattern_management/public/types.ts +++ b/src/plugins/index_pattern_management/public/types.ts @@ -33,14 +33,7 @@ export interface IndexPatternManagmentContext { indexPatternFieldEditor: IndexPatternFieldEditorStart; indexPatternManagementStart: IndexPatternManagementStart; setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; - getMlCardState: () => MlCardState; fieldFormatEditors: IndexPatternFieldEditorStart['fieldFormatEditors']; } export type IndexPatternManagmentContextValue = KibanaReactContextValue; - -export enum MlCardState { - HIDDEN, - DISABLED, - ENABLED, -} diff --git a/test/api_integration/apis/index_patterns/fields_api/update_fields/main.ts b/test/api_integration/apis/index_patterns/fields_api/update_fields/main.ts index 3bec22de48ee66..16861f3c28051e 100644 --- a/test/api_integration/apis/index_patterns/fields_api/update_fields/main.ts +++ b/test/api_integration/apis/index_patterns/fields_api/update_fields/main.ts @@ -22,6 +22,7 @@ export default function ({ getService }: FtrProviderContext) { indexPattern = ( await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, index_pattern: { title: basicIndex, }, diff --git a/test/api_integration/apis/index_patterns/index.js b/test/api_integration/apis/index_patterns/index.js index 9c1e1bba0ab9a5..656b4e506fa23d 100644 --- a/test/api_integration/apis/index_patterns/index.js +++ b/test/api_integration/apis/index_patterns/index.js @@ -15,5 +15,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./scripted_fields_crud')); loadTestFile(require.resolve('./fields_api')); loadTestFile(require.resolve('./default_index_pattern')); + loadTestFile(require.resolve('./runtime_fields_crud')); + loadTestFile(require.resolve('./integration')); }); } diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts b/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts index 31c3f13a6e05fb..500a642f60850e 100644 --- a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts +++ b/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts @@ -117,7 +117,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response.body.index_pattern.fields.bar.type).to.be('boolean'); }); - it('Can add scripted fields, other fields created from es index', async () => { + it('can add scripted fields, other fields created from es index', async () => { const title = `basic_index*`; const response = await supertest.post('/api/index_patterns/index_pattern').send({ override: true, @@ -159,6 +159,32 @@ export default function ({ getService }: FtrProviderContext) { expect(response.body.index_pattern.fields.bar.esTypes[0]).to.be('test-type'); expect(response.body.index_pattern.fields.bar.scripted).to.be(true); }); + + it('can add runtime fields', async () => { + const title = `basic_index*`; + const response = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: 'emit(doc["foo"].value)', + }, + }, + }, + }, + }); + + expect(response.status).to.be(200); + expect(response.body.index_pattern.title).to.be(title); + + expect(response.body.index_pattern.runtimeFieldMap.runtimeFoo.type).to.be('keyword'); + expect(response.body.index_pattern.runtimeFieldMap.runtimeFoo.script.source).to.be( + 'emit(doc["foo"].value)' + ); + }); }); it('can specify optional typeMeta attribute when creating an index pattern', async () => { diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts b/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts index 2f62ea231b7229..598001644eedbe 100644 --- a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts +++ b/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts @@ -64,5 +64,25 @@ export default function ({ getService }: FtrProviderContext) { '[request body.refresh_fields]: expected value of type [boolean] but got [number]' ); }); + + it('returns an error when unknown runtime field type', async () => { + const title = `basic_index*`; + const response = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'wrong-type', + script: { + source: 'emit(doc["foo"].value)', + }, + }, + }, + }, + }); + + expect(response.status).to.be(400); + }); }); } diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts b/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts index cd34724e6cda35..7532278d7eb128 100644 --- a/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts +++ b/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts @@ -284,5 +284,53 @@ export default function ({ getService }: FtrProviderContext) { expect(response3.body.index_pattern.intervalName).to.be('intervalName2'); expect(response3.body.index_pattern.typeMeta.baz).to.be('qux'); }); + + it('can update runtime fields', async () => { + const title = `basic_index*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: 'emit(doc["foo"].value)', + }, + }, + }, + }, + }); + + expect(response1.status).to.be(200); + expect(response1.body.index_pattern.title).to.be(title); + + expect(response1.body.index_pattern.runtimeFieldMap.runtimeFoo.type).to.be('keyword'); + expect(response1.body.index_pattern.runtimeFieldMap.runtimeFoo.script.source).to.be( + 'emit(doc["foo"].value)' + ); + + const id = response1.body.index_pattern.id; + const response2 = await supertest.post('/api/index_patterns/index_pattern/' + id).send({ + index_pattern: { + runtimeFieldMap: { + runtimeBar: { + type: 'keyword', + script: { + source: 'emit(doc["foo"].value)', + }, + }, + }, + }, + }); + + expect(response2.body.index_pattern.runtimeFieldMap.runtimeBar.type).to.be('keyword'); + expect(response2.body.index_pattern.runtimeFieldMap.runtimeFoo).to.be(undefined); + + const response3 = await supertest.get('/api/index_patterns/index_pattern/' + id); + + expect(response3.body.index_pattern.runtimeFieldMap.runtimeBar.type).to.be('keyword'); + expect(response3.body.index_pattern.runtimeFieldMap.runtimeFoo).to.be(undefined); + }); }); } diff --git a/test/api_integration/apis/index_patterns/integration/index.ts b/test/api_integration/apis/index_patterns/integration/index.ts new file mode 100644 index 00000000000000..6fd5f644ae8949 --- /dev/null +++ b/test/api_integration/apis/index_patterns/integration/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +/** + * Test usage of different index patterns APIs in combination + */ +export default function ({ loadTestFile }: FtrProviderContext) { + describe('integration', () => { + loadTestFile(require.resolve('./integration')); + }); +} diff --git a/test/api_integration/apis/index_patterns/integration/integration.ts b/test/api_integration/apis/index_patterns/integration/integration.ts new file mode 100644 index 00000000000000..22f07553733235 --- /dev/null +++ b/test/api_integration/apis/index_patterns/integration/integration.ts @@ -0,0 +1,119 @@ +/* + * 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 expect from '@kbn/expect'; +import _ from 'lodash'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +/** + * Test usage of different index patterns APIs in combination + */ +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('integration', () => { + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + }); + + it('create an index pattern, add a runtime field, add a field formatter, then re-create the same index pattern', async () => { + const title = `basic_index*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + }, + }); + const id = response1.body.index_pattern.id; + const response2 = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field`) + .send({ + name: 'runtimeBar', + runtimeField: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }); + + expect(response2.status).to.be(200); + + const response3 = await supertest + .post(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/fields`) + .send({ + fields: { + runtimeBar: { + count: 123, + customLabel: 'test', + }, + }, + }); + + expect(response3.status).to.be(200); + + const response4 = await supertest + .post(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/fields`) + .send({ + fields: { + runtimeBar: { + format: { + id: 'duration', + params: { inputFormat: 'milliseconds', outputFormat: 'humanizePrecise' }, + }, + }, + }, + }); + + expect(response4.status).to.be(200); + + const response5 = await supertest.get( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + + expect(response5.status).to.be(200); + + const resultIndexPattern = response5.body.index_pattern; + + const runtimeField = resultIndexPattern.fields.runtimeBar; + expect(runtimeField.name).to.be('runtimeBar'); + expect(runtimeField.runtimeField.type).to.be('long'); + expect(runtimeField.runtimeField.script.source).to.be("emit(doc['field_name'].value)"); + expect(runtimeField.scripted).to.be(false); + + expect(resultIndexPattern.fieldFormats.runtimeBar.id).to.be('duration'); + expect(resultIndexPattern.fieldFormats.runtimeBar.params.inputFormat).to.be('milliseconds'); + expect(resultIndexPattern.fieldFormats.runtimeBar.params.outputFormat).to.be( + 'humanizePrecise' + ); + + expect(resultIndexPattern.fieldAttrs.runtimeBar.count).to.be(123); + expect(resultIndexPattern.fieldAttrs.runtimeBar.customLabel).to.be('test'); + + // check that retrieved object is transient and a clone can be created + const response6 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: resultIndexPattern, + }); + + expect(response6.status).to.be(200); + const recreatedIndexPattern = response6.body.index_pattern; + + expect(_.omit(recreatedIndexPattern, 'version')).to.eql( + _.omit(resultIndexPattern, 'version') + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/errors.ts new file mode 100644 index 00000000000000..8ce9e3b36b5c8d --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/errors.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('errors', () => { + it('returns an error field object is not provided', async () => { + const title = `foo-${Date.now()}-${Math.random()}*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + index_pattern: { + title, + }, + }); + const id = response1.body.index_pattern.id; + const response2 = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field`) + .send({}); + + expect(response2.status).to.be(400); + expect(response2.body.statusCode).to.be(400); + expect(response2.body.message).to.be( + '[request body.name]: expected value of type [string] but got [undefined]' + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/index.ts new file mode 100644 index 00000000000000..2cb90ca087f497 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('create_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/main.ts new file mode 100644 index 00000000000000..e262b9d838e97a --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/main.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + }); + + it('can create a new runtime field', async () => { + const title = `basic_index*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + }, + }); + const id = response1.body.index_pattern.id; + const response2 = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field`) + .send({ + name: 'runtimeBar', + runtimeField: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }); + + expect(response2.status).to.be(200); + expect(response2.body.field.name).to.be('runtimeBar'); + expect(response2.body.field.runtimeField.type).to.be('long'); + expect(response2.body.field.runtimeField.script.source).to.be( + "emit(doc['field_name'].value)" + ); + expect(response2.body.field.scripted).to.be(false); + }); + + it('newly created runtime field is available in the index_pattern object', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + }, + }); + + await supertest + .post(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field`) + .send({ + name: 'runtimeBar', + runtimeField: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }); + + const response2 = await supertest.get( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + + expect(response2.status).to.be(200); + + const field = response2.body.index_pattern.fields.runtimeBar; + + expect(field.name).to.be('runtimeBar'); + expect(field.runtimeField.type).to.be('long'); + expect(field.runtimeField.script.source).to.be("emit(doc['field_name'].value)"); + expect(field.scripted).to.be(false); + await supertest.delete( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/errors.ts new file mode 100644 index 00000000000000..b41a630889ff8a --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/errors.ts @@ -0,0 +1,81 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('errors', () => { + const basicIndex = 'b*sic_index'; + let indexPattern: any; + + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + + indexPattern = ( + await supertest.post('/api/index_patterns/index_pattern').send({ + index_pattern: { + title: basicIndex, + }, + }) + ).body.index_pattern; + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + + if (indexPattern) { + await supertest.delete('/api/index_patterns/index_pattern/' + indexPattern.id); + } + }); + + it('returns 404 error on non-existing index_pattern', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest.delete( + `/api/index_patterns/index_pattern/${id}/runtime_field/foo` + ); + + expect(response.status).to.be(404); + }); + + it('returns 404 error on non-existing runtime field', async () => { + const response1 = await supertest.delete( + `/api/index_patterns/index_pattern/${indexPattern.id}/runtime_field/test` + ); + + expect(response1.status).to.be(404); + }); + + it('returns error when attempting to delete a field which is not a runtime field', async () => { + const response2 = await supertest.delete( + `/api/index_patterns/index_pattern/${indexPattern.id}/runtime_field/foo` + ); + + expect(response2.status).to.be(400); + expect(response2.body.statusCode).to.be(400); + expect(response2.body.message).to.be('Only runtime fields can be deleted.'); + }); + + it('returns error when ID is too long', async () => { + const id = `xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx`; + const response = await supertest.delete( + `/api/index_patterns/index_pattern/${id}/runtime_field/foo` + ); + + expect(response.status).to.be(400); + expect(response.body.message).to.be( + '[request params.id]: value has length [1759] but it must have a maximum length of [1000].' + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/index.ts new file mode 100644 index 00000000000000..a14201e750ddaf --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('delete_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/main.ts new file mode 100644 index 00000000000000..3c74aa336e440c --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/main.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + }); + + it('can delete a runtime field', async () => { + const title = `basic_index*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeBar: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }, + }, + }); + + const response2 = await supertest.get( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + + expect(typeof response2.body.index_pattern.fields.runtimeBar).to.be('object'); + + const response3 = await supertest.delete( + `/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field/runtimeBar` + ); + + expect(response3.status).to.be(200); + + const response4 = await supertest.get( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + + expect(typeof response4.body.index_pattern.fields.runtimeBar).to.be('undefined'); + await supertest.delete( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/errors.ts new file mode 100644 index 00000000000000..3608089e4641a2 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/errors.ts @@ -0,0 +1,81 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('errors', () => { + const basicIndex = '*asic_index'; + let indexPattern: any; + + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + + indexPattern = ( + await supertest.post('/api/index_patterns/index_pattern').send({ + index_pattern: { + title: basicIndex, + }, + }) + ).body.index_pattern; + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + + if (indexPattern) { + await supertest.delete('/api/index_patterns/index_pattern/' + indexPattern.id); + } + }); + + it('returns 404 error on non-existing index_pattern', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest.get( + `/api/index_patterns/index_pattern/${id}/runtime_field/foo` + ); + + expect(response.status).to.be(404); + }); + + it('returns 404 error on non-existing runtime field', async () => { + const response1 = await supertest.get( + `/api/index_patterns/index_pattern/${indexPattern.id}/runtime_field/sf` + ); + + expect(response1.status).to.be(404); + }); + + it('returns error when ID is too long', async () => { + const id = `xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx`; + const response = await supertest.get( + `/api/index_patterns/index_pattern/${id}/runtime_field/foo` + ); + + expect(response.status).to.be(400); + expect(response.body.message).to.be( + '[request params.id]: value has length [1759] but it must have a maximum length of [1000].' + ); + }); + + it('returns error when attempting to fetch a field which is not a runtime field', async () => { + const response2 = await supertest.get( + `/api/index_patterns/index_pattern/${indexPattern.id}/runtime_field/foo` + ); + + expect(response2.status).to.be(400); + expect(response2.body.statusCode).to.be(400); + expect(response2.body.message).to.be('Only runtime fields can be retrieved.'); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/index.ts new file mode 100644 index 00000000000000..2e48ba64841eeb --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('get_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/main.ts new file mode 100644 index 00000000000000..fa0283d69d8e3b --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/main.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 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + }); + + it('can fetch a runtime field', async () => { + const title = `basic_index*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + runtimeBar: { + type: 'keyword', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }, + }, + }); + + expect(response1.status).to.be(200); + + const response2 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeFoo' + ); + + expect(response2.status).to.be(200); + expect(typeof response2.body.field).to.be('object'); + expect(response2.body.field.name).to.be('runtimeFoo'); + expect(response2.body.field.type).to.be('string'); + expect(response2.body.field.scripted).to.be(false); + expect(response2.body.field.runtimeField.script.source).to.be( + "emit(doc['field_name'].value)" + ); + await supertest.delete( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/index.ts new file mode 100644 index 00000000000000..7a727a3e867550 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('runtime_fields_crud', () => { + loadTestFile(require.resolve('./create_runtime_field')); + loadTestFile(require.resolve('./get_runtime_field')); + loadTestFile(require.resolve('./delete_runtime_field')); + loadTestFile(require.resolve('./put_runtime_field')); + loadTestFile(require.resolve('./update_runtime_field')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/errors.ts new file mode 100644 index 00000000000000..9faca08238033e --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/errors.ts @@ -0,0 +1,69 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('errors', () => { + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + }); + + it('returns 404 error on non-existing index_pattern', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest + .put(`/api/index_patterns/index_pattern/${id}/runtime_field`) + .send({ + name: 'runtimeBar', + runtimeField: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }); + + expect(response.status).to.be(404); + }); + + it('returns error on non-runtime field update attempt', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + }, + }); + + const response2 = await supertest + .put(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field`) + .send({ + name: 'bar', + runtimeField: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }); + + expect(response2.status).to.be(400); + expect(response2.body.message).to.be('Only runtime fields can be updated'); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/index.ts new file mode 100644 index 00000000000000..724f18e57f3efd --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('put_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/main.ts new file mode 100644 index 00000000000000..92d8c6fd6d3c25 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/main.ts @@ -0,0 +1,122 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + }); + + it('can overwrite an existing field', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + runtimeBar: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + }, + }, + }); + + const response2 = await supertest + .put(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field`) + .send({ + name: 'runtimeFoo', + runtimeField: { + type: 'long', + script: { + source: "doc['field_name'].value", + }, + }, + }); + + expect(response2.status).to.be(200); + + const response3 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeFoo' + ); + + expect(response3.status).to.be(200); + expect(response3.body.field.type).to.be('number'); + + const response4 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeBar' + ); + + expect(response4.status).to.be(200); + expect(response4.body.field.type).to.be('string'); + }); + + it('can add a new runtime field', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + }, + }, + }); + + await supertest + .put(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field`) + .send({ + name: 'runtimeBar', + runtimeField: { + type: 'long', + script: { + source: "doc['field_name'].value", + }, + }, + }); + + const response2 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeBar' + ); + + expect(response2.status).to.be(200); + expect(typeof response2.body.field.runtimeField).to.be('object'); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/errors.ts new file mode 100644 index 00000000000000..3980821c0fd096 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/errors.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('errors', () => { + it('returns 404 error on non-existing index_pattern', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field/foo`) + .send({ + runtimeField: { + script: { + source: "doc['something_new'].value", + }, + }, + }); + + expect(response.status).to.be(404); + }); + + it('returns error when field name is specified', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field/foo`) + .send({ + name: 'foo', + runtimeField: { + script: { + source: "doc['something_new'].value", + }, + }, + }); + + expect(response.status).to.be(400); + expect(response.body.statusCode).to.be(400); + expect(response.body.message).to.be( + "[request body.name]: a value wasn't expected to be present" + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/index.ts new file mode 100644 index 00000000000000..f5d556ca9994ab --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('update_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/main.ts new file mode 100644 index 00000000000000..6b924570a0e45b --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/main.ts @@ -0,0 +1,76 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + }); + + it('can update an existing field', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + runtimeBar: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + }, + }, + }); + + const response2 = await supertest + .post( + `/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field/runtimeFoo` + ) + .send({ + runtimeField: { + script: { + source: "doc['something_new'].value", + }, + }, + }); + + expect(response2.status).to.be(200); + + const response3 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeFoo' + ); + + expect(response3.status).to.be(200); + expect(response3.body.field.type).to.be('string'); + expect(response3.body.field.runtimeField.type).to.be('keyword'); + expect(response3.body.field.runtimeField.script.source).to.be("doc['something_new'].value"); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/main.ts b/test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/main.ts index 1210e7247f72dd..663deae1d34769 100644 --- a/test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/main.ts +++ b/test/api_integration/apis/index_patterns/scripted_fields_crud/create_scripted_field/main.ts @@ -54,6 +54,7 @@ export default function ({ getService }: FtrProviderContext) { it('newly created scripted field is materialized in the index_pattern object', async () => { const title = `basic_index`; const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, index_pattern: { title, }, diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/main.ts b/test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/main.ts index 466af26f6e1272..b9ce3e84d53904 100644 --- a/test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/main.ts +++ b/test/api_integration/apis/index_patterns/scripted_fields_crud/delete_scripted_field/main.ts @@ -27,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) { it('can remove a scripted field', async () => { const title = `basic_index`; const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, index_pattern: { title, fields: { diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/main.ts b/test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/main.ts index 64d909480260e5..c38c3a01708a74 100644 --- a/test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/main.ts +++ b/test/api_integration/apis/index_patterns/scripted_fields_crud/get_scripted_field/main.ts @@ -27,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) { it('can fetch a scripted field', async () => { const title = `basic_index`; const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, index_pattern: { title, fields: { diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/main.ts b/test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/main.ts index 96f5924e7c132f..16b19583068187 100644 --- a/test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/main.ts +++ b/test/api_integration/apis/index_patterns/scripted_fields_crud/put_scripted_field/main.ts @@ -27,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) { it('can overwrite an existing field', async () => { const title = `basic_index`; const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, index_pattern: { title, fields: { diff --git a/test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/main.ts b/test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/main.ts index 055b3fe9abe04d..4ffc98e0660bca 100644 --- a/test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/main.ts +++ b/test/api_integration/apis/index_patterns/scripted_fields_crud/update_scripted_field/main.ts @@ -27,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) { it('can update an existing field', async () => { const title = `basic_index`; const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, index_pattern: { title, fields: { diff --git a/test/api_integration/apis/kql_telemetry/kql_telemetry.ts b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts index 09e36b9078792a..5770ed0866a900 100644 --- a/test/api_integration/apis/kql_telemetry/kql_telemetry.ts +++ b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts @@ -17,8 +17,16 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('es'); describe('telemetry API', () => { - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should increment the opt *in* counter in the .kibana/kql-telemetry document', async () => { await supertest diff --git a/test/api_integration/apis/saved_objects/bulk_create.ts b/test/api_integration/apis/saved_objects/bulk_create.ts index 1f76567e973b22..5867b8125303a5 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.ts +++ b/test/api_integration/apis/saved_objects/bulk_create.ts @@ -39,7 +39,10 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { KIBANA_VERSION = await getKibanaVersion(getService); await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_ID }); - await kibanaServer.importExport.load('saved_objects/basic', { space: SPACE_ID }); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json', + { space: SPACE_ID } + ); }); after(() => kibanaServer.spaces.delete(SPACE_ID)); diff --git a/test/api_integration/apis/saved_objects/bulk_get.ts b/test/api_integration/apis/saved_objects/bulk_get.ts index 81e86913aaf86f..e349482960678b 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.ts +++ b/test/api_integration/apis/saved_objects/bulk_get.ts @@ -34,10 +34,16 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { KIBANA_VERSION = await getKibanaVersion(getService); - await kibanaServer.importExport.load('saved_objects/basic'); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); }); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should return 200 with individual responses', async () => await supertest diff --git a/test/api_integration/apis/saved_objects/bulk_update.ts b/test/api_integration/apis/saved_objects/bulk_update.ts index 8740652fc39538..cf402bf2f62217 100644 --- a/test/api_integration/apis/saved_objects/bulk_update.ts +++ b/test/api_integration/apis/saved_objects/bulk_update.ts @@ -15,8 +15,16 @@ export default function ({ getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); describe('bulkUpdate', () => { - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should return 200', async () => { const response = await supertest diff --git a/test/api_integration/apis/saved_objects/create.ts b/test/api_integration/apis/saved_objects/create.ts index dfa7ceb503dfd4..00018e47c9dd36 100644 --- a/test/api_integration/apis/saved_objects/create.ts +++ b/test/api_integration/apis/saved_objects/create.ts @@ -19,10 +19,16 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { KIBANA_VERSION = await getKibanaVersion(getService); - await kibanaServer.importExport.load('saved_objects/basic'); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); }); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should return 200', async () => { await supertest diff --git a/test/api_integration/apis/saved_objects/delete.ts b/test/api_integration/apis/saved_objects/delete.ts index 9a4525df1b5f75..fc38050deabffe 100644 --- a/test/api_integration/apis/saved_objects/delete.ts +++ b/test/api_integration/apis/saved_objects/delete.ts @@ -14,8 +14,16 @@ export default function ({ getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); describe('delete', () => { - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should return 200 when deleting a doc', async () => await supertest diff --git a/test/api_integration/apis/saved_objects/export.ts b/test/api_integration/apis/saved_objects/export.ts index c42afd4e773c53..6314fbbe675d0e 100644 --- a/test/api_integration/apis/saved_objects/export.ts +++ b/test/api_integration/apis/saved_objects/export.ts @@ -24,7 +24,10 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { KIBANA_VERSION = await getKibanaVersion(getService); await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_ID }); - await kibanaServer.importExport.load('saved_objects/basic', { space: SPACE_ID }); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json', + { space: SPACE_ID } + ); }); after(() => kibanaServer.spaces.delete(SPACE_ID)); diff --git a/test/api_integration/apis/saved_objects/find.ts b/test/api_integration/apis/saved_objects/find.ts index 02fa2d325f1713..a38043c7c93524 100644 --- a/test/api_integration/apis/saved_objects/find.ts +++ b/test/api_integration/apis/saved_objects/find.ts @@ -18,12 +18,18 @@ export default function ({ getService }: FtrProviderContext) { describe('find', () => { before(async () => { await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_ID }); - await kibanaServer.importExport.load('saved_objects/basic', { space: SPACE_ID }); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json', + { space: SPACE_ID } + ); await kibanaServer.spaces.create({ id: `${SPACE_ID}-foo`, name: `${SPACE_ID}-foo` }); - await kibanaServer.importExport.load('saved_objects/basic/foo-ns', { - space: `${SPACE_ID}-foo`, - }); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic/foo-ns.json', + { + space: `${SPACE_ID}-foo`, + } + ); }); after(async () => { @@ -255,10 +261,18 @@ export default function ({ getService }: FtrProviderContext) { }); describe('`has_reference` and `has_reference_operator` parameters', () => { - before(() => kibanaServer.importExport.load('saved_objects/references', { space: SPACE_ID })); - after(() => - kibanaServer.importExport.unload('saved_objects/references', { space: SPACE_ID }) - ); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json', + { space: SPACE_ID } + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json', + { space: SPACE_ID } + ); + }); it('search for a reference', async () => { await supertest @@ -319,12 +333,18 @@ export default function ({ getService }: FtrProviderContext) { }); describe('searching for special characters', () => { - before(() => - kibanaServer.importExport.load('saved_objects/find_edgecases', { space: SPACE_ID }) - ); - after(() => - kibanaServer.importExport.unload('saved_objects/find_edgecases', { space: SPACE_ID }) - ); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/find_edgecases.json', + { space: SPACE_ID } + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/find_edgecases.json', + { space: SPACE_ID } + ); + }); it('can search for objects with dashes', async () => await supertest diff --git a/test/api_integration/apis/saved_objects/get.ts b/test/api_integration/apis/saved_objects/get.ts index 77d7b4faacb414..8122308e449303 100644 --- a/test/api_integration/apis/saved_objects/get.ts +++ b/test/api_integration/apis/saved_objects/get.ts @@ -19,9 +19,15 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { KIBANA_VERSION = await getKibanaVersion(getService); - await kibanaServer.importExport.load('saved_objects/basic'); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); }); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); it('should return 200', async () => await supertest diff --git a/test/api_integration/apis/saved_objects/import.ts b/test/api_integration/apis/saved_objects/import.ts index 8d3c0b7bbcea3f..c899f082ec4d3f 100644 --- a/test/api_integration/apis/saved_objects/import.ts +++ b/test/api_integration/apis/saved_objects/import.ts @@ -43,8 +43,16 @@ export default function ({ getService }: FtrProviderContext) { }; describe('with basic data existing', () => { - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should return 415 when no file passed in', async () => { await supertest diff --git a/test/api_integration/apis/saved_objects/resolve.ts b/test/api_integration/apis/saved_objects/resolve.ts index fcfef0aeb6b582..a00a44f98223d0 100644 --- a/test/api_integration/apis/saved_objects/resolve.ts +++ b/test/api_integration/apis/saved_objects/resolve.ts @@ -22,8 +22,16 @@ export default function ({ getService }: FtrProviderContext) { }); describe('with kibana index', () => { - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should return 200', async () => await supertest diff --git a/test/api_integration/apis/saved_objects/resolve_import_errors.ts b/test/api_integration/apis/saved_objects/resolve_import_errors.ts index 43ff01d321f9fa..7ca61a26a11c1e 100644 --- a/test/api_integration/apis/saved_objects/resolve_import_errors.ts +++ b/test/api_integration/apis/saved_objects/resolve_import_errors.ts @@ -36,7 +36,10 @@ export default function ({ getService }: FtrProviderContext) { describe('with basic data existing', () => { before(async () => { await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_ID }); - await kibanaServer.importExport.load('saved_objects/basic', { space: SPACE_ID }); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json', + { space: SPACE_ID } + ); }); after(() => kibanaServer.spaces.delete(SPACE_ID)); diff --git a/test/api_integration/apis/saved_objects/update.ts b/test/api_integration/apis/saved_objects/update.ts index 75b8651ee64a7c..1c73d0788e51ca 100644 --- a/test/api_integration/apis/saved_objects/update.ts +++ b/test/api_integration/apis/saved_objects/update.ts @@ -14,8 +14,16 @@ export default function ({ getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); describe('update', () => { - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should return 200', async () => { await supertest .put(`/api/saved_objects/visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab`) diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index 6aefd74f0f36b2..6e36303cc1fe07 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -25,8 +25,16 @@ export default function ({ getService }: FtrProviderContext) { }); describe('with kibana index', () => { - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should return 200 with individual responses', async () => await supertest @@ -85,8 +93,16 @@ export default function ({ getService }: FtrProviderContext) { }); describe('`hasReference` and `hasReferenceOperator` parameters', () => { - before(() => kibanaServer.importExport.load('saved_objects/references')); - after(() => kibanaServer.importExport.unload('saved_objects/references')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json' + ); + }); it('search for a reference', async () => { await supertest diff --git a/test/api_integration/apis/saved_objects_management/get.ts b/test/api_integration/apis/saved_objects_management/get.ts index eb0b832cb2ed14..3b49a28ca40223 100644 --- a/test/api_integration/apis/saved_objects_management/get.ts +++ b/test/api_integration/apis/saved_objects_management/get.ts @@ -18,8 +18,16 @@ export default function ({ getService }: FtrProviderContext) { const existingObject = 'visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab'; const nonexistentObject = 'wigwags/foo'; - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('should return 200 for object that exists and inject metadata', async () => await supertest diff --git a/test/api_integration/apis/saved_objects_management/relationships.ts b/test/api_integration/apis/saved_objects_management/relationships.ts index ffc97b13859571..aa488942edbeb7 100644 --- a/test/api_integration/apis/saved_objects_management/relationships.ts +++ b/test/api_integration/apis/saved_objects_management/relationships.ts @@ -43,8 +43,16 @@ export default function ({ getService }: FtrProviderContext) { }); describe('relationships', () => { - before(() => kibanaServer.importExport.load('management/saved_objects/relationships')); - after(() => kibanaServer.importExport.unload('management/saved_objects/relationships')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/management/saved_objects/relationships.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/management/saved_objects/relationships.json' + ); + }); const baseApiUrl = `/api/kibana/management/saved_objects/relationships`; const defaultTypes = ['visualization', 'index-pattern', 'search', 'dashboard']; diff --git a/test/api_integration/apis/shorten/index.js b/test/api_integration/apis/shorten/index.js index 9af979b6af95d7..86c39426205ad3 100644 --- a/test/api_integration/apis/shorten/index.js +++ b/test/api_integration/apis/shorten/index.js @@ -13,8 +13,16 @@ export default function ({ getService }) { const kibanaServer = getService('kibanaServer'); describe('url shortener', () => { - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); it('generates shortened urls', async () => { const resp = await supertest diff --git a/test/api_integration/apis/stats/stats.js b/test/api_integration/apis/stats/stats.js index 2ba14e29a3d1a8..61936a73da38da 100644 --- a/test/api_integration/apis/stats/stats.js +++ b/test/api_integration/apis/stats/stats.js @@ -47,8 +47,16 @@ export default function ({ getService }) { const kibanaServer = getService('kibanaServer'); describe('kibana stats api', () => { - before(() => kibanaServer.importExport.load('saved_objects/basic')); - after(() => kibanaServer.importExport.unload('saved_objects/basic')); + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); describe('basic', () => { it('should return the stats without cluster_uuid with no query string params', () => { diff --git a/test/api_integration/apis/suggestions/suggestions.js b/test/api_integration/apis/suggestions/suggestions.js index 526cb9669f2788..292e3f599d81a1 100644 --- a/test/api_integration/apis/suggestions/suggestions.js +++ b/test/api_integration/apis/suggestions/suggestions.js @@ -14,13 +14,17 @@ export default function ({ getService }) { describe('Suggestions API', function () { before(async () => { await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); - await kibanaServer.importExport.load('index_patterns/basic_kibana'); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/index_patterns/basic_kibana.json' + ); }); after(async () => { await esArchiver.unload( 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' ); - await kibanaServer.importExport.unload('index_patterns/basic_kibana'); + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/index_patterns/basic_kibana.json' + ); }); it('should return 200 with special characters', () => diff --git a/test/common/services/kibana_server/kibana_server.ts b/test/common/services/kibana_server/kibana_server.ts index 63803bd511bd14..f20fa4cafa55e2 100644 --- a/test/common/services/kibana_server/kibana_server.ts +++ b/test/common/services/kibana_server/kibana_server.ts @@ -22,7 +22,6 @@ export function KibanaServerProvider({ getService }: FtrProviderContext): KbnCli url, certificateAuthorities: config.get('servers.kibana.certificateAuthorities'), uiSettingDefaults: defaults, - importExportDir: config.get('kbnArchiver.directory'), }); if (defaults) { diff --git a/test/examples/config.js b/test/examples/config.js index cb6c487c564c3b..d47748e5f22a97 100644 --- a/test/examples/config.js +++ b/test/examples/config.js @@ -28,6 +28,7 @@ export default async function ({ readConfigFile }) { require.resolve('./state_sync'), require.resolve('./routing'), require.resolve('./expressions_explorer'), + require.resolve('./index_pattern_field_editor_example'), ], services: { ...functionalConfig.get('services'), diff --git a/test/examples/index_pattern_field_editor_example/index.ts b/test/examples/index_pattern_field_editor_example/index.ts new file mode 100644 index 00000000000000..0cd23a33c84762 --- /dev/null +++ b/test/examples/index_pattern_field_editor_example/index.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { PluginFunctionalProviderContext } from 'test/plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ + getService, + getPageObjects, + loadTestFile, +}: PluginFunctionalProviderContext) { + const browser = getService('browser'); + const es = getService('es'); + const PageObjects = getPageObjects(['common', 'header', 'settings']); + + describe('index pattern field editor example', function () { + this.tags('ciGroup2'); + before(async () => { + await browser.setWindowSize(1300, 900); + await es.transport.request({ + path: '/blogs/_doc', + method: 'POST', + body: { user: 'matt', message: 20 }, + }); + + await PageObjects.settings.navigateTo(); + await PageObjects.settings.createIndexPattern('blogs', null); + await PageObjects.common.navigateToApp('indexPatternFieldEditorExample'); + }); + + loadTestFile(require.resolve('./index_pattern_field_editor_example')); + }); +} diff --git a/test/examples/index_pattern_field_editor_example/index_pattern_field_editor_example.ts b/test/examples/index_pattern_field_editor_example/index_pattern_field_editor_example.ts new file mode 100644 index 00000000000000..5744c8e64f5c11 --- /dev/null +++ b/test/examples/index_pattern_field_editor_example/index_pattern_field_editor_example.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 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 { PluginFunctionalProviderContext } from 'test/plugin_functional/services'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: PluginFunctionalProviderContext) { + const testSubjects = getService('testSubjects'); + + describe('', () => { + it('finds an index pattern', async () => { + await testSubjects.existOrFail('indexPatternTitle'); + }); + it('opens the field editor', async () => { + await testSubjects.click('addField'); + await testSubjects.existOrFail('flyoutTitle'); + }); + }); +} diff --git a/test/functional/apps/discover/_data_grid.ts b/test/functional/apps/discover/_data_grid.ts index eaefb3ba38f050..efd97fce3f7f5d 100644 --- a/test/functional/apps/discover/_data_grid.ts +++ b/test/functional/apps/discover/_data_grid.ts @@ -24,7 +24,7 @@ export default function ({ before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await kibanaServer.uiSettings.replace(defaultSettings); await PageObjects.common.navigateToApp('discover'); diff --git a/test/functional/apps/discover/_data_grid_context.ts b/test/functional/apps/discover/_data_grid_context.ts index bfce69ca08d552..3d9e01e1dee19d 100644 --- a/test/functional/apps/discover/_data_grid_context.ts +++ b/test/functional/apps/discover/_data_grid_context.ts @@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('discover data grid context tests', () => { before(async () => { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await kibanaServer.uiSettings.update(defaultSettings); diff --git a/test/functional/apps/discover/_data_grid_doc_table.ts b/test/functional/apps/discover/_data_grid_doc_table.ts index bbd633df6fa1ed..00e6a5025e2af6 100644 --- a/test/functional/apps/discover/_data_grid_doc_table.ts +++ b/test/functional/apps/discover/_data_grid_doc_table.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('load kibana index with default index pattern'); await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await kibanaServer.uiSettings.replace(defaultSettings); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts index 8296b518bee32c..94e8e942f86ba2 100644 --- a/test/functional/apps/discover/_data_grid_field_data.ts +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await kibanaServer.uiSettings.update(defaultSettings); diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index f26d1f4536e558..dce6bfba9cd99c 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('load kibana index with default index pattern'); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); // and load a set of makelogs data await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); diff --git a/test/functional/apps/discover/_discover_fields_api.ts b/test/functional/apps/discover/_discover_fields_api.ts index d3df79dc8658b6..614a0794ffb3b2 100644 --- a/test/functional/apps/discover/_discover_fields_api.ts +++ b/test/functional/apps/discover/_discover_fields_api.ts @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('load kibana index with default index pattern'); await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await kibanaServer.uiSettings.replace(defaultSettings); log.debug('discover'); diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index ffe989f8d498ce..b5a40030b26016 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('load kibana index with default index pattern'); await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); // and load a set of makelogs data await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index e8698b984f6cc0..338d17ba31ff49 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -24,7 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', diff --git a/test/functional/apps/discover/_field_data_with_fields_api.ts b/test/functional/apps/discover/_field_data_with_fields_api.ts index 7de123bb44f2a6..110e255d18c756 100644 --- a/test/functional/apps/discover/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/_field_data_with_fields_api.ts @@ -24,7 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', diff --git a/test/functional/apps/discover/_filter_editor.ts b/test/functional/apps/discover/_filter_editor.ts index 4e9839d4e799ae..8bcb4382bb3bf3 100644 --- a/test/functional/apps/discover/_filter_editor.ts +++ b/test/functional/apps/discover/_filter_editor.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('load kibana index with default index pattern'); await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); // and load a set of makelogs data await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); diff --git a/test/functional/apps/discover/_inspector.ts b/test/functional/apps/discover/_inspector.ts index 9d4fd93eb3a9f0..17f358ec748714 100644 --- a/test/functional/apps/discover/_inspector.ts +++ b/test/functional/apps/discover/_inspector.ts @@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); // delete .kibana index and update configDoc await kibanaServer.uiSettings.replace({ diff --git a/test/functional/apps/discover/_large_string.ts b/test/functional/apps/discover/_large_string.ts index 3e426c6237d89b..de3f0f2c40ae10 100644 --- a/test/functional/apps/discover/_large_string.ts +++ b/test/functional/apps/discover/_large_string.ts @@ -23,7 +23,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_large_strings']); - await kibanaServer.importExport.load('testlargestring'); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/testlargestring.json' + ); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/hamlet'); await kibanaServer.uiSettings.replace({ defaultIndex: 'testlargestring' }); }); diff --git a/test/functional/apps/discover/_saved_queries.ts b/test/functional/apps/discover/_saved_queries.ts index 67806c9d16a940..20f2cab907d9bf 100644 --- a/test/functional/apps/discover/_saved_queries.ts +++ b/test/functional/apps/discover/_saved_queries.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('load kibana index with default index pattern'); await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); // and load a set of makelogs data await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); diff --git a/test/functional/apps/discover/_shared_links.ts b/test/functional/apps/discover/_shared_links.ts index 151e8555eea77e..62364739db311b 100644 --- a/test/functional/apps/discover/_shared_links.ts +++ b/test/functional/apps/discover/_shared_links.ts @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('load kibana index with default index pattern'); await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); - await kibanaServer.importExport.load('discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await kibanaServer.uiSettings.replace({ diff --git a/test/functional/apps/visualize/legacy/index.ts b/test/functional/apps/visualize/legacy/index.ts index 4e43ec90d16999..d474287860b25e 100644 --- a/test/functional/apps/visualize/legacy/index.ts +++ b/test/functional/apps/visualize/legacy/index.ts @@ -23,7 +23,7 @@ export default function ({ getPageObjects, getService, loadTestFile }: FtrProvid await browser.setWindowSize(1280, 800); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/long_window_logstash'); - await kibanaServer.importExport.load('visualize'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/visualize.json'); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', [UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b', diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index f77db553e015f9..a11a254509e7a8 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -49,7 +49,9 @@ export class VisualizePageObject extends FtrService { public async initTests(isNewLibrary = false) { await this.kibanaServer.savedObjects.clean({ types: ['visualization'] }); - await this.kibanaServer.importExport.load('visualize'); + await this.kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/visualize.json' + ); await this.kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', diff --git a/x-pack/examples/reporting_example/public/components/app.tsx b/x-pack/examples/reporting_example/public/components/app.tsx index 0174ec2a17ad4d..5f6f5d17afafba 100644 --- a/x-pack/examples/reporting_example/public/components/app.tsx +++ b/x-pack/examples/reporting_example/public/components/app.tsx @@ -6,8 +6,9 @@ */ import { + EuiButton, EuiCard, - EuiCode, + EuiContextMenu, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, @@ -17,7 +18,7 @@ import { EuiPageContent, EuiPageContentBody, EuiPageHeader, - EuiPanel, + EuiPopover, EuiText, EuiTitle, } from '@elastic/eui'; @@ -50,7 +51,19 @@ export const ReportingExampleApp = ({ reporting, screenshotMode, }: ReportingExampleAppDeps) => { - const { getDefaultLayoutSelectors, ReportingAPIClient } = reporting; + const { getDefaultLayoutSelectors } = reporting; + + // Context Menu + const [isPopoverOpen, setPopover] = useState(false); + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + // Async Logos const [logos, setLogos] = useState([]); useEffect(() => { @@ -61,7 +74,7 @@ export const ReportingExampleApp = ({ }); }); - const getPDFJobParams = (): JobParamsPDF => { + const getPDFJobParamsDefault = (): JobParamsPDF => { return { layout: { id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT, @@ -73,7 +86,40 @@ export const ReportingExampleApp = ({ }; }; - // Render the application DOM. + const panels = [ + { id: 0, items: [{ name: 'PDF Reports', icon: 'document', panel: 1 }] }, + { + id: 1, + initialFocusedItemIndex: 1, + title: 'PDF Reports', + items: [ + { name: 'No Layout Option', icon: 'document', panel: 2 }, + { name: 'Canvas Layout Option', icon: 'canvasApp', panel: 3 }, + ], + }, + { + id: 2, + title: 'No Layout Option', + content: ( + + ), + }, + { + id: 3, + title: 'Canvas Layout Option', + content: ( + + ), + }, + ]; + return ( @@ -87,34 +133,21 @@ export const ReportingExampleApp = ({ -

- Use the ReportingStart.components.ScreenCapturePanel{' '} - component to add the Reporting panel to your page. -

- - +

Example of a Sharing menu using components from Reporting

- - - - - - - + Share} + isOpen={isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + -

- The logos below are in a data-shared-items-container element - for Reporting. -

-
{logos.map((item, index) => ( diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index c157765afb3590..6847b17bcef4b8 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -37,7 +37,7 @@ import { Alert, RecoveredActionGroup } from '../../common'; import { omit } from 'lodash'; import { UntypedNormalizedAlertType } from '../alert_type_registry'; import { alertTypeRegistryMock } from '../alert_type_registry.mock'; -import uuid from 'uuid'; + const alertType: jest.Mocked = { id: 'test', name: 'My test alert', @@ -373,6 +373,8 @@ describe('Task Runner', () => { expect(eventLogger.logEvent).toHaveBeenNthCalledWith(1, { event: { action: 'new-instance', + duration: 0, + start: '1970-01-01T00:00:00.000Z', }, kibana: { alerting: { @@ -395,6 +397,8 @@ describe('Task Runner', () => { expect(eventLogger.logEvent).toHaveBeenNthCalledWith(2, { event: { action: 'active-instance', + duration: 0, + start: '1970-01-01T00:00:00.000Z', }, kibana: { alerting: { @@ -525,6 +529,8 @@ describe('Task Runner', () => { expect(eventLogger.logEvent).toHaveBeenNthCalledWith(1, { event: { action: 'new-instance', + duration: 0, + start: '1970-01-01T00:00:00.000Z', }, kibana: { alerting: { @@ -546,6 +552,8 @@ describe('Task Runner', () => { expect(eventLogger.logEvent).toHaveBeenNthCalledWith(2, { event: { action: 'active-instance', + duration: 0, + start: '1970-01-01T00:00:00.000Z', }, kibana: { alerting: { @@ -669,7 +677,11 @@ describe('Task Runner', () => { meta: { lastScheduledActions: { date: '1970-01-01T00:00:00.000Z', group: 'default' }, }, - state: { bar: false }, + state: { + bar: false, + start: '1969-12-31T00:00:00.000Z', + duration: 86400000000000, + }, }, }, }, @@ -699,6 +711,8 @@ describe('Task Runner', () => { Object { "event": Object { "action": "active-instance", + "duration": 86400000000000, + "start": "1969-12-31T00:00:00.000Z", }, "kibana": Object { "alerting": Object { @@ -924,17 +938,221 @@ describe('Task Runner', () => { const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; expect(eventLogger.logEvent).toHaveBeenCalledTimes(4); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "new-instance", + "duration": 0, + "start": "1970-01-01T00:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' created new instance: '1'", + }, + ], + Array [ + Object { + "event": Object { + "action": "active-instance", + "duration": 0, + "start": "1970-01-01T00:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", + }, + ], + Array [ + Object { + "event": Object { + "action": "execute-action", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "action_subgroup": undefined, + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + Object { + "id": "1", + "namespace": undefined, + "type": "action", + "type_id": "action", + }, + ], + }, + "message": "alert: test:1: 'alert-name' instanceId: '1' scheduled actionGroup: 'default' action: action:1", + }, + ], + Array [ + Object { + "@timestamp": "1970-01-01T00:00:00.000Z", + "event": Object { + "action": "execute", + "outcome": "success", + }, + "kibana": Object { + "alerting": Object { + "status": "active", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + ] + `); + }); + + test('fire recovered actions for execution for the alertInstances which is in the recovered state', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + + alertType.executor.mockImplementation( + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + string + >) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { + meta: {}, + state: { + bar: false, + start: '1969-12-31T00:00:00.000Z', + duration: 80000000000, + }, + }, + '2': { + meta: {}, + state: { + bar: false, + start: '1969-12-31T06:00:00.000Z', + duration: 70000000000, + }, + }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + alertsClient.get.mockResolvedValue(mockedAlertTypeSavedObject); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + const runnerResult = await taskRunner.run(); + expect(runnerResult.state.alertInstances).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "lastScheduledActions": Object { + "date": 1970-01-01T00:00:00.000Z, + "group": "default", + "subgroup": undefined, + }, + }, + "state": Object { + "bar": false, + "duration": 86400000000000, + "start": "1969-12-31T00:00:00.000Z", + }, + }, + } + `); + + const logger = taskRunnerFactoryInitializerParams.logger; + expect(logger.debug).toHaveBeenCalledTimes(4); + expect(logger.debug).nthCalledWith(1, 'executing alert test:1 at 1970-01-01T00:00:00.000Z'); + expect(logger.debug).nthCalledWith( + 2, + `alert test:1: 'alert-name' has 1 active alert instances: [{\"instanceId\":\"1\",\"actionGroup\":\"default\"}]` + ); + expect(logger.debug).nthCalledWith( + 3, + `alert test:1: 'alert-name' has 1 recovered alert instances: [\"2\"]` + ); + expect(logger.debug).nthCalledWith( + 4, + 'alertExecutionStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(5); expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` Array [ Array [ Object { "event": Object { - "action": "new-instance", + "action": "recovered-instance", + "duration": 64800000000000, + "end": "1970-01-01T00:00:00.000Z", + "start": "1969-12-31T06:00:00.000Z", }, "kibana": Object { "alerting": Object { - "action_group_id": "default", - "instance_id": "1", + "instance_id": "2", }, "saved_objects": Array [ Object { @@ -946,13 +1164,15 @@ describe('Task Runner', () => { }, ], }, - "message": "test:1: 'alert-name' created new instance: '1'", + "message": "test:1: 'alert-name' instance '2' has recovered", }, ], Array [ Object { "event": Object { "action": "active-instance", + "duration": 86400000000000, + "start": "1969-12-31T00:00:00.000Z", }, "kibana": Object { "alerting": Object { @@ -972,6 +1192,36 @@ describe('Task Runner', () => { "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", }, ], + Array [ + Object { + "event": Object { + "action": "execute-action", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "recovered", + "action_subgroup": undefined, + "instance_id": "2", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + Object { + "id": "2", + "namespace": undefined, + "type": "action", + "type_id": "action", + }, + ], + }, + "message": "alert: test:1: 'alert-name' instanceId: '2' scheduled actionGroup: 'recovered' action: action:2", + }, + ], Array [ Object { "event": Object { @@ -1028,84 +1278,7 @@ describe('Task Runner', () => { ], ] `); - }); - - test('fire recovered actions for execution for the alertInstances which is in the recovered state', async () => { - taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); - taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); - - alertType.executor.mockImplementation( - async ({ - services: executorServices, - }: AlertExecutorOptions< - AlertTypeParams, - AlertTypeState, - AlertInstanceState, - AlertInstanceContext, - string - >) => { - executorServices.alertInstanceFactory('1').scheduleActions('default'); - } - ); - const taskRunner = new TaskRunner( - alertType, - { - ...mockedTaskInstance, - state: { - ...mockedTaskInstance.state, - alertInstances: { - '1': { meta: {}, state: { bar: false } }, - '2': { meta: {}, state: { bar: false } }, - }, - }, - }, - taskRunnerFactoryInitializerParams - ); - alertsClient.get.mockResolvedValue(mockedAlertTypeSavedObject); - encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - const runnerResult = await taskRunner.run(); - expect(runnerResult.state.alertInstances).toMatchInlineSnapshot(` - Object { - "1": Object { - "meta": Object { - "lastScheduledActions": Object { - "date": 1970-01-01T00:00:00.000Z, - "group": "default", - "subgroup": undefined, - }, - }, - "state": Object { - "bar": false, - }, - }, - } - `); - - const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(4); - expect(logger.debug).nthCalledWith(1, 'executing alert test:1 at 1970-01-01T00:00:00.000Z'); - expect(logger.debug).nthCalledWith( - 2, - `alert test:1: 'alert-name' has 1 active alert instances: [{\"instanceId\":\"1\",\"actionGroup\":\"default\"}]` - ); - expect(logger.debug).nthCalledWith( - 3, - `alert test:1: 'alert-name' has 1 recovered alert instances: [\"2\"]` - ); - expect(logger.debug).nthCalledWith( - 4, - 'alertExecutionStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' - ); - const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; - expect(eventLogger.logEvent).toHaveBeenCalledTimes(5); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(2); expect(actionsClient.enqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -1129,7 +1302,7 @@ describe('Task Runner', () => { }); test('should skip alertInstances which werent active on the previous execution', async () => { - const alertId = uuid.v4(); + const alertId = 'e558aaad-fd81-46d2-96fc-3bd8fc3dc03f'; taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); @@ -1344,11 +1517,19 @@ describe('Task Runner', () => { alertInstances: { '1': { meta: { lastScheduledActions: { group: 'default', date } }, - state: { bar: false }, + state: { + bar: false, + start: '1969-12-31T00:00:00.000Z', + duration: 80000000000, + }, }, '2': { meta: { lastScheduledActions: { group: 'default', date } }, - state: { bar: false }, + state: { + bar: false, + start: '1969-12-31T06:00:00.000Z', + duration: 70000000000, + }, }, }, }, @@ -1377,6 +1558,8 @@ describe('Task Runner', () => { }, "state": Object { "bar": false, + "duration": 86400000000000, + "start": "1969-12-31T00:00:00.000Z", }, }, } @@ -1390,6 +1573,9 @@ describe('Task Runner', () => { Object { "event": Object { "action": "recovered-instance", + "duration": 64800000000000, + "end": "1970-01-01T00:00:00.000Z", + "start": "1969-12-31T06:00:00.000Z", }, "kibana": Object { "alerting": Object { @@ -1413,6 +1599,8 @@ describe('Task Runner', () => { Object { "event": Object { "action": "active-instance", + "duration": 86400000000000, + "start": "1969-12-31T00:00:00.000Z", }, "kibana": Object { "alerting": Object { @@ -2035,9 +2223,759 @@ describe('Task Runner', () => { references: [], }); + const logger = taskRunnerFactoryInitializerParams.logger; return taskRunner.run().catch((ex) => { expect(ex).toMatchInlineSnapshot(`[Error: Saved object [alert/1] not found]`); + expect(logger.debug).toHaveBeenCalledWith( + `Executing Alert "1" has resulted in Error: Saved object [alert/1] not found` + ); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).nthCalledWith( + 1, + `Unable to execute rule "1" because Saved object [alert/1] not found - this rule will not be rescheduled. To restart rule execution, try disabling and re-enabling this rule.` + ); expect(isUnrecoverableError(ex)).toBeTruthy(); }); }); + + test('correctly logs warning when Alert Task Runner throws due to failing to fetch the alert in a space', async () => { + alertsClient.get.mockImplementation(() => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('alert', '1'); + }); + + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + params: { + ...mockedTaskInstance.params, + spaceId: 'test space', + }, + }, + taskRunnerFactoryInitializerParams + ); + + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + const logger = taskRunnerFactoryInitializerParams.logger; + return taskRunner.run().catch((ex) => { + expect(ex).toMatchInlineSnapshot(`[Error: Saved object [alert/1] not found]`); + expect(logger.debug).toHaveBeenCalledWith( + `Executing Alert "1" has resulted in Error: Saved object [alert/1] not found` + ); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).nthCalledWith( + 1, + `Unable to execute rule "1" in the "test space" space because Saved object [alert/1] not found - this rule will not be rescheduled. To restart rule execution, try disabling and re-enabling this rule.` + ); + }); + }); + + test('start time is logged for new alerts', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + alertType.executor.mockImplementation( + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + string + >) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + executorServices.alertInstanceFactory('2').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: {}, + }, + }, + taskRunnerFactoryInitializerParams + ); + alertsClient.get.mockResolvedValue({ + ...mockedAlertTypeSavedObject, + notifyWhen: 'onActionGroupChange', + actions: [], + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(5); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "new-instance", + "duration": 0, + "start": "1970-01-01T00:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' created new instance: '1'", + }, + ], + Array [ + Object { + "event": Object { + "action": "new-instance", + "duration": 0, + "start": "1970-01-01T00:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "2", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' created new instance: '2'", + }, + ], + Array [ + Object { + "event": Object { + "action": "active-instance", + "duration": 0, + "start": "1970-01-01T00:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", + }, + ], + Array [ + Object { + "event": Object { + "action": "active-instance", + "duration": 0, + "start": "1970-01-01T00:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "2", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' active instance: '2' in actionGroup: 'default'", + }, + ], + Array [ + Object { + "@timestamp": "1970-01-01T00:00:00.000Z", + "event": Object { + "action": "execute", + "outcome": "success", + }, + "kibana": Object { + "alerting": Object { + "status": "active", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + ] + `); + }); + + test('duration is updated for active alerts when alert state contains start time', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + alertType.executor.mockImplementation( + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + string + >) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + executorServices.alertInstanceFactory('2').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { + meta: {}, + state: { + bar: false, + start: '1969-12-31T00:00:00.000Z', + duration: 80000000000, + }, + }, + '2': { + meta: {}, + state: { + bar: false, + start: '1969-12-31T06:00:00.000Z', + duration: 70000000000, + }, + }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + alertsClient.get.mockResolvedValue({ + ...mockedAlertTypeSavedObject, + notifyWhen: 'onActionGroupChange', + actions: [], + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(3); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "active-instance", + "duration": 86400000000000, + "start": "1969-12-31T00:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", + }, + ], + Array [ + Object { + "event": Object { + "action": "active-instance", + "duration": 64800000000000, + "start": "1969-12-31T06:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "2", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' active instance: '2' in actionGroup: 'default'", + }, + ], + Array [ + Object { + "@timestamp": "1970-01-01T00:00:00.000Z", + "event": Object { + "action": "execute", + "outcome": "success", + }, + "kibana": Object { + "alerting": Object { + "status": "active", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + ] + `); + }); + + test('duration is not calculated for active alerts when alert state does not contain start time', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + alertType.executor.mockImplementation( + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + string + >) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + executorServices.alertInstanceFactory('2').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { + meta: {}, + state: { bar: false }, + }, + '2': { + meta: {}, + state: { bar: false }, + }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + alertsClient.get.mockResolvedValue({ + ...mockedAlertTypeSavedObject, + notifyWhen: 'onActionGroupChange', + actions: [], + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(3); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "active-instance", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", + }, + ], + Array [ + Object { + "event": Object { + "action": "active-instance", + }, + "kibana": Object { + "alerting": Object { + "action_group_id": "default", + "instance_id": "2", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' active instance: '2' in actionGroup: 'default'", + }, + ], + Array [ + Object { + "@timestamp": "1970-01-01T00:00:00.000Z", + "event": Object { + "action": "execute", + "outcome": "success", + }, + "kibana": Object { + "alerting": Object { + "status": "active", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + ] + `); + }); + + test('end is logged for active alerts when alert state contains start time and alert recovers', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + alertType.executor.mockImplementation(async () => {}); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { + meta: {}, + state: { + bar: false, + start: '1969-12-31T00:00:00.000Z', + duration: 80000000000, + }, + }, + '2': { + meta: {}, + state: { + bar: false, + start: '1969-12-31T06:00:00.000Z', + duration: 70000000000, + }, + }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + alertsClient.get.mockResolvedValue({ + ...mockedAlertTypeSavedObject, + notifyWhen: 'onActionGroupChange', + actions: [], + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(3); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "recovered-instance", + "duration": 86400000000000, + "end": "1970-01-01T00:00:00.000Z", + "start": "1969-12-31T00:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' instance '1' has recovered", + }, + ], + Array [ + Object { + "event": Object { + "action": "recovered-instance", + "duration": 64800000000000, + "end": "1970-01-01T00:00:00.000Z", + "start": "1969-12-31T06:00:00.000Z", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "2", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' instance '2' has recovered", + }, + ], + Array [ + Object { + "@timestamp": "1970-01-01T00:00:00.000Z", + "event": Object { + "action": "execute", + "outcome": "success", + }, + "kibana": Object { + "alerting": Object { + "status": "ok", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + ] + `); + }); + + test('end calculation is skipped for active alerts when alert state does not contain start time and alert recovers', async () => { + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + alertType.executor.mockImplementation( + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + string + >) => {} + ); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { + meta: {}, + state: { bar: false }, + }, + '2': { + meta: {}, + state: { bar: false }, + }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + alertsClient.get.mockResolvedValue({ + ...mockedAlertTypeSavedObject, + notifyWhen: 'onActionGroupChange', + actions: [], + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(3); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "recovered-instance", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "1", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' instance '1' has recovered", + }, + ], + Array [ + Object { + "event": Object { + "action": "recovered-instance", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "2", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "test:1: 'alert-name' instance '2' has recovered", + }, + ], + Array [ + Object { + "@timestamp": "1970-01-01T00:00:00.000Z", + "event": Object { + "action": "execute", + "outcome": "success", + }, + "kibana": Object { + "alerting": Object { + "status": "ok", + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": undefined, + "rel": "primary", + "type": "alert", + "type_id": "test", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + ] + `); + }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index fd82b38b493d79..4a214efceaa7d7 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -323,6 +323,12 @@ export class TaskRunner< alertLabel, }); + trackAlertDurations({ + originalAlerts: originalAlertInstances, + currentAlerts: instancesWithScheduledActions, + recoveredAlerts: recoveredAlertInstances, + }); + generateNewAndRecoveredInstanceEvents({ eventLogger, originalAlertInstances, @@ -581,6 +587,10 @@ export class TaskRunner< ), schedule: resolveErr(schedule, (error) => { if (isAlertSavedObjectNotFoundError(error, alertId)) { + const spaceMessage = spaceId ? `in the "${spaceId}" space ` : ''; + this.logger.warn( + `Unable to execute rule "${alertId}" ${spaceMessage}because ${error.message} - this rule will not be rescheduled. To restart rule execution, try disabling and re-enabling this rule.` + ); throwUnrecoverableError(error); } return { interval: taskSchedule?.interval ?? FALLBACK_RETRY_INTERVAL }; @@ -589,6 +599,61 @@ export class TaskRunner< } } +interface TrackAlertDurationsParams< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +> { + originalAlerts: Dictionary>; + currentAlerts: Dictionary>; + recoveredAlerts: Dictionary>; +} + +function trackAlertDurations< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +>(params: TrackAlertDurationsParams) { + const currentTime = new Date().toISOString(); + const { currentAlerts, originalAlerts, recoveredAlerts } = params; + const originalAlertIds = Object.keys(originalAlerts); + const currentAlertIds = Object.keys(currentAlerts); + const recoveredAlertIds = Object.keys(recoveredAlerts); + const newAlertIds = without(currentAlertIds, ...originalAlertIds); + + // Inject start time into instance state of new instances + for (const id of newAlertIds) { + const state = currentAlerts[id].getState(); + currentAlerts[id].replaceState({ ...state, start: currentTime }); + } + + // Calculate duration to date for active instances + for (const id of currentAlertIds) { + const state = originalAlertIds.includes(id) + ? originalAlerts[id].getState() + : currentAlerts[id].getState(); + const duration = state.start + ? (new Date(currentTime).valueOf() - new Date(state.start as string).valueOf()) * 1000 * 1000 // nanoseconds + : undefined; + currentAlerts[id].replaceState({ + ...state, + ...(state.start ? { start: state.start } : {}), + ...(duration !== undefined ? { duration } : {}), + }); + } + + // Inject end time into instance state of recovered instances + for (const id of recoveredAlertIds) { + const state = recoveredAlerts[id].getState(); + const duration = state.start + ? (new Date(currentTime).valueOf() - new Date(state.start as string).valueOf()) * 1000 * 1000 // nanoseconds + : undefined; + recoveredAlerts[id].replaceState({ + ...state, + ...(duration ? { duration } : {}), + ...(state.start ? { end: currentTime } : {}), + }); + } +} + interface GenerateNewAndRecoveredInstanceEventsParams< InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext @@ -624,38 +689,66 @@ function generateNewAndRecoveredInstanceEvents< for (const id of recoveredAlertInstanceIds) { const { group: actionGroup, subgroup: actionSubgroup } = recoveredAlertInstances[id].getLastScheduledActions() ?? {}; + const state = recoveredAlertInstances[id].getState(); const message = `${params.alertLabel} instance '${id}' has recovered`; - logInstanceEvent(id, EVENT_LOG_ACTIONS.recoveredInstance, message, actionGroup, actionSubgroup); + logInstanceEvent( + id, + EVENT_LOG_ACTIONS.recoveredInstance, + message, + state, + actionGroup, + actionSubgroup + ); } for (const id of newIds) { const { actionGroup, subgroup: actionSubgroup } = currentAlertInstances[id].getScheduledActionOptions() ?? {}; + const state = currentAlertInstances[id].getState(); const message = `${params.alertLabel} created new instance: '${id}'`; - logInstanceEvent(id, EVENT_LOG_ACTIONS.newInstance, message, actionGroup, actionSubgroup); + logInstanceEvent( + id, + EVENT_LOG_ACTIONS.newInstance, + message, + state, + actionGroup, + actionSubgroup + ); } for (const id of currentAlertInstanceIds) { const { actionGroup, subgroup: actionSubgroup } = currentAlertInstances[id].getScheduledActionOptions() ?? {}; + const state = currentAlertInstances[id].getState(); const message = `${params.alertLabel} active instance: '${id}' in ${ actionSubgroup ? `actionGroup(subgroup): '${actionGroup}(${actionSubgroup})'` : `actionGroup: '${actionGroup}'` }`; - logInstanceEvent(id, EVENT_LOG_ACTIONS.activeInstance, message, actionGroup, actionSubgroup); + logInstanceEvent( + id, + EVENT_LOG_ACTIONS.activeInstance, + message, + state, + actionGroup, + actionSubgroup + ); } function logInstanceEvent( instanceId: string, action: string, message: string, + state: InstanceState, group?: string, subgroup?: string ) { const event: IEvent = { event: { action, + ...(state?.start ? { start: state.start as string } : {}), + ...(state?.end ? { end: state.end as string } : {}), + ...(state?.duration !== undefined ? { duration: state.duration as number } : {}), }, kibana: { alerting: { diff --git a/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx new file mode 100644 index 00000000000000..c6394f09b0d3cf --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiEmptyPrompt } from '@elastic/eui'; +import React from 'react'; +import { Redirect, RouteComponentProps } from 'react-router-dom'; +import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { getRedirectToTransactionDetailPageUrl } from '../TraceLink/get_redirect_to_transaction_detail_page_url'; + +const CentralizedContainer = euiStyled.div` + height: 100%; + display: flex; +`; + +export function TransactionLink({ + match, +}: RouteComponentProps<{ transactionId: string }>) { + const { transactionId } = match.params; + const { urlParams } = useUrlParams(); + const { rangeFrom, rangeTo } = urlParams; + + const { data = { transaction: null }, status } = useFetcher( + (callApmApi) => { + if (transactionId) { + return callApmApi({ + endpoint: 'GET /api/apm/transactions/{transactionId}', + params: { + path: { + transactionId, + }, + }, + }); + } + }, + [transactionId] + ); + if (transactionId && status === FETCH_STATUS.SUCCESS) { + if (data.transaction) { + return ( + + ); + } + + return ; + } + + return ( + + Fetching transaction...} + /> + + ); +} diff --git a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx index 3d530f4614d82d..36580d38e660da 100644 --- a/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx +++ b/x-pack/plugins/apm/public/components/routing/apm_route_config.tsx @@ -20,6 +20,7 @@ import { AnomalyDetection } from '../app/Settings/anomaly_detection'; import { ApmIndices } from '../app/Settings/ApmIndices'; import { CustomizeUI } from '../app/Settings/CustomizeUI'; import { TraceLink } from '../app/TraceLink'; +import { TransactionLink } from '../app/transaction_link'; import { TransactionDetails } from '../app/transaction_details'; import { enableServiceOverview } from '../../../common/ui_settings_keys'; import { redirectTo } from './redirect_to'; @@ -498,6 +499,12 @@ export const apmRouteConfig: APMRouteDefinition[] = [ component: TraceLink, breadcrumb: null, }, + { + exact: true, + path: '/link-to/transaction/:transactionId', + component: TransactionLink, + breadcrumb: null, + }, ]; function RedirectToDefaultServiceRouteView( diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts index b4323ae7f51e28..6987ef07577348 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts @@ -21,11 +21,11 @@ export function getTransaction({ setup, }: { transactionId: string; - traceId: string; - setup: Setup & SetupTimeRange; + traceId?: string; + setup: Setup | (Setup & SetupTimeRange); }) { return withApmSpan('get_transaction', async () => { - const { start, end, apmEventClient } = setup; + const { apmEventClient } = setup; const resp = await apmEventClient.search({ apm: { @@ -37,8 +37,8 @@ export function getTransaction({ bool: { filter: asMutableArray([ { term: { [TRANSACTION_ID]: transactionId } }, - { term: { [TRACE_ID]: traceId } }, - ...rangeQuery(start, end), + ...(traceId ? [{ term: { [TRACE_ID]: traceId } }] : []), + ...('start' in setup ? rangeQuery(setup.start, setup.end) : []), ]), }, }, diff --git a/x-pack/plugins/apm/server/routes/traces.ts b/x-pack/plugins/apm/server/routes/traces.ts index dd392982b02fd2..7fce04644f2205 100644 --- a/x-pack/plugins/apm/server/routes/traces.ts +++ b/x-pack/plugins/apm/server/routes/traces.ts @@ -14,6 +14,7 @@ import { environmentRt, kueryRt, rangeRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { getRootTransactionByTraceId } from '../lib/transactions/get_transaction_by_trace'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { getTransaction } from '../lib/transactions/get_transaction'; const tracesRoute = createApmServerRoute({ endpoint: 'GET /api/apm/traces', @@ -70,7 +71,24 @@ const rootTransactionByTraceIdRoute = createApmServerRoute({ }, }); +const transactionByIdRoute = createApmServerRoute({ + endpoint: 'GET /api/apm/transactions/{transactionId}', + params: t.type({ + path: t.type({ + transactionId: t.string, + }), + }), + options: { tags: ['access:apm'] }, + handler: async (resources) => { + const { params } = resources; + const { transactionId } = params.path; + const setup = await setupRequest(resources); + return { transaction: await getTransaction({ transactionId, setup }) }; + }, +}); + export const traceRouteRepository = createApmServerRouteRepository() .add(tracesByIdRoute) .add(tracesRoute) - .add(rootTransactionByTraceIdRoute); + .add(rootTransactionByTraceIdRoute) + .add(transactionByIdRoute); diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts index a797a8bda061b6..7a23137e7ef60e 100644 --- a/x-pack/plugins/canvas/i18n/components.ts +++ b/x-pack/plugins/canvas/i18n/components.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { BOLD_MD_TOKEN, CANVAS, HTML, JSON, KIBANA, PDF, POST, URL, ZIP } from './constants'; +import { BOLD_MD_TOKEN, CANVAS, HTML, JSON, PDF, URL, ZIP } from './constants'; export const ComponentStrings = { AddEmbeddableFlyout: { @@ -1418,95 +1418,10 @@ export const ComponentStrings = { URL, }, }), - getCopyReportingConfigMessage: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyReportingConfigMessage', { - defaultMessage: 'Copied reporting configuration to clipboard', - }), getCopyShareConfigMessage: () => i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage', { defaultMessage: 'Copied share markup to clipboard', }), - getExportPDFErrorTitle: (workpadName: string) => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.exportPDFErrorMessage', { - defaultMessage: "Failed to create {PDF} for '{workpadName}'", - values: { - PDF, - workpadName, - }, - }), - getExportPDFMessage: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.exportPDFMessage', { - defaultMessage: 'Exporting {PDF}. You can track the progress in Management.', - values: { - PDF, - }, - }), - getExportPDFTitle: (workpadName: string) => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.exportPDFTitle', { - defaultMessage: "{PDF} export of workpad '{workpadName}'", - values: { - PDF, - workpadName, - }, - }), - getPDFFullPageLayoutHelpText: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.FullPageLayoutHelpText', { - defaultMessage: 'Remove borders and footer logo', - }), - getPDFFullPageLayoutLabel: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.FullPageLayoutLabel', { - defaultMessage: 'Full page layout', - }), - getPDFPanelAdvancedOptionsLabel: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelAdvancedOptionsLabel', { - defaultMessage: 'Advanced options', - }), - getPDFPanelCopyAriaLabel: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyAriaLabel', { - defaultMessage: - 'Alternatively, you can generate a {PDF} from a script or with Watcher by using this {URL}. Press Enter to copy the {URL} to clipboard.', - values: { - PDF, - URL, - }, - }), - getPDFPanelCopyButtonLabel: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyButtonLabel', { - defaultMessage: 'Copy {POST} {URL}', - values: { - POST, - URL, - }, - }), - getPDFPanelCopyDescription: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyDescription', { - defaultMessage: - 'Alternatively, copy this {POST} {URL} to call generation from outside {KIBANA} or from Watcher.', - values: { - POST, - KIBANA, - URL, - }, - }), - getPDFPanelGenerateButtonLabel: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateButtonLabel', { - defaultMessage: 'Generate {PDF}', - values: { - PDF, - }, - }), - getPDFPanelGenerateDescription: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateDescription', { - defaultMessage: - '{PDF}s can take a minute or two to generate based on the size of your workpad.', - values: { - PDF, - }, - }), - getPDFPanelOptionsLabel: () => - i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelOptionsLabel', { - defaultMessage: 'Options', - }), getShareableZipErrorTitle: (workpadName: string) => i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteErrorTitle', { defaultMessage: diff --git a/x-pack/plugins/canvas/kibana.json b/x-pack/plugins/canvas/kibana.json index 6213ecb58347c1..5faeaefc9e3922 100644 --- a/x-pack/plugins/canvas/kibana.json +++ b/x-pack/plugins/canvas/kibana.json @@ -29,7 +29,6 @@ "kibanaUtils", "lens", "maps", - "reporting", "savedObjects", "visualizations" ] diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/share_menu.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/share_menu.stories.storyshot index 302e015ea1d3a3..c78ff5bf781eca 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/share_menu.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/share_menu.stories.storyshot @@ -1,6 +1,37 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots components/WorkpadHeader/ShareMenu default 1`] = ` +exports[`Storyshots components/WorkpadHeader/ShareMenu minimal 1`] = ` +
+
+
+ +
+
+
+`; + +exports[`Storyshots components/WorkpadHeader/ShareMenu with Reporting 1`] = `
( -
- 'PDF URL String'} - onCopy={action('onCopy')} - onExport={action('onExport')} - /> -
- )); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/share_menu.stories.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/share_menu.stories.tsx index bca96f3851e371..20e52b40bc702d 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/share_menu.stories.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/share_menu.stories.tsx @@ -5,19 +5,34 @@ * 2.0. */ -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; import React from 'react'; +import { platformService } from '../../../../services/stubs/platform'; +import { reportingService } from '../../../../services/stubs/reporting'; import { ShareMenu } from '../share_menu.component'; -storiesOf('components/WorkpadHeader/ShareMenu', module).add('default', () => ( +storiesOf('components/WorkpadHeader/ShareMenu', module).add('minimal', () => ( { - action(`getExportUrl('${type}')`); - return type; + /> +)); + +storiesOf('components/WorkpadHeader/ShareMenu', module).add('with Reporting', () => ( + )); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/pdf_panel.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/pdf_panel.tsx deleted file mode 100644 index b412bd5328a5bd..00000000000000 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/pdf_panel.tsx +++ /dev/null @@ -1,98 +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, { useState } from 'react'; -import { - EuiAccordion, - EuiButton, - EuiFormRow, - EuiHorizontalRule, - EuiSpacer, - EuiSwitch, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { Clipboard } from '../../clipboard'; -import { LayoutType } from './utils'; - -import { ComponentStrings } from '../../../../i18n/components'; -const { WorkpadHeaderShareMenu: strings } = ComponentStrings; - -interface Props { - /** Retrieve URL that will invoke PDF Report generation. */ - getPdfURL: (layout: LayoutType) => string; - /** Handler to invoke when the PDF is exported */ - onExport: (layout: LayoutType) => void; - /** Handler to invoke when the URL is copied to the clipboard. */ - onCopy: () => void; -} - -/** - * A panel displayed in the Export Menu with options in which to generate PDF Reports. - */ -export const PDFPanel = ({ getPdfURL, onExport, onCopy }: Props) => { - const [reportLayout, setReportLayout] = useState('preserve_layout'); - - return ( -
- -

{strings.getPDFPanelGenerateDescription()}

-
- - -
{strings.getPDFPanelOptionsLabel()}
-
- - - - reportLayout === 'canvas' - ? setReportLayout('preserve_layout') - : setReportLayout('canvas') - } - data-test-subj="reportModeToggle" - /> - - onExport(reportLayout)} - size="s" - style={{ width: '100%' }} - data-test-subj="generateReportButton" - > - {strings.getPDFPanelGenerateButtonLabel()} - - - - - -

{strings.getPDFPanelCopyDescription()}

-
- - - - {strings.getPDFPanelCopyButtonLabel()} - - -
-
- ); -}; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx index 0d2e877bebdfd3..d4cb4d0736bb14 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.component.tsx @@ -5,47 +5,47 @@ * 2.0. */ -import React, { FunctionComponent, useState } from 'react'; -import PropTypes from 'prop-types'; import { EuiButtonEmpty, EuiContextMenu, EuiIcon } from '@elastic/eui'; +import { IBasePath } from 'kibana/public'; +import PropTypes from 'prop-types'; +import React, { FunctionComponent, useState } from 'react'; +import { ReportingStart } from '../../../../../reporting/public'; import { ComponentStrings } from '../../../../i18n/components'; import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; -import { Popover, ClosePopoverFn } from '../../popover'; -import { PDFPanel } from './pdf_panel'; +import { ClosePopoverFn, Popover } from '../../popover'; import { ShareWebsiteFlyout } from './flyout'; -import { LayoutType } from './utils'; +import { CanvasWorkpadSharingData, getPdfJobParams } from './utils'; const { WorkpadHeaderShareMenu: strings } = ComponentStrings; type CopyTypes = 'pdf' | 'reportingConfig'; type ExportTypes = 'pdf' | 'json'; -type ExportUrlTypes = 'pdf'; type CloseTypes = 'share'; export type OnCopyFn = (type: CopyTypes) => void; -export type OnExportFn = (type: ExportTypes, layout?: LayoutType) => void; +export type OnExportFn = (type: ExportTypes) => void; export type OnCloseFn = (type: CloseTypes) => void; -export type GetExportUrlFn = (type: ExportUrlTypes, layout: LayoutType) => string; export interface Props { - /** Flag to include the Reporting option only if Reporting is enabled */ - includeReporting: boolean; - /** Handler to invoke when an export URL is copied to the clipboard. */ - onCopy: OnCopyFn; + /** Canvas workpad to export as PDF **/ + sharingData: CanvasWorkpadSharingData; + sharingServices: { + /** BasePath dependency **/ + basePath: IBasePath; + /** Reporting dependency **/ + reporting?: ReportingStart; + }; /** Handler to invoke when an end product is exported. */ onExport: OnExportFn; - /** Handler to retrive an export URL based on the type of export requested. */ - getExportUrl: GetExportUrlFn; } /** * The Menu for Exporting a Workpad from Canvas. */ export const ShareMenu: FunctionComponent = ({ - includeReporting, - onCopy, + sharingData, + sharingServices: services, onExport, - getExportUrl, }) => { const [showFlyout, setShowFlyout] = useState(false); @@ -53,22 +53,6 @@ export const ShareMenu: FunctionComponent = ({ setShowFlyout(false); }; - const getPDFPanel = (closePopover: ClosePopoverFn) => { - return ( - getExportUrl('pdf', layoutType)} - onExport={(layoutType) => { - onExport('pdf', layoutType); - closePopover(); - }} - onCopy={() => { - onCopy('pdf'); - closePopover(); - }} - /> - ); - }; - const getPanelTree = (closePopover: ClosePopoverFn) => ({ id: 0, items: [ @@ -80,14 +64,20 @@ export const ShareMenu: FunctionComponent = ({ closePopover(); }, }, - includeReporting + services.reporting != null ? { name: strings.getShareDownloadPDFTitle(), icon: 'document', panel: { id: 1, title: strings.getShareDownloadPDFTitle(), - content: getPDFPanel(closePopover), + content: ( + getPdfJobParams(sharingData, services.basePath)} + layoutOption="canvas" + onClose={closePopover} + /> + ), }, 'data-test-subj': 'sharePanel-PDFReports', } @@ -132,8 +122,5 @@ export const ShareMenu: FunctionComponent = ({ }; ShareMenu.propTypes = { - includeReporting: PropTypes.bool.isRequired, - onCopy: PropTypes.func.isRequired, onExport: PropTypes.func.isRequired, - getExportUrl: PropTypes.func.isRequired, }; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts index 47b5e755d439cf..fc4906817cf6fc 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.ts @@ -7,16 +7,12 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; -import { jobCompletionNotifications } from '../../../../../../plugins/reporting/public'; -import { getWorkpad, getPages } from '../../../state/selectors/workpad'; -import { getWindow } from '../../../lib/get_window'; +import { ComponentStrings } from '../../../../i18n'; +import { CanvasWorkpad, State } from '../../../../types'; import { downloadWorkpad } from '../../../lib/download_workpad'; -import { ShareMenu as Component, Props as ComponentProps } from './share_menu.component'; -import { getPdfUrl, createPdf } from './utils'; -import { State, CanvasWorkpad } from '../../../../types'; import { withServices, WithServicesProps } from '../../../services'; - -import { ComponentStrings } from '../../../../i18n'; +import { getPages, getWorkpad } from '../../../state/selectors/workpad'; +import { Props as ComponentProps, ShareMenu as Component } from './share_menu.component'; const { WorkpadHeaderShareMenu: strings } = ComponentStrings; @@ -25,17 +21,6 @@ const mapStateToProps = (state: State) => ({ pageCount: getPages(state).length, }); -const getAbsoluteUrl = (path: string) => { - const { location } = getWindow(); - - if (!location) { - return path; - } // fallback for mocked window object - - const { protocol, hostname, port } = location; - return `${protocol}//${hostname}:${port}${path}`; -}; - interface Props { workpad: CanvasWorkpad; pageCount: number; @@ -45,63 +30,28 @@ export const ShareMenu = compose( connect(mapStateToProps), withServices, withProps( - ({ workpad, pageCount, services }: Props & WithServicesProps): ComponentProps => ({ - includeReporting: services.reporting.includeReporting(), - getExportUrl: (type, layout) => { - if (type === 'pdf') { - const pdfUrl = getPdfUrl( - workpad, - layout, - { pageCount }, - services.platform.getBasePathInterface() - ); - return getAbsoluteUrl(pdfUrl); - } - - throw new Error(strings.getUnknownExportErrorMessage(type)); - }, - onCopy: (type) => { - switch (type) { - case 'pdf': - services.notify.info(strings.getCopyPDFMessage()); - break; - case 'reportingConfig': - services.notify.info(strings.getCopyReportingConfigMessage()); - break; - default: - throw new Error(strings.getUnknownExportErrorMessage(type)); - } - }, - onExport: (type, layout) => { - switch (type) { - case 'pdf': - return createPdf( - workpad, - layout || 'preserve_layout', - { pageCount }, - services.platform.getBasePathInterface() - ) - .then(({ data }: { data: { job: { id: string } } }) => { - services.notify.info(strings.getExportPDFMessage(), { - title: strings.getExportPDFTitle(workpad.name), - }); - - // register the job so a completion notification shows up when it's ready - jobCompletionNotifications.add(data.job.id); - }) - .catch((err: Error) => { - services.notify.error(err, { - title: strings.getExportPDFErrorTitle(workpad.name), - 'data-test-subj': 'queueReportError', - }); - }); - case 'json': - downloadWorkpad(workpad.id); - return; - default: - throw new Error(strings.getUnknownExportErrorMessage(type)); - } - }, - }) + ({ workpad, pageCount, services }: Props & WithServicesProps): ComponentProps => { + const { + platform, + reporting: { start: reporting }, + } = services; + + return { + sharingServices: { basePath: platform.getBasePathInterface(), reporting }, + sharingData: { workpad, pageCount }, + onExport: (type) => { + switch (type) { + case 'pdf': + // notifications are automatically handled by the Reporting plugin + break; + case 'json': + downloadWorkpad(workpad.id); + return; + default: + throw new Error(strings.getUnknownExportErrorMessage(type)); + } + }, + }; + } ) )(Component); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts index df82feb088379f..fd6f4bb894991c 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts @@ -7,9 +7,8 @@ jest.mock('../../../../common/lib/fetch'); -import { getPdfUrl, createPdf, LayoutType } from './utils'; +import { getPdfJobParams } from './utils'; import { workpads } from '../../../../__fixtures__/workpads'; -import { fetch } from '../../../../common/lib/fetch'; import { IBasePath } from 'kibana/public'; const basePath = ({ @@ -17,33 +16,36 @@ const basePath = ({ get: () => 'basepath/s/spacey', serverBasePath: `basepath`, } as unknown) as IBasePath; -const workpad = workpads[0]; - -test('getPdfUrl returns the correct url for canvas layout', () => { - ['canvas', 'preserve_layout'].forEach((layout) => { - const url = getPdfUrl(workpad, layout as LayoutType, { pageCount: 2 }, basePath); - - expect(url).toMatchInlineSnapshot( - `"basepath/s/spacey//api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(dimensions:(height:0,width:0),id:${layout}),objectType:'canvas%20workpad',relativeUrls:!(%2Fs%2Fspacey%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F1,%2Fs%2Fspacey%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F2),title:'base%20workpad')"` - ); - }); -}); - -test('createPdf posts to create the pdf with canvas layout', () => { - ['canvas', 'preserve_layout'].forEach((layout, index) => { - createPdf(workpad, layout as LayoutType, { pageCount: 2 }, basePath); - - expect(fetch.post).toBeCalled(); - - const args = (fetch.post as jest.MockedFunction).mock.calls[index]; - - expect(args[0]).toMatchInlineSnapshot( - `"basepath/s/spacey//api/reporting/generate/printablePdf"` - ); - expect(args[1]).toMatchInlineSnapshot(` - Object { - "jobParams": "(browserTimezone:America/New_York,layout:(dimensions:(height:0,width:0),id:${layout}),objectType:'canvas workpad',relativeUrls:!(/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/1,/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/2),title:'base workpad')", - } - `); - }); +const workpadSharingData = { workpad: workpads[0], pageCount: 12 }; + +test('getPdfJobParams returns the correct job params for canvas layout', () => { + const jobParams = getPdfJobParams(workpadSharingData, basePath); + expect(jobParams).toMatchInlineSnapshot(` + Object { + "browserTimezone": "America/New_York", + "layout": Object { + "dimensions": Object { + "height": 0, + "width": 0, + }, + "id": "canvas", + }, + "objectType": "canvas workpad", + "relativeUrls": Array [ + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/1", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/2", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/3", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/4", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/5", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/6", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/7", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/8", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/9", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/10", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/11", + "/s/spacey/app/canvas#/export/workpad/pdf/base-workpad/page/12", + ], + "title": "base workpad", + } + `); }); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts index 40797ef0977819..9586242bc7386f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts @@ -5,33 +5,24 @@ * 2.0. */ -import rison from 'rison-node'; import { IBasePath } from 'kibana/public'; import moment from 'moment-timezone'; -import { fetch } from '../../../../common/lib/fetch'; +import rison from 'rison-node'; +import { BaseParams } from '../../../../../reporting/common/types'; import { CanvasWorkpad } from '../../../../types'; -import { url } from '../../../../../../../src/plugins/kibana_utils/public'; -interface PageCount { +export interface CanvasWorkpadSharingData { + workpad: Pick; pageCount: number; } -export type LayoutType = 'canvas' | 'preserve_layout'; - -type Arguments = [CanvasWorkpad, LayoutType, PageCount, IBasePath]; - -interface PdfUrlData { - createPdfUri: string; - createPdfPayload: { jobParams: string }; -} +// TODO: get the correct type from Reporting plugin +type JobParamsPDF = BaseParams & { relativeUrls: string[] }; -function getPdfUrlParts( - { id, name: title, width, height }: CanvasWorkpad, - layoutType: LayoutType, - { pageCount }: PageCount, +export function getPdfJobParams( + { workpad: { id, name: title, width, height }, pageCount }: CanvasWorkpadSharingData, basePath: IBasePath -): PdfUrlData { - const reportingEntry = basePath.prepend('/api/reporting/generate'); +): JobParamsPDF { const urlPrefix = basePath.get().replace(basePath.serverBasePath, ''); // for Spaces prefix, which is included in basePath.get() const canvasEntry = `${urlPrefix}/app/canvas#`; @@ -51,34 +42,14 @@ function getPdfUrlParts( workpadUrls.push(rison.encode(`${canvasEntry}/export/workpad/pdf/${id}/page/${i}`)); } - const jobParams = { + return { browserTimezone: moment.tz.guess(), layout: { dimensions: { width, height }, - id: layoutType, + id: 'canvas', }, objectType: 'canvas workpad', relativeUrls: workpadUrls, title, }; - - return { - createPdfUri: `${reportingEntry}/printablePdf`, - createPdfPayload: { - jobParams: rison.encode(jobParams), - }, - }; -} - -export function getPdfUrl(...args: Arguments): string { - const urlParts = getPdfUrlParts(...args); - const param = (key: string, val: any) => - url.encodeUriQuery(key, true) + (val === true ? '' : '=' + url.encodeUriQuery(val, true)); - - return `${urlParts.createPdfUri}?${param('jobParams', urlParts.createPdfPayload.jobParams)}`; -} - -export function createPdf(...args: Arguments) { - const { createPdfUri, createPdfPayload } = getPdfUrlParts(...args); - return fetch.post(createPdfUri, createPdfPayload); } diff --git a/x-pack/plugins/canvas/public/services/reporting.ts b/x-pack/plugins/canvas/public/services/reporting.ts index 3299363cd5c7f1..4fa40401472c65 100644 --- a/x-pack/plugins/canvas/public/services/reporting.ts +++ b/x-pack/plugins/canvas/public/services/reporting.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { ReportingStart } from '../../../reporting/public'; import { CanvasServiceFactory } from './'; export interface ReportingService { - includeReporting: () => boolean; + start?: ReportingStart; } export const reportingServiceFactory: CanvasServiceFactory = ( @@ -18,18 +19,24 @@ export const reportingServiceFactory: CanvasServiceFactory = ( startPlugins ): ReportingService => { const { reporting } = startPlugins; + + const reportingEnabled = () => ({ start: reporting }); + const reportingDisabled = () => ({ start: undefined }); + if (!reporting) { // Reporting is not enabled - return { includeReporting: () => false }; + return reportingDisabled(); } if (reporting.usesUiCapabilities()) { - // Canvas has declared Reporting as a subfeature with the `generatePdf` UI Capability - return { - includeReporting: () => coreStart.application.capabilities.canvas?.generatePdf === true, - }; + if (coreStart.application.capabilities.canvas?.generatePdf === true) { + // Canvas has declared Reporting as a subfeature with the `generatePdf` UI Capability + return reportingEnabled(); + } else { + return reportingDisabled(); + } } - // Reporting is enabled as an Elasticsearch feature (Legacy/Deprecated) - return { includeReporting: () => true }; + // Legacy/Deprecated: Reporting is enabled as an Elasticsearch feature + return reportingEnabled(); }; diff --git a/x-pack/plugins/canvas/public/services/stubs/reporting.ts b/x-pack/plugins/canvas/public/services/stubs/reporting.ts index f257dd14543ecd..b32dfc51cbfde8 100644 --- a/x-pack/plugins/canvas/public/services/stubs/reporting.ts +++ b/x-pack/plugins/canvas/public/services/stubs/reporting.ts @@ -8,5 +8,16 @@ import { ReportingService } from '../reporting'; export const reportingService: ReportingService = { - includeReporting: () => true, + start: { + usesUiCapabilities: () => true, + components: { + ReportingPanelPDF: () => (null as unknown) as JSX.Element, + }, + getDefaultLayoutSelectors: () => ({ + screenshot: 'stub', + renderComplete: 'stub', + itemsCountAttribute: 'stub', + timefilterDurationAttribute: 'stub', + }), + }, }; diff --git a/x-pack/plugins/canvas/server/sample_data/ecommerce_saved_objects.json b/x-pack/plugins/canvas/server/sample_data/ecommerce_saved_objects.json index 113fba55553760..be5bc213e59a4a 100644 --- a/x-pack/plugins/canvas/server/sample_data/ecommerce_saved_objects.json +++ b/x-pack/plugins/canvas/server/sample_data/ecommerce_saved_objects.json @@ -28,7 +28,7 @@ "height": 510, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" \n| mapColumn \"maleCount\" exp={getCell \"customer_gender\" | if {compare to=\"MALE\"} then=1 else=0} | math \"sum(maleCount) / count(maleCount)\" \n| revealImage origin=\"bottom\" image={asset \"asset-aaa14d64-2c1c-47f2-95c0-21306ee18cba\"} emptyImage={asset \"asset-960c8c6e-da72-412d-9d04-34a98cdb5760\"}" + "expression": "essql query=\"select COUNT(*) as total_count, SUM(CASE WHEN customer_gender='MALE' THEN 1 else 0 END) as male_count from kibana_sample_data_ecommerce\"\n| math \"male_count / total_count\" \n| revealImage origin=\"bottom\" image={asset \"asset-aaa14d64-2c1c-47f2-95c0-21306ee18cba\"} emptyImage={asset \"asset-960c8c6e-da72-412d-9d04-34a98cdb5760\"}" }, { "id": "element-4a3fef74-5d8c-4bbe-8f3f-fe55afdd4b60", @@ -50,7 +50,7 @@ "height": 520, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\"\n| mapColumn \"maleCount\" exp={getCell \"customer_gender\" | if {compare to=\"FEMALE\"} then=1 else=0}\n| math \"sum(maleCount) / count(maleCount)\"\n| revealImage origin=\"bottom\" image={asset \"asset-2f64bd10-953d-4163-90e9-a55e9ca4c52a\"} emptyImage={asset \"asset-3a26727a-b756-44be-a82c-273dd85bda09\"}", + "expression": "essql query=\"select COUNT(*) as total_count, SUM(CASE WHEN customer_gender='FEMALE' THEN 1 else 0 END) as female_count from kibana_sample_data_ecommerce\"\n| math \"female_count / total_count\" \n| revealImage origin=\"bottom\" image={asset \"asset-2f64bd10-953d-4163-90e9-a55e9ca4c52a\"} emptyImage={asset \"asset-3a26727a-b756-44be-a82c-273dd85bda09\"}", "filter": null }, { @@ -194,7 +194,7 @@ "height": 56, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" | mapColumn \"femaleCount\" exp={getCell \"customer_gender\" | if {compare to=\"FEMALE\"} then=1 else=0} | math \"round(100 * sum(femaleCount) / count(femaleCount))\" | markdown {context} \"%\" font={font family=\"Avenir\" size=48 align=\"center\" color=\"#eb6c66\" weight=\"normal\" underline=false italic=false}" + "expression": "essql query=\"select COUNT(*) as total_count, SUM(CASE WHEN customer_gender='FEMALE' THEN 1 else 0 END) as female_count from kibana_sample_data_ecommerce\"\n| math \"round(100 * female_count / total_count)\" | markdown {context} \"%\" font={font family=\"Avenir\" size=48 align=\"center\" color=\"#eb6c66\" weight=\"normal\" underline=false italic=false}" }, { "id": "element-9e0b6230-2bc9-4995-8207-043e3063faeb", @@ -205,7 +205,7 @@ "height": 56, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" | mapColumn \"maleCount\" exp={getCell \"customer_gender\" | if {compare to=\"MALE\"} then=1 else=0} | math \"round(100 * sum(maleCount) / count(maleCount))\" | markdown {context} \"%\" font={font family=\"Avenir\" size=48 align=\"center\" color=\"#f8bd4a\" weight=\"normal\" underline=false italic=false}" + "expression": "essql query=\"select COUNT(*) as total_count, SUM(CASE WHEN customer_gender='MALE' THEN 1 else 0 END) as male_count from kibana_sample_data_ecommerce\"\n| math \"round(100 * male_count / total_count)\" | markdown {context} \"%\" font={font family=\"Avenir\" size=48 align=\"center\" color=\"#f8bd4a\" weight=\"normal\" underline=false italic=false}" }, { "id": "element-2185edff-ac50-4162-b583-3bfd6469e925", @@ -216,7 +216,7 @@ "height": 721, "angle": 0 }, - "expression": "filters | demodata | markdown \"\" | render containerStyle={containerStyle backgroundColor=\"#ede9e7\"}" + "expression": "markdown \"\" | render containerStyle={containerStyle backgroundColor=\"#ede9e7\"}" }, { "id": "element-71b63f54-0961-4ed2-a85d-45584b48a631", @@ -535,7 +535,7 @@ "height": 202, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" \n| mapColumn \"maleCount\" exp={getCell \"customer_gender\" | if {compare to=\"MALE\"} then=1 else=0} | math \"sum(maleCount) / count(maleCount)\" \n| revealImage origin=\"bottom\" image={asset \"asset-803ec373-2608-4f6f-8cf9-0dbb2f6437ce\"} emptyImage={asset \"asset-18070a2a-cd01-410a-ba89-a4505e2fbc5b\"}" + "expression": "essql query=\"select COUNT(*) as total_count, SUM(CASE WHEN customer_gender='MALE' THEN 1 else 0 END) as male_count from kibana_sample_data_ecommerce\"\n| math \"male_count / total_count\" \n| revealImage origin=\"bottom\" image={asset \"asset-803ec373-2608-4f6f-8cf9-0dbb2f6437ce\"} emptyImage={asset \"asset-18070a2a-cd01-410a-ba89-a4505e2fbc5b\"}" }, { "id": "element-2379c3ca-2c31-4948-8412-d14115500efc", @@ -546,7 +546,7 @@ "height": 202, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" \n| mapColumn \"maleCount\" exp={getCell \"customer_gender\" | if {compare to=\"FEMALE\"} then=1 else=0} | math \"sum(maleCount) / count(maleCount)\" \n| revealImage origin=\"bottom\" image={asset \"asset-e644a484-4097-40b9-a08e-7250ba963059\"} emptyImage={asset \"asset-7e4f7119-b2d8-4527-9bd8-887cb25974e7\"}" + "expression": "essql query=\"select COUNT(*) as total_count, SUM(CASE WHEN customer_gender='FEMALE' THEN 1 else 0 END) as female_count from kibana_sample_data_ecommerce\"\n| math \"female_count / total_count\" \n| revealImage origin=\"bottom\" image={asset \"asset-e644a484-4097-40b9-a08e-7250ba963059\"} emptyImage={asset \"asset-7e4f7119-b2d8-4527-9bd8-887cb25974e7\"}" }, { "id": "element-3f52813f-7d0e-4ec7-9aad-c731b670d88d", @@ -964,7 +964,7 @@ "height": 63, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" | mapColumn \"femaleCount\" exp={getCell \"customer_gender\" | if {compare to=\"FEMALE\"} then=1 else=0} | math \"round(100 * sum(femaleCount) / count(femaleCount))\" | markdown {context} font={font family=\"nexa bold, Avenir\" size=48 align=\"center\" color=\"#F05A24\" weight=\"bold\" underline=false italic=false}" + "expression": "essql query=\"select COUNT(*) as total_count, SUM(CASE WHEN customer_gender='FEMALE' THEN 1 else 0 END) as female_count from kibana_sample_data_ecommerce\"\n| math \"round(100 * female_count / total_count)\" | markdown {context} font={font family=\"nexa bold, Avenir\" size=48 align=\"center\" color=\"#F05A24\" weight=\"bold\" underline=false italic=false}" }, { "id": "element-86b06b67-893e-4555-ad38-7fba9ea3153b", @@ -975,7 +975,7 @@ "height": 72, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" | mapColumn \"maleCount\" exp={getCell \"customer_gender\" | if {compare to=\"MALE\"} then=1 else=0} | math \"round(100 * sum(maleCount) / count(maleCount))\" | markdown {context} font={font family=\"nexa bold, Avenir\" size=48 align=\"center\" color=\"#00A89C\" weight=\"bold\" underline=false italic=false}" + "expression": "essql query=\"select COUNT(*) as total_count, SUM(CASE WHEN customer_gender='MALE' THEN 1 else 0 END) as male_count from kibana_sample_data_ecommerce\"\n| math \"round(100 * male_count / total_count)\" | markdown {context} font={font family=\"nexa bold, Avenir\" size=48 align=\"center\" color=\"#00A89C\" weight=\"bold\" underline=false italic=false}" }, { "id": "element-507337d9-6e0e-4752-8770-6ebe88e9b3da", diff --git a/x-pack/plugins/cases/common/api/connectors/index.ts b/x-pack/plugins/cases/common/api/connectors/index.ts index f9b7c8b12c2cd1..2a81396025d9af 100644 --- a/x-pack/plugins/cases/common/api/connectors/index.ts +++ b/x-pack/plugins/cases/common/api/connectors/index.ts @@ -38,6 +38,8 @@ export enum ConnectorTypes { none = '.none', } +export const connectorTypes = Object.values(ConnectorTypes); + const ConnectorJiraTypeFieldsRt = rt.type({ type: rt.literal(ConnectorTypes.jira), fields: rt.union([JiraFieldsRT, rt.null]), diff --git a/x-pack/plugins/cases/public/components/configure_cases/mapping.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/mapping.test.tsx index 0a1da1219342ed..3cf01be40eb4ae 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/mapping.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/mapping.test.tsx @@ -50,7 +50,7 @@ describe('Mapping', () => { wrappingComponent: TestProviders, }); expect(wrapper.find('[data-test-subj="field-mapping-desc"]').first().text()).toBe( - 'Field mappings require an established connection to ServiceNow ITSM. Please check your connection credentials.' + 'Failed to retrieve mappings for ServiceNow ITSM.' ); }); }); diff --git a/x-pack/plugins/cases/public/components/configure_cases/translations.ts b/x-pack/plugins/cases/public/components/configure_cases/translations.ts index 2fb2133ba470c1..a379b03a4f675f 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/translations.ts +++ b/x-pack/plugins/cases/public/components/configure_cases/translations.ts @@ -102,8 +102,7 @@ export const FIELD_MAPPING_DESC = (thirdPartyName: string): string => { export const FIELD_MAPPING_DESC_ERR = (thirdPartyName: string): string => { return i18n.translate('xpack.cases.configureCases.fieldMappingDescErr', { values: { thirdPartyName }, - defaultMessage: - 'Field mappings require an established connection to { thirdPartyName }. Please check your connection credentials.', + defaultMessage: 'Failed to retrieve mappings for { thirdPartyName }.', }); }; export const EDIT_FIELD_MAPPING_TITLE = (thirdPartyName: string): string => { diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index dd527122d06168..c232f73c2a2351 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -25,6 +25,7 @@ import { createCaseError, flattenCaseSavedObject, getAlertInfoFromComments } fro import { ENABLE_CASE_CONNECTOR } from '../../../common/constants'; import { CasesClient, CasesClientArgs, CasesClientInternal } from '..'; import { Operations } from '../../authorization'; +import { casesConnectors } from '../../connectors'; /** * Returns true if the case should be closed based on the configuration settings and whether the case @@ -110,8 +111,7 @@ export const push = async ( }); const connectorMappings = await casesClientInternal.configuration.getMappings({ - connectorId: connector.id, - connectorType: connector.actionTypeId, + connector: theCase.connector, }); if (connectorMappings.length === 0) { @@ -125,6 +125,7 @@ export const push = async ( connector: connector as ActionConnector, mappings: connectorMappings[0].attributes.mappings, alerts, + casesConnectors, }); const pushRes = await actionsClient.execute({ diff --git a/x-pack/plugins/cases/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts index 9f18fa4931e62e..bfd5d1279420be 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.test.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts @@ -30,6 +30,7 @@ import { } from './utils'; import { flattenCaseSavedObject } from '../../common'; import { SECURITY_SOLUTION_OWNER } from '../../../common'; +import { casesConnectors } from '../../connectors'; const formatComment = { commentId: commentObj.id, @@ -443,6 +444,7 @@ describe('utils', () => { connector, mappings, alerts: [], + casesConnectors, }); expect(res).toEqual({ @@ -471,6 +473,7 @@ describe('utils', () => { connector, mappings, alerts: [], + casesConnectors, }); expect(res.comments).toEqual([ @@ -501,6 +504,7 @@ describe('utils', () => { }, ], alerts: [], + casesConnectors, }); expect(res.comments).toEqual([]); @@ -531,6 +535,7 @@ describe('utils', () => { }, ], alerts: [], + casesConnectors, }); expect(res.comments).toEqual([ @@ -561,6 +566,7 @@ describe('utils', () => { connector, mappings, alerts: [], + casesConnectors, }); expect(res.comments).toEqual([ @@ -595,6 +601,7 @@ describe('utils', () => { connector, mappings, alerts: [], + casesConnectors, }); expect(res).toEqual({ @@ -626,6 +633,7 @@ describe('utils', () => { connector, mappings, alerts: [], + casesConnectors, }).catch((e) => { expect(e).not.toBeNull(); expect(e).toEqual( @@ -645,6 +653,7 @@ describe('utils', () => { connector: { ...connector, actionTypeId: 'not-supported' }, mappings, alerts: [], + casesConnectors, }).catch((e) => { expect(e).not.toBeNull(); expect(e).toEqual(new Error('Invalid external service')); diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts index ebcc5a07b4edde..d920c517a00044 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.ts @@ -12,17 +12,15 @@ import { CaseFullExternalService, CaseResponse, CaseUserActionsResponse, - CommentAttributes, - CommentRequestAlertType, - CommentRequestUserType, CommentResponse, CommentResponseAlertsType, CommentType, ConnectorMappingsAttributes, - ConnectorTypes, + CommentAttributes, + CommentRequestUserType, + CommentRequestAlertType, } from '../../../common'; import { ActionsClient } from '../../../../actions/server'; -import { externalServiceFormatters, FormatterConnectorTypes } from '../../connectors'; import { CasesClientGetAlertsResponse } from '../../client/alerts/types'; import { BasicParams, @@ -39,6 +37,7 @@ import { TransformFieldsArgs, } from './types'; import { getAlertIds } from '../utils'; +import { CasesConnectorsMap } from '../../connectors'; interface CreateIncidentArgs { actionsClient: ActionsClient; @@ -47,6 +46,7 @@ interface CreateIncidentArgs { connector: ActionConnector; mappings: ConnectorMappingsAttributes[]; alerts: CasesClientGetAlertsResponse; + casesConnectors: CasesConnectorsMap; } export const getLatestPushInfo = ( @@ -70,9 +70,6 @@ export const getLatestPushInfo = ( return null; }; -const isConnectorSupported = (connectorId: string): connectorId is FormatterConnectorTypes => - Object.values(ConnectorTypes).includes(connectorId as ConnectorTypes); - const getCommentContent = (comment: CommentResponse): string => { if (comment.type === CommentType.user) { return comment.comment; @@ -99,6 +96,7 @@ export const createIncident = async ({ connector, mappings, alerts, + casesConnectors, }: CreateIncidentArgs): Promise => { const { comments: caseComments, @@ -110,20 +108,15 @@ export const createIncident = async ({ updated_by: updatedBy, } = theCase; - if (!isConnectorSupported(connector.actionTypeId)) { - throw new Error('Invalid external service'); - } - const params = { title, description, createdAt, createdBy, updatedAt, updatedBy }; const latestPushInfo = getLatestPushInfo(connector.id, userActions); const externalId = latestPushInfo?.pushedInfo?.external_id ?? null; const defaultPipes = externalId ? ['informationUpdated'] : ['informationCreated']; let currentIncident: ExternalServiceParams | undefined; - const externalServiceFields = externalServiceFormatters[connector.actionTypeId].format( - theCase, - alerts - ); + const externalServiceFields = + casesConnectors.get(connector.actionTypeId)?.format(theCase, alerts) ?? {}; + let incident: Partial = { ...externalServiceFields }; if (externalId) { diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts index d95667d5eee047..2f486556e4ae54 100644 --- a/x-pack/plugins/cases/server/client/configure/client.ts +++ b/x-pack/plugins/cases/server/client/configure/client.ts @@ -26,7 +26,6 @@ import { excess, GetConfigureFindRequest, GetConfigureFindRequestRt, - GetFieldsResponse, throwErrors, CasesConfigurationsResponse, CaseConfigurationsResponseRt, @@ -41,7 +40,6 @@ import { } from '../../common'; import { CasesClientInternal } from '../client_internal'; import { CasesClientArgs } from '../types'; -import { getFields } from './get_fields'; import { getMappings } from './get_mappings'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -49,12 +47,7 @@ import { FindActionResult } from '../../../../actions/server/types'; import { ActionType } from '../../../../actions/common'; import { Operations } from '../../authorization'; import { combineAuthorizedAndOwnerFilter } from '../utils'; -import { - ConfigurationGetFields, - MappingsArgs, - CreateMappingsArgs, - UpdateMappingsArgs, -} from './types'; +import { MappingsArgs, CreateMappingsArgs, UpdateMappingsArgs } from './types'; import { createMappings } from './create_mappings'; import { updateMappings } from './update_mappings'; import { @@ -69,7 +62,6 @@ import { * @ignore */ export interface InternalConfigureSubClient { - getFields(params: ConfigurationGetFields): Promise; getMappings( params: MappingsArgs ): Promise['saved_objects']>; @@ -116,12 +108,9 @@ export const createInternalConfigurationSubClient = ( casesClientInternal: CasesClientInternal ): InternalConfigureSubClient => { const configureSubClient: InternalConfigureSubClient = { - getFields: (params: ConfigurationGetFields) => getFields(params, clientArgs), getMappings: (params: MappingsArgs) => getMappings(params, clientArgs), - createMappings: (params: CreateMappingsArgs) => - createMappings(params, clientArgs, casesClientInternal), - updateMappings: (params: UpdateMappingsArgs) => - updateMappings(params, clientArgs, casesClientInternal), + createMappings: (params: CreateMappingsArgs) => createMappings(params, clientArgs), + updateMappings: (params: UpdateMappingsArgs) => updateMappings(params, clientArgs), }; return Object.freeze(configureSubClient); @@ -194,8 +183,7 @@ async function get( if (connector != null) { try { mappings = await casesClientInternal.configuration.getMappings({ - connectorId: connector.id, - connectorType: connector.type, + connector: transformESConnectorToCaseConnector(connector), }); } catch (e) { error = e.isBoom @@ -303,22 +291,22 @@ async function update( try { const resMappings = await casesClientInternal.configuration.getMappings({ - connectorId: connector != null ? connector.id : configuration.attributes.connector.id, - connectorType: connector != null ? connector.type : configuration.attributes.connector.type, + connector: + connector != null + ? connector + : transformESConnectorToCaseConnector(configuration.attributes.connector), }); mappings = resMappings.length > 0 ? resMappings[0].attributes.mappings : []; if (connector != null) { if (resMappings.length !== 0) { mappings = await casesClientInternal.configuration.updateMappings({ - connectorId: connector.id, - connectorType: connector.type, + connector, mappingId: resMappings[0].id, }); } else { mappings = await casesClientInternal.configuration.createMappings({ - connectorId: connector.id, - connectorType: connector.type, + connector, owner: configuration.attributes.owner, }); } @@ -326,9 +314,9 @@ async function update( } catch (e) { error = e.isBoom ? e.output.payload.message - : `Error connecting to ${ + : `Error creating mapping for ${ connector != null ? connector.name : configuration.attributes.connector.name - } instance`; + }`; } const patch = await caseConfigureService.patch({ @@ -429,14 +417,13 @@ async function create( try { mappings = await casesClientInternal.configuration.createMappings({ - connectorId: configuration.connector.id, - connectorType: configuration.connector.type, + connector: configuration.connector, owner: configuration.owner, }); } catch (e) { error = e.isBoom ? e.output.payload.message - : `Error connecting to ${configuration.connector.name} instance`; + : `Error creating mapping for ${configuration.connector.name}`; } const post = await caseConfigureService.post({ diff --git a/x-pack/plugins/cases/server/client/configure/create_mappings.ts b/x-pack/plugins/cases/server/client/configure/create_mappings.ts index b01f10d7a9e43b..bb4c32ae57071a 100644 --- a/x-pack/plugins/cases/server/client/configure/create_mappings.ts +++ b/x-pack/plugins/cases/server/client/configure/create_mappings.ts @@ -5,40 +5,33 @@ * 2.0. */ -import { ConnectorMappingsAttributes, ConnectorTypes } from '../../../common/api'; +import { ConnectorMappingsAttributes } from '../../../common/api'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; import { createCaseError } from '../../common/error'; -import { CasesClientArgs, CasesClientInternal } from '..'; +import { CasesClientArgs } from '..'; import { CreateMappingsArgs } from './types'; +import { casesConnectors } from '../../connectors'; export const createMappings = async ( - { connectorType, connectorId, owner }: CreateMappingsArgs, - clientArgs: CasesClientArgs, - casesClientInternal: CasesClientInternal + { connector, owner }: CreateMappingsArgs, + clientArgs: CasesClientArgs ): Promise => { const { unsecuredSavedObjectsClient, connectorMappingsService, logger } = clientArgs; try { - if (connectorType === ConnectorTypes.none) { - return []; - } - - const res = await casesClientInternal.configuration.getFields({ - connectorId, - connectorType, - }); + const mappings = casesConnectors.get(connector.type)?.getMapping() ?? []; const theMapping = await connectorMappingsService.post({ unsecuredSavedObjectsClient, attributes: { - mappings: res.defaultMappings, + mappings, owner, }, references: [ { type: ACTION_SAVED_OBJECT_TYPE, name: `associated-${ACTION_SAVED_OBJECT_TYPE}`, - id: connectorId, + id: connector.id, }, ], }); @@ -46,7 +39,7 @@ export const createMappings = async ( return theMapping.attributes.mappings; } catch (error) { throw createCaseError({ - message: `Failed to create mapping connector id: ${connectorId} type: ${connectorType}: ${error}`, + message: `Failed to create mapping connector id: ${connector.id} type: ${connector.type}: ${error}`, error, logger, }); diff --git a/x-pack/plugins/cases/server/client/configure/get_fields.ts b/x-pack/plugins/cases/server/client/configure/get_fields.ts deleted file mode 100644 index 78627cfaca6ed1..00000000000000 --- a/x-pack/plugins/cases/server/client/configure/get_fields.ts +++ /dev/null @@ -1,37 +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 Boom from '@hapi/boom'; - -import { GetFieldsResponse } from '../../../common/api'; -import { createDefaultMapping, formatFields } from './utils'; -import { CasesClientArgs } from '..'; - -interface ConfigurationGetFields { - connectorId: string; - connectorType: string; -} - -export const getFields = async ( - { connectorType, connectorId }: ConfigurationGetFields, - clientArgs: CasesClientArgs -): Promise => { - const { actionsClient } = clientArgs; - const results = await actionsClient.execute({ - actionId: connectorId, - params: { - subAction: 'getFields', - subActionParams: {}, - }, - }); - if (results.status === 'error') { - throw Boom.failedDependency(results.serviceMessage); - } - const fields = formatFields(results.data, connectorType); - - return { fields, defaultMappings: createDefaultMapping(fields, connectorType) }; -}; diff --git a/x-pack/plugins/cases/server/client/configure/get_mappings.ts b/x-pack/plugins/cases/server/client/configure/get_mappings.ts index 3489c06b1da5ad..2fa0e8454bacfa 100644 --- a/x-pack/plugins/cases/server/client/configure/get_mappings.ts +++ b/x-pack/plugins/cases/server/client/configure/get_mappings.ts @@ -6,29 +6,25 @@ */ import { SavedObjectsFindResponse } from 'kibana/server'; -import { ConnectorMappings, ConnectorTypes } from '../../../common/api'; +import { ConnectorMappings } from '../../../common/api'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; import { createCaseError } from '../../common/error'; import { CasesClientArgs } from '..'; import { MappingsArgs } from './types'; export const getMappings = async ( - { connectorType, connectorId }: MappingsArgs, + { connector }: MappingsArgs, clientArgs: CasesClientArgs ): Promise['saved_objects']> => { const { unsecuredSavedObjectsClient, connectorMappingsService, logger } = clientArgs; try { - if (connectorType === ConnectorTypes.none) { - return []; - } - const myConnectorMappings = await connectorMappingsService.find({ unsecuredSavedObjectsClient, options: { hasReference: { type: ACTION_SAVED_OBJECT_TYPE, - id: connectorId, + id: connector.id, }, }, }); @@ -36,7 +32,7 @@ export const getMappings = async ( return myConnectorMappings.saved_objects; } catch (error) { throw createCaseError({ - message: `Failed to retrieve mapping connector id: ${connectorId} type: ${connectorType}: ${error}`, + message: `Failed to retrieve mapping connector id: ${connector.id} type: ${connector.type}: ${error}`, error, logger, }); diff --git a/x-pack/plugins/cases/server/client/configure/mock.ts b/x-pack/plugins/cases/server/client/configure/mock.ts deleted file mode 100644 index ad982a5cc12434..00000000000000 --- a/x-pack/plugins/cases/server/client/configure/mock.ts +++ /dev/null @@ -1,657 +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 { ConnectorField, ConnectorMappingsAttributes, ConnectorTypes } from '../../../common'; -import { - JiraGetFieldsResponse, - ResilientGetFieldsResponse, - ServiceNowGetFieldsResponse, -} from './utils.test'; -interface TestMappings { - [key: string]: ConnectorMappingsAttributes[]; -} -export const mappings: TestMappings = { - [ConnectorTypes.jira]: [ - { - source: 'title', - target: 'summary', - action_type: 'overwrite', - }, - { - source: 'description', - target: 'description', - action_type: 'overwrite', - }, - { - source: 'comments', - target: 'comments', - action_type: 'append', - }, - ], - [`${ConnectorTypes.jira}-alt`]: [ - { - source: 'title', - target: 'title', - action_type: 'overwrite', - }, - { - source: 'description', - target: 'description', - action_type: 'overwrite', - }, - { - source: 'comments', - target: 'comments', - action_type: 'append', - }, - ], - [ConnectorTypes.resilient]: [ - { - source: 'title', - target: 'name', - action_type: 'overwrite', - }, - { - source: 'description', - target: 'description', - action_type: 'overwrite', - }, - { - source: 'comments', - target: 'comments', - action_type: 'append', - }, - ], - [ConnectorTypes.serviceNowITSM]: [ - { - source: 'title', - target: 'short_description', - action_type: 'overwrite', - }, - { - source: 'description', - target: 'description', - action_type: 'overwrite', - }, - { - source: 'comments', - target: 'work_notes', - action_type: 'append', - }, - ], - [ConnectorTypes.serviceNowSIR]: [ - { - source: 'title', - target: 'short_description', - action_type: 'overwrite', - }, - { - source: 'description', - target: 'description', - action_type: 'overwrite', - }, - { - source: 'comments', - target: 'work_notes', - action_type: 'append', - }, - ], -}; - -const jiraFields: JiraGetFieldsResponse = { - summary: { - required: true, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'string', - }, - name: 'Summary', - }, - issuetype: { - required: true, - allowedValues: [ - { - self: 'https://siem-kibana.atlassian.net/rest/api/2/issuetype/10023', - id: '10023', - description: 'A problem or error.', - iconUrl: - 'https://siem-kibana.atlassian.net/secure/viewavatar?size=medium&avatarId=10303&avatarType=issuetype', - name: 'Bug', - subtask: false, - avatarId: 10303, - }, - ], - defaultValue: {}, - schema: { - type: 'issuetype', - }, - name: 'Issue Type', - }, - attachment: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'array', - items: 'attachment', - }, - name: 'Attachment', - }, - duedate: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'date', - }, - name: 'Due date', - }, - description: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'string', - }, - name: 'Description', - }, - project: { - required: true, - allowedValues: [ - { - self: 'https://siem-kibana.atlassian.net/rest/api/2/project/10015', - id: '10015', - key: 'RJ2', - name: 'RJ2', - projectTypeKey: 'business', - simplified: false, - avatarUrls: { - '48x48': - 'https://siem-kibana.atlassian.net/secure/projectavatar?pid=10015&avatarId=10412', - '24x24': - 'https://siem-kibana.atlassian.net/secure/projectavatar?size=small&s=small&pid=10015&avatarId=10412', - '16x16': - 'https://siem-kibana.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10015&avatarId=10412', - '32x32': - 'https://siem-kibana.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10015&avatarId=10412', - }, - }, - ], - defaultValue: {}, - schema: { - type: 'project', - }, - name: 'Project', - }, - assignee: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'user', - }, - name: 'Assignee', - }, - labels: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'array', - items: 'string', - }, - name: 'Labels', - }, -}; -const resilientFields: ResilientGetFieldsResponse = [ - { input_type: 'text', name: 'addr', read_only: false, text: 'Address' }, - { - input_type: 'boolean', - name: 'alberta_health_risk_assessment', - read_only: false, - text: 'Alberta Health Risk Assessment', - }, - { input_type: 'number', name: 'hard_liability', read_only: true, text: 'Assessed Liability' }, - { input_type: 'text', name: 'city', read_only: false, text: 'City' }, - { input_type: 'select', name: 'country', read_only: false, text: 'Country/Region' }, - { input_type: 'select_owner', name: 'creator_id', read_only: true, text: 'Created By' }, - { input_type: 'select', name: 'crimestatus_id', read_only: false, text: 'Criminal Activity' }, - { input_type: 'boolean', name: 'data_encrypted', read_only: false, text: 'Data Encrypted' }, - { input_type: 'select', name: 'data_format', read_only: false, text: 'Data Format' }, - { input_type: 'datetimepicker', name: 'end_date', read_only: true, text: 'Date Closed' }, - { input_type: 'datetimepicker', name: 'create_date', read_only: true, text: 'Date Created' }, - { - input_type: 'datetimepicker', - name: 'determined_date', - read_only: false, - text: 'Date Determined', - }, - { - input_type: 'datetimepicker', - name: 'discovered_date', - read_only: false, - required: 'always', - text: 'Date Discovered', - }, - { input_type: 'datetimepicker', name: 'start_date', read_only: false, text: 'Date Occurred' }, - { input_type: 'select', name: 'exposure_dept_id', read_only: false, text: 'Department' }, - { input_type: 'textarea', name: 'description', read_only: false, text: 'Description' }, - { input_type: 'boolean', name: 'employee_involved', read_only: false, text: 'Employee Involved' }, - { input_type: 'boolean', name: 'data_contained', read_only: false, text: 'Exposure Resolved' }, - { input_type: 'select', name: 'exposure_type_id', read_only: false, text: 'Exposure Type' }, - { - input_type: 'multiselect', - name: 'gdpr_breach_circumstances', - read_only: false, - text: 'GDPR Breach Circumstances', - }, - { input_type: 'select', name: 'gdpr_breach_type', read_only: false, text: 'GDPR Breach Type' }, - { - input_type: 'textarea', - name: 'gdpr_breach_type_comment', - read_only: false, - text: 'GDPR Breach Type Comment', - }, - { input_type: 'select', name: 'gdpr_consequences', read_only: false, text: 'GDPR Consequences' }, - { - input_type: 'textarea', - name: 'gdpr_consequences_comment', - read_only: false, - text: 'GDPR Consequences Comment', - }, - { - input_type: 'select', - name: 'gdpr_final_assessment', - read_only: false, - text: 'GDPR Final Assessment', - }, - { - input_type: 'textarea', - name: 'gdpr_final_assessment_comment', - read_only: false, - text: 'GDPR Final Assessment Comment', - }, - { - input_type: 'select', - name: 'gdpr_identification', - read_only: false, - text: 'GDPR Identification', - }, - { - input_type: 'textarea', - name: 'gdpr_identification_comment', - read_only: false, - text: 'GDPR Identification Comment', - }, - { - input_type: 'select', - name: 'gdpr_personal_data', - read_only: false, - text: 'GDPR Personal Data', - }, - { - input_type: 'textarea', - name: 'gdpr_personal_data_comment', - read_only: false, - text: 'GDPR Personal Data Comment', - }, - { - input_type: 'boolean', - name: 'gdpr_subsequent_notification', - read_only: false, - text: 'GDPR Subsequent Notification', - }, - { input_type: 'number', name: 'id', read_only: true, text: 'ID' }, - { input_type: 'boolean', name: 'impact_likely', read_only: false, text: 'Impact Likely' }, - { - input_type: 'boolean', - name: 'ny_impact_likely', - read_only: false, - text: 'Impact Likely for New York', - }, - { - input_type: 'boolean', - name: 'or_impact_likely', - read_only: false, - text: 'Impact Likely for Oregon', - }, - { - input_type: 'boolean', - name: 'wa_impact_likely', - read_only: false, - text: 'Impact Likely for Washington', - }, - { input_type: 'boolean', name: 'confirmed', read_only: false, text: 'Incident Disposition' }, - { input_type: 'multiselect', name: 'incident_type_ids', read_only: false, text: 'Incident Type' }, - { - input_type: 'text', - name: 'exposure_individual_name', - read_only: false, - text: 'Individual Name', - }, - { - input_type: 'select', - name: 'harmstatus_id', - read_only: false, - text: 'Is harm/risk/misuse foreseeable?', - }, - { input_type: 'text', name: 'jurisdiction_name', read_only: false, text: 'Jurisdiction' }, - { - input_type: 'datetimepicker', - name: 'inc_last_modified_date', - read_only: true, - text: 'Last Modified', - }, - { - input_type: 'multiselect', - name: 'gdpr_lawful_data_processing_categories', - read_only: false, - text: 'Lawful Data Processing Categories', - }, - { input_type: 'multiselect_members', name: 'members', read_only: false, text: 'Members' }, - { input_type: 'text', name: 'name', read_only: false, required: 'always', text: 'Name' }, - { input_type: 'boolean', name: 'negative_pr_likely', read_only: false, text: 'Negative PR' }, - { input_type: 'datetimepicker', name: 'due_date', read_only: true, text: 'Next Due Date' }, - { - input_type: 'multiselect', - name: 'nist_attack_vectors', - read_only: false, - text: 'NIST Attack Vectors', - }, - { input_type: 'select', name: 'org_handle', read_only: true, text: 'Organization' }, - { input_type: 'select_owner', name: 'owner_id', read_only: false, text: 'Owner' }, - { input_type: 'select', name: 'phase_id', read_only: true, text: 'Phase' }, - { - input_type: 'select', - name: 'pipeda_other_factors', - read_only: false, - text: 'PIPEDA Other Factors', - }, - { - input_type: 'textarea', - name: 'pipeda_other_factors_comment', - read_only: false, - text: 'PIPEDA Other Factors Comment', - }, - { - input_type: 'select', - name: 'pipeda_overall_assessment', - read_only: false, - text: 'PIPEDA Overall Assessment', - }, - { - input_type: 'textarea', - name: 'pipeda_overall_assessment_comment', - read_only: false, - text: 'PIPEDA Overall Assessment Comment', - }, - { - input_type: 'select', - name: 'pipeda_probability_of_misuse', - read_only: false, - text: 'PIPEDA Probability of Misuse', - }, - { - input_type: 'textarea', - name: 'pipeda_probability_of_misuse_comment', - read_only: false, - text: 'PIPEDA Probability of Misuse Comment', - }, - { - input_type: 'select', - name: 'pipeda_sensitivity_of_pi', - read_only: false, - text: 'PIPEDA Sensitivity of PI', - }, - { - input_type: 'textarea', - name: 'pipeda_sensitivity_of_pi_comment', - read_only: false, - text: 'PIPEDA Sensitivity of PI Comment', - }, - { input_type: 'text', name: 'reporter', read_only: false, text: 'Reporting Individual' }, - { - input_type: 'select', - name: 'resolution_id', - read_only: false, - required: 'close', - text: 'Resolution', - }, - { - input_type: 'textarea', - name: 'resolution_summary', - read_only: false, - required: 'close', - text: 'Resolution Summary', - }, - { input_type: 'select', name: 'gdpr_harm_risk', read_only: false, text: 'Risk of Harm' }, - { input_type: 'select', name: 'severity_code', read_only: false, text: 'Severity' }, - { input_type: 'boolean', name: 'inc_training', read_only: true, text: 'Simulation' }, - { input_type: 'multiselect', name: 'data_source_ids', read_only: false, text: 'Source of Data' }, - { input_type: 'select', name: 'state', read_only: false, text: 'State' }, - { input_type: 'select', name: 'plan_status', read_only: false, text: 'Status' }, - { input_type: 'select', name: 'exposure_vendor_id', read_only: false, text: 'Vendor' }, - { - input_type: 'boolean', - name: 'data_compromised', - read_only: false, - text: 'Was personal information or personal data involved?', - }, - { - input_type: 'select', - name: 'workspace', - read_only: false, - required: 'always', - text: 'Workspace', - }, - { input_type: 'text', name: 'zip', read_only: false, text: 'Zip' }, -]; -const serviceNowFields: ServiceNowGetFieldsResponse = [ - { - column_label: 'Approval', - mandatory: 'false', - max_length: '40', - element: 'approval', - }, - { - column_label: 'Close notes', - mandatory: 'false', - max_length: '4000', - element: 'close_notes', - }, - { - column_label: 'Contact type', - mandatory: 'false', - max_length: '40', - element: 'contact_type', - }, - { - column_label: 'Correlation display', - mandatory: 'false', - max_length: '100', - element: 'correlation_display', - }, - { - column_label: 'Correlation ID', - mandatory: 'false', - max_length: '100', - element: 'correlation_id', - }, - { - column_label: 'Description', - mandatory: 'false', - max_length: '4000', - element: 'description', - }, - { - column_label: 'Number', - mandatory: 'false', - max_length: '40', - element: 'number', - }, - { - column_label: 'Short description', - mandatory: 'false', - max_length: '160', - element: 'short_description', - }, - { - column_label: 'Created by', - mandatory: 'false', - max_length: '40', - element: 'sys_created_by', - }, - { - column_label: 'Updated by', - mandatory: 'false', - max_length: '40', - element: 'sys_updated_by', - }, - { - column_label: 'Upon approval', - mandatory: 'false', - max_length: '40', - element: 'upon_approval', - }, - { - column_label: 'Upon reject', - mandatory: 'false', - max_length: '40', - element: 'upon_reject', - }, -]; -interface FormatFieldsTestData { - expected: ConnectorField[]; - fields: JiraGetFieldsResponse | ResilientGetFieldsResponse | ServiceNowGetFieldsResponse; - type: ConnectorTypes; -} -export const formatFieldsTestData: FormatFieldsTestData[] = [ - { - expected: [ - { id: 'summary', name: 'Summary', required: true, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'text' }, - ], - fields: jiraFields, - type: ConnectorTypes.jira, - }, - { - expected: [ - { id: 'addr', name: 'Address', required: false, type: 'text' }, - { id: 'city', name: 'City', required: false, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'textarea' }, - { - id: 'gdpr_breach_type_comment', - name: 'GDPR Breach Type Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_consequences_comment', - name: 'GDPR Consequences Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_final_assessment_comment', - name: 'GDPR Final Assessment Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_identification_comment', - name: 'GDPR Identification Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_personal_data_comment', - name: 'GDPR Personal Data Comment', - required: false, - type: 'textarea', - }, - { id: 'exposure_individual_name', name: 'Individual Name', required: false, type: 'text' }, - { id: 'jurisdiction_name', name: 'Jurisdiction', required: false, type: 'text' }, - { id: 'name', name: 'Name', required: true, type: 'text' }, - { - id: 'pipeda_other_factors_comment', - name: 'PIPEDA Other Factors Comment', - required: false, - type: 'textarea', - }, - { - id: 'pipeda_overall_assessment_comment', - name: 'PIPEDA Overall Assessment Comment', - required: false, - type: 'textarea', - }, - { - id: 'pipeda_probability_of_misuse_comment', - name: 'PIPEDA Probability of Misuse Comment', - required: false, - type: 'textarea', - }, - { - id: 'pipeda_sensitivity_of_pi_comment', - name: 'PIPEDA Sensitivity of PI Comment', - required: false, - type: 'textarea', - }, - { id: 'reporter', name: 'Reporting Individual', required: false, type: 'text' }, - { id: 'resolution_summary', name: 'Resolution Summary', required: false, type: 'textarea' }, - { id: 'zip', name: 'Zip', required: false, type: 'text' }, - ], - fields: resilientFields, - type: ConnectorTypes.resilient, - }, - { - expected: [ - { id: 'approval', name: 'Approval', required: false, type: 'text' }, - { id: 'close_notes', name: 'Close notes', required: false, type: 'textarea' }, - { id: 'contact_type', name: 'Contact type', required: false, type: 'text' }, - { id: 'correlation_display', name: 'Correlation display', required: false, type: 'text' }, - { id: 'correlation_id', name: 'Correlation ID', required: false, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'textarea' }, - { id: 'number', name: 'Number', required: false, type: 'text' }, - { id: 'short_description', name: 'Short description', required: false, type: 'text' }, - { id: 'sys_created_by', name: 'Created by', required: false, type: 'text' }, - { id: 'sys_updated_by', name: 'Updated by', required: false, type: 'text' }, - { id: 'upon_approval', name: 'Upon approval', required: false, type: 'text' }, - { id: 'upon_reject', name: 'Upon reject', required: false, type: 'text' }, - ], - fields: serviceNowFields, - type: ConnectorTypes.serviceNowITSM, - }, - { - expected: [ - { id: 'approval', name: 'Approval', required: false, type: 'text' }, - { id: 'close_notes', name: 'Close notes', required: false, type: 'textarea' }, - { id: 'contact_type', name: 'Contact type', required: false, type: 'text' }, - { id: 'correlation_display', name: 'Correlation display', required: false, type: 'text' }, - { id: 'correlation_id', name: 'Correlation ID', required: false, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'textarea' }, - { id: 'number', name: 'Number', required: false, type: 'text' }, - { id: 'short_description', name: 'Short description', required: false, type: 'text' }, - { id: 'sys_created_by', name: 'Created by', required: false, type: 'text' }, - { id: 'sys_updated_by', name: 'Updated by', required: false, type: 'text' }, - { id: 'upon_approval', name: 'Upon approval', required: false, type: 'text' }, - { id: 'upon_reject', name: 'Upon reject', required: false, type: 'text' }, - ], - fields: serviceNowFields, - type: ConnectorTypes.serviceNowSIR, - }, -]; -export const mockGetFieldsResponse = { - status: 'ok', - data: jiraFields, - actionId: '123', -}; - -export const actionsErrResponse = { - status: 'error', - serviceMessage: 'this is an actions error', -}; diff --git a/x-pack/plugins/cases/server/client/configure/types.ts b/x-pack/plugins/cases/server/client/configure/types.ts index a34251690db48d..aca3436c59082e 100644 --- a/x-pack/plugins/cases/server/client/configure/types.ts +++ b/x-pack/plugins/cases/server/client/configure/types.ts @@ -5,9 +5,10 @@ * 2.0. */ +import { CaseConnector } from '../../../common'; + export interface MappingsArgs { - connectorType: string; - connectorId: string; + connector: CaseConnector; } export interface CreateMappingsArgs extends MappingsArgs { @@ -17,8 +18,3 @@ export interface CreateMappingsArgs extends MappingsArgs { export interface UpdateMappingsArgs extends MappingsArgs { mappingId: string; } - -export interface ConfigurationGetFields { - connectorId: string; - connectorType: string; -} diff --git a/x-pack/plugins/cases/server/client/configure/update_mappings.ts b/x-pack/plugins/cases/server/client/configure/update_mappings.ts index 7eccf4cbbe5829..3d529e51e75614 100644 --- a/x-pack/plugins/cases/server/client/configure/update_mappings.ts +++ b/x-pack/plugins/cases/server/client/configure/update_mappings.ts @@ -5,40 +5,33 @@ * 2.0. */ -import { ConnectorMappingsAttributes, ConnectorTypes } from '../../../common/api'; +import { ConnectorMappingsAttributes } from '../../../common/api'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; import { createCaseError } from '../../common/error'; -import { CasesClientArgs, CasesClientInternal } from '..'; +import { CasesClientArgs } from '..'; import { UpdateMappingsArgs } from './types'; +import { casesConnectors } from '../../connectors'; export const updateMappings = async ( - { connectorType, connectorId, mappingId }: UpdateMappingsArgs, - clientArgs: CasesClientArgs, - casesClientInternal: CasesClientInternal + { connector, mappingId }: UpdateMappingsArgs, + clientArgs: CasesClientArgs ): Promise => { const { unsecuredSavedObjectsClient, connectorMappingsService, logger } = clientArgs; try { - if (connectorType === ConnectorTypes.none) { - return []; - } - - const res = await casesClientInternal.configuration.getFields({ - connectorId, - connectorType, - }); + const mappings = casesConnectors.get(connector.type)?.getMapping() ?? []; const theMapping = await connectorMappingsService.update({ unsecuredSavedObjectsClient, mappingId, attributes: { - mappings: res.defaultMappings, + mappings, }, references: [ { type: ACTION_SAVED_OBJECT_TYPE, name: `associated-${ACTION_SAVED_OBJECT_TYPE}`, - id: connectorId, + id: connector.id, }, ], }); @@ -46,7 +39,7 @@ export const updateMappings = async ( return theMapping.attributes.mappings ?? []; } catch (error) { throw createCaseError({ - message: `Failed to create mapping connector id: ${connectorId} type: ${connectorType}: ${error}`, + message: `Failed to create mapping connector id: ${connector.id} type: ${connector.type}: ${error}`, error, logger, }); diff --git a/x-pack/plugins/cases/server/client/configure/utils.test.ts b/x-pack/plugins/cases/server/client/configure/utils.test.ts deleted file mode 100644 index 41d62f5a9b91f9..00000000000000 --- a/x-pack/plugins/cases/server/client/configure/utils.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { - JiraGetFieldsResponse, - ResilientGetFieldsResponse, - ServiceNowGetFieldsResponse, -} from '../../../../actions/server/types'; -import { createDefaultMapping, formatFields } from './utils'; -import { mappings, formatFieldsTestData } from './mock'; - -describe('client/configure/utils', () => { - describe('formatFields', () => { - formatFieldsTestData.forEach(({ expected, fields, type }) => { - it(`normalizes ${type} fields to common type ConnectorField`, () => { - const result = formatFields(fields, type); - expect(result).toEqual(expected); - }); - }); - }); - describe('createDefaultMapping', () => { - formatFieldsTestData.forEach(({ expected, fields, type }) => { - it(`normalizes ${type} fields to common type ConnectorField`, () => { - const result = createDefaultMapping(expected, type); - expect(result).toEqual(mappings[type]); - }); - }); - }); -}); diff --git a/x-pack/plugins/cases/server/client/configure/utils.ts b/x-pack/plugins/cases/server/client/configure/utils.ts deleted file mode 100644 index 24efb6ca54b3a3..00000000000000 --- a/x-pack/plugins/cases/server/client/configure/utils.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ConnectorField, ConnectorMappingsAttributes, ConnectorTypes } from '../../../common'; -import { - JiraGetFieldsResponse, - ResilientGetFieldsResponse, - ServiceNowGetFieldsResponse, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../actions/server/types'; - -const normalizeJiraFields = (jiraFields: JiraGetFieldsResponse): ConnectorField[] => - Object.keys(jiraFields).reduce( - (acc, data) => - jiraFields[data].schema.type === 'string' - ? [ - ...acc, - { - id: data, - name: jiraFields[data].name, - required: jiraFields[data].required, - type: 'text', - }, - ] - : acc, - [] - ); - -const normalizeResilientFields = (resilientFields: ResilientGetFieldsResponse): ConnectorField[] => - resilientFields.reduce( - (acc: ConnectorField[], data) => - (data.input_type === 'textarea' || data.input_type === 'text') && !data.read_only - ? [ - ...acc, - { - id: data.name, - name: data.text, - required: data.required === 'always', - type: data.input_type, - }, - ] - : acc, - [] - ); -const normalizeServiceNowFields = (snFields: ServiceNowGetFieldsResponse): ConnectorField[] => - snFields.reduce( - (acc, data) => [ - ...acc, - { - id: data.element, - name: data.column_label, - required: data.mandatory === 'true', - type: parseFloat(data.max_length) > 160 ? 'textarea' : 'text', - }, - ], - [] - ); - -export const formatFields = (theData: unknown, theType: string): ConnectorField[] => { - switch (theType) { - case ConnectorTypes.jira: - return normalizeJiraFields(theData as JiraGetFieldsResponse); - case ConnectorTypes.resilient: - return normalizeResilientFields(theData as ResilientGetFieldsResponse); - case ConnectorTypes.serviceNowITSM: - return normalizeServiceNowFields(theData as ServiceNowGetFieldsResponse); - case ConnectorTypes.serviceNowSIR: - return normalizeServiceNowFields(theData as ServiceNowGetFieldsResponse); - default: - return []; - } -}; - -const getPreferredFields = (theType: string) => { - let title: string = ''; - let description: string = ''; - let comments: string = ''; - - if (theType === ConnectorTypes.jira) { - title = 'summary'; - description = 'description'; - comments = 'comments'; - } else if (theType === ConnectorTypes.resilient) { - title = 'name'; - description = 'description'; - comments = 'comments'; - } else if ( - theType === ConnectorTypes.serviceNowITSM || - theType === ConnectorTypes.serviceNowSIR - ) { - title = 'short_description'; - description = 'description'; - comments = 'work_notes'; - } - - return { title, description, comments }; -}; - -export const createDefaultMapping = ( - fields: ConnectorField[], - theType: string -): ConnectorMappingsAttributes[] => { - const { description, title, comments } = getPreferredFields(theType); - return [ - { - source: 'title', - target: title, - action_type: 'overwrite', - }, - { - source: 'description', - target: description, - action_type: 'overwrite', - }, - { - source: 'comments', - target: comments, - action_type: 'append', - }, - ]; -}; diff --git a/x-pack/plugins/cases/server/connectors/factory.ts b/x-pack/plugins/cases/server/connectors/factory.ts new file mode 100644 index 00000000000000..64e3e6f3eb225b --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/factory.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ConnectorTypes } from '../../common/api'; +import { getCaseConnector as getJiraCaseConnector } from './jira'; +import { getCaseConnector as getResilientCaseConnector } from './resilient'; +import { getServiceNowITSMCaseConnector, getServiceNowSIRCaseConnector } from './servicenow'; +import { ICasesConnector, CasesConnectorsMap } from './types'; + +const mapping: Record = { + [ConnectorTypes.jira]: getJiraCaseConnector(), + [ConnectorTypes.serviceNowITSM]: getServiceNowITSMCaseConnector(), + [ConnectorTypes.serviceNowSIR]: getServiceNowSIRCaseConnector(), + [ConnectorTypes.resilient]: getResilientCaseConnector(), + [ConnectorTypes.none]: null, +}; + +const isConnectorTypeSupported = (type: string): type is ConnectorTypes => + Object.values(ConnectorTypes).includes(type as ConnectorTypes); + +export const casesConnectors: CasesConnectorsMap = { + get: (type: string): ICasesConnector | undefined | null => + isConnectorTypeSupported(type) ? mapping[type] : undefined, +}; diff --git a/x-pack/plugins/cases/server/connectors/index.ts b/x-pack/plugins/cases/server/connectors/index.ts index 4b6f845a961f21..b5dc1cc4a8ff9e 100644 --- a/x-pack/plugins/cases/server/connectors/index.ts +++ b/x-pack/plugins/cases/server/connectors/index.ts @@ -7,20 +7,16 @@ import { RegisterConnectorsArgs, - ExternalServiceFormatterMapper, CommentSchemaType, ContextTypeGeneratedAlertType, ContextTypeAlertSchemaType, } from './types'; import { getActionType as getCaseConnector } from './case'; -import { serviceNowITSMExternalServiceFormatter } from './servicenow/itsm_formatter'; -import { serviceNowSIRExternalServiceFormatter } from './servicenow/sir_formatter'; -import { jiraExternalServiceFormatter } from './jira/external_service_formatter'; -import { resilientExternalServiceFormatter } from './resilient/external_service_formatter'; -import { CommentRequest, CommentType } from '../../common'; +import { CommentRequest, CommentType } from '../../common/api'; export * from './types'; export { transformConnectorComment } from './case'; +export { casesConnectors } from './factory'; /** * Separator used for creating a json parsable array from the mustache syntax that the alerting framework @@ -41,13 +37,6 @@ export const registerConnectors = ({ ); }; -export const externalServiceFormatters: ExternalServiceFormatterMapper = { - '.servicenow': serviceNowITSMExternalServiceFormatter, - '.servicenow-sir': serviceNowSIRExternalServiceFormatter, - '.jira': jiraExternalServiceFormatter, - '.resilient': resilientExternalServiceFormatter, -}; - export const isCommentGeneratedAlert = ( comment: CommentSchemaType | CommentRequest ): comment is ContextTypeGeneratedAlertType => { diff --git a/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.test.ts b/x-pack/plugins/cases/server/connectors/jira/format.test.ts similarity index 71% rename from x-pack/plugins/cases/server/connectors/jira/external_service_formatter.test.ts rename to x-pack/plugins/cases/server/connectors/jira/format.test.ts index f5d76aeddf3130..edca4cf68250ce 100644 --- a/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.test.ts +++ b/x-pack/plugins/cases/server/connectors/jira/format.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { CaseResponse } from '../../../common'; -import { jiraExternalServiceFormatter } from './external_service_formatter'; +import { CaseResponse } from '../../../common/api'; +import { format } from './format'; describe('Jira formatter', () => { const theCase = { @@ -15,21 +15,18 @@ describe('Jira formatter', () => { } as CaseResponse; it('it formats correctly', async () => { - const res = await jiraExternalServiceFormatter.format(theCase, []); + const res = await format(theCase, []); expect(res).toEqual({ ...theCase.connector.fields, labels: theCase.tags }); }); it('it formats correctly when fields do not exist ', async () => { const invalidFields = { tags: ['tag'], connector: { fields: null } } as CaseResponse; - const res = await jiraExternalServiceFormatter.format(invalidFields, []); + const res = await format(invalidFields, []); expect(res).toEqual({ priority: null, issueType: null, parent: null, labels: theCase.tags }); }); it('it replace white spaces with hyphens on tags', async () => { - const res = await jiraExternalServiceFormatter.format( - { ...theCase, tags: ['a tag with spaces'] }, - [] - ); + const res = await format({ ...theCase, tags: ['a tag with spaces'] }, []); expect(res).toEqual({ ...theCase.connector.fields, labels: ['a-tag-with-spaces'] }); }); }); diff --git a/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.ts b/x-pack/plugins/cases/server/connectors/jira/format.ts similarity index 59% rename from x-pack/plugins/cases/server/connectors/jira/external_service_formatter.ts rename to x-pack/plugins/cases/server/connectors/jira/format.ts index 15ee2fd468ddaa..4caa23634d8875 100644 --- a/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.ts +++ b/x-pack/plugins/cases/server/connectors/jira/format.ts @@ -5,14 +5,10 @@ * 2.0. */ -import { JiraFieldsType, ConnectorJiraTypeFields } from '../../../common'; -import { ExternalServiceFormatter } from '../types'; +import { ConnectorJiraTypeFields } from '../../../common/api'; +import { Format } from './types'; -interface ExternalServiceParams extends JiraFieldsType { - labels: string[]; -} - -const format: ExternalServiceFormatter['format'] = (theCase) => { +export const format: Format = (theCase, alerts) => { const { priority = null, issueType = null, parent = null } = (theCase.connector.fields as ConnectorJiraTypeFields['fields']) ?? {}; return { @@ -23,7 +19,3 @@ const format: ExternalServiceFormatter['format'] = (theCa parent, }; }; - -export const jiraExternalServiceFormatter: ExternalServiceFormatter = { - format, -}; diff --git a/x-pack/plugins/cases/server/connectors/jira/index.ts b/x-pack/plugins/cases/server/connectors/jira/index.ts new file mode 100644 index 00000000000000..9a2a00ac23b390 --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/jira/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. + */ + +import { getMapping } from './mapping'; +import { format } from './format'; +import { JiraCaseConnector } from './types'; + +export const getCaseConnector = (): JiraCaseConnector => ({ + getMapping, + format, +}); diff --git a/x-pack/plugins/cases/server/connectors/jira/mapping.ts b/x-pack/plugins/cases/server/connectors/jira/mapping.ts new file mode 100644 index 00000000000000..8f8a914b4e091a --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/jira/mapping.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GetMapping } from './types'; + +export const getMapping: GetMapping = () => { + return [ + { + source: 'title', + target: 'summary', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ]; +}; diff --git a/x-pack/plugins/cases/server/connectors/jira/types.ts b/x-pack/plugins/cases/server/connectors/jira/types.ts new file mode 100644 index 00000000000000..59d5741d381b96 --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/jira/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { JiraFieldsType } from '../../../common/api'; +import { ICasesConnector } from '../types'; + +interface ExternalServiceFormatterParams extends JiraFieldsType { + labels: string[]; +} + +export type JiraCaseConnector = ICasesConnector; +export type Format = ICasesConnector['format']; +export type GetMapping = ICasesConnector['getMapping']; diff --git a/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.test.ts b/x-pack/plugins/cases/server/connectors/resilient/format.test.ts similarity index 76% rename from x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.test.ts rename to x-pack/plugins/cases/server/connectors/resilient/format.test.ts index b7096179b0fab6..20ba0bc3789343 100644 --- a/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.test.ts +++ b/x-pack/plugins/cases/server/connectors/resilient/format.test.ts @@ -6,7 +6,7 @@ */ import { CaseResponse } from '../../../common'; -import { resilientExternalServiceFormatter } from './external_service_formatter'; +import { format } from './format'; describe('IBM Resilient formatter', () => { const theCase = { @@ -14,13 +14,13 @@ describe('IBM Resilient formatter', () => { } as CaseResponse; it('it formats correctly', async () => { - const res = await resilientExternalServiceFormatter.format(theCase, []); + const res = await format(theCase, []); expect(res).toEqual({ ...theCase.connector.fields }); }); it('it formats correctly when fields do not exist ', async () => { const invalidFields = { tags: ['a tag'], connector: { fields: null } } as CaseResponse; - const res = await resilientExternalServiceFormatter.format(invalidFields, []); + const res = await format(invalidFields, []); expect(res).toEqual({ incidentTypes: null, severityCode: null }); }); }); diff --git a/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.ts b/x-pack/plugins/cases/server/connectors/resilient/format.ts similarity index 56% rename from x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.ts rename to x-pack/plugins/cases/server/connectors/resilient/format.ts index 6dea452565d7c2..3e966d87686d20 100644 --- a/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.ts +++ b/x-pack/plugins/cases/server/connectors/resilient/format.ts @@ -5,15 +5,11 @@ * 2.0. */ -import { ResilientFieldsType, ConnectorResillientTypeFields } from '../../../common'; -import { ExternalServiceFormatter } from '../types'; +import { ConnectorResillientTypeFields } from '../../../common/api'; +import { Format } from './types'; -const format: ExternalServiceFormatter['format'] = (theCase) => { +export const format: Format = (theCase, alerts) => { const { incidentTypes = null, severityCode = null } = (theCase.connector.fields as ConnectorResillientTypeFields['fields']) ?? {}; return { incidentTypes, severityCode }; }; - -export const resilientExternalServiceFormatter: ExternalServiceFormatter = { - format, -}; diff --git a/x-pack/plugins/cases/server/connectors/resilient/index.ts b/x-pack/plugins/cases/server/connectors/resilient/index.ts new file mode 100644 index 00000000000000..a946d0d7fa1c58 --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/resilient/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. + */ + +import { getMapping } from './mapping'; +import { format } from './format'; +import { ResilientCaseConnector } from './types'; + +export const getCaseConnector = (): ResilientCaseConnector => ({ + getMapping, + format, +}); diff --git a/x-pack/plugins/cases/server/connectors/resilient/mapping.ts b/x-pack/plugins/cases/server/connectors/resilient/mapping.ts new file mode 100644 index 00000000000000..0226073711dfb9 --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/resilient/mapping.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GetMapping } from './types'; + +export const getMapping: GetMapping = () => { + return [ + { + source: 'title', + target: 'name', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ]; +}; diff --git a/x-pack/plugins/cases/server/connectors/resilient/types.ts b/x-pack/plugins/cases/server/connectors/resilient/types.ts new file mode 100644 index 00000000000000..f895dccf652145 --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/resilient/types.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ResilientFieldsType } from '../../../common/api'; +import { ICasesConnector } from '../types'; + +export type ResilientCaseConnector = ICasesConnector; +export type Format = ICasesConnector['format']; +export type GetMapping = ICasesConnector['getMapping']; diff --git a/x-pack/plugins/cases/server/connectors/servicenow/index.ts b/x-pack/plugins/cases/server/connectors/servicenow/index.ts new file mode 100644 index 00000000000000..e16a76ff5f79fb --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/servicenow/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getMapping as getServiceNowITSMMapping } from './itsm_mapping'; +import { format as formatServiceNowITSM } from './itsm_format'; +import { getMapping as getServiceNowSIRMapping } from './sir_mapping'; +import { format as formatServiceNowSIR } from './sir_format'; + +import { ServiceNowITSMCasesConnector, ServiceNowSIRCasesConnector } from './types'; + +export const getServiceNowITSMCaseConnector = (): ServiceNowITSMCasesConnector => ({ + getMapping: getServiceNowITSMMapping, + format: formatServiceNowITSM, +}); + +export const getServiceNowSIRCaseConnector = (): ServiceNowSIRCasesConnector => ({ + getMapping: getServiceNowSIRMapping, + format: formatServiceNowSIR, +}); diff --git a/x-pack/plugins/cases/server/connectors/servicenow/itsm_formmater.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.test.ts similarity index 74% rename from x-pack/plugins/cases/server/connectors/servicenow/itsm_formmater.test.ts rename to x-pack/plugins/cases/server/connectors/servicenow/itsm_format.test.ts index 78242e4c3848ab..2cc1816e7fa67e 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/itsm_formmater.test.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { CaseResponse } from '../../../common'; -import { serviceNowITSMExternalServiceFormatter } from './itsm_formatter'; +import { CaseResponse } from '../../../common/api'; +import { format } from './itsm_format'; describe('ITSM formatter', () => { const theCase = { @@ -16,13 +16,13 @@ describe('ITSM formatter', () => { } as CaseResponse; it('it formats correctly', async () => { - const res = await serviceNowITSMExternalServiceFormatter.format(theCase, []); + const res = await format(theCase, []); expect(res).toEqual(theCase.connector.fields); }); it('it formats correctly when fields do not exist ', async () => { const invalidFields = { connector: { fields: null } } as CaseResponse; - const res = await serviceNowITSMExternalServiceFormatter.format(invalidFields, []); + const res = await format(invalidFields, []); expect(res).toEqual({ severity: null, urgency: null, diff --git a/x-pack/plugins/cases/server/connectors/servicenow/itsm_formatter.ts b/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts similarity index 58% rename from x-pack/plugins/cases/server/connectors/servicenow/itsm_formatter.ts rename to x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts index a4fa8a198fea77..9bf8c3e7e8b2e0 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/itsm_formatter.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts @@ -5,15 +5,11 @@ * 2.0. */ -import { ServiceNowITSMFieldsType, ConnectorServiceNowITSMTypeFields } from '../../../common'; -import { ExternalServiceFormatter } from '../types'; +import { ConnectorServiceNowITSMTypeFields } from '../../../common/api'; +import { ServiceNowITSMFormat } from './types'; -const format: ExternalServiceFormatter['format'] = (theCase) => { +export const format: ServiceNowITSMFormat = (theCase, alerts) => { const { severity = null, urgency = null, impact = null, category = null, subcategory = null } = (theCase.connector.fields as ConnectorServiceNowITSMTypeFields['fields']) ?? {}; return { severity, urgency, impact, category, subcategory }; }; - -export const serviceNowITSMExternalServiceFormatter: ExternalServiceFormatter = { - format, -}; diff --git a/x-pack/plugins/cases/server/connectors/servicenow/itsm_mapping.ts b/x-pack/plugins/cases/server/connectors/servicenow/itsm_mapping.ts new file mode 100644 index 00000000000000..a94d72576d6e38 --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/servicenow/itsm_mapping.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ServiceNowITSMGetMapping } from './types'; + +export const getMapping: ServiceNowITSMGetMapping = () => { + return [ + { + source: 'title', + target: 'short_description', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'work_notes', + action_type: 'append', + }, + ]; +}; diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts similarity index 90% rename from x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.test.ts rename to x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts index 1f7716424cfa9b..fa103d4c1142d6 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.test.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts @@ -6,7 +6,7 @@ */ import { CaseResponse } from '../../../common'; -import { serviceNowSIRExternalServiceFormatter } from './sir_formatter'; +import { format } from './sir_format'; describe('ITSM formatter', () => { const theCase = { @@ -24,7 +24,7 @@ describe('ITSM formatter', () => { } as CaseResponse; it('it formats correctly without alerts', async () => { - const res = await serviceNowSIRExternalServiceFormatter.format(theCase, []); + const res = await format(theCase, []); expect(res).toEqual({ dest_ip: null, source_ip: null, @@ -38,7 +38,7 @@ describe('ITSM formatter', () => { it('it formats correctly when fields do not exist ', async () => { const invalidFields = { connector: { fields: null } } as CaseResponse; - const res = await serviceNowSIRExternalServiceFormatter.format(invalidFields, []); + const res = await format(invalidFields, []); expect(res).toEqual({ dest_ip: null, source_ip: null, @@ -73,7 +73,7 @@ describe('ITSM formatter', () => { url: { full: 'https://attack.com/api' }, }, ]; - const res = await serviceNowSIRExternalServiceFormatter.format(theCase, alerts); + const res = await format(theCase, alerts); expect(res).toEqual({ dest_ip: '192.168.1.1,192.168.1.4', source_ip: '192.168.1.2,192.168.1.3', @@ -109,7 +109,7 @@ describe('ITSM formatter', () => { url: { full: 'https://attack.com/api' }, }, ]; - const res = await serviceNowSIRExternalServiceFormatter.format(theCase, alerts); + const res = await format(theCase, alerts); expect(res).toEqual({ dest_ip: '192.168.1.1', source_ip: '192.168.1.2,192.168.1.3', @@ -150,7 +150,7 @@ describe('ITSM formatter', () => { connector: { fields: { ...theCase.connector.fields, destIp: false, malwareHash: false } }, } as CaseResponse; - const res = await serviceNowSIRExternalServiceFormatter.format(newCase, alerts); + const res = await format(newCase, alerts); expect(res).toEqual({ dest_ip: null, source_ip: '192.168.1.2,192.168.1.3', diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts similarity index 76% rename from x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.ts rename to x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts index 1c528cd2b47bfb..1c6e993d395696 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts @@ -5,23 +5,10 @@ * 2.0. */ import { get } from 'lodash/fp'; -import { ConnectorServiceNowSIRTypeFields } from '../../../common'; -import { ExternalServiceFormatter } from '../types'; -interface ExternalServiceParams { - dest_ip: string | null; - source_ip: string | null; - category: string | null; - subcategory: string | null; - malware_hash: string | null; - malware_url: string | null; - priority: string | null; -} -type SirFieldKey = 'dest_ip' | 'source_ip' | 'malware_hash' | 'malware_url'; -type AlertFieldMappingAndValues = Record< - string, - { alertPath: string; sirFieldKey: SirFieldKey; add: boolean } ->; -const format: ExternalServiceFormatter['format'] = (theCase, alerts) => { +import { ConnectorServiceNowSIRTypeFields } from '../../../common/api'; +import { ServiceNowSIRFormat, SirFieldKey, AlertFieldMappingAndValues } from './types'; + +export const format: ServiceNowSIRFormat = (theCase, alerts) => { const { destIp = null, sourceIp = null, @@ -83,6 +70,3 @@ const format: ExternalServiceFormatter['format'] = (theCa priority, }; }; -export const serviceNowSIRExternalServiceFormatter: ExternalServiceFormatter = { - format, -}; diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_mapping.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_mapping.ts new file mode 100644 index 00000000000000..04d9809bc8b996 --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_mapping.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ServiceNowSIRGetMapping } from './types'; + +export const getMapping: ServiceNowSIRGetMapping = () => { + return [ + { + source: 'title', + target: 'short_description', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'work_notes', + action_type: 'append', + }, + ]; +}; diff --git a/x-pack/plugins/cases/server/connectors/servicenow/types.ts b/x-pack/plugins/cases/server/connectors/servicenow/types.ts new file mode 100644 index 00000000000000..500d1d22e3dcb8 --- /dev/null +++ b/x-pack/plugins/cases/server/connectors/servicenow/types.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ServiceNowITSMFieldsType } from '../../../common/api'; +import { ICasesConnector } from '../types'; + +export interface ServiceNowSIRFieldsType { + dest_ip: string | null; + source_ip: string | null; + category: string | null; + subcategory: string | null; + malware_hash: string | null; + malware_url: string | null; + priority: string | null; +} + +export type SirFieldKey = 'dest_ip' | 'source_ip' | 'malware_hash' | 'malware_url'; +export type AlertFieldMappingAndValues = Record< + string, + { alertPath: string; sirFieldKey: SirFieldKey; add: boolean } +>; + +// ServiceNow ITSM +export type ServiceNowITSMCasesConnector = ICasesConnector; +export type ServiceNowITSMFormat = ICasesConnector['format']; +export type ServiceNowITSMGetMapping = ICasesConnector['getMapping']; + +// ServiceNow SIR +export type ServiceNowSIRCasesConnector = ICasesConnector; +export type ServiceNowSIRFormat = ICasesConnector['format']; +export type ServiceNowSIRGetMapping = ICasesConnector['getMapping']; diff --git a/x-pack/plugins/cases/server/connectors/types.ts b/x-pack/plugins/cases/server/connectors/types.ts index 98cbe9683546b6..2fab59037b1bdd 100644 --- a/x-pack/plugins/cases/server/connectors/types.ts +++ b/x-pack/plugins/cases/server/connectors/types.ts @@ -6,7 +6,7 @@ */ import { Logger } from 'kibana/server'; -import { CaseResponse, ConnectorTypes } from '../../common/api'; +import { CaseResponse, ConnectorMappingsAttributes } from '../../common/api'; import { CasesClientGetAlertsResponse } from '../client/alerts/types'; import { CasesClientFactory } from '../client/factory'; import { RegisterActionType } from '../types'; @@ -26,12 +26,11 @@ export interface RegisterConnectorsArgs extends GetActionTypeParams { registerActionType: RegisterActionType; } -export type FormatterConnectorTypes = Exclude; - -export interface ExternalServiceFormatter { +export interface ICasesConnector { format: (theCase: CaseResponse, alerts: CasesClientGetAlertsResponse) => TExternalServiceParams; + getMapping: () => ConnectorMappingsAttributes[]; } -export type ExternalServiceFormatterMapper = { - [x in FormatterConnectorTypes]: ExternalServiceFormatter; -}; +export interface CasesConnectorsMap { + get: (type: string) => ICasesConnector | undefined | null; +} diff --git a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts index bddceef8d782e6..ef5e0ebb5bbc10 100644 --- a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -10,20 +10,13 @@ import { AssociationType, CaseStatuses, CaseType, - CaseUserActionAttributes, CommentAttributes, CommentType, - ConnectorMappings, ConnectorTypes, ESCaseAttributes, ESCasesConfigureAttributes, } from '../../../../common'; -import { - CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, - CASE_USER_ACTION_SAVED_OBJECT, - SECURITY_SOLUTION_OWNER, -} from '../../../../common/constants'; -import { mappings } from '../../../client/configure/mock'; +import { SECURITY_SOLUTION_OWNER } from '../../../../common/constants'; export const mockCases: Array> = [ { @@ -485,79 +478,3 @@ export const mockCaseConfigure: Array> = version: 'WzYsMV0=', }, ]; - -export const mockCaseMappings: Array> = [ - { - type: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, - id: 'mock-mappings-1', - attributes: { - mappings: mappings[ConnectorTypes.jira], - owner: SECURITY_SOLUTION_OWNER, - }, - references: [], - }, -]; - -export const mockCaseMappingsResilient: Array> = [ - { - type: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, - id: 'mock-mappings-1', - attributes: { - mappings: mappings[ConnectorTypes.resilient], - owner: SECURITY_SOLUTION_OWNER, - }, - references: [], - }, -]; - -export const mockCaseMappingsBad: Array>> = [ - { - type: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, - id: 'mock-mappings-bad', - attributes: {}, - references: [], - }, -]; - -export const mockUserActions: Array> = [ - { - type: CASE_USER_ACTION_SAVED_OBJECT, - id: 'mock-user-actions-1', - attributes: { - action_field: ['description', 'status', 'tags', 'title', 'connector', 'settings'], - action: 'create', - action_at: '2021-02-03T17:41:03.771Z', - action_by: { - email: 'elastic@elastic.co', - full_name: 'Elastic', - username: 'elastic', - }, - new_value: - '{"title":"A case","tags":["case"],"description":"Yeah!","connector":{"id":"connector-od","name":"My Connector","type":".servicenow-sir","fields":{"category":"Denial of Service","destIp":true,"malwareHash":true,"malwareUrl":true,"priority":"2","sourceIp":true,"subcategory":"45"}},"settings":{"syncAlerts":true}}', - old_value: null, - owner: SECURITY_SOLUTION_OWNER, - }, - version: 'WzYsMV0=', - references: [], - }, - { - type: CASE_USER_ACTION_SAVED_OBJECT, - id: 'mock-user-actions-2', - attributes: { - action_field: ['comment'], - action: 'create', - action_at: '2021-02-03T17:44:21.067Z', - action_by: { - email: 'elastic@elastic.co', - full_name: 'Elastic', - username: 'elastic', - }, - new_value: - '{"type":"alert","alertId":"cec3da90fb37a44407145adf1593f3b0d5ad94c4654201f773d63b5d4706128e","index":".siem-signals-default-000008"}', - old_value: null, - owner: SECURITY_SOLUTION_OWNER, - }, - version: 'WzYsMV0=', - references: [], - }, -]; diff --git a/x-pack/plugins/data_visualizer/kibana.json b/x-pack/plugins/data_visualizer/kibana.json index 3934f0ee3417f7..b024a52e647218 100644 --- a/x-pack/plugins/data_visualizer/kibana.json +++ b/x-pack/plugins/data_visualizer/kibana.json @@ -19,6 +19,7 @@ "lens" ], "requiredBundles": [ + "home", "kibanaReact", "maps", "esUiShared" diff --git a/x-pack/plugins/data_visualizer/public/plugin.ts b/x-pack/plugins/data_visualizer/public/plugin.ts index 20d2e93fd68790..66109de1b14636 100644 --- a/x-pack/plugins/data_visualizer/public/plugin.ts +++ b/x-pack/plugins/data_visualizer/public/plugin.ts @@ -19,7 +19,7 @@ import type { SecurityPluginSetup } from '../../security/public'; import type { LensPublicStart } from '../../lens/public'; import { getFileDataVisualizerComponent, getIndexDataVisualizerComponent } from './api'; import { getMaxBytesFormatted } from './application/common/util/get_max_bytes'; -import { registerHomeAddData } from './register_home'; +import { registerHomeAddData, registerHomeFeatureCatalogue } from './register_home'; export interface DataVisualizerSetupDependencies { home?: HomePublicPluginSetup; @@ -48,6 +48,7 @@ export class DataVisualizerPlugin public setup(core: CoreSetup, plugins: DataVisualizerSetupDependencies) { if (plugins.home) { registerHomeAddData(plugins.home); + registerHomeFeatureCatalogue(plugins.home); } } diff --git a/x-pack/plugins/data_visualizer/public/register_home.ts b/x-pack/plugins/data_visualizer/public/register_home.ts index 0b438dc309c4b1..3e8973784433ce 100644 --- a/x-pack/plugins/data_visualizer/public/register_home.ts +++ b/x-pack/plugins/data_visualizer/public/register_home.ts @@ -7,14 +7,34 @@ import { i18n } from '@kbn/i18n'; import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; +import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; import { FileDataVisualizerWrapper } from './lazy_load_bundle/component_wrapper'; +const FILE_DATA_VIS_TAB_ID = 'fileDataViz'; + export function registerHomeAddData(home: HomePublicPluginSetup) { home.addData.registerAddDataTab({ - id: 'fileDataViz', + id: FILE_DATA_VIS_TAB_ID, name: i18n.translate('xpack.dataVisualizer.file.embeddedTabTitle', { defaultMessage: 'Upload file', }), component: FileDataVisualizerWrapper, }); } + +export function registerHomeFeatureCatalogue(home: HomePublicPluginSetup) { + home.featureCatalogue.register({ + id: `file_data_visualizer`, + title: i18n.translate('xpack.dataVisualizer.title', { + defaultMessage: 'Upload a file', + }), + description: i18n.translate('xpack.dataVisualizer.description', { + defaultMessage: 'Import your own CSV, NDJSON, or log file.', + }), + icon: 'document', + path: `/app/home#/tutorial_directory/${FILE_DATA_VIS_TAB_ID}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + order: 520, + }); +} diff --git a/x-pack/plugins/fleet/common/services/__snapshots__/package_to_package_policy.test.ts.snap b/x-pack/plugins/fleet/common/services/__snapshots__/package_to_package_policy.test.ts.snap new file mode 100644 index 00000000000000..d49743fa487f49 --- /dev/null +++ b/x-pack/plugins/fleet/common/services/__snapshots__/package_to_package_policy.test.ts.snap @@ -0,0 +1,804 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Fleet - packageToPackagePolicy packageToPackagePolicy returns package policy with multiple policy templates (aka has integrations 1`] = ` +Object { + "description": undefined, + "enabled": true, + "inputs": Array [ + Object { + "enabled": true, + "policy_template": "billing", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.billing", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "cost_explorer_config.group_by_dimension_keys": Object { + "type": "text", + "value": Array [ + "AZ", + "INSTANCE_TYPE", + "SERVICE", + ], + }, + "cost_explorer_config.group_by_tag_keys": Object { + "type": "text", + "value": Array [ + "aws:createdBy", + ], + }, + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "12h", + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "cloudtrail", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.cloudtrail", + "type": "logs", + }, + "enabled": true, + "vars": Object { + "api_timeout": Object { + "type": "text", + "value": undefined, + }, + "fips_enabled": Object { + "type": "bool", + "value": false, + }, + "queue_url": Object { + "type": "text", + "value": undefined, + }, + "visibility_timeout": Object { + "type": "text", + "value": undefined, + }, + }, + }, + ], + "type": "s3", + }, + Object { + "enabled": false, + "policy_template": "cloudtrail", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.cloudtrail", + "type": "logs", + }, + "enabled": false, + "vars": Object { + "interval": Object { + "type": "text", + "value": "10s", + }, + "password": Object { + "type": "password", + "value": undefined, + }, + "search": Object { + "type": "text", + "value": "search sourcetype=aws:cloudtrail", + }, + "ssl": Object { + "type": "yaml", + "value": undefined, + }, + "tags": Object { + "type": "text", + "value": Array [ + "forwarded", + ], + }, + "url": Object { + "type": "text", + "value": "https://server.example.com:8089", + }, + "username": Object { + "type": "text", + "value": undefined, + }, + }, + }, + ], + "type": "httpjson", + }, + Object { + "enabled": true, + "policy_template": "cloudwatch", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.cloudwatch_logs", + "type": "logs", + }, + "enabled": true, + "vars": Object { + "api_timeout": Object { + "type": "text", + "value": undefined, + }, + "fips_enabled": Object { + "type": "bool", + "value": false, + }, + "queue_url": Object { + "type": "text", + "value": undefined, + }, + "visibility_timeout": Object { + "type": "text", + "value": undefined, + }, + }, + }, + ], + "type": "s3", + }, + Object { + "enabled": true, + "policy_template": "cloudwatch", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.cloudwatch_metrics", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "metrics": Object { + "type": "yaml", + "value": "- namespace: AWS/EC2 + resource_type: ec2:instance + name: + - CPUUtilization + - DiskWriteOps + statistic: + - Average + - Maximum + # dimensions: + # - name: InstanceId + # value: i-123456 + # tags: + # - key: created-by + # value: foo +", + }, + "period": Object { + "type": "text", + "value": "300s", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "dynamodb", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.dynamodb", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "5m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + "tags_filter": Object { + "type": "yaml", + "value": "# - key: \\"created-by\\" + # value: \\"foo\\" +", + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "ebs", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.ebs", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "5m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + "tags_filter": Object { + "type": "yaml", + "value": "# - key: \\"created-by\\" + # value: \\"foo\\" +", + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "ec2", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.ec2_logs", + "type": "logs", + }, + "enabled": true, + "vars": Object { + "api_timeout": Object { + "type": "text", + "value": undefined, + }, + "fips_enabled": Object { + "type": "bool", + "value": false, + }, + "queue_url": Object { + "type": "text", + "value": undefined, + }, + "visibility_timeout": Object { + "type": "text", + "value": undefined, + }, + }, + }, + ], + "type": "s3", + }, + Object { + "enabled": true, + "policy_template": "ec2", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.ec2_metrics", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "5m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + "tags_filter": Object { + "type": "yaml", + "value": "# - key: \\"created-by\\" + # value: \\"foo\\" +", + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "elb", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.elb_logs", + "type": "logs", + }, + "enabled": true, + "vars": Object { + "api_timeout": Object { + "type": "text", + "value": undefined, + }, + "fips_enabled": Object { + "type": "bool", + "value": false, + }, + "queue_url": Object { + "type": "text", + "value": undefined, + }, + "visibility_timeout": Object { + "type": "text", + "value": undefined, + }, + }, + }, + ], + "type": "s3", + }, + Object { + "enabled": true, + "policy_template": "elb", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.elb_metrics", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "1m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + "tags_filter": Object { + "type": "yaml", + "value": "# - key: \\"created-by\\" + # value: \\"foo\\" +", + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "lambda", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.lambda", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "5m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + "tags_filter": Object { + "type": "yaml", + "value": "# - key: \\"created-by\\" + # value: \\"foo\\" +", + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "natgateway", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.natgateway", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "1m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "rds", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.rds", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "1m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + "tags_filter": Object { + "type": "yaml", + "value": "# - key: \\"created-by\\" + # value: \\"foo\\" +", + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "s3", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.s3access", + "type": "logs", + }, + "enabled": true, + "vars": Object { + "api_timeout": Object { + "type": "text", + "value": undefined, + }, + "fips_enabled": Object { + "type": "bool", + "value": false, + }, + "queue_url": Object { + "type": "text", + "value": undefined, + }, + "visibility_timeout": Object { + "type": "text", + "value": undefined, + }, + }, + }, + ], + "type": "s3", + }, + Object { + "enabled": true, + "policy_template": "s3", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.s3_daily_storage", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "24h", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + }, + }, + Object { + "data_stream": Object { + "dataset": "aws.s3_request", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "1m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "sns", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.sns", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "5m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + "tags_filter": Object { + "type": "yaml", + "value": "# - key: \\"created-by\\" + # value: \\"foo\\" +", + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "sqs", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.sqs", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "5m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "transitgateway", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.transitgateway", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "1m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "usage", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.usage", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "1m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + }, + }, + ], + "type": "aws/metrics", + }, + Object { + "enabled": true, + "policy_template": "vpcflow", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.vpcflow", + "type": "logs", + }, + "enabled": true, + "vars": Object { + "api_timeout": Object { + "type": "text", + "value": undefined, + }, + "fips_enabled": Object { + "type": "bool", + "value": false, + }, + "queue_url": Object { + "type": "text", + "value": undefined, + }, + "visibility_timeout": Object { + "type": "text", + "value": undefined, + }, + }, + }, + ], + "type": "s3", + }, + Object { + "enabled": true, + "policy_template": "vpn", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "aws.vpn", + "type": "metrics", + }, + "enabled": true, + "vars": Object { + "latency": Object { + "type": "text", + "value": undefined, + }, + "period": Object { + "type": "text", + "value": "1m", + }, + "regions": Object { + "type": "text", + "value": Array [], + }, + "tags_filter": Object { + "type": "yaml", + "value": "# - key: \\"created-by\\" + # value: \\"foo\\" +", + }, + }, + }, + ], + "type": "aws/metrics", + }, + ], + "name": "aws-1", + "namespace": "default", + "output_id": "some-output-id", + "package": Object { + "name": "aws", + "title": "AWS", + "version": "0.5.3", + }, + "policy_id": "some-agent-policy-id", + "vars": Object { + "access_key_id": Object { + "type": "text", + "value": undefined, + }, + "credential_profile_name": Object { + "type": "text", + "value": undefined, + }, + "endpoint": Object { + "type": "text", + "value": "amazonaws.com", + }, + "role_arn": Object { + "type": "text", + "value": undefined, + }, + "secret_access_key": Object { + "type": "text", + "value": undefined, + }, + "session_token": Object { + "type": "text", + "value": undefined, + }, + "shared_credential_file": Object { + "type": "text", + "value": undefined, + }, + }, +} +`; diff --git a/x-pack/plugins/fleet/common/services/__snapshots__/validate_package_policy.test.ts.snap b/x-pack/plugins/fleet/common/services/__snapshots__/validate_package_policy.test.ts.snap new file mode 100644 index 00000000000000..72f4182d87629f --- /dev/null +++ b/x-pack/plugins/fleet/common/services/__snapshots__/validate_package_policy.test.ts.snap @@ -0,0 +1,301 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Fleet - validatePackagePolicy() works for packages with multiple policy templates (aka integrations) returns errors for invalid package policy 1`] = ` +Object { + "description": null, + "inputs": Object { + "billing-aws/metrics": Object { + "streams": Object { + "aws.billing": Object { + "vars": Object { + "cost_explorer_config.group_by_dimension_keys": null, + "cost_explorer_config.group_by_tag_keys": null, + "latency": null, + "period": null, + }, + }, + }, + }, + "cloudtrail-httpjson": Object { + "streams": Object { + "aws.cloudtrail": Object { + "vars": Object { + "interval": null, + "password": null, + "search": null, + "ssl": null, + "tags": null, + "url": null, + "username": null, + }, + }, + }, + }, + "cloudtrail-s3": Object { + "streams": Object { + "aws.cloudtrail": Object { + "vars": Object { + "api_timeout": null, + "fips_enabled": null, + "queue_url": Array [ + "Queue URL is required", + ], + "visibility_timeout": null, + }, + }, + }, + }, + "cloudwatch-aws/metrics": Object { + "streams": Object { + "aws.cloudwatch_metrics": Object { + "vars": Object { + "latency": null, + "metrics": null, + "period": null, + "regions": null, + }, + }, + }, + }, + "cloudwatch-s3": Object { + "streams": Object { + "aws.cloudwatch_logs": Object { + "vars": Object { + "api_timeout": null, + "fips_enabled": null, + "queue_url": Array [ + "Queue URL is required", + ], + "visibility_timeout": null, + }, + }, + }, + }, + "dynamodb-aws/metrics": Object { + "streams": Object { + "aws.dynamodb": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + "tags_filter": null, + }, + }, + }, + }, + "ebs-aws/metrics": Object { + "streams": Object { + "aws.ebs": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + "tags_filter": null, + }, + }, + }, + }, + "ec2-aws/metrics": Object { + "streams": Object { + "aws.ec2_metrics": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + "tags_filter": null, + }, + }, + }, + }, + "ec2-s3": Object { + "streams": Object { + "aws.ec2_logs": Object { + "vars": Object { + "api_timeout": null, + "fips_enabled": null, + "queue_url": Array [ + "Queue URL is required", + ], + "visibility_timeout": null, + }, + }, + }, + }, + "elb-aws/metrics": Object { + "streams": Object { + "aws.elb_metrics": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + "tags_filter": null, + }, + }, + }, + }, + "elb-s3": Object { + "streams": Object { + "aws.elb_logs": Object { + "vars": Object { + "api_timeout": null, + "fips_enabled": null, + "queue_url": Array [ + "Queue URL is required", + ], + "visibility_timeout": null, + }, + }, + }, + }, + "lambda-aws/metrics": Object { + "streams": Object { + "aws.lambda": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + "tags_filter": null, + }, + }, + }, + }, + "natgateway-aws/metrics": Object { + "streams": Object { + "aws.natgateway": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + }, + }, + }, + }, + "rds-aws/metrics": Object { + "streams": Object { + "aws.rds": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + "tags_filter": null, + }, + }, + }, + }, + "s3-aws/metrics": Object { + "streams": Object { + "aws.s3_daily_storage": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + }, + }, + "aws.s3_request": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + }, + }, + }, + }, + "s3-s3": Object { + "streams": Object { + "aws.s3access": Object { + "vars": Object { + "api_timeout": null, + "fips_enabled": null, + "queue_url": Array [ + "Queue URL is required", + ], + "visibility_timeout": null, + }, + }, + }, + }, + "sns-aws/metrics": Object { + "streams": Object { + "aws.sns": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + "tags_filter": null, + }, + }, + }, + }, + "sqs-aws/metrics": Object { + "streams": Object { + "aws.sqs": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + }, + }, + }, + }, + "transitgateway-aws/metrics": Object { + "streams": Object { + "aws.transitgateway": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + }, + }, + }, + }, + "usage-aws/metrics": Object { + "streams": Object { + "aws.usage": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + }, + }, + }, + }, + "vpcflow-s3": Object { + "streams": Object { + "aws.vpcflow": Object { + "vars": Object { + "api_timeout": null, + "fips_enabled": null, + "queue_url": Array [ + "Queue URL is required", + ], + "visibility_timeout": null, + }, + }, + }, + }, + "vpn-aws/metrics": Object { + "streams": Object { + "aws.vpn": Object { + "vars": Object { + "latency": null, + "period": null, + "regions": null, + "tags_filter": null, + }, + }, + }, + }, + }, + "name": null, + "namespace": null, + "vars": Object { + "access_key_id": null, + "credential_profile_name": null, + "endpoint": null, + "role_arn": null, + "secret_access_key": null, + "session_token": null, + "shared_credential_file": null, + }, +} +`; diff --git a/x-pack/plugins/fleet/common/services/fixtures/aws_package.ts b/x-pack/plugins/fleet/common/services/fixtures/aws_package.ts new file mode 100644 index 00000000000000..2b93cca3d4e4d2 --- /dev/null +++ b/x-pack/plugins/fleet/common/services/fixtures/aws_package.ts @@ -0,0 +1,2742 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const AWS_PACKAGE = { + name: 'aws', + title: 'AWS', + version: '0.5.3', + release: 'beta', + description: 'AWS Integration', + type: 'integration', + download: '/epr/aws/aws-0.5.3.zip', + path: '/package/aws/0.5.3', + icons: [ + { + src: '/img/logo_aws.svg', + path: '/package/aws/0.5.3/img/logo_aws.svg', + title: 'logo aws', + size: '32x32', + type: 'image/svg+xml', + }, + ], + format_version: '1.0.0', + readme: '/package/aws/0.5.3/docs/README.md', + license: 'basic', + categories: ['aws', 'cloud', 'network', 'security'], + conditions: { + 'kibana.version': '^7.12.0', + }, + screenshots: [ + { + src: '/img/metricbeat-aws-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-overview.png', + title: 'metricbeat aws overview', + size: '3848x2440', + type: 'image/png', + }, + ], + assets: { + kibana: { + dashboard: [], + map: [], + search: [], + visualization: [], + }, + elasticsearch: { + ingest_pipeline: [], + }, + }, + policy_templates: [ + { + name: 'billing', + title: 'AWS Billing', + description: 'Collect AWS billing metrics', + data_streams: ['billing'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect billing metrics', + description: 'Collect billing metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_billing.svg', + path: '/package/aws/0.5.3/img/logo_billing.svg', + title: 'AWS Billing logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + screenshots: [ + { + src: '/img/metricbeat-aws-billing-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-billing-overview.png', + title: 'metricbeat aws billing overview', + size: '2640x2240', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/billing.md', + }, + { + name: 'cloudtrail', + title: 'AWS Cloudtrail', + description: 'Collect logs from AWS Cloudtrail', + data_streams: ['cloudtrail'], + inputs: [ + { + type: 's3', + title: 'Collect logs from Cloudtrail service', + description: 'Collecting Cloudtrail logs using S3 input', + input_group: 'logs', + }, + { + type: 'httpjson', + title: 'Collect logs from third-party REST API (experimental)', + description: 'Collect logs from third-party REST API (experimental)', + input_group: 'logs', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_cloudtrail.svg', + path: '/package/aws/0.5.3/img/logo_cloudtrail.svg', + title: 'AWS Cloudtrail logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + screenshots: [ + { + src: '/img/filebeat-aws-cloudtrail.png', + path: '/package/aws/0.5.3/img/filebeat-aws-cloudtrail.png', + title: 'filebeat aws cloudtrail', + size: '1702x1063', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/cloudtrail.md', + }, + { + name: 'cloudwatch', + title: 'AWS CloudWatch', + description: 'Collect logs and metrics from CloudWatch', + data_streams: ['cloudwatch_logs', 'cloudwatch_metrics'], + inputs: [ + { + type: 's3', + title: 'Collect logs from CloudWatch', + description: 'Collecting logs from CloudWatch using S3 input', + input_group: 'logs', + }, + { + type: 'aws/metrics', + title: 'Collect metrics from CloudWatch', + description: 'Collecting metrics from AWS CloudWatch', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_cloudwatch.svg', + path: '/package/aws/0.5.3/img/logo_cloudwatch.svg', + title: 'AWS CloudWatch logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + readme: '/package/aws/0.5.3/docs/cloudwatch.md', + }, + { + name: 'dynamodb', + title: 'AWS DynamoDB', + description: 'Collect AWS DynamoDB metrics', + data_streams: ['dynamodb'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect dynamodb metrics', + description: 'Collect dynamodb metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_dynamodb.svg', + path: '/package/aws/0.5.3/img/logo_dynamodb.svg', + title: 'AWS DynamoDB logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + categories: ['datastore'], + screenshots: [ + { + src: '/img/metricbeat-aws-dynamodb-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-dynamodb-overview.png', + title: 'metricbeat aws dynamodb overview', + size: '2640x2240', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/dynamodb.md', + }, + { + name: 'ebs', + title: 'AWS EBS', + description: 'Collect AWS EBS metrics', + data_streams: ['ebs'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect EBS metrics', + description: 'Collect EBS metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_ebs.svg', + path: '/package/aws/0.5.3/img/logo_ebs.svg', + title: 'AWS EBS logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + categories: ['datastore'], + screenshots: [ + { + src: '/img/metricbeat-aws-ebs-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-ebs-overview.png', + title: 'metricbeat aws ebs overview', + size: '2640x2240', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/ebs.md', + }, + { + name: 'ec2', + title: 'AWS EC2', + description: 'Collect logs and metrics from EC2 service', + data_streams: ['ec2_logs', 'ec2_metrics'], + inputs: [ + { + type: 's3', + title: 'Collect logs from EC2 service', + description: 'Collecting EC2 logs using S3 input', + input_group: 'logs', + }, + { + type: 'aws/metrics', + title: 'Collect metrics from EC2 service', + description: 'Collecting EC2 metrics using AWS CloudWatch', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_ec2.svg', + path: '/package/aws/0.5.3/img/logo_ec2.svg', + title: 'AWS EC2 logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + screenshots: [ + { + src: '/img/metricbeat-aws-ec2-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-ec2-overview.png', + title: 'metricbeat aws ec2 overview', + size: '2640x2240', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/ec2.md', + }, + { + name: 'elb', + title: 'AWS ELB', + description: 'Collect logs and metrics from ELB service', + data_streams: ['elb_logs', 'elb_metrics'], + inputs: [ + { + type: 's3', + title: 'Collect logs from ELB service', + description: 'Collecting ELB logs using S3 input', + input_group: 'logs', + }, + { + type: 'aws/metrics', + title: 'Collect metrics from ELB service', + description: 'Collecting ELB metrics using AWS CloudWatch', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_elb.svg', + path: '/package/aws/0.5.3/img/logo_elb.svg', + title: 'AWS ELB logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + screenshots: [ + { + src: '/img/metricbeat-aws-elb-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-elb-overview.png', + title: 'metricbeat aws elb overview', + size: '2640x2240', + type: 'image/png', + }, + { + src: '/img/filebeat-aws-elb-overview.png', + path: '/package/aws/0.5.3/img/filebeat-aws-elb-overview.png', + title: 'filebeat aws elb overview', + size: '1684x897', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/elb.md', + }, + { + name: 'lambda', + title: 'AWS Lambda', + description: 'Collect AWS Lambda metrics', + data_streams: ['lambda'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect Lambda metrics', + description: 'Collect Lambda metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_lambda.svg', + path: '/package/aws/0.5.3/img/logo_lambda.svg', + title: 'AWS Lambda logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + screenshots: [ + { + src: '/img/metricbeat-aws-lambda-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-lambda-overview.png', + title: 'metricbeat aws lambda overview', + size: '2640x2240', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/lambda.md', + }, + { + name: 'natgateway', + title: 'AWS NATGateway', + description: 'Collect AWS NATGateway metrics', + data_streams: ['natgateway'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect NATGateway metrics', + description: 'Collect NATGateway metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_natgateway.svg', + path: '/package/aws/0.5.3/img/logo_natgateway.svg', + title: 'AWS NATGateway logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + readme: '/package/aws/0.5.3/docs/natgateway.md', + }, + { + name: 'rds', + title: 'AWS RDS', + description: 'Collect AWS RDS metrics', + data_streams: ['rds'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect RDS metrics', + description: 'Collect RDS metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_rds.svg', + path: '/package/aws/0.5.3/img/logo_rds.svg', + title: 'AWS RDS logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + categories: ['datastore'], + screenshots: [ + { + src: '/img/metricbeat-aws-rds-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-rds-overview.png', + title: 'metricbeat aws rds overview', + size: '2640x2240', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/rds.md', + }, + { + name: 's3', + title: 'AWS S3', + description: 'Collect AWS S3 metrics', + data_streams: ['s3_daily_storage', 's3_request', 's3access'], + inputs: [ + { + type: 's3', + title: 'Collect S3 access logs', + description: 'Collecting S3 access logs using S3 input', + input_group: 'logs', + }, + { + type: 'aws/metrics', + title: 'Collect metrics from S3', + description: 'Collecting S3 metrics using AWS CloudWatch', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_s3.svg', + path: '/package/aws/0.5.3/img/logo_s3.svg', + title: 'AWS S3 logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + categories: ['datastore'], + screenshots: [ + { + src: '/img/metricbeat-aws-s3-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-s3-overview.png', + title: 'metricbeat aws s3 overview', + size: '2640x2240', + type: 'image/png', + }, + { + src: '/img/filebeat-aws-s3access-overview.png', + path: '/package/aws/0.5.3/img/filebeat-aws-s3access-overview.png', + title: 'filebeat aws s3access overview', + size: '1684x897', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/s3.md', + }, + { + name: 'sns', + title: 'AWS SNS', + description: 'Collect AWS SNS metrics', + data_streams: ['sns'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect SNS metrics', + description: 'Collect SNS metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_sns.svg', + path: '/package/aws/0.5.3/img/logo_sns.svg', + title: 'AWS SNS logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + screenshots: [ + { + src: '/img/metricbeat-aws-sns-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-sns-overview.png', + title: 'metricbeat aws sns overview', + size: '2640x2240', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/sns.md', + }, + { + name: 'sqs', + title: 'AWS SQS', + description: 'Collect AWS SQS metrics', + data_streams: ['sqs'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect SQS metrics', + description: 'Collect SQS metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_sqs.svg', + path: '/package/aws/0.5.3/img/logo_sqs.svg', + title: 'AWS SQS logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + screenshots: [ + { + src: '/img/metricbeat-aws-sqs-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-sqs-overview.png', + title: 'metricbeat aws sqs overview', + size: '2640x2240', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/sqs.md', + }, + { + name: 'transitgateway', + title: 'AWS Transit Gateway', + description: 'Collect AWS Transit Gateway metrics', + data_streams: ['transitgateway'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect Transit Gateway metrics', + description: 'Collect Transit Gateway metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_transitgateway.svg', + path: '/package/aws/0.5.3/img/logo_transitgateway.svg', + title: 'AWS Transit Gateway logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + readme: '/package/aws/0.5.3/docs/transitgateway.md', + }, + { + name: 'usage', + title: 'AWS Usage', + description: 'Collect AWS Usage metrics', + data_streams: ['usage'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect Usage metrics', + description: 'Collect Usage metrics', + input_group: 'metrics', + }, + ], + multiple: true, + screenshots: [ + { + src: '/img/metricbeat-aws-usage-overview.png', + path: '/package/aws/0.5.3/img/metricbeat-aws-usage-overview.png', + title: 'metricbeat aws sns overview', + size: '2640x2240', + type: 'image/png', + }, + ], + readme: '/package/aws/0.5.3/docs/usage.md', + }, + { + name: 'vpcflow', + title: 'AWS VPC Flow', + description: 'Collect AWS vpcflow logs', + data_streams: ['vpcflow'], + inputs: [ + { + type: 's3', + title: 'Collect VPC Flow logs', + description: 'Collecting VPC Flow logs using S3 input', + input_group: 'logs', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_vpcflow.svg', + path: '/package/aws/0.5.3/img/logo_vpcflow.svg', + title: 'AWS VPC logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + readme: '/package/aws/0.5.3/docs/vpcflow.md', + }, + { + name: 'vpn', + title: 'AWS VPN', + description: 'Collect AWS VPN metrics', + data_streams: ['vpn'], + inputs: [ + { + type: 'aws/metrics', + title: 'Collect VPN metrics', + description: 'Collect VPN metrics', + input_group: 'metrics', + }, + ], + multiple: true, + icons: [ + { + src: '/img/logo_vpn.svg', + path: '/package/aws/0.5.3/img/logo_vpn.svg', + title: 'AWS VPN logo', + size: '32x32', + type: 'image/svg+xml', + }, + ], + categories: ['network'], + readme: '/package/aws/0.5.3/docs/vpn.md', + }, + ], + data_streams: [ + { + type: 'metrics', + dataset: 'aws.billing', + title: 'AWS billing metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '12h', + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'cost_explorer_config.group_by_dimension_keys', + type: 'text', + title: 'Cost Explorer Group By Dimension Keys', + multi: true, + required: false, + show_user: true, + default: ['AZ', 'INSTANCE_TYPE', 'SERVICE'], + }, + { + name: 'cost_explorer_config.group_by_tag_keys', + type: 'text', + title: 'Cost Explorer Group By Tag Keys', + multi: true, + required: false, + show_user: true, + default: ['aws:createdBy'], + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS Billing metrics', + description: 'Collect AWS billing metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'billing', + }, + { + type: 'logs', + dataset: 'aws.cloudtrail', + title: 'AWS CloudTrail logs', + release: 'beta', + ingest_pipeline: 'default', + streams: [ + { + input: 's3', + vars: [ + { + name: 'visibility_timeout', + type: 'text', + title: 'Visibility Timeout', + description: + 'The duration that the received messages are hidden from subsequent retrieve requests after being retrieved by a ReceiveMessage request. The maximum is 12 hours.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'api_timeout', + type: 'text', + title: 'API Timeout', + description: + 'The maximum duration of AWS API can take. The maximum is half of the visibility timeout value.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'queue_url', + type: 'text', + title: 'Queue URL', + description: 'URL of the AWS SQS queue that messages will be received from.', + multi: false, + required: true, + show_user: true, + }, + { + name: 'fips_enabled', + type: 'bool', + title: 'Enable S3 FIPS', + description: + 'Enabling this option changes the service name from `s3` to `s3-fips` for connecting to the correct service endpoint.', + multi: false, + required: false, + show_user: false, + default: false, + }, + ], + template_path: 's3.yml.hbs', + title: 'AWS CloudTrail logs', + description: 'Collect AWS CloudTrail logs using s3 input', + enabled: true, + }, + { + input: 'httpjson', + vars: [ + { + name: 'url', + type: 'text', + title: 'URL of Splunk Enterprise Server', + description: 'i.e. scheme://host:port, path is automatic', + multi: false, + required: true, + show_user: true, + default: 'https://server.example.com:8089', + }, + { + name: 'username', + type: 'text', + title: 'Splunk REST API Username', + multi: false, + required: true, + show_user: true, + }, + { + name: 'password', + type: 'password', + title: 'Splunk REST API Password', + multi: false, + required: true, + show_user: true, + }, + { + name: 'ssl', + type: 'yaml', + title: 'SSL Configuration', + description: + 'i.e. certificate_authorities, supported_protocols, verification_mode etc.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'interval', + type: 'text', + title: 'Interval to query Splunk Enterprise REST API', + description: 'Go Duration syntax (eg. 10s)', + multi: false, + required: true, + show_user: true, + default: '10s', + }, + { + name: 'search', + type: 'text', + title: 'Splunk search string', + multi: false, + required: true, + show_user: true, + default: 'search sourcetype=aws:cloudtrail', + }, + { + name: 'tags', + type: 'text', + title: 'Tags', + multi: true, + required: false, + show_user: false, + default: ['forwarded'], + }, + ], + template_path: 'httpjson.yml.hbs', + title: 'AWS CloudTrail logs via Splunk Enterprise REST API', + description: 'Collect AWS CloudTrail logs via Splunk Enterprise REST API', + enabled: false, + }, + ], + package: 'aws', + path: 'cloudtrail', + }, + { + type: 'logs', + dataset: 'aws.cloudwatch_logs', + title: 'AWS CloudWatch logs', + release: 'beta', + ingest_pipeline: 'default', + streams: [ + { + input: 's3', + vars: [ + { + name: 'visibility_timeout', + type: 'text', + title: 'Visibility Timeout', + description: + 'The duration that the received messages are hidden from subsequent retrieve requests after being retrieved by a ReceiveMessage request. The maximum is 12 hours.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'api_timeout', + type: 'text', + title: 'API Timeout', + description: + 'The maximum duration of AWS API can take. The maximum is half of the visibility timeout value.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'queue_url', + type: 'text', + title: 'Queue URL', + description: 'URL of the AWS SQS queue that messages will be received from.', + multi: false, + required: true, + show_user: true, + }, + { + name: 'fips_enabled', + type: 'bool', + title: 'Enable S3 FIPS', + description: + 'Enabling this option changes the service name from `s3` to `s3-fips` for connecting to the correct service endpoint.', + multi: false, + required: false, + show_user: false, + default: false, + }, + ], + template_path: 's3.yml.hbs', + title: 'AWS CloudWatch logs', + description: 'Collect AWS CloudWatch logs using s3 input', + enabled: true, + }, + ], + package: 'aws', + path: 'cloudwatch_logs', + }, + { + type: 'metrics', + dataset: 'aws.cloudwatch_metrics', + title: 'AWS CloudWatch metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '300s', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'metrics', + type: 'yaml', + title: 'Metrics', + multi: false, + required: true, + show_user: true, + default: + '- namespace: AWS/EC2\n resource_type: ec2:instance\n name:\n - CPUUtilization\n - DiskWriteOps\n statistic:\n - Average\n - Maximum\n # dimensions:\n # - name: InstanceId\n # value: i-123456\n # tags:\n # - key: created-by\n # value: foo\n', + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS CloudWatch metrics', + description: 'Collect AWS CloudWatch metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'cloudwatch_metrics', + }, + { + type: 'metrics', + dataset: 'aws.dynamodb', + title: 'AWS DynamoDB metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '5m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'tags_filter', + type: 'yaml', + title: 'Tags Filter', + multi: false, + required: false, + show_user: false, + default: '# - key: "created-by"\n # value: "foo"\n', + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS DynamoDB metrics', + description: 'Collect AWS DynamoDB metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'dynamodb', + }, + { + type: 'metrics', + dataset: 'aws.ebs', + title: 'AWS EBS metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '5m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'tags_filter', + type: 'yaml', + title: 'Tags Filter', + multi: false, + required: false, + show_user: false, + default: '# - key: "created-by"\n # value: "foo"\n', + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS EBS metrics', + description: 'Collect AWS EBS metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'ebs', + }, + { + type: 'logs', + dataset: 'aws.ec2_logs', + title: 'AWS EC2 logs', + release: 'beta', + ingest_pipeline: 'default', + streams: [ + { + input: 's3', + vars: [ + { + name: 'visibility_timeout', + type: 'text', + title: 'Visibility Timeout', + description: + 'The duration that the received messages are hidden from subsequent retrieve requests after being retrieved by a ReceiveMessage request. The maximum is 12 hours.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'api_timeout', + type: 'text', + title: 'API Timeout', + description: + 'The maximum duration of AWS API can take. The maximum is half of the visibility timeout value.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'queue_url', + type: 'text', + title: 'Queue URL', + description: 'URL of the AWS SQS queue that messages will be received from.', + multi: false, + required: true, + show_user: true, + }, + { + name: 'fips_enabled', + type: 'bool', + title: 'Enable S3 FIPS', + description: + 'Enabling this option changes the service name from `s3` to `s3-fips` for connecting to the correct service endpoint.', + multi: false, + required: false, + show_user: false, + default: false, + }, + ], + template_path: 's3.yml.hbs', + title: 'AWS EC2 logs', + description: 'Collect AWS EC2 logs using s3 input', + enabled: true, + }, + ], + package: 'aws', + path: 'ec2_logs', + }, + { + type: 'metrics', + dataset: 'aws.ec2_metrics', + title: 'AWS EC2 metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '5m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'tags_filter', + type: 'yaml', + title: 'Tags Filter', + multi: false, + required: false, + show_user: false, + default: '# - key: "created-by"\n # value: "foo"\n', + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS EC2 metrics', + description: 'Collect AWS EC2 metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'ec2_metrics', + }, + { + type: 'logs', + dataset: 'aws.elb_logs', + title: 'AWS ELB logs', + release: 'beta', + ingest_pipeline: 'default', + streams: [ + { + input: 's3', + vars: [ + { + name: 'visibility_timeout', + type: 'text', + title: 'Visibility Timeout', + description: + 'The duration that the received messages are hidden from subsequent retrieve requests after being retrieved by a ReceiveMessage request. The maximum is 12 hours.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'api_timeout', + type: 'text', + title: 'API Timeout', + description: + 'The maximum duration of AWS API can take. The maximum is half of the visibility timeout value.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'queue_url', + type: 'text', + title: 'Queue URL', + description: 'URL of the AWS SQS queue that messages will be received from.', + multi: false, + required: true, + show_user: true, + }, + { + name: 'fips_enabled', + type: 'bool', + title: 'Enable S3 FIPS', + description: + 'Enabling this option changes the service name from `s3` to `s3-fips` for connecting to the correct service endpoint.', + multi: false, + required: false, + show_user: false, + default: false, + }, + ], + template_path: 's3.yml.hbs', + title: 'AWS ELB logs', + description: 'Collect AWS ELB logs using s3 input', + enabled: true, + }, + ], + package: 'aws', + path: 'elb_logs', + }, + { + type: 'metrics', + dataset: 'aws.elb_metrics', + title: 'AWS ELB metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '1m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'tags_filter', + type: 'yaml', + title: 'Tags Filter', + multi: false, + required: false, + show_user: false, + default: '# - key: "created-by"\n # value: "foo"\n', + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS ELB metrics', + description: 'Collect AWS ELB metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'elb_metrics', + }, + { + type: 'metrics', + dataset: 'aws.lambda', + title: 'AWS Lambda metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '5m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'tags_filter', + type: 'yaml', + title: 'Tags Filter', + multi: false, + required: false, + show_user: false, + default: '# - key: "created-by"\n # value: "foo"\n', + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS Lambda metrics', + description: 'Collect AWS Lambda metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'lambda', + }, + { + type: 'metrics', + dataset: 'aws.natgateway', + title: 'AWS NAT gateway metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '1m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS NAT gateway metrics', + description: 'Collect AWS NAT gateway metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'natgateway', + }, + { + type: 'metrics', + dataset: 'aws.rds', + title: 'AWS RDS metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '1m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'tags_filter', + type: 'yaml', + title: 'Tags Filter', + multi: false, + required: false, + show_user: false, + default: '# - key: "created-by"\n # value: "foo"\n', + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS RDS metrics', + description: 'Collect AWS RDS metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'rds', + }, + { + type: 'metrics', + dataset: 'aws.s3_daily_storage', + title: 'AWS S3 daily storage metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '24h', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS S3 daily storage metrics', + description: 'Collect AWS S3 daily storage metrics', + enabled: true, + }, + ], + package: 'aws', + path: 's3_daily_storage', + }, + { + type: 'metrics', + dataset: 'aws.s3_request', + title: 'AWS S3 request metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '1m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS S3 request metrics', + description: 'Collect AWS S3 request metrics', + enabled: true, + }, + ], + package: 'aws', + path: 's3_request', + }, + { + type: 'logs', + dataset: 'aws.s3access', + title: 'AWS s3access logs', + release: 'beta', + ingest_pipeline: 'default', + streams: [ + { + input: 's3', + vars: [ + { + name: 'visibility_timeout', + type: 'text', + title: 'Visibility Timeout', + description: + 'The duration that the received messages are hidden from subsequent retrieve requests after being retrieved by a ReceiveMessage request. The maximum is 12 hours.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'api_timeout', + type: 'text', + title: 'API Timeout', + description: + 'The maximum duration of AWS API can take. The maximum is half of the visibility timeout value.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'queue_url', + type: 'text', + title: 'Queue URL', + description: 'URL of the AWS SQS queue that messages will be received from.', + multi: false, + required: true, + show_user: true, + }, + { + name: 'fips_enabled', + type: 'bool', + title: 'Enable S3 FIPS', + description: + 'Enabling this option changes the service name from `s3` to `s3-fips` for connecting to the correct service endpoint.', + multi: false, + required: false, + show_user: false, + default: false, + }, + ], + template_path: 's3.yml.hbs', + title: 'AWS s3access logs', + description: 'Collect AWS s3access logs using s3 input', + enabled: true, + }, + ], + package: 'aws', + path: 's3access', + }, + { + type: 'metrics', + dataset: 'aws.sns', + title: 'AWS SNS metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '5m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'tags_filter', + type: 'yaml', + title: 'Tags Filter', + multi: false, + required: false, + show_user: false, + default: '# - key: "created-by"\n # value: "foo"\n', + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS SNS metrics', + description: 'Collect AWS SNS metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'sns', + }, + { + type: 'metrics', + dataset: 'aws.sqs', + title: 'AWS SQS metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '5m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS SQS metrics', + description: 'Collect AWS SQS metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'sqs', + }, + { + type: 'metrics', + dataset: 'aws.transitgateway', + title: 'AWS Transit Gateway metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '1m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS Transit Gateway metrics', + description: 'Collect AWS Transit Gateway metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'transitgateway', + }, + { + type: 'metrics', + dataset: 'aws.usage', + title: 'AWS usage metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '1m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS usage metrics', + description: 'Collect AWS usage metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'usage', + }, + { + type: 'logs', + dataset: 'aws.vpcflow', + title: 'AWS vpcflow logs', + release: 'beta', + ingest_pipeline: 'default', + streams: [ + { + input: 's3', + vars: [ + { + name: 'visibility_timeout', + type: 'text', + title: 'Visibility Timeout', + description: + 'The duration that the received messages are hidden from subsequent retrieve requests after being retrieved by a ReceiveMessage request. The maximum is 12 hours.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'api_timeout', + type: 'text', + title: 'API Timeout', + description: + 'The maximum duration of AWS API can take. The maximum is half of the visibility timeout value.', + multi: false, + required: false, + show_user: false, + }, + { + name: 'queue_url', + type: 'text', + title: 'Queue URL', + description: 'URL of the AWS SQS queue that messages will be received from.', + multi: false, + required: true, + show_user: true, + }, + { + name: 'fips_enabled', + type: 'bool', + title: 'Enable S3 FIPS', + description: + 'Enabling this option changes the service name from `s3` to `s3-fips` for connecting to the correct service endpoint.', + multi: false, + required: false, + show_user: false, + default: false, + }, + ], + template_path: 's3.yml.hbs', + title: 'AWS vpcflow logs', + description: 'Collect AWS vpcflow logs using s3 input', + enabled: true, + }, + ], + package: 'aws', + path: 'vpcflow', + }, + { + type: 'metrics', + dataset: 'aws.vpn', + title: 'AWS VPN metrics', + release: 'beta', + streams: [ + { + input: 'aws/metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '1m', + }, + { + name: 'regions', + type: 'text', + title: 'Regions', + multi: true, + required: false, + show_user: true, + }, + { + name: 'latency', + type: 'text', + title: 'Latency', + multi: false, + required: false, + show_user: false, + }, + { + name: 'tags_filter', + type: 'yaml', + title: 'Tags Filter', + multi: false, + required: false, + show_user: false, + default: '# - key: "created-by"\n # value: "foo"\n', + }, + ], + template_path: 'stream.yml.hbs', + title: 'AWS VPN metrics', + description: 'Collect AWS VPN metrics', + enabled: true, + }, + ], + package: 'aws', + path: 'vpn', + }, + ], + owner: { + github: 'elastic/integrations', + }, + vars: [ + { + name: 'shared_credential_file', + type: 'text', + title: 'Shared Credential File', + description: 'Directory of the shared credentials file', + multi: false, + required: false, + show_user: false, + }, + { + name: 'credential_profile_name', + type: 'text', + title: 'Credential Profile Name', + multi: false, + required: false, + show_user: true, + }, + { + name: 'access_key_id', + type: 'text', + title: 'Access Key ID', + multi: false, + required: false, + show_user: false, + }, + { + name: 'secret_access_key', + type: 'text', + title: 'Secret Access Key', + multi: false, + required: false, + show_user: false, + }, + { + name: 'session_token', + type: 'text', + title: 'Session Token', + multi: false, + required: false, + show_user: false, + }, + { + name: 'role_arn', + type: 'text', + title: 'Role ARN', + multi: false, + required: false, + show_user: false, + }, + { + name: 'endpoint', + type: 'text', + title: 'Endpoint', + description: 'URL of the entry point for an AWS web service', + multi: false, + required: false, + show_user: false, + default: 'amazonaws.com', + }, + ], + latestVersion: '0.5.3', + removable: true, + status: 'not_installed', +}; + +export const INVALID_AWS_POLICY = { + name: 'aws-1', + namespace: 'default', + package: { name: 'aws', title: 'AWS', version: '0.5.3' }, + enabled: true, + policy_id: 'some-agent-policy-id', + output_id: 'some-output-id', + inputs: [ + { + type: 'aws/metrics', + policy_template: 'billing', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.billing' }, + vars: { + period: { value: '12h', type: 'text' }, + latency: { type: 'text' }, + 'cost_explorer_config.group_by_dimension_keys': { + value: ['AZ', 'INSTANCE_TYPE', 'SERVICE'], + type: 'text', + }, + 'cost_explorer_config.group_by_tag_keys': { value: ['aws:createdBy'], type: 'text' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'cloudtrail', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.cloudtrail' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'httpjson', + policy_template: 'cloudtrail', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'logs', dataset: 'aws.cloudtrail' }, + vars: { + url: { value: 'https://server.example.com:8089', type: 'text' }, + username: { type: 'text' }, + password: { type: 'password' }, + ssl: { type: 'yaml' }, + interval: { value: '10s', type: 'text' }, + search: { value: 'search sourcetype=aws:cloudtrail', type: 'text' }, + tags: { value: ['forwarded'], type: 'text' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'cloudwatch', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.cloudwatch_logs' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'cloudwatch', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.cloudwatch_metrics' }, + vars: { + period: { value: '300s', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + metrics: { + value: + '- namespace: AWS/EC2\n resource_type: ec2:instance\n name:\n - CPUUtilization\n - DiskWriteOps\n statistic:\n - Average\n - Maximum\n # dimensions:\n # - name: InstanceId\n # value: i-123456\n # tags:\n # - key: created-by\n # value: foo\n', + type: 'yaml', + }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'dynamodb', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.dynamodb' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'ebs', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.ebs' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'ec2', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.ec2_logs' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'ec2', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.ec2_metrics' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'elb', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.elb_logs' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'elb', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.elb_metrics' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'lambda', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.lambda' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'natgateway', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.natgateway' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'rds', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.rds' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 's3', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.s3access' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 's3', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.s3_daily_storage' }, + vars: { + period: { value: '24h', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.s3_request' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'sns', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.sns' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'sqs', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.sqs' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'transitgateway', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.transitgateway' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'usage', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.usage' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'vpcflow', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.vpcflow' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'vpn', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.vpn' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + ], + vars: { + shared_credential_file: { type: 'text' }, + credential_profile_name: { type: 'text' }, + access_key_id: { type: 'text' }, + secret_access_key: { type: 'text' }, + session_token: { type: 'text' }, + role_arn: { type: 'text' }, + endpoint: { value: 'amazonaws.com', type: 'text' }, + }, +}; + +export const VALID_AWS_POLICY = { + name: 'aws-1', + namespace: 'default', + package: { name: 'aws', title: 'AWS', version: '0.5.3' }, + enabled: true, + policy_id: 'some-agent-policy-id', + output_id: 'some-output-id', + inputs: [ + { + type: 'aws/metrics', + policy_template: 'billing', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.billing' }, + vars: { + period: { value: '12h', type: 'text' }, + latency: { type: 'text' }, + 'cost_explorer_config.group_by_dimension_keys': { + value: ['AZ', 'INSTANCE_TYPE', 'SERVICE'], + type: 'text', + }, + 'cost_explorer_config.group_by_tag_keys': { value: ['aws:createdBy'], type: 'text' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'cloudtrail', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.cloudtrail' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text', value: 'http://localhost' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'httpjson', + policy_template: 'cloudtrail', + enabled: false, + streams: [ + { + enabled: false, + data_stream: { type: 'logs', dataset: 'aws.cloudtrail' }, + vars: { + url: { value: 'https://server.example.com:8089', type: 'text' }, + username: { type: 'text' }, + password: { type: 'password' }, + ssl: { type: 'yaml' }, + interval: { value: '10s', type: 'text' }, + search: { value: 'search sourcetype=aws:cloudtrail', type: 'text' }, + tags: { value: ['forwarded'], type: 'text' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'cloudwatch', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.cloudwatch_logs' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text', value: 'http://localhost' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'cloudwatch', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.cloudwatch_metrics' }, + vars: { + period: { value: '300s', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + metrics: { + value: + '- namespace: AWS/EC2\n resource_type: ec2:instance\n name:\n - CPUUtilization\n - DiskWriteOps\n statistic:\n - Average\n - Maximum\n # dimensions:\n # - name: InstanceId\n # value: i-123456\n # tags:\n # - key: created-by\n # value: foo\n', + type: 'yaml', + }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'dynamodb', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.dynamodb' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'ebs', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.ebs' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'ec2', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.ec2_logs' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text', value: 'http://localhost' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'ec2', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.ec2_metrics' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'elb', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.elb_logs' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text', value: 'http://localhost' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'elb', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.elb_metrics' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'lambda', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.lambda' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'natgateway', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.natgateway' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'rds', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.rds' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 's3', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.s3access' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text', value: 'http://localhost' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 's3', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.s3_daily_storage' }, + vars: { + period: { value: '24h', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.s3_request' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'sns', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.sns' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'sqs', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.sqs' }, + vars: { + period: { value: '5m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'transitgateway', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.transitgateway' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'usage', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.usage' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + }, + }, + ], + }, + { + type: 's3', + policy_template: 'vpcflow', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'aws.vpcflow' }, + vars: { + visibility_timeout: { type: 'text' }, + api_timeout: { type: 'text' }, + queue_url: { type: 'text', value: 'http://localhost' }, + fips_enabled: { value: false, type: 'bool' }, + }, + }, + ], + }, + { + type: 'aws/metrics', + policy_template: 'vpn', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'aws.vpn' }, + vars: { + period: { value: '1m', type: 'text' }, + regions: { value: [], type: 'text' }, + latency: { type: 'text' }, + tags_filter: { value: '# - key: "created-by"\n # value: "foo"\n', type: 'yaml' }, + }, + }, + ], + }, + ], + vars: { + shared_credential_file: { type: 'text' }, + credential_profile_name: { type: 'text' }, + access_key_id: { type: 'text' }, + secret_access_key: { type: 'text' }, + session_token: { type: 'text' }, + role_arn: { type: 'text' }, + endpoint: { value: 'amazonaws.com', type: 'text' }, + }, +}; diff --git a/x-pack/plugins/fleet/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts index 1fea5033e645c0..86361ae1633995 100644 --- a/x-pack/plugins/fleet/common/services/index.ts +++ b/x-pack/plugins/fleet/common/services/index.ts @@ -7,7 +7,11 @@ export * from './routes'; export * as AgentStatusKueryHelper from './agent_status'; -export { packageToPackagePolicyInputs, packageToPackagePolicy } from './package_to_package_policy'; +export { + packageToPackagePolicyInputs, + packageToPackagePolicy, + getStreamsForInputType, +} from './package_to_package_policy'; export { storedPackagePoliciesToAgentInputs } from './package_policies_to_agent_inputs'; export { fullAgentPolicyToYaml } from './full_agent_policy_to_yaml'; export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limited_package'; @@ -17,3 +21,12 @@ export { isDiffPathProtocol } from './is_diff_path_protocol'; export { LicenseService } from './license'; export { isAgentUpgradeable } from './is_agent_upgradeable'; export { doesPackageHaveIntegrations } from './packages_with_integrations'; +export { + PackagePolicyValidationResults, + PackagePolicyConfigValidationResults, + PackagePolicyInputValidationResults, + validatePackagePolicy, + validatePackagePolicyConfig, + validationHasErrors, + countValidationErrors, +} from './validate_package_policy'; diff --git a/x-pack/plugins/fleet/common/services/limited_package.ts b/x-pack/plugins/fleet/common/services/limited_package.ts index e247b61869de12..601f680c8bf039 100644 --- a/x-pack/plugins/fleet/common/services/limited_package.ts +++ b/x-pack/plugins/fleet/common/services/limited_package.ts @@ -7,9 +7,10 @@ import type { PackageInfo, AgentPolicy, PackagePolicy } from '../types'; -// Assume packages only ever include 1 config template for now export const isPackageLimited = (packageInfo: PackageInfo): boolean => { - return packageInfo.policy_templates?.[0]?.multiple === false; + return (packageInfo.policy_templates || []).some( + (policyTemplate) => policyTemplate.multiple === false + ); }; export const doesAgentPolicyAlreadyIncludePackage = ( diff --git a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts index 65b853ed5b38ff..0e5cab08edfb79 100644 --- a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts @@ -8,6 +8,7 @@ import type { PackageInfo } from '../types'; import { packageToPackagePolicy, packageToPackagePolicyInputs } from './package_to_package_policy'; +import { AWS_PACKAGE } from './fixtures/aws_package'; describe('Fleet - packageToPackagePolicy', () => { const mockPackage: PackageInfo = { @@ -59,7 +60,7 @@ describe('Fleet - packageToPackagePolicy', () => { expect( packageToPackagePolicyInputs(({ ...mockPackage, - policy_templates: [{ inputs: [] }], + policy_templates: [{ name: 'test_template', inputs: [] }], } as unknown) as PackageInfo) ).toEqual([]); }); @@ -68,17 +69,17 @@ describe('Fleet - packageToPackagePolicy', () => { expect( packageToPackagePolicyInputs(({ ...mockPackage, - policy_templates: [{ inputs: [{ type: 'foo' }] }], + policy_templates: [{ name: 'test_template', inputs: [{ type: 'foo' }] }], } as unknown) as PackageInfo) - ).toEqual([{ type: 'foo', enabled: true, streams: [] }]); + ).toEqual([{ type: 'foo', enabled: true, policy_template: 'test_template', streams: [] }]); expect( packageToPackagePolicyInputs(({ ...mockPackage, - policy_templates: [{ inputs: [{ type: 'foo' }, { type: 'bar' }] }], + policy_templates: [{ name: 'test_template', inputs: [{ type: 'foo' }, { type: 'bar' }] }], } as unknown) as PackageInfo) ).toEqual([ - { type: 'foo', enabled: true, streams: [] }, - { type: 'bar', enabled: true, streams: [] }, + { type: 'foo', enabled: true, policy_template: 'test_template', streams: [] }, + { type: 'bar', enabled: true, policy_template: 'test_template', streams: [] }, ]); }); @@ -91,24 +92,34 @@ describe('Fleet - packageToPackagePolicy', () => { { type: 'logs', dataset: 'bar', streams: [{ input: 'bar' }] }, { type: 'logs', dataset: 'bar2', streams: [{ input: 'bar' }] }, ], - policy_templates: [ - { - inputs: [{ type: 'foo' }, { type: 'bar' }], - }, - ], + policy_templates: [{ name: 'test_template', inputs: [{ type: 'foo' }, { type: 'bar' }] }], } as unknown) as PackageInfo) ).toEqual([ { type: 'foo', + policy_template: 'test_template', enabled: true, - streams: [{ enabled: true, data_stream: { dataset: 'foo', type: 'logs' } }], + streams: [ + { + enabled: true, + data_stream: { dataset: 'foo', type: 'logs' }, + }, + ], }, { type: 'bar', + policy_template: 'test_template', + enabled: true, streams: [ - { enabled: true, data_stream: { dataset: 'bar', type: 'logs' } }, - { enabled: true, data_stream: { dataset: 'bar2', type: 'logs' } }, + { + enabled: true, + data_stream: { dataset: 'bar', type: 'logs' }, + }, + { + enabled: true, + data_stream: { dataset: 'bar2', type: 'logs' }, + }, ], }, ]); @@ -145,15 +156,12 @@ describe('Fleet - packageToPackagePolicy', () => { ], }, ], - policy_templates: [ - { - inputs: [{ type: 'foo' }, { type: 'bar' }], - }, - ], + policy_templates: [{ name: 'test_template', inputs: [{ type: 'foo' }, { type: 'bar' }] }], } as unknown) as PackageInfo) ).toEqual([ { type: 'foo', + policy_template: 'test_template', enabled: true, streams: [ { @@ -165,6 +173,7 @@ describe('Fleet - packageToPackagePolicy', () => { }, { type: 'bar', + policy_template: 'test_template', enabled: true, streams: [ { @@ -236,6 +245,7 @@ describe('Fleet - packageToPackagePolicy', () => { ], policy_templates: [ { + name: 'test_template', inputs: [ { type: 'foo', @@ -262,6 +272,8 @@ describe('Fleet - packageToPackagePolicy', () => { ).toEqual([ { type: 'foo', + policy_template: 'test_template', + enabled: true, vars: { 'foo-input-var-name': { value: 'foo-input-var-value' }, @@ -280,6 +292,8 @@ describe('Fleet - packageToPackagePolicy', () => { }, { type: 'bar', + policy_template: 'test_template', + enabled: true, vars: { 'bar-input-var-name': { value: ['value1', 'value2'] }, @@ -304,6 +318,8 @@ describe('Fleet - packageToPackagePolicy', () => { }, { type: 'with-disabled-streams', + policy_template: 'test_template', + enabled: false, streams: [ { @@ -339,6 +355,7 @@ describe('Fleet - packageToPackagePolicy', () => { }, }); }); + it('returns package policy with custom name', () => { expect(packageToPackagePolicy(mockPackage, '1', '2', 'default', 'pkgPolicy-1')).toEqual({ policy_id: '1', @@ -354,6 +371,7 @@ describe('Fleet - packageToPackagePolicy', () => { }, }); }); + it('returns package policy with namespace and description', () => { expect( packageToPackagePolicy( @@ -379,6 +397,7 @@ describe('Fleet - packageToPackagePolicy', () => { }, }); }); + it('returns package policy with inputs', () => { const mockPackageWithPolicyTemplates = ({ ...mockPackage, @@ -401,5 +420,17 @@ describe('Fleet - packageToPackagePolicy', () => { }, }); }); + + it('returns package policy with multiple policy templates (aka has integrations', () => { + expect( + packageToPackagePolicy( + (AWS_PACKAGE as unknown) as PackageInfo, + 'some-agent-policy-id', + 'some-output-id', + 'default', + 'aws-1' + ) + ).toMatchSnapshot(); + }); }); }); diff --git a/x-pack/plugins/fleet/common/services/package_to_package_policy.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.ts index 8f79e633eed0c2..0d40adb4bf7dc3 100644 --- a/x-pack/plugins/fleet/common/services/package_to_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/package_to_package_policy.ts @@ -7,8 +7,8 @@ import type { PackageInfo, - RegistryPolicyTemplate, RegistryVarsEntry, + RegistryInput, RegistryStream, PackagePolicyConfigRecord, NewPackagePolicyInput, @@ -17,13 +17,20 @@ import type { PackagePolicyConfigRecordEntry, } from '../types'; -const getStreamsForInputType = ( +import { doesPackageHaveIntegrations } from './'; + +export const getStreamsForInputType = ( inputType: string, - packageInfo: PackageInfo + packageInfo: PackageInfo, + dataStreamPaths: string[] = [] ): Array => { const streams: Array = []; + const dataStreams = packageInfo.data_streams || []; + const dataStreamsToSearch = dataStreamPaths.length + ? dataStreams.filter((dataStream) => dataStreamPaths.includes(dataStream.path)) + : dataStreams; - (packageInfo.data_streams || []).forEach((dataStream) => { + dataStreamsToSearch.forEach((dataStream) => { (dataStream.streams || []).forEach((stream) => { if (stream.input === inputType) { streams.push({ @@ -59,48 +66,87 @@ const varsReducer = ( * This service creates a package policy inputs definition from defaults provided in package info */ export const packageToPackagePolicyInputs = ( - packageInfo: PackageInfo -): NewPackagePolicy['inputs'] => { - const inputs: NewPackagePolicy['inputs'] = []; - - // Assume package will only ever ship one package policy template for now - const packagePolicyTemplate: RegistryPolicyTemplate | null = - packageInfo.policy_templates && packageInfo.policy_templates[0] - ? packageInfo.policy_templates[0] - : null; - - // Create package policy input property - if (packagePolicyTemplate?.inputs?.length) { - // Map each package package policy input to agent policy package policy input - packagePolicyTemplate.inputs.forEach((packageInput) => { - // Map each package input stream into package policy input stream - const streams: NewPackagePolicyInputStream[] = getStreamsForInputType( - packageInput.type, - packageInfo - ).map((packageStream) => { - const stream: NewPackagePolicyInputStream = { - enabled: packageStream.enabled === false ? false : true, - data_stream: packageStream.data_stream, - }; - if (packageStream.vars && packageStream.vars.length) { - stream.vars = packageStream.vars.reduce(varsReducer, {}); - } - return stream; - }); - - const input: NewPackagePolicyInput = { - type: packageInput.type, - enabled: streams.length ? !!streams.find((stream) => stream.enabled) : true, - streams, + packageInfo: PackageInfo, + integrationToEnable?: string +): NewPackagePolicyInput[] => { + const hasIntegrations = doesPackageHaveIntegrations(packageInfo); + const inputs: NewPackagePolicyInput[] = []; + const packageInputsByPolicyTemplateAndType: { + [key: string]: RegistryInput & { data_streams?: string[]; policy_template: string }; + } = {}; + + packageInfo.policy_templates?.forEach((packagePolicyTemplate) => { + packagePolicyTemplate.inputs?.forEach((packageInput) => { + const inputKey = `${packagePolicyTemplate.name}-${packageInput.type}`; + const input = { + ...packageInput, + ...(packagePolicyTemplate.data_streams + ? { data_streams: packagePolicyTemplate.data_streams } + : {}), + policy_template: packagePolicyTemplate.name, }; + packageInputsByPolicyTemplateAndType[inputKey] = input; + }); + }); - if (packageInput.vars && packageInput.vars.length) { - input.vars = packageInput.vars.reduce(varsReducer, {}); - } + Object.values(packageInputsByPolicyTemplateAndType).forEach((packageInput) => { + const streamsForInput: NewPackagePolicyInputStream[] = []; + let varsForInput: PackagePolicyConfigRecord = {}; - inputs.push(input); + // Map each package input stream into package policy input stream + const streams = getStreamsForInputType( + packageInput.type, + packageInfo, + packageInput.data_streams + ).map((packageStream) => { + const stream: NewPackagePolicyInputStream = { + enabled: packageStream.enabled === false ? false : true, + data_stream: packageStream.data_stream, + }; + if (packageStream.vars && packageStream.vars.length) { + stream.vars = packageStream.vars.reduce(varsReducer, {}); + } + return stream; }); - } + + // If non-integration package, collect input-level vars, otherwise skip them, + // we do not support input-level vars for packages with integrations yet) + if (packageInput.vars?.length && !hasIntegrations) { + varsForInput = packageInput.vars.reduce(varsReducer, {}); + } + + streamsForInput.push(...streams); + + // Check if we should enable this input by the streams below it + // Enable it if at least one of its streams is enabled + let enableInput = streamsForInput.length + ? !!streamsForInput.find((stream) => stream.enabled) + : true; + + // If we are wanting to enabling this input, check if we only want + // to enable specific integrations (aka `policy_template`s) + if ( + enableInput && + hasIntegrations && + integrationToEnable && + integrationToEnable !== packageInput.policy_template + ) { + enableInput = false; + } + + const input: NewPackagePolicyInput = { + type: packageInput.type, + policy_template: packageInput.policy_template, + enabled: enableInput, + streams: streamsForInput, + }; + + if (Object.keys(varsForInput).length) { + input.vars = varsForInput; + } + + inputs.push(input); + }); return inputs; }; @@ -119,7 +165,8 @@ export const packageToPackagePolicy = ( outputId: string, namespace: string = '', packagePolicyName?: string, - description?: string + description?: string, + integrationToEnable?: string ): NewPackagePolicy => { const packagePolicy: NewPackagePolicy = { name: packagePolicyName || `${packageInfo.name}-1`, @@ -133,7 +180,8 @@ export const packageToPackagePolicy = ( enabled: true, policy_id: agentPolicyId, output_id: outputId, - inputs: packageToPackagePolicyInputs(packageInfo), + inputs: packageToPackagePolicyInputs(packageInfo, integrationToEnable), + vars: undefined, }; if (packageInfo.vars?.length) { diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts new file mode 100644 index 00000000000000..95dbf156040a17 --- /dev/null +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts @@ -0,0 +1,635 @@ +/* + * 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 { installationStatuses } from '../constants'; +import type { PackageInfo, NewPackagePolicy, RegistryPolicyTemplate } from '../types'; + +import { validatePackagePolicy, validationHasErrors } from './validate_package_policy'; +import { AWS_PACKAGE, INVALID_AWS_POLICY, VALID_AWS_POLICY } from './fixtures/aws_package'; + +describe('Fleet - validatePackagePolicy()', () => { + describe('works for packages with single policy template (aka no integrations)', () => { + const mockPackage = ({ + name: 'mock-package', + title: 'Mock package', + version: '0.0.0', + description: 'description', + type: 'mock', + categories: [], + requirement: { kibana: { versions: '' }, elasticsearch: { versions: '' } }, + format_version: '', + download: '', + path: '', + assets: { + kibana: { + dashboard: [], + visualization: [], + search: [], + 'index-pattern': [], + }, + }, + status: installationStatuses.NotInstalled, + data_streams: [ + { + dataset: 'foo', + streams: [ + { + input: 'foo', + title: 'Foo', + vars: [{ name: 'var-name', type: 'yaml' }], + }, + ], + }, + { + dataset: 'bar', + streams: [ + { + input: 'bar', + title: 'Bar', + vars: [{ name: 'var-name', type: 'yaml', required: true }], + }, + { + input: 'with-no-stream-vars', + title: 'Bar stream no vars', + enabled: true, + }, + ], + }, + { + dataset: 'bar2', + streams: [ + { + input: 'bar', + title: 'Bar 2', + vars: [{ default: 'bar2-var-value', name: 'var-name', type: 'text' }], + }, + ], + }, + { + dataset: 'disabled', + streams: [ + { + input: 'with-disabled-streams', + title: 'Disabled', + enabled: false, + vars: [{ multi: true, required: true, name: 'var-name', type: 'text' }], + }, + ], + }, + { + dataset: 'disabled2', + streams: [ + { + input: 'with-disabled-streams', + title: 'Disabled 2', + enabled: false, + }, + ], + }, + ], + policy_templates: [ + { + name: 'pkgPolicy1', + title: 'Package policy 1', + description: 'test package policy', + inputs: [ + { + type: 'foo', + title: 'Foo', + vars: [ + { default: 'foo-input-var-value', name: 'foo-input-var-name', type: 'text' }, + { + default: 'foo-input2-var-value', + name: 'foo-input2-var-name', + required: true, + type: 'text', + }, + { name: 'foo-input3-var-name', type: 'text', required: true, multi: true }, + ], + }, + { + type: 'bar', + title: 'Bar', + vars: [ + { + default: ['value1', 'value2'], + name: 'bar-input-var-name', + type: 'text', + multi: true, + }, + { name: 'bar-input2-var-name', required: true, type: 'text' }, + ], + }, + { + type: 'with-no-config-or-streams', + title: 'With no config or streams', + }, + { + type: 'with-disabled-streams', + title: 'With disabled streams', + }, + { + type: 'with-no-stream-vars', + enabled: true, + vars: [{ required: true, name: 'var-name', type: 'text' }], + }, + ], + }, + ], + } as unknown) as PackageInfo; + + const validPackagePolicy: NewPackagePolicy = { + name: 'pkgPolicy1-1', + namespace: 'default', + policy_id: 'test-policy', + enabled: true, + output_id: 'test-output', + inputs: [ + { + type: 'foo', + policy_template: 'pkgPolicy1', + enabled: true, + vars: { + 'foo-input-var-name': { value: 'foo-input-var-value', type: 'text' }, + 'foo-input2-var-name': { value: 'foo-input2-var-value', type: 'text' }, + 'foo-input3-var-name': { value: ['test'], type: 'text' }, + }, + streams: [ + { + data_stream: { dataset: 'foo', type: 'logs' }, + enabled: true, + vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, + }, + ], + }, + { + type: 'bar', + policy_template: 'pkgPolicy1', + enabled: true, + vars: { + 'bar-input-var-name': { value: ['value1', 'value2'], type: 'text' }, + 'bar-input2-var-name': { value: 'test', type: 'text' }, + }, + streams: [ + { + data_stream: { dataset: 'bar', type: 'logs' }, + enabled: true, + vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, + }, + { + data_stream: { dataset: 'bar2', type: 'logs' }, + enabled: true, + vars: { 'var-name': { value: undefined, type: 'text' } }, + }, + ], + }, + { + type: 'with-no-config-or-streams', + policy_template: 'pkgPolicy1', + enabled: true, + streams: [], + }, + { + type: 'with-disabled-streams', + policy_template: 'pkgPolicy1', + enabled: true, + streams: [ + { + data_stream: { dataset: 'disabled', type: 'logs' }, + enabled: false, + vars: { 'var-name': { value: undefined, type: 'text' } }, + }, + { + data_stream: { dataset: 'disabled2', type: 'logs' }, + enabled: false, + }, + ], + }, + { + type: 'with-no-stream-vars', + policy_template: 'pkgPolicy1', + enabled: true, + vars: { + 'var-name': { value: 'test', type: 'text' }, + }, + streams: [ + { + data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' }, + enabled: true, + }, + ], + }, + ], + }; + + const invalidPackagePolicy: NewPackagePolicy = { + ...validPackagePolicy, + name: '', + inputs: [ + { + type: 'foo', + policy_template: 'pkgPolicy1', + enabled: true, + vars: { + 'foo-input-var-name': { value: undefined, type: 'text' }, + 'foo-input2-var-name': { value: '', type: 'text' }, + 'foo-input3-var-name': { value: [], type: 'text' }, + }, + streams: [ + { + data_stream: { dataset: 'foo', type: 'logs' }, + enabled: true, + vars: { 'var-name': { value: 'invalidyaml: test\n foo bar:', type: 'yaml' } }, + }, + ], + }, + { + type: 'bar', + policy_template: 'pkgPolicy1', + enabled: true, + vars: { + 'bar-input-var-name': { value: 'invalid value for multi', type: 'text' }, + 'bar-input2-var-name': { value: undefined, type: 'text' }, + }, + streams: [ + { + data_stream: { dataset: 'bar', type: 'logs' }, + enabled: true, + vars: { 'var-name': { value: ' \n\n', type: 'yaml' } }, + }, + { + data_stream: { dataset: 'bar2', type: 'logs' }, + enabled: true, + vars: { 'var-name': { value: undefined, type: 'text' } }, + }, + ], + }, + { + type: 'with-no-config-or-streams', + policy_template: 'pkgPolicy1', + enabled: true, + streams: [], + }, + { + type: 'with-disabled-streams', + policy_template: 'pkgPolicy1', + enabled: true, + streams: [ + { + data_stream: { dataset: 'disabled', type: 'logs' }, + enabled: false, + vars: { + 'var-name': { + value: 'invalid value but not checked due to not enabled', + type: 'text', + }, + }, + }, + { + data_stream: { dataset: 'disabled2', type: 'logs' }, + enabled: false, + }, + ], + }, + { + type: 'with-no-stream-vars', + policy_template: 'pkgPolicy1', + enabled: true, + vars: { + 'var-name': { value: undefined, type: 'text' }, + }, + streams: [ + { + data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' }, + enabled: true, + }, + ], + }, + ], + }; + + const noErrorsValidationResults = { + name: null, + description: null, + namespace: null, + inputs: { + foo: { + vars: { + 'foo-input-var-name': null, + 'foo-input2-var-name': null, + 'foo-input3-var-name': null, + }, + streams: { foo: { vars: { 'var-name': null } } }, + }, + bar: { + vars: { 'bar-input-var-name': null, 'bar-input2-var-name': null }, + streams: { + bar: { vars: { 'var-name': null } }, + bar2: { vars: { 'var-name': null } }, + }, + }, + 'with-disabled-streams': { + streams: { + disabled: { + vars: { 'var-name': null }, + }, + disabled2: {}, + }, + }, + 'with-no-stream-vars': { + streams: { + 'with-no-stream-vars-bar': {}, + }, + vars: { 'var-name': null }, + }, + }, + }; + + it('returns no errors for valid package policy', () => { + expect(validatePackagePolicy(validPackagePolicy, mockPackage)).toEqual( + noErrorsValidationResults + ); + }); + + it('returns errors for invalid package policy', () => { + expect(validatePackagePolicy(invalidPackagePolicy, mockPackage)).toEqual({ + name: ['Name is required'], + description: null, + namespace: null, + inputs: { + foo: { + vars: { + 'foo-input-var-name': null, + 'foo-input2-var-name': ['foo-input2-var-name is required'], + 'foo-input3-var-name': ['foo-input3-var-name is required'], + }, + streams: { foo: { vars: { 'var-name': ['Invalid YAML format'] } } }, + }, + bar: { + vars: { + 'bar-input-var-name': ['Invalid format'], + 'bar-input2-var-name': ['bar-input2-var-name is required'], + }, + streams: { + bar: { vars: { 'var-name': ['var-name is required'] } }, + bar2: { vars: { 'var-name': null } }, + }, + }, + 'with-disabled-streams': { + streams: { + disabled: { vars: { 'var-name': null } }, + disabled2: {}, + }, + }, + 'with-no-stream-vars': { + vars: { + 'var-name': ['var-name is required'], + }, + streams: { 'with-no-stream-vars-bar': {} }, + }, + }, + }); + }); + + it('returns no errors for disabled inputs', () => { + const disabledInputs = invalidPackagePolicy.inputs.map((input) => ({ + ...input, + enabled: false, + })); + expect( + validatePackagePolicy({ ...validPackagePolicy, inputs: disabledInputs }, mockPackage) + ).toEqual(noErrorsValidationResults); + }); + + it('returns only package policy and input-level errors for disabled streams', () => { + const inputsWithDisabledStreams = invalidPackagePolicy.inputs.map((input) => + input.streams + ? { + ...input, + streams: input.streams.map((stream) => ({ ...stream, enabled: false })), + } + : input + ); + expect( + validatePackagePolicy( + { ...invalidPackagePolicy, inputs: inputsWithDisabledStreams }, + mockPackage + ) + ).toEqual({ + name: ['Name is required'], + description: null, + namespace: null, + inputs: { + foo: { + vars: { + 'foo-input-var-name': null, + 'foo-input2-var-name': ['foo-input2-var-name is required'], + 'foo-input3-var-name': ['foo-input3-var-name is required'], + }, + streams: { foo: { vars: { 'var-name': null } } }, + }, + bar: { + vars: { + 'bar-input-var-name': ['Invalid format'], + 'bar-input2-var-name': ['bar-input2-var-name is required'], + }, + streams: { + bar: { vars: { 'var-name': null } }, + bar2: { vars: { 'var-name': null } }, + }, + }, + 'with-disabled-streams': { + streams: { + disabled: { + vars: { 'var-name': null }, + }, + disabled2: {}, + }, + }, + 'with-no-stream-vars': { + vars: { + 'var-name': ['var-name is required'], + }, + streams: { 'with-no-stream-vars-bar': {} }, + }, + }, + }); + }); + + it('returns no errors for packages with no package policies', () => { + expect( + validatePackagePolicy(validPackagePolicy, { + ...mockPackage, + policy_templates: undefined, + }) + ).toEqual({ + name: null, + description: null, + namespace: null, + inputs: null, + }); + expect( + validatePackagePolicy(validPackagePolicy, { + ...mockPackage, + policy_templates: [], + }) + ).toEqual({ + name: null, + description: null, + namespace: null, + inputs: null, + }); + }); + + it('returns no errors for packages with no inputs', () => { + expect( + validatePackagePolicy(validPackagePolicy, { + ...mockPackage, + policy_templates: [{} as RegistryPolicyTemplate], + }) + ).toEqual({ + name: null, + description: null, + namespace: null, + inputs: null, + }); + expect( + validatePackagePolicy(validPackagePolicy, { + ...mockPackage, + policy_templates: [({ inputs: [] } as unknown) as RegistryPolicyTemplate], + }) + ).toEqual({ + name: null, + description: null, + namespace: null, + inputs: null, + }); + }); + }); + + describe('works for packages with multiple policy templates (aka integrations)', () => { + it('returns errors for invalid package policy', () => { + expect( + validatePackagePolicy( + INVALID_AWS_POLICY as NewPackagePolicy, + (AWS_PACKAGE as unknown) as PackageInfo + ) + ).toMatchSnapshot(); + }); + + it('returns no errors for valid package policy', () => { + expect( + validationHasErrors( + validatePackagePolicy( + VALID_AWS_POLICY as NewPackagePolicy, + (AWS_PACKAGE as unknown) as PackageInfo + ) + ) + ).toBe(false); + }); + }); +}); + +describe('Fleet - validationHasErrors()', () => { + it('returns true for stream validation results with errors', () => { + expect( + validationHasErrors({ + vars: { foo: ['foo error'], bar: null }, + }) + ).toBe(true); + }); + + it('returns false for stream validation results with no errors', () => { + expect( + validationHasErrors({ + vars: { foo: null, bar: null }, + }) + ).toBe(false); + }); + + it('returns true for input validation results with errors', () => { + expect( + validationHasErrors({ + vars: { foo: ['foo error'], bar: null }, + streams: { stream1: { vars: { foo: null, bar: null } } }, + }) + ).toBe(true); + expect( + validationHasErrors({ + vars: { foo: null, bar: null }, + streams: { stream1: { vars: { foo: ['foo error'], bar: null } } }, + }) + ).toBe(true); + }); + + it('returns false for input validation results with no errors', () => { + expect( + validationHasErrors({ + vars: { foo: null, bar: null }, + streams: { stream1: { vars: { foo: null, bar: null } } }, + }) + ).toBe(false); + }); + + it('returns true for package policy validation results with errors', () => { + expect( + validationHasErrors({ + name: ['name error'], + description: null, + namespace: null, + inputs: { + input1: { + vars: { foo: null, bar: null }, + streams: { stream1: { vars: { foo: null, bar: null } } }, + }, + }, + }) + ).toBe(true); + expect( + validationHasErrors({ + name: null, + description: null, + namespace: null, + inputs: { + input1: { + vars: { foo: ['foo error'], bar: null }, + streams: { stream1: { vars: { foo: null, bar: null } } }, + }, + }, + }) + ).toBe(true); + expect( + validationHasErrors({ + name: null, + description: null, + namespace: null, + inputs: { + input1: { + vars: { foo: null, bar: null }, + streams: { stream1: { vars: { foo: ['foo error'], bar: null } } }, + }, + }, + }) + ).toBe(true); + }); + + it('returns false for package policy validation results with no errors', () => { + expect( + validationHasErrors({ + name: null, + description: null, + namespace: null, + inputs: { + input1: { + vars: { foo: null, bar: null }, + streams: { stream1: { vars: { foo: null, bar: null } } }, + }, + }, + }) + ).toBe(false); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.ts similarity index 76% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts rename to x-pack/plugins/fleet/common/services/validate_package_policy.ts index de1b8df9f95974..b8673aa8b2301d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.ts @@ -5,21 +5,22 @@ * 2.0. */ +import { getFlattenedObject } from '@kbn/std'; import { i18n } from '@kbn/i18n'; import { safeLoad } from 'js-yaml'; import { keyBy } from 'lodash'; -import { getFlattenedObject, isValidNamespace } from '../../../../services'; import type { NewPackagePolicy, PackagePolicyInput, PackagePolicyInputStream, PackagePolicyConfigRecordEntry, PackageInfo, - RegistryInput, RegistryStream, RegistryVarsEntry, -} from '../../../../types'; +} from '../types'; + +import { isValidNamespace, doesPackageHaveIntegrations } from './'; type Errors = string[] | null; @@ -48,6 +49,7 @@ export const validatePackagePolicy = ( packagePolicy: NewPackagePolicy, packageInfo: PackageInfo ): PackagePolicyValidationResults => { + const hasIntegrations = doesPackageHaveIntegrations(packageInfo); const validationResults: PackagePolicyValidationResults = { name: null, description: null, @@ -81,54 +83,61 @@ export const validatePackagePolicy = ( if ( !packageInfo.policy_templates || packageInfo.policy_templates.length === 0 || - !packageInfo.policy_templates[0] || - !packageInfo.policy_templates[0].inputs || - packageInfo.policy_templates[0].inputs.length === 0 + !packageInfo.policy_templates.find( + (policyTemplate) => policyTemplate.inputs && policyTemplate.inputs.length > 0 + ) ) { validationResults.inputs = null; return validationResults; } - const registryInputsByType: Record< - string, - RegistryInput - > = packageInfo.policy_templates[0].inputs.reduce((inputs, registryInput) => { - inputs[registryInput.type] = registryInput; - return inputs; - }, {} as Record); - - const registryStreamsByDataset: Record = ( - packageInfo.data_streams || [] - ).reduce((dataStreams, registryDataStream) => { - dataStreams[registryDataStream.dataset] = registryDataStream.streams || []; - return dataStreams; - }, {} as Record); - - // Validate each package policy input with either its own config fields or streams + // Build cache for fast var definition lookup + const inputVarDefsByPolicyTemplateAndType = packageInfo.policy_templates.reduce< + Record> + >((varDefs, policyTemplate) => { + (policyTemplate.inputs || []).forEach((input) => { + const varDefKey = hasIntegrations ? `${policyTemplate.name}-${input.type}` : input.type; + if ((input.vars || []).length) { + varDefs[varDefKey] = keyBy(input.vars || [], 'name'); + } + }); + return varDefs; + }, {}); + const streamsByDatasetAndInput = (packageInfo.data_streams || []).reduce< + Record + >((streams, dataStream) => { + dataStream.streams?.forEach((stream) => { + streams[`${dataStream.dataset}-${stream.input}`] = stream; + }); + return streams; + }, {}); + const streamVarDefsByDatasetAndInput = Object.entries(streamsByDatasetAndInput).reduce< + Record> + >((varDefs, [path, stream]) => { + varDefs[path] = keyBy(stream.vars || [], 'name'); + return varDefs; + }, {}); + + // Validate each package policy input with either its own var fields and stream vars packagePolicy.inputs.forEach((input) => { if (!input.vars && !input.streams) { return; } - + const inputKey = hasIntegrations ? `${input.policy_template}-${input.type}` : input.type; const inputValidationResults: PackagePolicyInputValidationResults = { vars: undefined, streams: {}, }; - const inputVarsByName = (registryInputsByType[input.type].vars || []).reduce( - (vars, registryVar) => { - vars[registryVar.name] = registryVar; - return vars; - }, - {} as Record - ); - - // Validate input-level config fields - const inputConfigs = Object.entries(input.vars || {}); - if (inputConfigs.length) { - inputValidationResults.vars = inputConfigs.reduce((results, [name, configEntry]) => { + // Validate input-level var fields + const inputVars = Object.entries(input.vars || {}); + if (inputVars.length) { + inputValidationResults.vars = inputVars.reduce((results, [name, configEntry]) => { results[name] = input.enabled - ? validatePackagePolicyConfig(configEntry, inputVarsByName[name]) + ? validatePackagePolicyConfig( + configEntry, + inputVarDefsByPolicyTemplateAndType[inputKey][name] + ) : null; return results; }, {} as ValidationEntry); @@ -136,28 +145,20 @@ export const validatePackagePolicy = ( delete inputValidationResults.vars; } - // Validate each input stream with config fields + // Validate each input stream with var definitions if (input.streams.length) { input.streams.forEach((stream) => { const streamValidationResults: PackagePolicyConfigValidationResults = {}; + const streamVarDefs = + streamVarDefsByDatasetAndInput[`${stream.data_stream.dataset}-${input.type}`]; // Validate stream-level config fields if (stream.vars) { - const streamVarsByName = ( - ( - registryStreamsByDataset[stream.data_stream.dataset].find( - (registryStream) => registryStream.input === input.type - ) || {} - ).vars || [] - ).reduce((vars, registryVar) => { - vars[registryVar.name] = registryVar; - return vars; - }, {} as Record); streamValidationResults.vars = Object.entries(stream.vars).reduce( (results, [name, configEntry]) => { results[name] = - input.enabled && stream.enabled - ? validatePackagePolicyConfig(configEntry, streamVarsByName[name]) + streamVarDefs[name] && input.enabled && stream.enabled + ? validatePackagePolicyConfig(configEntry, streamVarDefs[name]) : null; return results; }, @@ -172,7 +173,7 @@ export const validatePackagePolicy = ( } if (inputValidationResults.vars || inputValidationResults.streams) { - validationResults.inputs![input.type] = inputValidationResults; + validationResults.inputs![inputKey] = inputValidationResults; } }); diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index 753100f6225567..a9393abcc57ef8 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -61,9 +61,9 @@ export interface FullAgentPolicyInput { } export interface FullAgentPolicyOutputPermissions { - [role: string]: { - cluster: string[]; - indices: Array<{ + [packagePolicyName: string]: { + cluster?: string[]; + indices?: Array<{ names: string[]; privileges: string[]; }>; diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 5551453b8975c2..0ef9f8b7ace36a 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -276,6 +276,7 @@ export enum RegistryDataStreamKeys { ingest_pipeline = 'ingest_pipeline', elasticsearch = 'elasticsearch', dataset_is_prefix = 'dataset_is_prefix', + permissions = 'permissions', } export interface RegistryDataStream { @@ -291,6 +292,7 @@ export interface RegistryDataStream { [RegistryDataStreamKeys.ingest_pipeline]?: string; [RegistryDataStreamKeys.elasticsearch]?: RegistryElasticsearch; [RegistryDataStreamKeys.dataset_is_prefix]?: boolean; + [RegistryDataStreamKeys.permissions]?: RegistryDataStreamPermissions; } export interface RegistryElasticsearch { @@ -298,6 +300,11 @@ export interface RegistryElasticsearch { 'index_template.mappings'?: object; } +export interface RegistryDataStreamPermissions { + cluster?: string[]; + indices?: string[]; +} + export type RegistryVarType = 'integer' | 'bool' | 'password' | 'text' | 'yaml' | 'string'; export enum RegistryVarsEntryKeys { name = 'name', diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts index c0b74c2a7b025f..40c3a0c66f15cf 100644 --- a/x-pack/plugins/fleet/common/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts @@ -37,6 +37,7 @@ export interface PackagePolicyInputStream extends NewPackagePolicyInputStream { export interface NewPackagePolicyInput { type: string; + policy_template?: string; enabled: boolean; keep_enabled?: boolean; vars?: PackagePolicyConfigRecord; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/index.ts index ac5a78a4ea7095..5e927c5b0e3d6f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/index.ts @@ -7,5 +7,4 @@ export * from '../../../components'; -export * from './enrollment_instructions'; export * from './search_bar'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx index eeb8f18d17d4f6..fd980475dc9194 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx @@ -91,10 +91,10 @@ const breadcrumbGetters: { }), }, ], - add_integration_to_policy: ({ pkgTitle, pkgkey }) => [ + add_integration_to_policy: ({ pkgTitle, pkgkey, integration }) => [ INTEGRATIONS_BASE_BREADCRUMB, { - href: pagePathGetters.integration_details_overview({ pkgkey })[1], + href: pagePathGetters.integration_details_overview({ pkgkey, integration })[1], text: pkgTitle, useIntegrationsBasePath: true, }, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx index 5f8bdd3176d2a0..ecc538bd95e2a6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx @@ -11,8 +11,7 @@ import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; import type { AgentPolicy } from '../../../types'; import { useCapabilities } from '../../../hooks'; -import { ContextMenuActions } from '../../../components'; -import { AgentEnrollmentFlyout } from '../../agents/components'; +import { AgentEnrollmentFlyout, ContextMenuActions } from '../../../components'; import { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout'; import { AgentPolicyCopyProvider } from './agent_policy_copy_provider'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts index 09282a7de7cb9b..439e474d416cb5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts @@ -11,8 +11,6 @@ export { LinkedAgentCount } from '../../../components'; export { AgentPolicyForm, agentPolicyFormValidation } from './agent_policy_form'; export { AgentPolicyCopyProvider } from './agent_policy_copy_provider'; export { AgentPolicyDeleteProvider } from './agent_policy_delete_provider'; -export { PackagePolicyDeleteProvider } from './package_policy_delete_provider'; export { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout'; export { ConfirmDeployAgentPolicyModal } from './confirm_deploy_modal'; -export { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; export { AgentPolicyActionMenu } from './actions_menu'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx index d2bc49bdf00e07..060c49a84c5aae 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { WithHeaderLayout } from '../../../../layouts'; -import type { AgentPolicy, PackageInfo } from '../../../../types'; +import type { AgentPolicy, PackageInfo, RegistryPolicyTemplate } from '../../../../types'; import { PackageIcon } from '../../../../components'; import type { CreatePackagePolicyFrom } from '../types'; @@ -29,6 +29,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ onCancel?: React.ReactEventHandler; agentPolicy?: AgentPolicy; packageInfo?: PackageInfo; + integrationInfo?: RegistryPolicyTemplate; 'data-test-subj'?: string; }> = memo( ({ @@ -37,6 +38,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ onCancel, agentPolicy, packageInfo, + integrationInfo, children, 'data-test-subj': dataTestSubj, }) => { @@ -47,8 +49,9 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ @@ -68,7 +71,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ id="xpack.fleet.createPackagePolicy.pageTitleWithPackageName" defaultMessage="Add {packageName} integration" values={{ - packageName: packageInfo.title, + packageName: integrationInfo?.title || packageInfo.title, }} /> )} @@ -98,7 +101,14 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ ); - }, [dataTestSubj, from, packageInfo]); + }, [ + dataTestSubj, + from, + integrationInfo?.icons, + integrationInfo?.name, + integrationInfo?.title, + packageInfo, + ]); const pageDescription = useMemo(() => { return from === 'edit' || from === 'package-edit' ? ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx index 6563918a5efb2c..3e4b120a28f8e7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx @@ -60,19 +60,26 @@ const StepsWithLessPadding = styled(EuiSteps)` } `; +interface AddToPolicyParams { + pkgkey: string; + integration?: string; +} + +interface AddFromPolicyParams { + policyId: string; +} + export const CreatePackagePolicyPage: React.FunctionComponent = () => { const { notifications } = useStartServices(); const { agents: { enabled: isFleetEnabled }, } = useConfig(); - const { - params: { policyId, pkgkey }, - } = useRouteMatch<{ policyId: string; pkgkey: string }>(); + const { params } = useRouteMatch(); const { getHref, getPath } = useLink(); const history = useHistory(); const handleNavigateTo = useNavigateToCallback(); const routeState = useIntraAppState(); - const from: CreatePackagePolicyFrom = policyId ? 'policy' : 'package'; + const from: CreatePackagePolicyFrom = 'policyId' in params ? 'policy' : 'package'; // Agent policy and package info states const [agentPolicy, setAgentPolicy] = useState(); @@ -215,9 +222,11 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { return routeState.onCancelUrl; } return from === 'policy' - ? getHref('policy_details', { policyId: agentPolicyId || policyId }) - : getHref('integration_details_overview', { pkgkey }); - }, [agentPolicyId, policyId, from, getHref, pkgkey, routeState]); + ? getHref('policy_details', { + policyId: agentPolicyId || (params as AddFromPolicyParams).policyId, + }) + : getHref('integration_details_overview', { pkgkey: (params as AddToPolicyParams).pkgkey }); + }, [agentPolicyId, params, from, getHref, routeState]); const cancelClickHandler: ReactEventHandler = useCallback( (ev) => { @@ -230,14 +239,14 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { ); // Save package policy - const savePackagePolicy = async () => { + const savePackagePolicy = useCallback(async () => { setFormState('LOADING'); const result = await sendCreatePackagePolicy(packagePolicy); setFormState('SUBMITTED'); return result; - }; + }, [packagePolicy]); - const onSubmit = async () => { + const onSubmit = useCallback(async () => { if (formState === 'VALID' && hasErrors) { setFormState('INVALID'); return; @@ -255,7 +264,11 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { : routeState.onSaveNavigateTo ); } else { - history.push(getPath('policy_details', { policyId: agentPolicy?.id || policyId })); + history.push( + getPath('policy_details', { + policyId: agentPolicy?.id || (params as AddFromPolicyParams).policyId, + }) + ); } notifications.toasts.addSuccess({ @@ -282,27 +295,54 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { }); setFormState('VALID'); } - }; - - const layoutProps = { - from, - cancelUrl, - onCancel: cancelClickHandler, + }, [ + agentCount, agentPolicy, - packageInfo, - }; + formState, + getPath, + handleNavigateTo, + hasErrors, + history, + notifications.toasts, + packagePolicy.name, + params, + routeState, + savePackagePolicy, + ]); + + const integrationInfo = useMemo( + () => + (params as AddToPolicyParams).integration + ? packageInfo?.policy_templates?.find( + (policyTemplate) => policyTemplate.name === (params as AddToPolicyParams).integration + ) + : undefined, + [packageInfo?.policy_templates, params] + ); + + const layoutProps = useMemo( + () => ({ + from, + cancelUrl, + onCancel: cancelClickHandler, + agentPolicy, + packageInfo, + integrationInfo, + }), + [agentPolicy, cancelClickHandler, cancelUrl, from, integrationInfo, packageInfo] + ); const stepSelectAgentPolicy = useMemo( () => ( ), - [pkgkey, updatePackageInfo, agentPolicy, updateAgentPolicy] + [params, updatePackageInfo, agentPolicy, updateAgentPolicy] ); const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create'); @@ -310,14 +350,14 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { const stepSelectPackage = useMemo( () => ( ), - [policyId, updateAgentPolicy, packageInfo, updatePackageInfo] + [params, updateAgentPolicy, packageInfo, updatePackageInfo] ); const stepConfigurePackagePolicy = useMemo( @@ -333,12 +373,14 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { updatePackagePolicy={updatePackagePolicy} validationResults={validationResults!} submitAttempted={formState === 'INVALID'} + integrationToEnable={integrationInfo?.name} /> {/* Only show the out-of-box configuration step if a UI extension is NOT registered */} {!ExtensionView && ( { updatePackagePolicy, validationResults, formState, + integrationInfo?.name, ExtensionView, handleExtensionViewOnChange, ] @@ -406,8 +449,9 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { {from === 'package' ? packageInfo && ( ) : agentPolicy && ( @@ -476,8 +520,13 @@ const PolicyBreadcrumb: React.FunctionComponent<{ const IntegrationBreadcrumb: React.FunctionComponent<{ pkgTitle: string; pkgkey: string; -}> = ({ pkgTitle, pkgkey }) => { - useBreadcrumbs('add_integration_to_policy', { pkgTitle, pkgkey }); + integration?: string; +}> = ({ pkgTitle, pkgkey, integration }) => { + useBreadcrumbs('add_integration_to_policy', { + pkgTitle, + pkgkey, + ...(integration ? { integration } : {}), + }); return null; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/index.ts index 0c2dc6c1c64912..0e1953316fd53c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/index.ts @@ -15,4 +15,4 @@ export { validatePackagePolicyConfig, validationHasErrors, countValidationErrors, -} from './validate_package_policy'; +} from '../../../../services'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts deleted file mode 100644 index 180a585a4305ac..00000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts +++ /dev/null @@ -1,600 +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 { installationStatuses } from '../../../../../../../common/constants'; -import type { PackageInfo, NewPackagePolicy, RegistryPolicyTemplate } from '../../../../types'; - -import { validatePackagePolicy, validationHasErrors } from './validate_package_policy'; - -describe('Fleet - validatePackagePolicy()', () => { - const mockPackage = ({ - name: 'mock-package', - title: 'Mock package', - version: '0.0.0', - description: 'description', - type: 'mock', - categories: [], - requirement: { kibana: { versions: '' }, elasticsearch: { versions: '' } }, - format_version: '', - download: '', - path: '', - assets: { - kibana: { - dashboard: [], - visualization: [], - search: [], - 'index-pattern': [], - }, - }, - status: installationStatuses.NotInstalled, - data_streams: [ - { - dataset: 'foo', - streams: [ - { - input: 'foo', - title: 'Foo', - vars: [{ name: 'var-name', type: 'yaml' }], - }, - ], - }, - { - dataset: 'bar', - streams: [ - { - input: 'bar', - title: 'Bar', - vars: [{ name: 'var-name', type: 'yaml', required: true }], - }, - { - input: 'with-no-stream-vars', - title: 'Bar stream no vars', - enabled: true, - }, - ], - }, - { - dataset: 'bar2', - streams: [ - { - input: 'bar', - title: 'Bar 2', - vars: [{ default: 'bar2-var-value', name: 'var-name', type: 'text' }], - }, - ], - }, - { - dataset: 'disabled', - streams: [ - { - input: 'with-disabled-streams', - title: 'Disabled', - enabled: false, - vars: [{ multi: true, required: true, name: 'var-name', type: 'text' }], - }, - ], - }, - { - dataset: 'disabled2', - streams: [ - { - input: 'with-disabled-streams', - title: 'Disabled 2', - enabled: false, - }, - ], - }, - ], - policy_templates: [ - { - name: 'pkgPolicy1', - title: 'Package policy 1', - description: 'test package policy', - inputs: [ - { - type: 'foo', - title: 'Foo', - vars: [ - { default: 'foo-input-var-value', name: 'foo-input-var-name', type: 'text' }, - { - default: 'foo-input2-var-value', - name: 'foo-input2-var-name', - required: true, - type: 'text', - }, - { name: 'foo-input3-var-name', type: 'text', required: true, multi: true }, - ], - }, - { - type: 'bar', - title: 'Bar', - vars: [ - { - default: ['value1', 'value2'], - name: 'bar-input-var-name', - type: 'text', - multi: true, - }, - { name: 'bar-input2-var-name', required: true, type: 'text' }, - ], - }, - { - type: 'with-no-config-or-streams', - title: 'With no config or streams', - }, - { - type: 'with-disabled-streams', - title: 'With disabled streams', - }, - { - type: 'with-no-stream-vars', - enabled: true, - vars: [{ required: true, name: 'var-name', type: 'text' }], - }, - ], - }, - ], - } as unknown) as PackageInfo; - - const validPackagePolicy: NewPackagePolicy = { - name: 'pkgPolicy1-1', - namespace: 'default', - policy_id: 'test-policy', - enabled: true, - output_id: 'test-output', - inputs: [ - { - type: 'foo', - enabled: true, - vars: { - 'foo-input-var-name': { value: 'foo-input-var-value', type: 'text' }, - 'foo-input2-var-name': { value: 'foo-input2-var-value', type: 'text' }, - 'foo-input3-var-name': { value: ['test'], type: 'text' }, - }, - streams: [ - { - data_stream: { dataset: 'foo', type: 'logs' }, - enabled: true, - vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, - }, - ], - }, - { - type: 'bar', - enabled: true, - vars: { - 'bar-input-var-name': { value: ['value1', 'value2'], type: 'text' }, - 'bar-input2-var-name': { value: 'test', type: 'text' }, - }, - streams: [ - { - data_stream: { dataset: 'bar', type: 'logs' }, - enabled: true, - vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, - }, - { - data_stream: { dataset: 'bar2', type: 'logs' }, - enabled: true, - vars: { 'var-name': { value: undefined, type: 'text' } }, - }, - ], - }, - { - type: 'with-no-config-or-streams', - enabled: true, - streams: [], - }, - { - type: 'with-disabled-streams', - enabled: true, - streams: [ - { - data_stream: { dataset: 'disabled', type: 'logs' }, - enabled: false, - vars: { 'var-name': { value: undefined, type: 'text' } }, - }, - { - data_stream: { dataset: 'disabled2', type: 'logs' }, - enabled: false, - }, - ], - }, - { - type: 'with-no-stream-vars', - enabled: true, - vars: { - 'var-name': { value: 'test', type: 'text' }, - }, - streams: [ - { - data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' }, - enabled: true, - }, - ], - }, - ], - }; - - const invalidPackagePolicy: NewPackagePolicy = { - ...validPackagePolicy, - name: '', - inputs: [ - { - type: 'foo', - enabled: true, - vars: { - 'foo-input-var-name': { value: undefined, type: 'text' }, - 'foo-input2-var-name': { value: '', type: 'text' }, - 'foo-input3-var-name': { value: [], type: 'text' }, - }, - streams: [ - { - data_stream: { dataset: 'foo', type: 'logs' }, - enabled: true, - vars: { 'var-name': { value: 'invalidyaml: test\n foo bar:', type: 'yaml' } }, - }, - ], - }, - { - type: 'bar', - enabled: true, - vars: { - 'bar-input-var-name': { value: 'invalid value for multi', type: 'text' }, - 'bar-input2-var-name': { value: undefined, type: 'text' }, - }, - streams: [ - { - data_stream: { dataset: 'bar', type: 'logs' }, - enabled: true, - vars: { 'var-name': { value: ' \n\n', type: 'yaml' } }, - }, - { - data_stream: { dataset: 'bar2', type: 'logs' }, - enabled: true, - vars: { 'var-name': { value: undefined, type: 'text' } }, - }, - ], - }, - { - type: 'with-no-config-or-streams', - enabled: true, - streams: [], - }, - { - type: 'with-disabled-streams', - enabled: true, - streams: [ - { - data_stream: { dataset: 'disabled', type: 'logs' }, - enabled: false, - vars: { - 'var-name': { - value: 'invalid value but not checked due to not enabled', - type: 'text', - }, - }, - }, - { - data_stream: { dataset: 'disabled2', type: 'logs' }, - enabled: false, - }, - ], - }, - { - type: 'with-no-stream-vars', - enabled: true, - vars: { - 'var-name': { value: undefined, type: 'text' }, - }, - streams: [ - { - data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' }, - enabled: true, - }, - ], - }, - ], - }; - - const noErrorsValidationResults = { - name: null, - description: null, - namespace: null, - inputs: { - foo: { - vars: { - 'foo-input-var-name': null, - 'foo-input2-var-name': null, - 'foo-input3-var-name': null, - }, - streams: { foo: { vars: { 'var-name': null } } }, - }, - bar: { - vars: { 'bar-input-var-name': null, 'bar-input2-var-name': null }, - streams: { - bar: { vars: { 'var-name': null } }, - bar2: { vars: { 'var-name': null } }, - }, - }, - 'with-disabled-streams': { - streams: { - disabled: { - vars: { 'var-name': null }, - }, - disabled2: {}, - }, - }, - 'with-no-stream-vars': { - streams: { - 'with-no-stream-vars-bar': {}, - }, - vars: { 'var-name': null }, - }, - }, - }; - - it('returns no errors for valid package policy', () => { - expect(validatePackagePolicy(validPackagePolicy, mockPackage)).toEqual( - noErrorsValidationResults - ); - }); - - it('returns errors for invalid package policy', () => { - expect(validatePackagePolicy(invalidPackagePolicy, mockPackage)).toEqual({ - name: ['Name is required'], - description: null, - namespace: null, - inputs: { - foo: { - vars: { - 'foo-input-var-name': null, - 'foo-input2-var-name': ['foo-input2-var-name is required'], - 'foo-input3-var-name': ['foo-input3-var-name is required'], - }, - streams: { foo: { vars: { 'var-name': ['Invalid YAML format'] } } }, - }, - bar: { - vars: { - 'bar-input-var-name': ['Invalid format'], - 'bar-input2-var-name': ['bar-input2-var-name is required'], - }, - streams: { - bar: { vars: { 'var-name': ['var-name is required'] } }, - bar2: { vars: { 'var-name': null } }, - }, - }, - 'with-disabled-streams': { - streams: { - disabled: { vars: { 'var-name': null } }, - disabled2: {}, - }, - }, - 'with-no-stream-vars': { - vars: { - 'var-name': ['var-name is required'], - }, - streams: { 'with-no-stream-vars-bar': {} }, - }, - }, - }); - }); - - it('returns no errors for disabled inputs', () => { - const disabledInputs = invalidPackagePolicy.inputs.map((input) => ({ - ...input, - enabled: false, - })); - expect( - validatePackagePolicy({ ...validPackagePolicy, inputs: disabledInputs }, mockPackage) - ).toEqual(noErrorsValidationResults); - }); - - it('returns only package policy and input-level errors for disabled streams', () => { - const inputsWithDisabledStreams = invalidPackagePolicy.inputs.map((input) => - input.streams - ? { - ...input, - streams: input.streams.map((stream) => ({ ...stream, enabled: false })), - } - : input - ); - expect( - validatePackagePolicy( - { ...invalidPackagePolicy, inputs: inputsWithDisabledStreams }, - mockPackage - ) - ).toEqual({ - name: ['Name is required'], - description: null, - namespace: null, - inputs: { - foo: { - vars: { - 'foo-input-var-name': null, - 'foo-input2-var-name': ['foo-input2-var-name is required'], - 'foo-input3-var-name': ['foo-input3-var-name is required'], - }, - streams: { foo: { vars: { 'var-name': null } } }, - }, - bar: { - vars: { - 'bar-input-var-name': ['Invalid format'], - 'bar-input2-var-name': ['bar-input2-var-name is required'], - }, - streams: { - bar: { vars: { 'var-name': null } }, - bar2: { vars: { 'var-name': null } }, - }, - }, - 'with-disabled-streams': { - streams: { - disabled: { - vars: { 'var-name': null }, - }, - disabled2: {}, - }, - }, - 'with-no-stream-vars': { - vars: { - 'var-name': ['var-name is required'], - }, - streams: { 'with-no-stream-vars-bar': {} }, - }, - }, - }); - }); - - it('returns no errors for packages with no package policies', () => { - expect( - validatePackagePolicy(validPackagePolicy, { - ...mockPackage, - policy_templates: undefined, - }) - ).toEqual({ - name: null, - description: null, - namespace: null, - inputs: null, - }); - expect( - validatePackagePolicy(validPackagePolicy, { - ...mockPackage, - policy_templates: [], - }) - ).toEqual({ - name: null, - description: null, - namespace: null, - inputs: null, - }); - }); - - it('returns no errors for packages with no inputs', () => { - expect( - validatePackagePolicy(validPackagePolicy, { - ...mockPackage, - policy_templates: [{} as RegistryPolicyTemplate], - }) - ).toEqual({ - name: null, - description: null, - namespace: null, - inputs: null, - }); - expect( - validatePackagePolicy(validPackagePolicy, { - ...mockPackage, - policy_templates: [({ inputs: [] } as unknown) as RegistryPolicyTemplate], - }) - ).toEqual({ - name: null, - description: null, - namespace: null, - inputs: null, - }); - }); -}); - -describe('Fleet - validationHasErrors()', () => { - it('returns true for stream validation results with errors', () => { - expect( - validationHasErrors({ - vars: { foo: ['foo error'], bar: null }, - }) - ).toBe(true); - }); - - it('returns false for stream validation results with no errors', () => { - expect( - validationHasErrors({ - vars: { foo: null, bar: null }, - }) - ).toBe(false); - }); - - it('returns true for input validation results with errors', () => { - expect( - validationHasErrors({ - vars: { foo: ['foo error'], bar: null }, - streams: { stream1: { vars: { foo: null, bar: null } } }, - }) - ).toBe(true); - expect( - validationHasErrors({ - vars: { foo: null, bar: null }, - streams: { stream1: { vars: { foo: ['foo error'], bar: null } } }, - }) - ).toBe(true); - }); - - it('returns false for input validation results with no errors', () => { - expect( - validationHasErrors({ - vars: { foo: null, bar: null }, - streams: { stream1: { vars: { foo: null, bar: null } } }, - }) - ).toBe(false); - }); - - it('returns true for package policy validation results with errors', () => { - expect( - validationHasErrors({ - name: ['name error'], - description: null, - namespace: null, - inputs: { - input1: { - vars: { foo: null, bar: null }, - streams: { stream1: { vars: { foo: null, bar: null } } }, - }, - }, - }) - ).toBe(true); - expect( - validationHasErrors({ - name: null, - description: null, - namespace: null, - inputs: { - input1: { - vars: { foo: ['foo error'], bar: null }, - streams: { stream1: { vars: { foo: null, bar: null } } }, - }, - }, - }) - ).toBe(true); - expect( - validationHasErrors({ - name: null, - description: null, - namespace: null, - inputs: { - input1: { - vars: { foo: null, bar: null }, - streams: { stream1: { vars: { foo: ['foo error'], bar: null } } }, - }, - }, - }) - ).toBe(true); - }); - - it('returns false for package policy validation results with no errors', () => { - expect( - validationHasErrors({ - name: null, - description: null, - namespace: null, - inputs: { - input1: { - vars: { foo: null, bar: null }, - streams: { stream1: { vars: { foo: null, bar: null } } }, - }, - }, - }) - ).toBe(false); - }); -}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx index e64598e583d353..1ff5d20baec068 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiHorizontalRule, EuiFlexGroup, @@ -15,86 +15,92 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import type { - PackageInfo, - RegistryStream, - NewPackagePolicy, - NewPackagePolicyInput, -} from '../../../types'; +import type { PackageInfo, NewPackagePolicy, NewPackagePolicyInput } from '../../../types'; import { Loading } from '../../../components'; +import { getStreamsForInputType, doesPackageHaveIntegrations } from '../../../services'; import type { PackagePolicyValidationResults } from './services'; import { PackagePolicyInputPanel } from './components'; -const findStreamsForInputType = ( - inputType: string, - packageInfo: PackageInfo -): Array => { - const streams: Array = []; - - (packageInfo.data_streams || []).forEach((dataStream) => { - (dataStream.streams || []).forEach((stream) => { - if (stream.input === inputType) { - streams.push({ - ...stream, - data_stream: { - dataset: dataStream.dataset, - }, - }); - } - }); - }); - - return streams; -}; - export const StepConfigurePackagePolicy: React.FunctionComponent<{ packageInfo: PackageInfo; + showOnlyIntegration?: string; packagePolicy: NewPackagePolicy; updatePackagePolicy: (fields: Partial) => void; validationResults: PackagePolicyValidationResults; submitAttempted: boolean; -}> = ({ packageInfo, packagePolicy, updatePackagePolicy, validationResults, submitAttempted }) => { +}> = ({ + packageInfo, + showOnlyIntegration, + packagePolicy, + updatePackagePolicy, + validationResults, + submitAttempted, +}) => { + const hasIntegrations = useMemo(() => doesPackageHaveIntegrations(packageInfo), [packageInfo]); + const packagePolicyTemplates = useMemo( + () => + showOnlyIntegration + ? (packageInfo.policy_templates || []).filter( + (policyTemplate) => policyTemplate.name === showOnlyIntegration + ) + : packageInfo.policy_templates || [], + [packageInfo.policy_templates, showOnlyIntegration] + ); + // Configure inputs (and their streams) // Assume packages only export one config template for now const renderConfigureInputs = () => - packageInfo.policy_templates && - packageInfo.policy_templates[0] && - packageInfo.policy_templates[0].inputs && - packageInfo.policy_templates[0].inputs.length ? ( + packagePolicyTemplates.length ? ( <> - {packageInfo.policy_templates[0].inputs.map((packageInput) => { - const packagePolicyInput = packagePolicy.inputs.find( - (input) => input.type === packageInput.type - ); - const packageInputStreams = findStreamsForInputType(packageInput.type, packageInfo); - return packagePolicyInput ? ( - - ) => { - const indexOfUpdatedInput = packagePolicy.inputs.findIndex( - (input) => input.type === packageInput.type - ); - const newInputs = [...packagePolicy.inputs]; - newInputs[indexOfUpdatedInput] = { - ...newInputs[indexOfUpdatedInput], - ...updatedInput, - }; - updatePackagePolicy({ - inputs: newInputs, - }); - }} - inputValidationResults={validationResults!.inputs![packagePolicyInput.type]} - forceShowErrors={submitAttempted} - /> - - - ) : null; + {packagePolicyTemplates.map((policyTemplate) => { + return (policyTemplate.inputs || []).map((packageInput) => { + const packagePolicyInput = packagePolicy.inputs.find( + (input) => + input.type === packageInput.type && + (hasIntegrations ? input.policy_template === policyTemplate.name : true) + ); + const packageInputStreams = getStreamsForInputType( + packageInput.type, + packageInfo, + hasIntegrations ? policyTemplate.data_streams : [] + ); + return packagePolicyInput ? ( + + ) => { + const indexOfUpdatedInput = packagePolicy.inputs.findIndex( + (input) => + input.type === packageInput.type && + (hasIntegrations ? input.policy_template === policyTemplate.name : true) + ); + const newInputs = [...packagePolicy.inputs]; + newInputs[indexOfUpdatedInput] = { + ...newInputs[indexOfUpdatedInput], + ...updatedInput, + }; + updatePackagePolicy({ + inputs: newInputs, + }); + }} + inputValidationResults={ + validationResults!.inputs![ + hasIntegrations + ? `${policyTemplate.name}-${packagePolicyInput.type}` + : packagePolicyInput.type + ] + } + forceShowErrors={submitAttempted} + /> + + + ) : null; + }); })} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx index 380e49a1d8dd97..7444bed6ed3fdb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx @@ -38,7 +38,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ agentPolicy: AgentPolicy; packageInfo: PackageInfo; packagePolicy: NewPackagePolicy; - integration?: string; + integrationToEnable?: string; updatePackagePolicy: (fields: Partial) => void; validationResults: PackagePolicyValidationResults; submitAttempted: boolean; @@ -47,7 +47,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ agentPolicy, packageInfo, packagePolicy, - integration, + integrationToEnable, updatePackagePolicy, validationResults, submitAttempted, @@ -95,7 +95,8 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ ? pkgPoliciesWithMatchingNames[pkgPoliciesWithMatchingNames.length - 1] + 1 : 1 }`, - packagePolicy.description + packagePolicy.description, + integrationToEnable ) ); } @@ -107,7 +108,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ namespace: agentPolicy.namespace, }); } - }, [packagePolicy, agentPolicy, packageInfo, updatePackagePolicy, integration]); + }, [packagePolicy, agentPolicy, packageInfo, updatePackagePolicy, integrationToEnable]); return validationResults ? ( = ({ }) => { const { getHref } = useLink(); const hasWriteCapabilities = useCapabilities().write; - const refreshAgentPolicy = useAgentPolicyRefresh(); // With the package policies provided on input, generate the list of package policies // used in the InMemoryTable (flattens some values for search) as well as @@ -168,71 +164,15 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ actions: [ { render: (packagePolicy: InMemoryPackagePolicy) => { - const menuItems = [ - // FIXME: implement View package policy action - // {}} - // key="packagePolicyView" - // > - // - // , - - - , - // FIXME: implement Copy package policy action - // {}} key="packagePolicyCopy"> - // - // , - ]; - - if (!agentPolicy.is_managed) { - menuItems.push( - - {(deletePackagePoliciesPrompt) => { - return ( - { - deletePackagePoliciesPrompt([packagePolicy.id], refreshAgentPolicy); - }} - > - - - ); - }} - - ); - } - return ; + return ( + + ); }, }, ], }, ], - [agentPolicy, getHref, hasWriteCapabilities, refreshAgentPolicy] + [agentPolicy] ); return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx index e91d32a78ec963..1ea1a7de53b959 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx @@ -20,13 +20,13 @@ import { sendUpdateAgentPolicy, useConfig, sendGetAgentStatus, + useAgentPolicyRefresh, } from '../../../../../hooks'; import { AgentPolicyForm, agentPolicyFormValidation, ConfirmDeployAgentPolicyModal, } from '../../../components'; -import { useAgentPolicyRefresh } from '../../hooks'; const FormWrapper = styled.div` max-width: 800px; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/index.ts index 07daef8bb2ced8..fb269f95213d9b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/index.ts @@ -6,4 +6,3 @@ */ export { useGetAgentStatus, AgentStatusRefreshContext } from './use_agent_status'; -export { AgentPolicyRefreshContext, useAgentPolicyRefresh } from './use_config'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx index 7138abb081a77f..9d3fe64b6639d3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx @@ -28,6 +28,7 @@ import styled from 'styled-components'; import type { AgentPolicy, AgentPolicyDetailsDeployAgentAction } from '../../../types'; import { FLEET_ROUTING_PATHS } from '../../../constants'; import { + AgentPolicyRefreshContext, useGetOneAgentPolicy, useLink, useBreadcrumbs, @@ -39,7 +40,7 @@ import { Loading, Error } from '../../../components'; import { WithHeaderLayout } from '../../../layouts'; import { LinkedAgentCount, AgentPolicyActionMenu } from '../components'; -import { AgentPolicyRefreshContext, useGetAgentStatus, AgentStatusRefreshContext } from './hooks'; +import { useGetAgentStatus, AgentStatusRefreshContext } from './hooks'; import { PackagePoliciesView, SettingsView } from './components'; const Divider = styled.div` diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx index 540b06d7a9786b..fb80611e5295c8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -23,8 +23,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import type { Agent, AgentPolicy } from '../../../../../types'; import { useKibanaVersion } from '../../../../../hooks'; import { isAgentUpgradeable } from '../../../../../services'; -import { AgentPolicyPackageBadges } from '../../../components/agent_policy_package_badges'; -import { AgentPolicySummaryLine } from '../../../../../components'; +import { AgentPolicyPackageBadges, AgentPolicySummaryLine } from '../../../../../components'; // Allows child text to be truncated const FlexItemWithMinWidth = styled(EuiFlexItem)` diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index 70cb6cddad5fad..672b8718c9cbe7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -22,7 +22,6 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; -import { AgentEnrollmentFlyout } from '../components'; import type { Agent, AgentPolicy, SimplifiedAgentStatus } from '../../../types'; import { usePagination, @@ -37,7 +36,11 @@ import { useKibanaVersion, useStartServices, } from '../../../hooks'; -import { AgentPolicySummaryLine, ContextMenuActions } from '../../../components'; +import { + AgentEnrollmentFlyout, + AgentPolicySummaryLine, + ContextMenuActions, +} from '../../../components'; import { AgentStatusKueryHelper, isAgentUpgradeable } from '../../../services'; import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; import { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx index 83f09789a94148..20d366b01af2bb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx @@ -26,10 +26,15 @@ import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { DownloadStep } from '../components/agent_enrollment_flyout/steps'; -import { useStartServices, useGetOutputs, sendGenerateServiceToken } from '../../../hooks'; -import { PLATFORM_OPTIONS, usePlatform } from '../hooks/use_platform'; -import type { PLATFORM_TYPE } from '../hooks/use_platform'; +import { DownloadStep } from '../../../components'; +import { + useStartServices, + useGetOutputs, + sendGenerateServiceToken, + usePlatform, + PLATFORM_OPTIONS, +} from '../../../hooks'; +import type { PLATFORM_TYPE } from '../../../hooks'; const FlexItemWithMinWidth = styled(EuiFlexItem)` min-width: 0px; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx index 8ee401d3c4ddf7..f15b58200ea889 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_modal/index.tsx @@ -24,7 +24,7 @@ import { useStartServices, useGetAgentPolicies, } from '../../../../hooks'; -import { AgentPolicyPackageBadges } from '../agent_policy_package_badges'; +import { AgentPolicyPackageBadges } from '../../../../components'; interface Props { onClose: () => void; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/index.tsx index 45f09c79d55337..966a7779413735 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/index.tsx @@ -7,7 +7,6 @@ export * from './loading'; export * from './agent_reassign_policy_modal'; -export * from './agent_enrollment_flyout'; export * from './agent_health'; export * from './agent_unenroll_modal'; export * from './agent_upgrade_modal'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx index 40642587c1a38f..67758282521b79 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx @@ -14,7 +14,7 @@ import { useRouteMatch } from 'react-router-dom'; import { FLEET_ROUTING_PATHS } from '../../../constants'; import { WithHeaderLayout } from '../../../layouts'; import { useCapabilities, useLink, useGetAgentPolicies } from '../../../hooks'; -import { AgentEnrollmentFlyout } from '../components'; +import { AgentEnrollmentFlyout } from '../../../components'; export const ListLayout: React.FunctionComponent<{}> = ({ children }) => { const { getHref } = useLink(); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx index 66e0c338dbbbcd..8dc9ad33962e0d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx @@ -22,6 +22,7 @@ import { import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; import { ENROLLMENT_API_KEYS_INDEX } from '../../../constants'; +import { NewEnrollmentTokenModal } from '../../../components'; import { useBreadcrumbs, usePagination, @@ -34,7 +35,6 @@ import { import type { EnrollmentAPIKey, GetAgentPoliciesResponseItem } from '../../../types'; import { SearchBar } from '../../../components/search_bar'; -import { NewEnrollmentTokenModal } from './components/new_enrollment_key_modal'; import { ConfirmEnrollmentTokenDelete } from './components/confirm_delete_modal'; const ApiKeyField: React.FunctionComponent<{ apiKeyId: string }> = ({ apiKeyId }) => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx index 9da10c1de2be06..f905fd1c89da27 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { WithHeaderLayout } from '../../layouts'; import { useGetAgentPolicies, useBreadcrumbs } from '../../hooks'; -import { AgentEnrollmentFlyout } from '../agents/components'; +import { AgentEnrollmentFlyout } from '../../components'; import { OverviewAgentSection } from './components/agent_section'; import { OverviewPolicySection } from './components/agent_policy_section'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx index 4063bc6371cbdc..04253994d3875e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx @@ -109,9 +109,7 @@ describe('when on integration detail', () => { pagePathGetters.integration_details_custom({ pkgkey: 'nginx-0.3.7' })[1] ); }); - expect(testRenderer.history.location.pathname).toEqual( - '/app/integrations/detail/nginx-0.3.7/overview' - ); + expect(testRenderer.history.location.pathname).toEqual('/detail/nginx-0.3.7/overview'); }); }); @@ -204,12 +202,30 @@ describe('when on integration detail', () => { expect(firstRowAgentCount.tagName).not.toEqual('A'); }); + it('should show add agent button if agent count is zero', async () => { + await mockedApi.waitForApi(); + const firstRowAgentCount = renderResult.getAllByTestId('rowAgentCount')[0]; + expect(firstRowAgentCount.textContent).toEqual('0'); + + const addAgentButton = renderResult.getAllByTestId('addAgentButton')[0]; + expect(addAgentButton).not.toBeNull(); + }); + it('should show link for agent count if greater than zero', async () => { await mockedApi.waitForApi(); const secondRowAgentCount = renderResult.getAllByTestId('rowAgentCount')[1]; expect(secondRowAgentCount.textContent).toEqual('100'); expect(secondRowAgentCount.tagName).toEqual('A'); }); + + it('should NOT show add agent button if agent count is greater than zero', async () => { + await mockedApi.waitForApi(); + const secondRowAgentCount = renderResult.getAllByTestId('rowAgentCount')[1]; + expect(secondRowAgentCount.textContent).toEqual('100'); + + const addAgentButton = renderResult.getAllByTestId('addAgentButton')[1]; + expect(addAgentButton).toBeUndefined(); + }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx index d5dce6334762c0..7da7328fdebbca 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx @@ -5,23 +5,44 @@ * 2.0. */ -import React, { memo, useCallback, useMemo } from 'react'; +import React, { memo, useCallback, useMemo, useState } from 'react'; import { Redirect } from 'react-router-dom'; import type { CriteriaWithPagination, EuiTableFieldDataColumnType } from '@elastic/eui'; -import { EuiBasicTable, EuiLink, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiButtonIcon, + EuiBasicTable, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiToolTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedRelative, FormattedMessage } from '@kbn/i18n/react'; +import styled from 'styled-components'; import { InstallStatus } from '../../../../../types'; -import { useLink, useUrlPagination, useGetPackageInstallStatus } from '../../../../../hooks'; +import { + useLink, + useUrlPagination, + useGetPackageInstallStatus, + AgentPolicyRefreshContext, +} from '../../../../../hooks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; -import { AgentPolicySummaryLine } from '../../../../../components'; -import { LinkedAgentCount } from '../../../../../components'; +import { + AgentEnrollmentFlyout, + AgentPolicySummaryLine, + LinkedAgentCount, + PackagePolicyActionsMenu, +} from '../../../../../components'; import type { PackagePolicyAndAgentPolicy } from './use_package_policies_with_agent_policy'; import { usePackagePoliciesWithAgentPolicy } from './use_package_policies_with_agent_policy'; import { Persona } from './persona'; +const AddAgentButton = styled(EuiButtonIcon)` + margin-left: ${(props) => props.theme.eui.euiSizeS}; +`; + const IntegrationDetailsLink = memo<{ packagePolicy: PackagePolicyAndAgentPolicy['packagePolicy']; }>(({ packagePolicy }) => { @@ -39,16 +60,18 @@ const IntegrationDetailsLink = memo<{ ); }); + interface PackagePoliciesPanelProps { name: string; version: string; } export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps) => { + const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState(null); const { getPath } = useLink(); const getPackageInstallStatus = useGetPackageInstallStatus(); const packageInstallStatus = getPackageInstallStatus(name); const { pagination, pageSizeOptions, setPagination } = useUrlPagination(); - const { data, isLoading } = usePackagePoliciesWithAgentPolicy({ + const { data, isLoading, resendRequest: refreshPolicies } = usePackagePoliciesWithAgentPolicy({ page: pagination.currentPage, perPage: pagination.pageSize, kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${name}`, @@ -97,16 +120,36 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps defaultMessage: 'Agents', }), truncateText: true, - align: 'right', + align: 'left', width: '8ch', render({ packagePolicy, agentPolicy }: PackagePolicyAndAgentPolicy) { + const count = agentPolicy?.agents ?? 0; + return ( - + <> + + {count === 0 && ( + + setFlyoutOpenForPolicyId(agentPolicy.id)} + data-test-subj="addAgentButton" + /> + + )} + ); }, }, @@ -134,6 +177,19 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps ); }, }, + { + field: '', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.actions', { + defaultMessage: 'Actions', + }), + width: '8ch', + align: 'right', + render({ agentPolicy, packagePolicy }) { + return ( + + ); + }, + }, ], [] ); @@ -165,19 +221,31 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps } return ( - - - - + + + + + + + {flyoutOpenForPolicyId && ( + setFlyoutOpenForPolicyId(null)} + agentPolicies={ + data?.items + .filter(({ agentPolicy }) => agentPolicy.id === flyoutOpenForPolicyId) + .map(({ agentPolicy }) => agentPolicy) ?? [] + } /> - - + )} + ); }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts index aae43a1acd5689..33c1d3ff773023 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts @@ -43,11 +43,13 @@ export const usePackagePoliciesWithAgentPolicy = ( isLoading: boolean; error: Error | null; data?: GetPackagePoliciesWithAgentPolicy; + resendRequest: () => void; } => { const { data: packagePoliciesData, error, isLoading: isLoadingPackagePolicies, + resendRequest, } = useGetPackagePolicies(query); const agentPoliciesFilter = useMemo(() => { @@ -124,5 +126,6 @@ export const usePackagePoliciesWithAgentPolicy = ( data: enrichedData, error, isLoading: isLoadingPackagePolicies || isLoadingAgentPolicies, + resendRequest, }; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx similarity index 97% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx rename to x-pack/plugins/fleet/public/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx index a5050027e4e3c7..25602b7e108fdd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/advanced_agent_authentication_settings.tsx @@ -11,13 +11,13 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonEmpty, EuiButton, EuiCallOut, EuiSelect, EuiSpacer, EuiText } from '@elastic/eui'; -import { SO_SEARCH_LIMIT } from '../../../../constants'; -import type { GetEnrollmentAPIKeysResponse } from '../../../../types'; +import { SO_SEARCH_LIMIT } from '../../applications/fleet/constants'; +import type { GetEnrollmentAPIKeysResponse } from '../../applications/fleet/types'; import { sendGetEnrollmentAPIKeys, - sendCreateEnrollmentAPIKey, useStartServices, -} from '../../../../hooks'; + sendCreateEnrollmentAPIKey, +} from '../../applications/fleet/hooks'; interface Props { agentPolicyId?: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts similarity index 84% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts rename to x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts index fcd87fe6c164e5..8f638ccdf20bf7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts @@ -14,8 +14,10 @@ jest.mock('../../../../../../hooks/use_request', () => { }; }); -jest.mock('../../agent_requirements_page', () => { - const module = jest.requireActual('../../agent_requirements_page'); +jest.mock('../../applications/fleet/sections/agents/agent_requirements_page', () => { + const module = jest.requireActual( + '../../applications/fleet/sections/agents/agent_requirements_page' + ); return { ...module, FleetServerRequirementPage: jest.fn(), diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx similarity index 91% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx rename to x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx index 8ec72568860590..db9245b11b0f99 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx @@ -13,13 +13,13 @@ import { act } from '@testing-library/react'; import { coreMock } from 'src/core/public/mocks'; -import { KibanaContextProvider } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import type { AgentPolicy } from '../../../../../../../common'; -import { useGetSettings, sendGetFleetStatus } from '../../../../../../hooks/use_request'; -import { FleetStatusProvider, ConfigContext } from '../../../../../../hooks'; +import type { AgentPolicy } from '../../../common'; +import { useGetSettings, sendGetFleetStatus } from '../../hooks/use_request'; +import { FleetStatusProvider, ConfigContext } from '../../hooks'; -import { useFleetServerInstructions } from '../../agent_requirements_page'; +import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page'; import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep } from './steps'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.tsx similarity index 98% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx rename to x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.tsx index 88df4ec4d28560..f92b2d48259351 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSelect, EuiSpacer, EuiText } from '@elastic/eui'; -import type { AgentPolicy } from '../../../../types'; +import type { AgentPolicy } from '../../types'; import { AgentPolicyPackageBadges } from '../agent_policy_package_badges'; import { AdvancedAgentAuthenticationSettings } from './advanced_agent_authentication_settings'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx similarity index 95% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx rename to x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx index 171373b55c6b15..b91af80691033e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx @@ -22,7 +22,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useGetSettings, useUrlModal } from '../../../../hooks'; +import { useGetSettings, useUrlModal } from '../../hooks'; import { ManagedInstructions } from './managed_instructions'; import { StandaloneInstructions } from './standalone_instructions'; @@ -33,6 +33,11 @@ export interface Props extends BaseProps { onClose: () => void; } +export * from './agent_policy_selection'; +export * from './managed_instructions'; +export * from './standalone_instructions'; +export * from './steps'; + export const AgentEnrollmentFlyout: React.FunctionComponent = ({ onClose, agentPolicy, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx similarity index 94% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx rename to x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx index 4dfa813949c7a2..2bb8586a11503d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx @@ -11,20 +11,15 @@ import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/st import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - useGetOneEnrollmentAPIKey, - useGetSettings, - useLink, - useFleetStatus, -} from '../../../../hooks'; +import { useGetOneEnrollmentAPIKey, useGetSettings, useLink, useFleetStatus } from '../../hooks'; -import { ManualInstructions } from '../../../../components/enrollment_instructions'; +import { ManualInstructions } from '../../components/enrollment_instructions'; import { FleetServerRequirementPage, ServiceTokenStep, FleetServerCommandStep, useFleetServerInstructions, -} from '../../agent_requirements_page'; +} from '../../applications/fleet/sections/agents/agent_requirements_page'; import { DownloadStep, AgentPolicySelectionStep, AgentEnrollmentKeySelectionStep } from './steps'; import type { BaseProps } from './types'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/missing_fleet_server_host_callout.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/missing_fleet_server_host_callout.tsx similarity index 95% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/missing_fleet_server_host_callout.tsx rename to x-pack/plugins/fleet/public/components/agent_enrollment_flyout/missing_fleet_server_host_callout.tsx index 0ad475be24ccdc..f9598d59b86434 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/missing_fleet_server_host_callout.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/missing_fleet_server_host_callout.tsx @@ -12,7 +12,7 @@ import { EuiCallOut, EuiLink, EuiButton, EuiSpacer } from '@elastic/eui'; import { useUrlModal } from '../../../../hooks'; -export const MissingFleetServerHostCallout: React.FunctionComponent = () => { +const MissingFleetServerHostCallout: React.FunctionComponent = () => { const { setModal } = useUrlModal(); return ( = ({ agentPolicy, packagePolicy }) => { + const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); + const { getHref } = useLink(); + const hasWriteCapabilities = useCapabilities().write; + const refreshAgentPolicy = useAgentPolicyRefresh(); + + const onEnrollmentFlyoutClose = useMemo(() => { + return () => setIsEnrollmentFlyoutOpen(false); + }, []); + + const menuItems = [ + // FIXME: implement View package policy action + // {}} + // key="packagePolicyView" + // > + // + // , + setIsEnrollmentFlyoutOpen(true)} + key="addAgent" + > + + , + + + , + // FIXME: implement Copy package policy action + // {}} key="packagePolicyCopy"> + // + // , + ]; + + if (!agentPolicy.is_managed) { + menuItems.push( + + {(deletePackagePoliciesPrompt) => { + return ( + { + deletePackagePoliciesPrompt([packagePolicy.id], refreshAgentPolicy); + }} + > + + + ); + }} + + ); + } + return ( + <> + {isEnrollmentFlyoutOpen && ( + + + + )} + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx similarity index 98% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx rename to x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx index 198886e9b9c7ff..86f064405497b7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx @@ -10,9 +10,9 @@ import { EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useStartServices, sendRequest, sendDeletePackagePolicy, useConfig } from '../../../hooks'; -import { AGENT_API_ROUTES, AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; -import type { AgentPolicy } from '../../../types'; +import { useStartServices, sendRequest, sendDeletePackagePolicy, useConfig } from '../hooks'; +import { AGENT_API_ROUTES, AGENT_SAVED_OBJECT_TYPE } from '../../common/constants'; +import type { AgentPolicy } from '../types'; interface Props { agentPolicy: AgentPolicy; diff --git a/x-pack/plugins/fleet/public/hooks/index.ts b/x-pack/plugins/fleet/public/hooks/index.ts index 0b413f72094d9d..9f41e5c7cc92b5 100644 --- a/x-pack/plugins/fleet/public/hooks/index.ts +++ b/x-pack/plugins/fleet/public/hooks/index.ts @@ -24,3 +24,5 @@ export * from './use_url_params'; export * from './use_fleet_status'; export * from './use_ui_extension'; export * from './use_intra_app_state'; +export * from './use_platform'; +export * from './use_agent_policy_refresh'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_config.tsx b/x-pack/plugins/fleet/public/hooks/use_agent_policy_refresh.tsx similarity index 100% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_config.tsx rename to x-pack/plugins/fleet/public/hooks/use_agent_policy_refresh.tsx diff --git a/x-pack/plugins/fleet/public/hooks/use_link.ts b/x-pack/plugins/fleet/public/hooks/use_link.ts index df6f45af31d56b..6917e0f5c3b8e7 100644 --- a/x-pack/plugins/fleet/public/hooks/use_link.ts +++ b/x-pack/plugins/fleet/public/hooks/use_link.ts @@ -21,9 +21,7 @@ export const useLink = () => { const core = useStartServices(); return { getPath: (page: StaticPage | DynamicPage, values: DynamicPagePathValues = {}): string => { - const [basePath, path] = getSeparatePaths(page, values); - - return `${basePath}${path}`; + return getSeparatePaths(page, values)[1]; }, getAssetsPath: (path: string) => core.http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets/${path}`), diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/hooks/use_platform.tsx b/x-pack/plugins/fleet/public/hooks/use_platform.tsx similarity index 100% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/hooks/use_platform.tsx rename to x-pack/plugins/fleet/public/hooks/use_platform.tsx diff --git a/x-pack/plugins/fleet/public/services/index.ts b/x-pack/plugins/fleet/public/services/index.ts index 4575efe9637056..fbd3bddde744b8 100644 --- a/x-pack/plugins/fleet/public/services/index.ts +++ b/x-pack/plugins/fleet/public/services/index.ts @@ -30,6 +30,14 @@ export { LicenseService, isAgentUpgradeable, doesPackageHaveIntegrations, + PackagePolicyValidationResults, + PackagePolicyConfigValidationResults, + PackagePolicyInputValidationResults, + validatePackagePolicy, + validatePackagePolicyConfig, + validationHasErrors, + countValidationErrors, + getStreamsForInputType, } from '../../common'; export * from './pkg_key_from_package_info'; diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 10e5a6ac57f576..bd7bb98eb7c07c 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -235,6 +235,7 @@ const getSavedObjectTypes = ( enabled: false, properties: { type: { type: 'keyword' }, + policy_template: { type: 'keyword' }, enabled: { type: 'boolean' }, vars: { type: 'flattened' }, config: { type: 'flattened' }, diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 62b4578ab87b22..2a6036d99281e8 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -47,6 +47,10 @@ import type { Output, } from '../../common'; import { AgentPolicyNameExistsError, HostedAgentPolicyRestrictionRelatedError } from '../errors'; +import { + storedPackagePoliciesToAgentPermissions, + DEFAULT_PERMISSIONS, +} from '../services/package_policies_to_agent_permissions'; import { getPackageInfo } from './epm/packages'; import { getAgentsByKuery } from './agents'; @@ -745,30 +749,49 @@ class AgentPolicyService { }), }; + const permissions = (await storedPackagePoliciesToAgentPermissions( + soClient, + agentPolicy.package_policies + )) || { _fallback: DEFAULT_PERMISSIONS }; + + permissions._elastic_agent_checks = { + cluster: DEFAULT_PERMISSIONS.cluster, + }; + + // TODO fetch this from the elastic agent package + const monitoringOutput = fullAgentPolicy.agent?.monitoring.use_output; + const monitoringNamespace = fullAgentPolicy.agent?.monitoring.namespace; + if ( + fullAgentPolicy.agent?.monitoring.enabled && + monitoringNamespace && + monitoringOutput && + fullAgentPolicy.outputs[monitoringOutput]?.type === 'elasticsearch' + ) { + const names: string[] = []; + if (fullAgentPolicy.agent.monitoring.logs) { + names.push(`logs-elastic_agent.*-${monitoringNamespace}`); + } + if (fullAgentPolicy.agent.monitoring.metrics) { + names.push(`metrics-elastic_agent.*-${monitoringNamespace}`); + } + + permissions._elastic_agent_checks.indices = [ + { + names, + privileges: ['auto_configure', 'create_doc'], + }, + ]; + } + // Only add permissions if output.type is "elasticsearch" fullAgentPolicy.output_permissions = Object.keys(fullAgentPolicy.outputs).reduce< NonNullable - >((permissions, outputName) => { + >((outputPermissions, outputName) => { const output = fullAgentPolicy.outputs[outputName]; if (output && output.type === 'elasticsearch') { - permissions[outputName] = {}; - permissions[outputName]._fallback = { - cluster: ['monitor'], - indices: [ - { - names: [ - 'logs-*', - 'metrics-*', - 'traces-*', - '.logs-endpoint.diagnostic.collection-*', - 'synthetics-*', - ], - privileges: ['auto_configure', 'create_doc'], - }, - ], - }; + outputPermissions[outputName] = permissions; } - return permissions; + return outputPermissions; }, {}); // only add settings if not in standalone diff --git a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts index 2db6009270a3b6..dde6459addcbc5 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts @@ -224,24 +224,20 @@ export const getEsPackage = async ( ); const dataStreamManifest = safeLoad(soResDataStreamManifest.attributes.data_utf8); const { - title: dataStreamTitle, - release, ingest_pipeline: ingestPipeline, - type, dataset, streams: manifestStreams, + ...dataStreamManifestProps } = dataStreamManifest; const streams = parseAndVerifyStreams(manifestStreams, dataStreamPath); dataStreams.push({ dataset: dataset || `${pkgName}.${dataStreamPath}`, - title: dataStreamTitle, - release, package: pkgName, ingest_pipeline: ingestPipeline || 'default', path: dataStreamPath, - type, streams, + ...dataStreamManifestProps, }); }) ); diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts new file mode 100644 index 00000000000000..39759a6fc9e9c7 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts @@ -0,0 +1,341 @@ +/* + * 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. + */ + +jest.mock('./epm/packages'); +import type { SavedObjectsClientContract } from 'kibana/server'; + +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import type { PackagePolicy, RegistryDataStream } from '../types'; + +import { getPackageInfo } from './epm/packages'; +import { + getDataStreamPermissions, + storedPackagePoliciesToAgentPermissions, +} from './package_policies_to_agent_permissions'; + +const getPackageInfoMock = getPackageInfo as jest.MockedFunction; + +describe('storedPackagePoliciesToAgentPermissions()', () => { + let soClient: jest.Mocked; + beforeEach(() => { + soClient = savedObjectsClientMock.create(); + }); + + it('Returns `undefined` if there are no package policies', async () => { + const permissions = await storedPackagePoliciesToAgentPermissions(soClient, []); + expect(permissions).toBeUndefined(); + }); + + it('Returns the default permissions for string package policies', async () => { + const permissions = await storedPackagePoliciesToAgentPermissions(soClient, ['foo']); + expect(permissions).toMatchObject({ + _fallback: { + cluster: ['monitor'], + indices: [ + { + names: [ + 'logs-*', + 'metrics-*', + 'traces-*', + 'synthetics-*', + '.logs-endpoint.diagnostic.collection-*', + ], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }, + }); + }); + + it('Returns the default permissions if a package policy does not have a package', async () => { + const permissions = await storedPackagePoliciesToAgentPermissions(soClient, [ + { name: 'foo', package: undefined } as PackagePolicy, + ]); + + expect(permissions).toMatchObject({ + foo: { + cluster: ['monitor'], + indices: [ + { + names: [ + 'logs-*', + 'metrics-*', + 'traces-*', + 'synthetics-*', + '.logs-endpoint.diagnostic.collection-*', + ], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }, + }); + }); + + it('Returns the permissions for the enabled inputs', async () => { + getPackageInfoMock.mockResolvedValueOnce({ + name: 'test-package', + version: '0.0.0', + latestVersion: '0.0.0', + release: 'experimental', + format_version: '1.0.0', + title: 'Test Package', + description: '', + icons: [], + owner: { github: '' }, + status: 'not_installed', + assets: { + kibana: { + dashboard: [], + visualization: [], + search: [], + index_pattern: [], + map: [], + lens: [], + security_rule: [], + ml_module: [], + }, + elasticsearch: { + component_template: [], + ingest_pipeline: [], + ilm_policy: [], + transform: [], + index_template: [], + data_stream_ilm_policy: [], + }, + }, + data_streams: [ + { + type: 'logs', + dataset: 'some-logs', + title: '', + release: '', + package: 'test-package', + path: '', + ingest_pipeline: '', + streams: [{ input: 'test-logs', title: 'Test Logs', template_path: '' }], + }, + { + type: 'metrics', + dataset: 'some-metrics', + title: '', + release: '', + package: 'test-package', + path: '', + ingest_pipeline: '', + streams: [{ input: 'test-metrics', title: 'Test Logs', template_path: '' }], + }, + ], + }); + + const packagePolicies: PackagePolicy[] = [ + { + id: '12345', + name: 'test-policy', + namespace: 'test', + enabled: true, + package: { name: 'test-package', version: '0.0.0', title: 'Test Package' }, + inputs: [ + { + type: 'test-logs', + enabled: true, + streams: [ + { + id: 'test-logs', + enabled: true, + data_stream: { type: 'logs', dataset: 'some-logs' }, + }, + ], + }, + { + type: 'test-metrics', + enabled: false, + streams: [ + { + id: 'test-logs', + enabled: false, + data_stream: { type: 'metrics', dataset: 'some-metrics' }, + }, + ], + }, + ], + created_at: '', + updated_at: '', + created_by: '', + updated_by: '', + revision: 1, + policy_id: '', + output_id: '', + }, + ]; + + const permissions = await storedPackagePoliciesToAgentPermissions(soClient, packagePolicies); + expect(permissions).toMatchObject({ + 'test-policy': { + indices: [ + { + names: ['logs-some-logs-test'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }, + }); + }); + + it('Returns the dataset for the compiled data_streams', async () => { + getPackageInfoMock.mockResolvedValueOnce({ + name: 'test-package', + version: '0.0.0', + latestVersion: '0.0.0', + release: 'experimental', + format_version: '1.0.0', + title: 'Test Package', + description: '', + icons: [], + owner: { github: '' }, + status: 'not_installed', + assets: { + kibana: { + dashboard: [], + visualization: [], + search: [], + index_pattern: [], + map: [], + lens: [], + security_rule: [], + ml_module: [], + }, + elasticsearch: { + component_template: [], + ingest_pipeline: [], + ilm_policy: [], + transform: [], + index_template: [], + data_stream_ilm_policy: [], + }, + }, + data_streams: [ + { + type: 'logs', + dataset: 'some-logs', + title: '', + release: '', + package: 'test-package', + path: '', + ingest_pipeline: '', + streams: [{ input: 'test-logs', title: 'Test Logs', template_path: '' }], + }, + ], + }); + + const packagePolicies: PackagePolicy[] = [ + { + id: '12345', + name: 'test-policy', + namespace: 'test', + enabled: true, + package: { name: 'test-package', version: '0.0.0', title: 'Test Package' }, + inputs: [ + { + type: 'test-logs', + enabled: true, + streams: [ + { + id: 'test-logs', + enabled: true, + data_stream: { type: 'logs', dataset: 'some-logs' }, + compiled_stream: { data_stream: { dataset: 'compiled' } }, + }, + ], + }, + ], + created_at: '', + updated_at: '', + created_by: '', + updated_by: '', + revision: 1, + policy_id: '', + output_id: '', + }, + ]; + + const permissions = await storedPackagePoliciesToAgentPermissions(soClient, packagePolicies); + expect(permissions).toMatchObject({ + 'test-policy': { + indices: [ + { + names: ['logs-compiled-test'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }, + }); + }); +}); + +describe('getDataStreamPermissions()', () => { + it('returns defaults for a datastream with no permissions', () => { + const dataStream = { type: 'logs', dataset: 'test' } as RegistryDataStream; + const permissions = getDataStreamPermissions(dataStream); + + expect(permissions).toMatchObject({ + names: ['logs-test-*'], + privileges: ['auto_configure', 'create_doc'], + }); + }); + + it('adds the namespace to the index name', () => { + const dataStream = { type: 'logs', dataset: 'test' } as RegistryDataStream; + const permissions = getDataStreamPermissions(dataStream, 'namespace'); + + expect(permissions).toMatchObject({ + names: ['logs-test-namespace'], + privileges: ['auto_configure', 'create_doc'], + }); + }); + + it('appends a wildcard if dataset is prefix', () => { + const dataStream = { + type: 'logs', + dataset: 'test', + dataset_is_prefix: true, + } as RegistryDataStream; + const permissions = getDataStreamPermissions(dataStream, 'namespace'); + + expect(permissions).toMatchObject({ + names: ['logs-test.*-namespace'], + privileges: ['auto_configure', 'create_doc'], + }); + }); + + it('prepends a dot if datastream is hidden', () => { + const dataStream = { + type: 'logs', + dataset: 'test', + hidden: true, + } as RegistryDataStream; + const permissions = getDataStreamPermissions(dataStream, 'namespace'); + + expect(permissions).toMatchObject({ + names: ['.logs-test-namespace'], + privileges: ['auto_configure', 'create_doc'], + }); + }); + + it('uses custom permissions if they are present in the datastream', () => { + const dataStream = { + type: 'logs', + dataset: 'test', + permissions: { indices: ['read', 'write'] }, + } as RegistryDataStream; + const permissions = getDataStreamPermissions(dataStream, 'namespace'); + + expect(permissions).toMatchObject({ + names: ['logs-test-namespace'], + privileges: ['read', 'write'], + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts new file mode 100644 index 00000000000000..bd73b88e7c893b --- /dev/null +++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts @@ -0,0 +1,152 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SavedObjectsClientContract } from 'kibana/server'; + +import type { FullAgentPolicyOutputPermissions, RegistryDataStreamPermissions } from '../../common'; +import { getPackageInfo } from '../../server/services/epm/packages'; + +import type { PackagePolicy } from '../types'; + +export const DEFAULT_PERMISSIONS = { + cluster: ['monitor'], + indices: [ + { + names: [ + 'logs-*', + 'metrics-*', + 'traces-*', + 'synthetics-*', + '.logs-endpoint.diagnostic.collection-*', + ], + privileges: ['auto_configure', 'create_doc'], + }, + ], +}; + +export async function storedPackagePoliciesToAgentPermissions( + soClient: SavedObjectsClientContract, + packagePolicies: string[] | PackagePolicy[] +): Promise { + if (packagePolicies.length === 0) { + return; + } + + // I'm not sure what permissions to return for this case, so let's return the defaults + if (typeof packagePolicies[0] === 'string') { + return { _fallback: DEFAULT_PERMISSIONS }; + } + + const permissionEntries = (packagePolicies as PackagePolicy[]).map>( + async (packagePolicy) => { + if (!packagePolicy.package) { + return [packagePolicy.name, DEFAULT_PERMISSIONS]; + } + + const pkg = await getPackageInfo({ + savedObjectsClient: soClient, + pkgName: packagePolicy.package.name, + pkgVersion: packagePolicy.package.version, + }); + + if (!pkg.data_streams || pkg.data_streams.length === 0) { + return [packagePolicy.name, undefined]; + } + + let dataStreamsForPermissions: DataStreamMeta[]; + + switch (pkg.name) { + case 'endpoint': + // - Endpoint doesn't store the `data_stream` metadata in + // `packagePolicy.inputs`, so we will use _all_ data_streams from the + // package. + dataStreamsForPermissions = pkg.data_streams; + break; + + case 'apm': + // - APM doesn't store the `data_stream` metadata in + // `packagePolicy.inputs`, so we will use _all_ data_streams from + // the package. + dataStreamsForPermissions = pkg.data_streams; + break; + + default: + // - Normal packages store some of the `data_stream` metadata in + // `packagePolicy.inputs[].streams[].data_stream` + // - The rest of the metadata needs to be fetched from the + // `data_stream` object in the package. The link is + // `packagePolicy.inputs[].type == pkg.data_streams.streams[].input` + // - Some packages (custom logs) have a compiled dataset, stored in + // `input.streams.compiled_stream.data_stream.dataset` + dataStreamsForPermissions = packagePolicy.inputs + .filter((i) => i.enabled) + .flatMap((input) => { + if (!input.streams) { + return []; + } + + const dataStreams_: DataStreamMeta[] = []; + + input.streams + .filter((s) => s.enabled) + .forEach((stream) => { + if (!('data_stream' in stream)) { + return; + } + + const ds = { + type: stream.data_stream.type, + dataset: + stream.compiled_stream?.data_stream?.dataset ?? stream.data_stream.dataset, + }; + + dataStreams_.push(ds); + }); + + return dataStreams_; + }); + } + + return [ + packagePolicy.name, + { + indices: dataStreamsForPermissions.map((ds) => + getDataStreamPermissions(ds, packagePolicy.namespace) + ), + }, + ]; + } + ); + + return Object.fromEntries(await Promise.all(permissionEntries)); +} + +interface DataStreamMeta { + type: string; + dataset: string; + dataset_is_prefix?: boolean; + hidden?: boolean; + permissions?: RegistryDataStreamPermissions; +} + +export function getDataStreamPermissions(dataStream: DataStreamMeta, namespace: string = '*') { + let index = `${dataStream.type}-${dataStream.dataset}`; + + if (dataStream.dataset_is_prefix) { + index = `${index}.*`; + } + + if (dataStream.hidden) { + index = `.${index}`; + } + + index += `-${namespace}`; + + return { + names: [index], + privileges: dataStream.permissions?.indices || ['auto_configure', 'create_doc'], + }; +} diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index 46e5db3a958646..a6958ba88449a0 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -408,6 +408,11 @@ describe('Package policy service', () => { ], policy_templates: [ { + name: 'template_1', + inputs: [{ type: 'log', template_path: 'some_template_path.yml' }], + }, + { + name: 'template_2', inputs: [{ type: 'log', template_path: 'some_template_path.yml' }], }, ], @@ -416,6 +421,7 @@ describe('Package policy service', () => { [ { type: 'log', + policy_template: 'template_1', enabled: true, vars: { hosts: { @@ -433,12 +439,24 @@ describe('Package policy service', () => { }, ], }, + { + type: 'log', + policy_template: 'template_2', + enabled: true, + vars: { + hosts: { + value: ['localhost'], + }, + }, + streams: [], + }, ] ); expect(inputs).toEqual([ { type: 'log', + policy_template: 'template_1', enabled: true, vars: { hosts: { @@ -465,6 +483,20 @@ describe('Package policy service', () => { }, ], }, + { + type: 'log', + policy_template: 'template_2', + enabled: true, + vars: { + hosts: { + value: ['localhost'], + }, + }, + compiled_input: { + hosts: ['localhost'], + }, + streams: [], + }, ]); }); diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 02f9731421ba04..93bcef458279c0 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -511,11 +511,17 @@ async function _compilePackagePolicyInput( vars: PackagePolicy['vars'], input: PackagePolicyInput ) { - if ((!input.enabled || !pkgInfo.policy_templates?.[0]?.inputs?.length) ?? 0 > 0) { + const packagePolicyTemplate = input.policy_template + ? pkgInfo.policy_templates?.find( + (policyTemplate) => policyTemplate.name === input.policy_template + ) + : pkgInfo.policy_templates?.[0]; + + if (!input.enabled || !packagePolicyTemplate || !packagePolicyTemplate.inputs?.length) { return undefined; } - const packageInputs = pkgInfo.policy_templates[0].inputs; + const packageInputs = packagePolicyTemplate.inputs; const packageInput = packageInputs.find((pkgInput) => pkgInput.type === input.type); if (!packageInput) { throw new Error(`Input template not found, unable to find input type ${input.type}`); diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts index 3735cfffeaa715..e69e38c1872846 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -45,6 +45,7 @@ const PackagePolicyBaseSchema = { inputs: schema.arrayOf( schema.object({ type: schema.string(), + policy_template: schema.maybe(schema.string()), enabled: schema.boolean(), keep_enabled: schema.maybe(schema.boolean()), vars: schema.maybe(ConfigRecordSchema), diff --git a/x-pack/plugins/graph/public/angular/templates/listing_ng_wrapper.html b/x-pack/plugins/graph/public/angular/templates/listing_ng_wrapper.html index 4e0c13442d2674..b2363ffbaa6411 100644 --- a/x-pack/plugins/graph/public/angular/templates/listing_ng_wrapper.html +++ b/x-pack/plugins/graph/public/angular/templates/listing_ng_wrapper.html @@ -9,4 +9,5 @@ initial-filter="initialFilter" initialPageSize="initialPageSize" core-start="coreStart" + class="kbnAppWrapper" > diff --git a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index 6c2c79baafe0a5..7fa63404a4abd8 100644 --- a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -86,8 +86,8 @@ function GuidancePanelComponent(props: GuidancePanelProps) { const kibana = useKibana(); const { services, overlays } = kibana; - const { savedObjects, uiSettings, chrome, application } = services; - if (!overlays || !chrome || !application) return null; + const { savedObjects, uiSettings, application } = services; + if (!overlays || !application) return null; const onOpenDatasourcePicker = () => { openSourceModal({ overlays, savedObjects, uiSettings }, onIndexPatternSelected); @@ -149,8 +149,9 @@ function GuidancePanelComponent(props: GuidancePanelProps) { ); if (noIndexPatterns) { - const managementUrl = chrome.navLinks.get('kibana:stack_management')!.url; - const indexPatternUrl = `${managementUrl}/kibana/indexPatterns`; + const indexPatternUrl = application.getUrlForApp('management', { + path: '/kibana/indexPatterns', + }); const sampleDataUrl = `${application.getUrlForApp('home')}#/tutorial_directory/sampleData`; content = ( diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index 92bac61f3fab3e..f34172765e1ddf 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -16,7 +16,6 @@ "embeddable", "uiActions", "kibanaLegacy", - "indexPatternManagement", "discover", "triggersActionsUi" ], diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx index 8375b0a0b1dfc5..d20042ddd94433 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx @@ -8,20 +8,21 @@ import React, { FC, useCallback, useMemo, useState } from 'react'; import { isEqual } from 'lodash'; import { - EuiPanel, - EuiPopover, - EuiContextMenuPanel, + EuiButtonEmpty, EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, EuiFlexGroup, EuiFlexItem, + EuiPanel, + EuiPopover, EuiSelect, - EuiTitle, EuiSpacer, - EuiContextMenuItem, - EuiButtonEmpty, + EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import useDebounce from 'react-use/lib/useDebounce'; import { OVERALL_LABEL, SWIMLANE_TYPE, VIEW_BY_JOB_LABEL } from './explorer_constants'; import { AddSwimlaneToDashboardControl } from './dashboard_controls/add_swimlane_to_dashboard_controls'; import { useMlKibana } from '../contexts/kibana'; @@ -93,6 +94,18 @@ export const AnomalyTimeline: FC = React.memo( viewBySwimlaneData, } = explorerState; + const [severityUpdate, setSeverityUpdate] = useState(swimLaneSeverity); + + useDebounce( + () => { + if (severityUpdate === swimLaneSeverity) return; + + explorerService.setSwimLaneSeverity(severityUpdate!); + }, + 500, + [severityUpdate, swimLaneSeverity] + ); + const annotations = useMemo(() => overallAnnotations.annotationsData, [overallAnnotations]); const menuItems = useMemo(() => { @@ -194,9 +207,9 @@ export const AnomalyTimeline: FC = React.memo( { - explorerService.setSwimLaneSeverity(update); + setSeverityUpdate(update); }, [])} /> diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/edit_job.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/edit_job.tsx index d2780691e551d9..408e86512ed6dc 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/edit_job.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/edit_job.tsx @@ -51,9 +51,9 @@ export const EditJob: FC = ({ job, jobOverride, existingGroupIds, ); const handleValidation = () => { - const jobGroupsValidationResult = - formState.jobGroups ?? - [].map((group) => groupValidator(group)).filter((result) => result !== null); + const jobGroupsValidationResult = (formState.jobGroups ?? []) + .map((group) => groupValidator(group)) + .filter((result) => result !== null); setValidationResult({ jobGroups: jobGroupsValidationResult, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx index b97c16e02d9007..0daf4f28f33d31 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx @@ -87,12 +87,11 @@ export const JobItem: FC = memo( - {jobGroups ?? - [].map((group) => ( - - {group} - - ))} + {(jobGroups ?? []).map((group) => ( + + {group} + + ))} {setupResult && setupResult.error && ( diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 42440883408ae5..1191f3b253fd7d 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -24,14 +24,12 @@ import type { } from 'src/plugins/share/public'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; import type { HomePublicPluginSetup } from 'src/plugins/home/public'; -import type { IndexPatternManagementSetup } from 'src/plugins/index_pattern_management/public'; import type { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; import type { SpacesPluginStart } from '../../spaces/public'; import { AppStatus, AppUpdater, DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import type { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import type { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public'; -import { MlCardState } from '../../../../src/plugins/index_pattern_management/public'; import type { LicenseManagementUIPluginSetup } from '../../license_management/public'; import type { LicensingPluginSetup } from '../../licensing/public'; @@ -78,7 +76,6 @@ export interface MlSetupDependencies { uiActions: UiActionsSetup; kibanaVersion: string; share: SharePluginSetup; - indexPatternManagement: IndexPatternManagementSetup; triggersActionsUi?: TriggersAndActionsUIPublicPluginSetup; alerting?: AlertingSetup; } @@ -148,12 +145,6 @@ export class MlPlugin implements Plugin { if (pluginsSetup.home) { registerFeature(pluginsSetup.home); } - - // register ML for the index pattern management no data screen. - pluginsSetup.indexPatternManagement.environment.update({ - ml: () => - capabilities.ml.canFindFileStructure ? MlCardState.ENABLED : MlCardState.HIDDEN, - }); } else { // if ml is disabled in elasticsearch, disable ML in kibana this.appUpdater$.next(() => ({ diff --git a/x-pack/plugins/ml/public/register_feature.ts b/x-pack/plugins/ml/public/register_feature.ts index 191e064c85b900..e8d64360df1230 100644 --- a/x-pack/plugins/ml/public/register_feature.ts +++ b/x-pack/plugins/ml/public/register_feature.ts @@ -13,10 +13,6 @@ import { import { PLUGIN_ID } from '../common/constants/app'; export const registerFeature = (home: HomePublicPluginSetup) => { - // register ML for the kibana home screen. - // so the file data visualizer appears to allow people to import data - home.environment.update({ ml: true }); - // register ML so it appears on the Kibana home page home.featureCatalogue.register({ id: PLUGIN_ID, @@ -37,19 +33,4 @@ export const registerFeature = (home: HomePublicPluginSetup) => { solutionId: 'kibana', order: 500, }); - - home.featureCatalogue.register({ - id: `${PLUGIN_ID}_file_data_visualizer`, - title: i18n.translate('xpack.ml.fileDataVisualizerTitle', { - defaultMessage: 'Upload a file', - }), - description: i18n.translate('xpack.ml.fileDataVisualizerDescription', { - defaultMessage: 'Import your own CSV, NDJSON, or log file.', - }), - icon: 'document', - path: '/app/ml/filedatavisualizer', - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, - order: 520, - }); }; diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json index a4412a6d732e99..3ff28ef3d8df3d 100755 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json @@ -13,10 +13,29 @@ "term": { "event.category": "network" } - }, + } + ], + "must": [ { - "term": { - "event.outcome": "deny" + "bool": { + "should": [ + { + "match": { + "event.outcome": { + "query": "deny", + "operator": "OR" + } + } + }, + { + "match": { + "event.type": { + "query": "denied", + "operator": "OR" + } + } + } + ] } } ] diff --git a/x-pack/plugins/reporting/common/constants.ts b/x-pack/plugins/reporting/common/constants.ts index 2a95557473fc09..f20c6c2d52fdd8 100644 --- a/x-pack/plugins/reporting/common/constants.ts +++ b/x-pack/plugins/reporting/common/constants.ts @@ -53,6 +53,7 @@ export const UI_SETTINGS_CSV_QUOTE_VALUES = 'csv:quoteValues'; export const UI_SETTINGS_DATEFORMAT_TZ = 'dateFormat:tz'; export const LAYOUT_TYPES = { + CANVAS: 'canvas', PRESERVE_LAYOUT: 'preserve_layout', PRINT: 'print', }; diff --git a/x-pack/plugins/reporting/public/components/__snapshots__/screen_capture_panel_content.test.tsx.snap b/x-pack/plugins/reporting/public/components/__snapshots__/screen_capture_panel_content.test.tsx.snap new file mode 100644 index 00000000000000..a6753211fba3b3 --- /dev/null +++ b/x-pack/plugins/reporting/public/components/__snapshots__/screen_capture_panel_content.test.tsx.snap @@ -0,0 +1,1191 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ScreenCapturePanelContent properly renders a view with "canvas" layout option 1`] = ` + +
+ +
+

+ + + Analytical Apps can take a minute or two to generate based upon the size of your test-object-type. + + +

+
+
+ +
+ + + } + labelType="label" + > +
+
+ + } + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + > +
+ + + + + Full page layout + + + +
+
+ +
+ + + Remove borders and footer logo + + +
+
+
+
+
+ + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+

+ + + Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. + + +

+
+
+ +
+ + + + + + + + + + + + +
+
+ +
+
+ +
+ +`; + +exports[`ScreenCapturePanelContent properly renders a view with "print" layout option 1`] = ` + +
+ +
+

+ + + Analytical Apps can take a minute or two to generate based upon the size of your test-object-type. + + +

+
+
+ +
+ + + } + labelType="label" + > +
+
+ + } + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + > +
+ + + + + Optimize for printing + + + +
+
+ +
+ + + Uses multiple pages, showing at most 2 visualizations per page + + +
+
+
+
+
+ + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+

+ + + Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. + + +

+
+
+ +
+ + + + + + + + + + + + +
+
+ +
+
+ +
+ +`; + +exports[`ScreenCapturePanelContent renders the default view properly 1`] = ` + +
+ +
+

+ + + Analytical Apps can take a minute or two to generate based upon the size of your test-object-type. + + +

+
+
+ +
+ + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+

+ + + Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. + + +

+
+
+ +
+ + + + + + + + + + + + +
+
+ +
+
+ +
+ +`; diff --git a/x-pack/plugins/reporting/public/components/index.ts b/x-pack/plugins/reporting/public/components/index.ts index 4adcc347dc7043..b8cccda2a6613b 100644 --- a/x-pack/plugins/reporting/public/components/index.ts +++ b/x-pack/plugins/reporting/public/components/index.ts @@ -11,3 +11,4 @@ export { getWarningFormulasToast } from './job_warning_formulas'; export { getWarningMaxSizeToast } from './job_warning_max_size'; export { getGeneralErrorToast } from './general_error'; export { ScreenCapturePanelContent } from './screen_capture_panel_content'; +export { getSharedComponents } from './shared'; diff --git a/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx b/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx index a38c37c8086893..4d7828b7894072 100644 --- a/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx +++ b/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx @@ -5,7 +5,17 @@ * 2.0. */ -import { EuiButton, EuiCopy, EuiForm, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; +import { + EuiAccordion, + EuiButton, + EuiCopy, + EuiForm, + EuiFormRow, + EuiHorizontalRule, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { Component, ReactElement } from 'react'; import { ToastsSetup } from 'src/core/public'; @@ -20,14 +30,12 @@ export interface Props { toasts: ToastsSetup; reportType: string; - /** - * Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator. - */ + /** Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator. **/ requiresSavedState: boolean; layoutId: string | undefined; objectId?: string; getJobParams: () => BaseParams; - options?: ReactElement; + options?: ReactElement | null; isDirty?: boolean; onClose?: () => void; intl: InjectedIntl; @@ -110,50 +118,61 @@ class ReportingPanelContentUi extends Component { ); } - const reportMsg = ( - - ); - return ( -

{reportMsg}

+

+ +

{this.props.options} {this.renderGenerateReportButton(false)} - - -

- -

-
- + - - {(copy) => ( - + + + +

- - )} - +

+
+ + + + {(copy) => ( + + + + )} + +
); } @@ -247,44 +266,12 @@ class ReportingPanelContentUi extends Component { } }) .catch((error: any) => { - if (error.message === 'not exportable') { - return this.props.toasts.addWarning({ - title: intl.formatMessage( - { - id: 'xpack.reporting.panelContent.whatCanBeExportedWarningTitle', - defaultMessage: 'Only saved {objectType} can be exported', - }, - { objectType: this.state.objectType } - ), - text: toMountPoint( - - ), - }); - } - - const defaultMessage = - error?.res?.status === 403 ? ( - - ) : ( - - ); - - this.props.toasts.addDanger({ + this.props.toasts.addError(error, { title: intl.formatMessage({ id: 'xpack.reporting.panelContent.notification.reportingErrorTitle', - defaultMessage: 'Reporting error', + defaultMessage: 'Failed to create report', }), - text: toMountPoint(error.message || defaultMessage), - 'data-test-subj': 'queueReportError', + toastMessage: error.body.message, }); }); }; diff --git a/x-pack/plugins/reporting/public/components/screen_capture_panel_content.test.tsx b/x-pack/plugins/reporting/public/components/screen_capture_panel_content.test.tsx new file mode 100644 index 00000000000000..84a6dcc3c0ba3f --- /dev/null +++ b/x-pack/plugins/reporting/public/components/screen_capture_panel_content.test.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mount } from 'enzyme'; +import React from 'react'; +import { IntlProvider } from 'react-intl'; +import { coreMock } from '../../../../../src/core/public/mocks'; +import { BaseParams } from '../../common/types'; +import { ReportingAPIClient } from '../lib/reporting_api_client'; +import { ScreenCapturePanelContent } from './screen_capture_panel_content'; + +const getJobParamsDefault: () => BaseParams = () => ({ + objectType: 'test-object-type', + title: 'Test Report Title', + browserTimezone: 'America/New_York', +}); + +test('ScreenCapturePanelContent renders the default view properly', () => { + const coreSetup = coreMock.createSetup(); + const component = mount( + + + + ); + expect(component.find('EuiForm')).toMatchSnapshot(); + expect(component.text()).not.toMatch('Full page layout'); + expect(component.text()).not.toMatch('Optimize for printing'); +}); + +test('ScreenCapturePanelContent properly renders a view with "canvas" layout option', () => { + const coreSetup = coreMock.createSetup(); + const component = mount( + + + + ); + expect(component.find('EuiForm')).toMatchSnapshot(); + expect(component.text()).toMatch('Full page layout'); +}); + +test('ScreenCapturePanelContent properly renders a view with "print" layout option', () => { + const coreSetup = coreMock.createSetup(); + const component = mount( + + + + ); + expect(component.find('EuiForm')).toMatchSnapshot(); + expect(component.text()).toMatch('Optimize for printing'); +}); diff --git a/x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx b/x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx index 21497ed2891562..fd6003f8656e87 100644 --- a/x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx +++ b/x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx @@ -5,11 +5,13 @@ * 2.0. */ -import { EuiSpacer, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { Component, Fragment } from 'react'; +import moment from 'moment'; +import React, { Component } from 'react'; import { ToastsSetup } from 'src/core/public'; -import { BaseParams } from '../../common/types'; +import { getDefaultLayoutSelectors } from '../../common'; +import { BaseParams, LayoutParams } from '../../common/types'; import { ReportingAPIClient } from '../lib/reporting_api_client'; import { ReportingPanelContent } from './reporting_panel_content'; @@ -17,33 +19,33 @@ export interface Props { apiClient: ReportingAPIClient; toasts: ToastsSetup; reportType: string; + layoutOption?: 'canvas' | 'print'; objectId?: string; getJobParams: () => BaseParams; + requiresSavedState: boolean; isDirty?: boolean; onClose?: () => void; } interface State { - isPreserveLayoutSupported: boolean; usePrintLayout: boolean; + useCanvasLayout: boolean; } export class ScreenCapturePanelContent extends Component { constructor(props: Props) { super(props); - const { objectType } = props.getJobParams(); - const isPreserveLayoutSupported = props.reportType !== 'png' && objectType !== 'visualization'; this.state = { - isPreserveLayoutSupported, usePrintLayout: false, + useCanvasLayout: false, }; } public render() { return ( { } private renderOptions = () => { - if (this.state.isPreserveLayoutSupported) { + if (this.props.layoutOption === 'print') { return ( - + + } + > { onChange={this.handlePrintLayoutChange} data-test-subj="usePrintLayout" /> - - + ); } - return ( - - - - ); + if (this.props.layoutOption === 'canvas') { + return ( + + } + > + + } + checked={this.state.useCanvasLayout} + onChange={this.handleCanvasLayoutChange} + data-test-subj="reportModeToggle" + /> + + ); + } + + return null; }; private handlePrintLayoutChange = (evt: EuiSwitchEvent) => { - this.setState({ usePrintLayout: evt.target.checked }); + this.setState({ usePrintLayout: evt.target.checked, useCanvasLayout: false }); }; - private getLayout = () => { + private handleCanvasLayoutChange = (evt: EuiSwitchEvent) => { + this.setState({ useCanvasLayout: evt.target.checked, usePrintLayout: false }); + }; + + private getLayout = (): Required => { + const { layout: outerLayout } = this.props.getJobParams(); + + let dimensions = outerLayout?.dimensions; + if (!dimensions) { + const el = document.querySelector('[data-shared-items-container]'); + const { height, width } = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; + dimensions = { height, width }; + } + + let selectors = outerLayout?.selectors; + if (!selectors) { + selectors = getDefaultLayoutSelectors(); + } + if (this.state.usePrintLayout) { - return { id: 'print' }; + return { id: 'print', dimensions, selectors }; } - const el = document.querySelector('[data-shared-items-container]'); - const bounds = el ? el.getBoundingClientRect() : { height: 768, width: 1024 }; + if (this.state.useCanvasLayout) { + return { id: 'canvas', dimensions, selectors }; + } - return { - id: this.props.reportType === 'png' ? 'png' : 'preserve_layout', - dimensions: { - height: bounds.height, - width: bounds.width, - }, - }; + return { id: 'preserve_layout', dimensions, selectors }; }; - private getJobParams = () => { + private getJobParams = (): Required => { + const outerParams = this.props.getJobParams(); + let browserTimezone = outerParams.browserTimezone; + if (!browserTimezone) { + browserTimezone = moment.tz.guess(); + } + return { ...this.props.getJobParams(), layout: this.getLayout(), + browserTimezone, }; }; } diff --git a/x-pack/plugins/reporting/public/components/shared/get_shared_components.tsx b/x-pack/plugins/reporting/public/components/shared/get_shared_components.tsx new file mode 100644 index 00000000000000..12d70c87019754 --- /dev/null +++ b/x-pack/plugins/reporting/public/components/shared/get_shared_components.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreSetup } from 'kibana/public'; +import React from 'react'; +import { ReportingAPIClient } from '../..'; +import { PDF_REPORT_TYPE } from '../../../common/constants'; +import type { Props as PanelPropsScreenCapture } from '../screen_capture_panel_content'; +import { ScreenCapturePanelContent } from '../screen_capture_panel_content_lazy'; + +interface IncludeOnCloseFn { + onClose: () => void; +} + +type PropsPDF = Pick & IncludeOnCloseFn; + +/* + * As of 7.14, the only shared component is a PDF report that is suited for Canvas integration. + * This is not planned to expand, as work is to be done on moving the export-type implementations out of Reporting + * Related Discuss issue: https://github.com/elastic/kibana/issues/101422 + */ +export function getSharedComponents(core: CoreSetup) { + return { + ReportingPanelPDF(props: PropsPDF) { + return ( + + ); + }, + }; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/hooks/index.tsx b/x-pack/plugins/reporting/public/components/shared/index.tsx similarity index 80% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/hooks/index.tsx rename to x-pack/plugins/reporting/public/components/shared/index.tsx index d9b3bcd80752dc..592538f1b84ccf 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/hooks/index.tsx +++ b/x-pack/plugins/reporting/public/components/shared/index.tsx @@ -5,4 +5,4 @@ * 2.0. */ -export * from './use_platform'; +export { getSharedComponents } from './get_shared_components'; diff --git a/x-pack/plugins/reporting/public/index.ts b/x-pack/plugins/reporting/public/index.ts index 7c82fda7554d4c..7179a81664b6f7 100644 --- a/x-pack/plugins/reporting/public/index.ts +++ b/x-pack/plugins/reporting/public/index.ts @@ -7,24 +7,20 @@ import { PluginInitializerContext } from 'src/core/public'; import { getDefaultLayoutSelectors } from '../common'; -import { ScreenCapturePanelContent } from './components/screen_capture_panel_content'; -import * as jobCompletionNotifications from './lib/job_completion_notifications'; +import { getSharedComponents } from './components'; import { ReportingAPIClient } from './lib/reporting_api_client'; import { ReportingPublicPlugin } from './plugin'; export interface ReportingSetup { - components: { - ScreenCapturePanel: typeof ScreenCapturePanelContent; - }; getDefaultLayoutSelectors: typeof getDefaultLayoutSelectors; - ReportingAPIClient: typeof ReportingAPIClient; usesUiCapabilities: () => boolean; + components: ReturnType; } export type ReportingStart = ReportingSetup; export { constants, getDefaultLayoutSelectors } from '../common'; -export { ReportingAPIClient, ReportingPublicPlugin as Plugin, jobCompletionNotifications }; +export { ReportingAPIClient, ReportingPublicPlugin as Plugin }; export function plugin(initializerContext: PluginInitializerContext) { return new ReportingPublicPlugin(initializerContext); diff --git a/x-pack/plugins/reporting/public/mocks.ts b/x-pack/plugins/reporting/public/mocks.ts new file mode 100644 index 00000000000000..414d1b0ae70fe3 --- /dev/null +++ b/x-pack/plugins/reporting/public/mocks.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { coreMock } from 'src/core/public/mocks'; +import { ReportingSetup } from '.'; +import { getDefaultLayoutSelectors } from '../common'; +import { getSharedComponents } from './components/shared'; + +type Setup = jest.Mocked; + +const createSetupContract = (): Setup => { + const coreSetup = coreMock.createSetup(); + return { + getDefaultLayoutSelectors: jest.fn().mockImplementation(getDefaultLayoutSelectors), + usesUiCapabilities: jest.fn().mockImplementation(() => true), + components: getSharedComponents(coreSetup), + }; +}; + +export const reportingPluginMock = { + createSetupContract, + createStartContract: createSetupContract, +}; diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index ff0d425faf54a5..577732fdb13927 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -29,10 +29,7 @@ import { constants, getDefaultLayoutSelectors } from '../common'; import { durationToNumber } from '../common/schema_utils'; import { JobId, JobSummarySet } from '../common/types'; import { ReportingSetup, ReportingStart } from './'; -import { - getGeneralErrorToast, - ScreenCapturePanelContent as ScreenCapturePanel, -} from './components'; +import { getGeneralErrorToast, getSharedComponents } from './components'; import { ReportingAPIClient } from './lib/reporting_api_client'; import { ReportingNotifierStreamHandler as StreamHandler } from './lib/stream_handler'; import { ReportingCsvPanelAction } from './panel_actions/get_csv_panel_action'; @@ -86,7 +83,6 @@ export class ReportingPublicPlugin ReportingPublicPluginSetupDendencies, ReportingPublicPluginStartDendencies > { - private readonly contract: ReportingStart; private readonly stop$ = new Rx.ReplaySubject(1); private readonly title = i18n.translate('xpack.reporting.management.reportingTitle', { defaultMessage: 'Reporting', @@ -95,21 +91,30 @@ export class ReportingPublicPlugin defaultMessage: 'Reporting', }); private config: ClientConfigType; + private contract?: ReportingSetup; constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); + } + + private getContract(core?: CoreSetup) { + if (core) { + this.contract = { + getDefaultLayoutSelectors, + usesUiCapabilities: () => this.config.roles?.enabled === false, + components: getSharedComponents(core), + }; + } - this.contract = { - ReportingAPIClient, - components: { ScreenCapturePanel }, - getDefaultLayoutSelectors, - usesUiCapabilities: () => this.config.roles?.enabled === false, - }; + if (!this.contract) { + throw new Error(`Setup error in Reporting plugin!`); + } + + return this.contract; } public setup(core: CoreSetup, setupDeps: ReportingPublicPluginSetupDendencies) { - const { http, notifications, getStartServices, uiSettings } = core; - const { toasts } = notifications; + const { http, getStartServices, uiSettings } = core; const { home, management, @@ -163,6 +168,9 @@ export class ReportingPublicPlugin new ReportingCsvPanelAction({ core, startServices$, license$, usesUiCapabilities }) ); + const reportingStart = this.getContract(core); + const { toasts } = core.notifications; + share.register( ReportingCsvShareProvider({ apiClient, @@ -173,6 +181,7 @@ export class ReportingPublicPlugin usesUiCapabilities, }) ); + share.register( reportingScreenshotShareProvider({ apiClient, @@ -184,7 +193,7 @@ export class ReportingPublicPlugin }) ); - return this.contract; + return reportingStart; } public start(core: CoreStart) { @@ -203,7 +212,7 @@ export class ReportingPublicPlugin ) .subscribe(); - return this.contract; + return this.getContract(); } public stop() { diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx index fc732b0ec7dfbe..7fe5268fc99102 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx @@ -65,13 +65,7 @@ export const ReportingCsvShareProvider = ({ ? moment.tz.guess() : uiSettings.get('dateFormat:tz'); - const getShareMenuItems = ({ - objectType, - objectId, - sharingData, - onClose, - isDirty, - }: ShareContext) => { + const getShareMenuItems = ({ objectType, objectId, sharingData, onClose }: ShareContext) => { if ('search' !== objectType) { return []; } @@ -114,7 +108,6 @@ export const ReportingCsvShareProvider = ({ layoutId={undefined} objectId={objectId} getJobParams={getJobParams} - isDirty={isDirty} onClose={onClose} /> ), diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index f4a952ef58298e..42f6ee5fcb898b 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -11,7 +11,7 @@ import React from 'react'; import * as Rx from 'rxjs'; import type { IUiSettingsClient, ToastsSetup } from 'src/core/public'; import { CoreStart } from 'src/core/public'; -import type { ShareContext } from '../../../../../src/plugins/share/public'; +import { ShareContext } from 'src/plugins/share/public'; import type { LicensingPluginSetup } from '../../../licensing/public'; import type { LayoutParams } from '../../common/types'; import type { JobParamsPNG } from '../../server/export_types/png/types'; @@ -167,6 +167,7 @@ export const reportingScreenshotShareProvider = ({ toasts={toasts} reportType="png" objectId={objectId} + requiresSavedState={true} getJobParams={getPngJobParams({ shareableUrl, apiClient, @@ -203,6 +204,8 @@ export const reportingScreenshotShareProvider = ({ toasts={toasts} reportType="printablePdf" objectId={objectId} + requiresSavedState={true} + layoutOption={objectType === 'dashboard' ? 'print' : undefined} getJobParams={getPdfJobParams({ shareableUrl, apiClient, 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 3c9be9a823c498..33072e8df5cec5 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -47,6 +47,7 @@ export interface HostIsolationResponse { export interface PendingActionsResponse { agent_id: string; pending_actions: { + /** Number of actions pending for each type. The `key` could be one of the `ISOLATION_ACTIONS` values. */ [key: string]: number; }; } diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx new file mode 100644 index 00000000000000..5cde22de697386 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx @@ -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 React, { memo, useMemo } from 'react'; +import { EuiBadge, EuiTextColor } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export interface EndpointHostIsolationStatusProps { + isIsolated: boolean; + /** the count of pending isolate actions */ + pendingIsolate?: number; + /** the count of pending unisoalte actions */ + pendingUnIsolate?: number; +} + +/** + * Component will display a host isoaltion status based on whether it is currently isolated or there are + * isolate/unisolate actions pending. If none of these are applicable, no UI component will be rendered + * (`null` is returned) + */ +export const EndpointHostIsolationStatus = memo( + ({ isIsolated, pendingIsolate = 0, pendingUnIsolate = 0 }) => { + return useMemo(() => { + // If nothing is pending and host is not currently isolated, then render nothing + if (!isIsolated && !pendingIsolate && !pendingUnIsolate) { + return null; + } + + // If nothing is pending, but host is isolated, then show isolation badge + if (!pendingIsolate && !pendingUnIsolate) { + return ( + + + + ); + } + + // If there are multiple types of pending isolation actions, then show count of actions with tooltip that displays breakdown + // TODO:PT implement edge case + // if () { + // + // } + + // Show 'pending [un]isolate' depending on what's pending + return ( + + + {pendingIsolate ? ( + + ) : ( + + )} + + + ); + }, [isIsolated, pendingIsolate, pendingUnIsolate]); + } +); + +EndpointHostIsolationStatus.displayName = 'EndpointHostIsolationStatus'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts index f5387a1b1a99c6..bd8e23e3a4559f 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts @@ -8,3 +8,4 @@ export * from './isolate_success'; export * from './isolate_form'; export * from './unisolate_form'; +export * from './endpoint_host_isolation_status'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index b62663bd787503..546116f82696b7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { HttpStart } from 'kibana/public'; +import { Dispatch } from 'redux'; +import { CoreStart, HttpStart } from 'kibana/public'; import { EndpointAction, HostInfo, @@ -13,6 +14,7 @@ import { HostIsolationResponse, HostResultList, Immutable, + ImmutableObject, } from '../../../../../common/endpoint/types'; import { GetPolicyListResponse } from '../../policy/types'; import { ImmutableMiddlewareAPI, ImmutableMiddlewareFactory } from '../../../../common/store'; @@ -54,6 +56,7 @@ import { import { isolateHost, unIsolateHost } from '../../../../common/lib/host_isolation'; import { AppAction } from '../../../../common/store/actions'; import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables'; +import { ServerReturnedEndpointPackageInfo } from './action'; type EndpointPageStore = ImmutableMiddlewareAPI; @@ -78,26 +81,14 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory, + dispatch: Dispatch, + coreStart: CoreStart +) { + if (endpointPackageInfo(state)) return; + + try { + const packageInfo = await sendGetEndpointSecurityPackage(coreStart.http); + dispatch({ + type: 'serverReturnedEndpointPackageInfo', + payload: packageInfo, + }); + } catch (error) { + // Ignore Errors, since this should not hinder the user's ability to use the UI + // eslint-disable-next-line no-console + console.error(error); + } +} diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 06b42145a6d48c..4d1ab0f3de8252 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -157,9 +157,9 @@ export const EndpointList = () => { const handleCreatePolicyClick = useNavigateToAppEventHandler( 'fleet', { - path: `#/policies/add-integration${ + path: `#/integrations/${ endpointPackageVersion ? `/endpoint-${endpointPackageVersion}` : '' - }`, + }/add-integration`, state: { onCancelNavigateTo: [ 'securitySolution:administration', diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx index 1407e5f64f156a..d01ccea5ba1f4a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx @@ -63,7 +63,7 @@ describe('Policy Details', () => { describe('when displayed with invalid id', () => { let releaseApiFailure: () => void; beforeEach(() => { - http.get.mockImplementationOnce(async () => { + http.get.mockImplementation(async () => { await new Promise((_, reject) => { releaseApiFailure = reject.bind(null, new Error('policy not found')); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx index 691601b69e3cd7..adc9438f27d743 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx @@ -633,18 +633,21 @@ describe('When on the Trusted Apps Page', () => { }); }); - it('should close the flyout', async () => { + it('should close the flyout', () => { expect(renderResult.queryByTestId('addTrustedAppFlyout')).toBeNull(); }); - it('should show success toast notification', async () => { + it('should show success toast notification', () => { expect(coreStart.notifications.toasts.addSuccess.mock.calls[0][0]).toEqual( '"one app" has been added to the Trusted Applications list.' ); }); - it('should trigger the List to reload', async () => { - expect(coreStart.http.get.mock.calls[0][0]).toEqual(TRUSTED_APPS_LIST_API); + it('should trigger the List to reload', () => { + const isCalled = coreStart.http.get.mock.calls.some( + (call) => call[0].toString() === TRUSTED_APPS_LIST_API + ); + expect(isCalled).toEqual(true); }); }); @@ -666,18 +669,18 @@ describe('When on the Trusted Apps Page', () => { }); }); - it('should continue to show the flyout', async () => { + it('should continue to show the flyout', () => { expect(renderResult.getByTestId('addTrustedAppFlyout')).not.toBeNull(); }); - it('should enable the Cancel Button', async () => { + it('should enable the Cancel Button', () => { expect( (renderResult.getByTestId('addTrustedAppFlyout-cancelButton') as HTMLButtonElement) .disabled ).toBe(false); }); - it('should show the dialog close button', async () => { + it('should show the dialog close button', () => { expect(renderResult.getByTestId('euiFlyoutCloseButton')).not.toBeNull(); }); @@ -688,7 +691,7 @@ describe('When on the Trusted Apps Page', () => { ).toBe(false); }); - it('should show API errors in the form', async () => { + it('should show API errors in the form', () => { expect(renderResult.container.querySelector('.euiForm__errors')).not.toBeNull(); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx index a6b545045a6272..f0198092ec1be8 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx @@ -9,11 +9,15 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import { OverviewEmpty } from '.'; import { useIngestEnabledCheck } from '../../../common/hooks/endpoint/ingest_enabled'; + +const endpointPackageVersion = '0.19.1'; + jest.mock('../../../common/lib/kibana'); jest.mock('../../../management/pages/endpoint_hosts/view/hooks', () => ({ useIngestUrl: jest .fn() .mockReturnValue({ appId: 'ingestAppId', appPath: 'ingestPath', url: 'ingestUrl' }), + useEndpointSelector: jest.fn().mockReturnValue({ endpointPackageVersion }), })); jest.mock('../../../common/hooks/endpoint/ingest_enabled', () => ({ @@ -57,7 +61,7 @@ describe('OverviewEmpty', () => { fill: false, label: 'Add Endpoint Security', onClick: undefined, - url: '/app/home#/tutorial_directory/security', + url: `#/integrations/endpoint-${endpointPackageVersion}/add-integration`, }, }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx index 6a77271e987715..028871d7be19d8 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx @@ -7,6 +7,7 @@ import React, { useMemo } from 'react'; import { omit } from 'lodash/fp'; +import { createStructuredSelector } from 'reselect'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiLink } from '@elastic/eui'; @@ -14,18 +15,33 @@ import * as i18nCommon from '../../../common/translations'; import { EmptyPage, EmptyPageActionsProps } from '../../../common/components/empty_page'; import { useKibana } from '../../../common/lib/kibana'; import { ADD_DATA_PATH } from '../../../../common/constants'; -import { useIngestUrl } from '../../../management/pages/endpoint_hosts/view/hooks'; +import { + useEndpointSelector, + useIngestUrl, +} from '../../../management/pages/endpoint_hosts/view/hooks'; import { useNavigateToAppEventHandler } from '../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { useIngestEnabledCheck } from '../../../common/hooks/endpoint/ingest_enabled'; +import { CreateStructuredSelector } from '../../../common/store'; +import { endpointPackageVersion as useEndpointPackageVersion } from '../../../management/pages/endpoint_hosts/store/selectors'; const OverviewEmptyComponent: React.FC = () => { const { http, docLinks } = useKibana().services; const basePath = http.basePath.get(); - const { appId: ingestAppId, appPath: ingestPath, url: ingestUrl } = useIngestUrl( - 'integrations?category=security' - ); - const handleOnClick = useNavigateToAppEventHandler(ingestAppId, { path: ingestPath }); + const selector = (createStructuredSelector as CreateStructuredSelector)({ + endpointPackageVersion: useEndpointPackageVersion, + }); + const { endpointPackageVersion } = useEndpointSelector(selector); + const { url: ingestUrl } = useIngestUrl(''); + + const endpointIntegrationUrlPath = endpointPackageVersion + ? `/endpoint-${endpointPackageVersion}/add-integration` + : ''; + const endpointIntegrationUrl = `#/integrations${endpointIntegrationUrlPath}`; + const handleEndpointClick = useNavigateToAppEventHandler('fleet', { + path: endpointIntegrationUrl, + }); const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); + const emptyPageActions: EmptyPageActionsProps = useMemo( () => ({ elasticAgent: { @@ -42,13 +58,13 @@ const OverviewEmptyComponent: React.FC = () => { }, endpoint: { label: i18nCommon.EMPTY_ACTION_ENDPOINT, - url: `${basePath}${ADD_DATA_PATH}`, + url: endpointIntegrationUrl, description: i18nCommon.EMPTY_ACTION_ENDPOINT_DESCRIPTION, - onClick: handleOnClick, + onClick: handleEndpointClick, fill: false, }, }), - [basePath, ingestUrl, handleOnClick] + [basePath, ingestUrl, endpointIntegrationUrl, handleEndpointClick] ); const emptyPageIngestDisabledActions = useMemo( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap index 1abe55b782c328..4f060746b92b0c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap @@ -7,7 +7,7 @@ Object { ], "mappings": Object { "_meta": Object { - "version": 35, + "version": 45, }, "dynamic": false, "properties": Object { @@ -365,6 +365,19 @@ Object { }, }, }, + "data_stream": Object { + "properties": Object { + "dataset": Object { + "type": "keyword", + }, + "namespace": Object { + "type": "keyword", + }, + "type": Object { + "type": "keyword", + }, + }, + }, "destination": Object { "properties": Object { "address": Object { @@ -1907,6 +1920,54 @@ Object { }, }, }, + "orchestrator": Object { + "properties": Object { + "api_version": Object { + "ignore_above": 1024, + "type": "keyword", + }, + "cluster": Object { + "properties": Object { + "name": Object { + "ignore_above": 1024, + "type": "keyword", + }, + "url": Object { + "ignore_above": 1024, + "type": "keyword", + }, + "version": Object { + "ignore_above": 1024, + "type": "keyword", + }, + }, + }, + "namespace": Object { + "ignore_above": 1024, + "type": "keyword", + }, + "organization": Object { + "ignore_above": 1024, + "type": "keyword", + }, + "resource": Object { + "properties": Object { + "name": Object { + "ignore_above": 1024, + "type": "keyword", + }, + "type": Object { + "ignore_above": 1024, + "type": "keyword", + }, + }, + }, + "type": Object { + "ignore_above": 1024, + "type": "keyword", + }, + }, + }, "organization": Object { "properties": Object { "id": Object { @@ -4467,6 +4528,6 @@ Object { }, }, }, - "version": 35, + "version": 45, } `; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/ecs_mapping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/ecs_mapping.json index 2967f4cb725e74..3d24384680f575 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/ecs_mapping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/ecs_mapping.json @@ -1,10 +1,8 @@ { - "index_patterns": [ - "try-ecs-*" - ], + "index_patterns": ["try-ecs-*"], "mappings": { "_meta": { - "version": "1.9.0" + "version": "1.10.0" }, "date_detection": false, "dynamic_templates": [ @@ -331,6 +329,19 @@ } } }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, "destination": { "properties": { "address": { @@ -1802,6 +1813,54 @@ } } }, + "orchestrator": { + "properties": { + "api_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "organization": { "properties": { "id": { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts index 9c39ad4ee35982..4691db1b19595e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.test.ts @@ -43,6 +43,49 @@ describe('get_signals_template', () => { expect(template.settings.mapping.total_fields.limit).toBeGreaterThanOrEqual(10000); }); + // If you see this test fail, you should track down any and all "constant_keyword" in your ecs_mapping.json and replace + // those with "keyword". The paths that fail in the array below will be something like: + // - Expected - 1 + // + Received + 5 + // + // - Array [] + // + Array [ + // + "mappings.properties.data_stream.properties.dataset", + // + "mappings.properties.data_stream.properties.namespace", + // + "mappings.properties.data_stream.properties.type", + // + ] + // which means that in your ecs_mapping you have paths such as "mappings.properties.data_stream.properties.dataset" which + // contain a constant_keyword which needs to be replaced with a normal keyword instead. + // + // The reason why we deviate from ECS standards here is because when you have a many to 1 relationship where you have + // several different indexes with different "constant_keyword" values you cannot copy them over into a single "constant_keyword". + // Instead you have to use "keyword". This test was first introduced when ECS 1.10 came out and data_stream.* values which had + // "constant_keyword" fields and we needed to change those to be "keyword" instead. + test('it should NOT have any "constant_keyword" and instead those should be replaced with regular "keyword" in the mapping', () => { + const template = getSignalsTemplate('test-index'); + + // Small recursive function to find any values of "constant_keyword" and mark which fields it was found on and then error on those fields + // The matchers from jest such as jest.toMatchObject do not support recursion, so I have to write it here: + // https://github.com/facebook/jest/issues/2506 + const recursiveConstantKeywordFound = (path: string, inputTemplate: object): string[] => + Object.entries(inputTemplate).reduce((accum, [key, innerValue]) => { + if (typeof innerValue === 'object') { + return [ + ...accum, + ...recursiveConstantKeywordFound(path !== '' ? `${path}.${key}` : key, innerValue), + ]; + } else { + if (key === 'type' && innerValue === 'constant_keyword') { + return [...accum, path]; + } else { + return accum; + } + } + }, []); + const constantKeywordsFound = recursiveConstantKeywordFound('', template); + expect(constantKeywordsFound).toEqual([]); + }); + test('it should match snapshot', () => { const template = getSignalsTemplate('test-index'); expect(template).toMatchSnapshot(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts index 0318218ed59001..53035ebf28cd78 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template.ts @@ -22,7 +22,7 @@ import otherMapping from './other_mappings.json'; incremented by 10 in order to add "room" for the aforementioned patch release */ -export const SIGNALS_TEMPLATE_VERSION = 35; +export const SIGNALS_TEMPLATE_VERSION = 45; export const MIN_EQL_RULE_INDEX_VERSION = 2; export const getSignalsTemplate = (index: string) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/delete_rules_by_query.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/delete_rules_by_query.sh new file mode 100755 index 00000000000000..d78726f9e23b5a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/delete_rules_by_query.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. +# + +set -e +./check_env_variables.sh + +QUERY=${1} + +# Example delete all rules +# ./delete_rules_by_query.sh + +# Example delete rules with tag "test" +# ./delete_rules_by_query.sh 'alert.attributes.tags: \"test\"' + +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_bulk_action \ + --data "{ + \"query\": \"$QUERY\", + \"action\": \"delete\" +}" | jq . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/disable_rules_by_query.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/disable_rules_by_query.sh new file mode 100755 index 00000000000000..1c7f05f4057cf1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/disable_rules_by_query.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. +# + +set -e +./check_env_variables.sh + +QUERY=${1} + +# Example disable all rules +# ./disable_rules_by_query.sh + +# Example disable rules with tag "test" +# ./disable_rules_by_query.sh 'alert.attributes.tags: \"test\"' + +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_bulk_action \ + --data "{ + \"query\": \"$QUERY\", + \"action\": \"disable\" +}" | jq . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/duplicate_rules_by_query.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/duplicate_rules_by_query.sh new file mode 100755 index 00000000000000..13da480d74f83d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/duplicate_rules_by_query.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. +# + +set -e +./check_env_variables.sh + +QUERY=${1} + +# Example duplicate all rules +# ./duplicate_rules_by_query.sh + +# Example duplicate rules with tag "test" +# ./duplicate_rules_by_query.sh 'alert.attributes.tags: \"test\"' + +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_bulk_action \ + --data "{ + \"query\": \"$QUERY\", + \"action\": \"duplicate\" +}" | jq . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/enable_rules_by_query.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/enable_rules_by_query.sh new file mode 100755 index 00000000000000..52bb51b6dd6dfb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/enable_rules_by_query.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. +# + +set -e +./check_env_variables.sh + +QUERY=${1} + +# Example enable all rules +# ./enable_rules_by_query.sh + +# Example enable rules with tag "test" +# ./enable_rules_by_query.sh 'alert.attributes.tags: \"test\"' + +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_bulk_action \ + --data "{ + \"query\": \"$QUERY\", + \"action\": \"enable\" +}" | jq . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/export_rules_by_query.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/export_rules_by_query.sh new file mode 100755 index 00000000000000..954fc2104262b6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/export_rules_by_query.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. +# + +set -e +./check_env_variables.sh + +QUERY=${1} + +# Example export all rules +# ./export_rules_by_query.sh + +# Example export rules with tag "test" +# ./export_rules_by_query.sh 'alert.attributes.tags: \"test\"' + +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_bulk_action \ + --data "{ + \"query\": \"$QUERY\", + \"action\": \"export\" +}" diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9861c11e178db8..4befc3d51b17a6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2879,8 +2879,6 @@ "indexPatternManagement.createIndexPattern.betaLabel": "ベータ", "indexPatternManagement.createIndexPattern.description": "インデックスパターンは、{single}または{multiple}データソース、{star}と一致します。", "indexPatternManagement.createIndexPattern.documentation": "ドキュメンテーションを表示", - "indexPatternManagement.createIndexPattern.emptyState.basicLicenseDescription": "この機能にはベーシックライセンスが必要です。", - "indexPatternManagement.createIndexPattern.emptyState.basicLicenseLabel": "基本", "indexPatternManagement.createIndexPattern.emptyState.checkDataButton": "新規データを確認", "indexPatternManagement.createIndexPattern.emptyState.createAnyway": "一部のインデックスは表示されない場合があります。{link}してください。", "indexPatternManagement.createIndexPattern.emptyState.createAnywayLink": "インデックスパターンを作成します", @@ -7005,20 +7003,7 @@ "xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel": "エレメントを更新", "xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip": "データを更新", "xpack.canvas.workpadHeaderShareMenu.copyPDFMessage": "{PDF}生成{URL}がクリップボードにコピーされました。", - "xpack.canvas.workpadHeaderShareMenu.copyReportingConfigMessage": "レポート構成がクリップボードにコピーされました", "xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage": "共有マークアップがクリップボードにコピーされました", - "xpack.canvas.workpadHeaderShareMenu.exportPDFErrorMessage": "'{workpadName}'の{PDF}を作成できませんでした", - "xpack.canvas.workpadHeaderShareMenu.exportPDFMessage": "{PDF}をエクスポートしています。管理で進捗を確認できます。", - "xpack.canvas.workpadHeaderShareMenu.exportPDFTitle": "ワークパッド '{workpadName}' の {PDF} エクスポート", - "xpack.canvas.workpadHeaderShareMenu.FullPageLayoutHelpText": "枠線とフッターロゴを削除", - "xpack.canvas.workpadHeaderShareMenu.FullPageLayoutLabel": "全ページレイアウト", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelAdvancedOptionsLabel": "高度なオプション", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyAriaLabel": "この {URL} を使用してスクリプトから、または Watcher で {PDF} を生成することもできます。{URL}をクリップボードにコピーするにはEnterキーを押してください。", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyButtonLabel": "{POST} {URL}をコピー", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyDescription": "{POST} {URL}をコピーして{KIBANA}外またはWatcherから生成を実行することもできます。", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateButtonLabel": "{PDF}を生成", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateDescription": "ワークパッドのサイズによって、{PDF} の生成には数分かかる場合があります。", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelOptionsLabel": "オプション", "xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle": "{JSON} をダウンロード", "xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle": "{PDF}レポート", "xpack.canvas.workpadHeaderShareMenu.shareMenuButtonLabel": "共有", @@ -14656,8 +14641,6 @@ "xpack.ml.fieldTypeIcon.numberTypeAriaLabel": "数字タイプ", "xpack.ml.fieldTypeIcon.textTypeAriaLabel": "テキストタイプ", "xpack.ml.fieldTypeIcon.unknownTypeAriaLabel": "不明なタイプ", - "xpack.ml.fileDataVisualizerDescription": "CSV、NDJSON、またはログファイルをインポートします。", - "xpack.ml.fileDataVisualizerTitle": "ファイルをアップロード", "xpack.ml.formatters.metricChangeDescription.actualSameAsTypicalDescription": "実際値が通常値と同じ", "xpack.ml.formatters.metricChangeDescription.moreThan100xHigherDescription": "100x よりも高い", "xpack.ml.formatters.metricChangeDescription.moreThan100xLowerDescription": "100x よりも低い", @@ -17881,14 +17864,10 @@ "xpack.reporting.panelContent.generateButtonLabel": "{reportingType} を生成", "xpack.reporting.panelContent.generationTimeDescription": "{objectType} のサイズによって、{reportingType} の作成には数分かかる場合があります。", "xpack.reporting.panelContent.howToCallGenerationDescription": "POST URL をコピーして Kibana 外または ウォッチャー から生成を実行することもできます。", - "xpack.reporting.panelContent.noPermissionToGenerateReportDescription": "このレポートを生成するパーミッションがありません。", - "xpack.reporting.panelContent.notification.cantReachServerDescription": "サーバーと通信できません。再試行してください。", "xpack.reporting.panelContent.notification.reportingErrorTitle": "レポートエラー", "xpack.reporting.panelContent.saveWorkDescription": "レポートの生成前に変更内容を保存してください。", "xpack.reporting.panelContent.successfullyQueuedReportNotificationDescription": "{path}で進捗状況を追跡", "xpack.reporting.panelContent.successfullyQueuedReportNotificationTitle": "{objectType} のレポートキュー", - "xpack.reporting.panelContent.whatCanBeExportedWarningDescription": "初めに変更内容を保存してください", - "xpack.reporting.panelContent.whatCanBeExportedWarningTitle": "保存された {objectType} のみエクスポートできます", "xpack.reporting.pdfFooterImageDescription": "PDFのフッターに使用するカスタム画像です", "xpack.reporting.pdfFooterImageLabel": "PDFフッター画像", "xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportMessage": "レポートには、スプレッドシートアプリケーションで式と解釈される可能性のある文字が含まれています。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 17b21e5e6d33cb..6d249800b57363 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2893,8 +2893,6 @@ "indexPatternManagement.createIndexPattern.betaLabel": "公测版", "indexPatternManagement.createIndexPattern.description": "索引模式可以匹配单个源,例如 {single} 或 {multiple} 个数据源、{star}。", "indexPatternManagement.createIndexPattern.documentation": "阅读文档", - "indexPatternManagement.createIndexPattern.emptyState.basicLicenseDescription": "此功能需要基本级许可证。", - "indexPatternManagement.createIndexPattern.emptyState.basicLicenseLabel": "基本级", "indexPatternManagement.createIndexPattern.emptyState.checkDataButton": "检查新数据", "indexPatternManagement.createIndexPattern.emptyState.createAnyway": "部分索引可能已隐藏。仍然尝试{link}。", "indexPatternManagement.createIndexPattern.emptyState.createAnywayLink": "创建索引模式", @@ -7053,20 +7051,7 @@ "xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel": "刷新元素", "xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip": "刷新数据", "xpack.canvas.workpadHeaderShareMenu.copyPDFMessage": "{PDF} 生成 {URL} 已复制到您的剪贴板。", - "xpack.canvas.workpadHeaderShareMenu.copyReportingConfigMessage": "已将报告配置复制到剪贴板", "xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage": "已将共享标记复制到剪贴板", - "xpack.canvas.workpadHeaderShareMenu.exportPDFErrorMessage": "无法为“{workpadName}”创建 {PDF}", - "xpack.canvas.workpadHeaderShareMenu.exportPDFMessage": "正在导出 {PDF}。可以在“管理”中跟踪进度。", - "xpack.canvas.workpadHeaderShareMenu.exportPDFTitle": "Workpad“{workpadName}”的 {PDF} 导出", - "xpack.canvas.workpadHeaderShareMenu.FullPageLayoutHelpText": "删除边框和页脚徽标", - "xpack.canvas.workpadHeaderShareMenu.FullPageLayoutLabel": "全页面布局", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelAdvancedOptionsLabel": "高级选项", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyAriaLabel": "或者,也可以从脚本或使用此 {URL} 通过 Watcher 生成 {PDF}。按 Enter 键可将 {URL} 复制到剪贴板。", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyButtonLabel": "复制 {POST} {URL}", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyDescription": "或者,复制此 {POST} {URL} 以从 {KIBANA} 外部或从 Watcher 调用生成。", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateButtonLabel": "生成 {PDF}", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateDescription": "{PDF} 可能会花费一两分钟生成,具体取决于 Workpad 的大小。", - "xpack.canvas.workpadHeaderShareMenu.pdfPanelOptionsLabel": "选项", "xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle": "下载为 {JSON}", "xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle": "{PDF} 报告", "xpack.canvas.workpadHeaderShareMenu.shareMenuButtonLabel": "共享", @@ -14849,8 +14834,6 @@ "xpack.ml.fieldTypeIcon.numberTypeAriaLabel": "数字类型", "xpack.ml.fieldTypeIcon.textTypeAriaLabel": "文本类型", "xpack.ml.fieldTypeIcon.unknownTypeAriaLabel": "未知类型", - "xpack.ml.fileDataVisualizerDescription": "导入您自己的 CSV、NDJSON 或日志文件。", - "xpack.ml.fileDataVisualizerTitle": "上传文件", "xpack.ml.formatters.metricChangeDescription.actualSameAsTypicalDescription": "实际上与典型模式相同", "xpack.ml.formatters.metricChangeDescription.moreThan100xHigherDescription": "高 100 多倍", "xpack.ml.formatters.metricChangeDescription.moreThan100xLowerDescription": "低 100 多倍", @@ -18122,14 +18105,10 @@ "xpack.reporting.panelContent.generateButtonLabel": "生成 {reportingType}", "xpack.reporting.panelContent.generationTimeDescription": "{reportingType} 可能会花费 1 或 2 分钟生成,取决于 {objectType} 的大小。", "xpack.reporting.panelContent.howToCallGenerationDescription": "或者,复制此 POST URL 以从 Kibana 外部或从 Watcher 调用生成。", - "xpack.reporting.panelContent.noPermissionToGenerateReportDescription": "您无权生成此报告。", - "xpack.reporting.panelContent.notification.cantReachServerDescription": "无法访问服务器。请重试。", "xpack.reporting.panelContent.notification.reportingErrorTitle": "报告错误", "xpack.reporting.panelContent.saveWorkDescription": "请在生成报告之前保存您的工作。", "xpack.reporting.panelContent.successfullyQueuedReportNotificationDescription": "在 {path} 中跟踪其进度", "xpack.reporting.panelContent.successfullyQueuedReportNotificationTitle": "已为 {objectType} 排队报告", - "xpack.reporting.panelContent.whatCanBeExportedWarningDescription": "请先保存您的工作", - "xpack.reporting.panelContent.whatCanBeExportedWarningTitle": "只会导出保存的 {objectType}", "xpack.reporting.pdfFooterImageDescription": "要在 PDF 的页脚中使用的定制图像", "xpack.reporting.pdfFooterImageLabel": "PDF 页脚图像", "xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportMessage": "报告包含电子表格应用程序可解释为公式的字符。", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index 20aec6974d395b..3da25cb4cf0d8d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -90,7 +90,7 @@ export const TriggersActionsUIHome: React.FunctionComponent - + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index 1bbffc850ee185..cad4dabbe8275d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -12,8 +12,14 @@ import { mountWithIntl, nextTick } from '@kbn/test/jest'; import { act } from '@testing-library/react'; import { AlertDetails } from './alert_details'; import { Alert, ActionType, AlertTypeModel, AlertType } from '../../../../types'; -import { EuiTitle, EuiBadge, EuiFlexItem, EuiSwitch, EuiButtonEmpty, EuiText } from '@elastic/eui'; -import { ViewInApp } from './view_in_app'; +import { + EuiBadge, + EuiFlexItem, + EuiSwitch, + EuiButtonEmpty, + EuiText, + EuiPageHeaderProps, +} from '@elastic/eui'; import { ActionGroup, AlertExecutionStatusErrorReasons, @@ -75,13 +81,7 @@ describe('alert_details', () => { expect( shallow( - ).containsMatchingElement( - -

- {alert.name} -

-
- ) + ).find('EuiPageHeader') ).toBeTruthy(); }); @@ -103,7 +103,7 @@ describe('alert_details', () => { expect( shallow( - ).containsMatchingElement({alertType.name}) + ).find({alertType.name}) ).toBeTruthy(); }); @@ -290,7 +290,7 @@ describe('alert_details', () => { expect( shallow( - ).containsMatchingElement() + ).find('ViewInApp') ).toBeTruthy(); }); @@ -309,16 +309,29 @@ describe('alert_details', () => { minimumLicenseRequired: 'basic', enabledInLicense: true, }; - - expect( - shallow( - - ) - .find(EuiButtonEmpty) - .find('[data-test-subj="openEditAlertFlyoutButton"]') - .first() - .exists() - ).toBeTruthy(); + const pageHeaderProps = shallow( + + ) + .find('EuiPageHeader') + .props() as EuiPageHeaderProps; + const rightSideItems = pageHeaderProps.rightSideItems; + expect(!!rightSideItems && rightSideItems[2]!).toMatchInlineSnapshot(` + + + + + + `); }); }); }); @@ -768,20 +781,34 @@ describe('edit button', () => { enabledInLicense: true, }; - expect( - shallow( - + ) + .find('EuiPageHeader') + .props() as EuiPageHeaderProps; + const rightSideItems = pageHeaderProps.rightSideItems; + expect(!!rightSideItems && rightSideItems[2]!).toMatchInlineSnapshot(` + + + - ) - .find(EuiButtonEmpty) - .find('[name="edit"]') - .first() - .exists() - ).toBeTruthy(); + + + `); }); it('should not render an edit button when alert editable but actions arent', () => { @@ -851,20 +878,34 @@ describe('edit button', () => { enabledInLicense: true, }; - expect( - shallow( - + ) + .find('EuiPageHeader') + .props() as EuiPageHeaderProps; + const rightSideItems = pageHeaderProps.rightSideItems; + expect(!!rightSideItems && rightSideItems[2]!).toMatchInlineSnapshot(` + + + - ) - .find(EuiButtonEmpty) - .find('[name="edit"]') - .first() - .exists() - ).toBeTruthy(); + + + `); }); }); @@ -885,7 +926,7 @@ describe('refresh button', () => { }; const requestRefresh = jest.fn(); - const refreshButton = shallow( + const wrapper = mountWithIntl( { {...mockAlertApis} requestRefresh={requestRefresh} /> - ) - .find('[data-test-subj="refreshAlertsButton"]') - .first(); + ); + const refreshButton = wrapper.find('[data-test-subj="refreshAlertsButton"]').first(); expect(refreshButton.exists()).toBeTruthy(); refreshButton.simulate('click'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index ba6c77a4403f1d..3e411913520adc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -12,14 +12,11 @@ import { useHistory } from 'react-router-dom'; import { EuiPageBody, EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, + EuiPageHeader, EuiText, EuiFlexGroup, EuiFlexItem, EuiBadge, - EuiPage, EuiPageContentBody, EuiSwitch, EuiCallOut, @@ -45,7 +42,7 @@ import { alertsErrorReasonTranslationsMapping } from '../../alerts_list/translat import { useKibana } from '../../../../common/lib/kibana'; import { alertReducer } from '../../alert_form/alert_reducer'; -type AlertDetailsProps = { +export type AlertDetailsProps = { alert: Alert; alertType: AlertType; actionTypes: ActionType[]; @@ -120,249 +117,245 @@ export const AlertDetails: React.FunctionComponent = ({ } }; + const rightPageHeaderButtons = hasEditButton + ? [ + <> + setEditFlyoutVisibility(true)} + name="edit" + disabled={!alertType.enabledInLicense} + > + + + {editFlyoutVisible && ( + { + setInitialAlert(alert); + setEditFlyoutVisibility(false); + }} + actionTypeRegistry={actionTypeRegistry} + alertTypeRegistry={alertTypeRegistry} + onSave={setAlert} + /> + )} + , + ] + : []; + return ( - - - - - - -

- {alert.name} -

-
-
- - - {hasEditButton ? ( - - <> - {' '} - setEditFlyoutVisibility(true)} - name="edit" - disabled={!alertType.enabledInLicense} - > - - - {editFlyoutVisible && ( - { - setInitialAlert(alert); - setEditFlyoutVisibility(false); - }} - actionTypeRegistry={actionTypeRegistry} - alertTypeRegistry={alertTypeRegistry} - onSave={setAlert} - /> - )} - - - ) : null} + + + + } + rightSideItems={[ + , + + + , + ...rightPageHeaderButtons, + ]} + /> + + + + + +

+ +

+
+ + {alertType.name} +
+ + {uniqueActions && uniqueActions.length ? ( + <> + +

+ +

+
+ + + {uniqueActions.map((action, index) => ( + + + {actionTypesByTypeId[action].name ?? action} + + + ))} + + + ) : null} +
+ + + - - - + { + if (isEnabled) { + setIsEnabled(false); + await disableAlert(alert); + // Reset dismiss if previously clicked + setDissmissAlertErrors(false); + } else { + setIsEnabled(true); + await enableAlert(alert); + } + requestRefresh(); + }} + label={ + + } + /> - + { + if (isMuted) { + setIsMuted(false); + await unmuteAlert(alert); + } else { + setIsMuted(true); + await muteAlert(alert); + } + requestRefresh(); + }} + label={ + + } + /> -
-
- - - - -

- -

-
- - {alertType.name} -
- - {uniqueActions && uniqueActions.length ? ( - <> - -

- -

-
- - - {uniqueActions.map((action, index) => ( - - - {actionTypesByTypeId[action].name ?? action} - - - ))} - - - ) : null} -
- - - - - { - if (isEnabled) { - setIsEnabled(false); - await disableAlert(alert); - // Reset dismiss if previously clicked - setDissmissAlertErrors(false); - } else { - setIsEnabled(true); - await enableAlert(alert); - } - requestRefresh(); - }} - label={ - - } - /> - - - { - if (isMuted) { - setIsMuted(false); - await unmuteAlert(alert); - } else { - setIsMuted(true); - await muteAlert(alert); - } - requestRefresh(); - }} - label={ + + + {alert.enabled && !dissmissAlertErrors && alert.executionStatus.status === 'error' ? ( + + + + + {alert.executionStatus.error?.message} + + + + + setDissmissAlertErrors(true)} + > - } - /> - - - - - {alert.enabled && !dissmissAlertErrors && alert.executionStatus.status === 'error' ? ( - - - - - {alert.executionStatus.error?.message} - - - + + + {alert.executionStatus.error?.reason === + AlertExecutionStatusErrorReasons.License && ( - setDissmissAlertErrors(true)} + target="_blank" > - + - {alert.executionStatus.error?.reason === - AlertExecutionStatusErrorReasons.License && ( - - - - - - )} - - - -
- ) : null} - - - {alert.enabled ? ( - - ) : ( - <> - - -

- -

-
- - )} + )} +
+
- -
- - + ) : null} + + + {alert.enabled ? ( + + ) : ( + <> + + +

+ +

+
+ + )} +
+
+ +
+ ); }; diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index e02cf44b0856eb..c34da1bc097bc0 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -12,7 +12,8 @@ import { PluginInitializerContext, AppMountParameters, } from 'kibana/public'; -import { of } from 'rxjs'; +import { from } from 'rxjs'; +import { map } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import { DEFAULT_APP_CATEGORIES } from '../../../../../src/core/public'; import { @@ -104,32 +105,41 @@ export class UptimePlugin }); plugins.observability.navigation.registerSections( - of([ - { - label: 'Uptime', - sortKey: 200, - entries: [ - { - label: i18n.translate('xpack.uptime.overview.heading', { - defaultMessage: 'Monitoring overview', - }), - app: 'uptime', - path: '/', - matchFullPath: true, - ignoreTrailingSlash: true, - }, - { - label: i18n.translate('xpack.uptime.certificatesPage.heading', { - defaultMessage: 'TLS Certificates', - }), - app: 'uptime', - path: '/certificates', - matchFullPath: true, - }, - ], - }, - ]) + from(core.getStartServices()).pipe( + map(([coreStart]) => { + if (coreStart.application.capabilities.uptime.show) { + return [ + { + label: 'Uptime', + sortKey: 200, + entries: [ + { + label: i18n.translate('xpack.uptime.overview.heading', { + defaultMessage: 'Monitoring overview', + }), + app: 'uptime', + path: '/', + matchFullPath: true, + ignoreTrailingSlash: true, + }, + { + label: i18n.translate('xpack.uptime.certificatesPage.heading', { + defaultMessage: 'TLS Certificates', + }), + app: 'uptime', + path: '/certificates', + matchFullPath: true, + }, + ], + }, + ]; + } + + return []; + }) + ) ); + core.application.register({ id: PLUGIN.ID, euiIconType: 'logoObservability', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index 40c0fe398bc57e..c1f6bcb9e15100 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -149,13 +149,17 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }); break; case 'new-instance': - validateInstanceEvent(event, `created new instance: 'instance'`); + validateInstanceEvent(event, `created new instance: 'instance'`, false); break; case 'recovered-instance': - validateInstanceEvent(event, `instance 'instance' has recovered`); + validateInstanceEvent(event, `instance 'instance' has recovered`, true); break; case 'active-instance': - validateInstanceEvent(event, `active instance: 'instance' in actionGroup: 'default'`); + validateInstanceEvent( + event, + `active instance: 'instance' in actionGroup: 'default'`, + false + ); break; // this will get triggered as we add new event actions default: @@ -163,7 +167,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { } } - function validateInstanceEvent(event: IValidatedEvent, subMessage: string) { + function validateInstanceEvent( + event: IValidatedEvent, + subMessage: string, + shouldHaveEventEnd: boolean + ) { validateEvent(event, { spaceId: Spaces.space1.id, savedObjects: [ @@ -172,6 +180,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { message: `test.patternFiring:${alertId}: 'abc' ${subMessage}`, instanceId: 'instance', actionGroupId: 'default', + shouldHaveEventEnd, }); } }); @@ -288,10 +297,10 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }); break; case 'new-instance': - validateInstanceEvent(event, `created new instance: 'instance'`); + validateInstanceEvent(event, `created new instance: 'instance'`, false); break; case 'recovered-instance': - validateInstanceEvent(event, `instance 'instance' has recovered`); + validateInstanceEvent(event, `instance 'instance' has recovered`, true); break; case 'active-instance': expect( @@ -299,7 +308,8 @@ export default function eventLogTests({ getService }: FtrProviderContext) { ).to.be(true); validateInstanceEvent( event, - `active instance: 'instance' in actionGroup(subgroup): 'default(${event?.kibana?.alerting?.action_subgroup})'` + `active instance: 'instance' in actionGroup(subgroup): 'default(${event?.kibana?.alerting?.action_subgroup})'`, + false ); break; // this will get triggered as we add new event actions @@ -308,7 +318,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { } } - function validateInstanceEvent(event: IValidatedEvent, subMessage: string) { + function validateInstanceEvent( + event: IValidatedEvent, + subMessage: string, + shouldHaveEventEnd: boolean + ) { validateEvent(event, { spaceId: Spaces.space1.id, savedObjects: [ @@ -317,6 +331,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { message: `test.patternFiring:${alertId}: 'abc' ${subMessage}`, instanceId: 'instance', actionGroupId: 'default', + shouldHaveEventEnd, }); } }); @@ -376,6 +391,7 @@ interface ValidateEventLogParams { savedObjects: SavedObject[]; outcome?: string; message: string; + shouldHaveEventEnd?: boolean; errorMessage?: string; status?: string; actionGroupId?: string; @@ -385,7 +401,7 @@ interface ValidateEventLogParams { export function validateEvent(event: IValidatedEvent, params: ValidateEventLogParams): void { const { spaceId, savedObjects, outcome, message, errorMessage } = params; - const { status, actionGroupId, instanceId, reason } = params; + const { status, actionGroupId, instanceId, reason, shouldHaveEventEnd } = params; if (status) { expect(event?.kibana?.alerting?.status).to.be(status); @@ -411,16 +427,23 @@ export function validateEvent(event: IValidatedEvent, params: ValidateEventLogPa if (duration !== undefined) { expect(typeof duration).to.be('number'); expect(eventStart).to.be.ok(); - expect(eventEnd).to.be.ok(); - const durationDiff = Math.abs( - Math.round(duration! / NANOS_IN_MILLIS) - (eventEnd - eventStart) - ); + if (shouldHaveEventEnd !== false) { + expect(eventEnd).to.be.ok(); + + const durationDiff = Math.abs( + Math.round(duration! / NANOS_IN_MILLIS) - (eventEnd - eventStart) + ); - // account for rounding errors - expect(durationDiff < 1).to.equal(true); - expect(eventStart <= eventEnd).to.equal(true); - expect(eventEnd <= dateNow).to.equal(true); + // account for rounding errors + expect(durationDiff < 1).to.equal(true); + expect(eventStart <= eventEnd).to.equal(true); + expect(eventEnd <= dateNow).to.equal(true); + } + + if (shouldHaveEventEnd === false) { + expect(eventEnd).not.to.be.ok(); + } } expect(event?.event?.outcome).to.equal(outcome); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log_alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log_alerts.ts new file mode 100644 index 00000000000000..2b92562b9bde5d --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log_alerts.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { Spaces } from '../../scenarios'; +import { getUrlPrefix, getTestAlertData, ObjectRemover, getEventLog } from '../../../common/lib'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { IValidatedEvent } from '../../../../../plugins/event_log/server'; + +// eslint-disable-next-line import/no-default-export +export default function eventLogAlertTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const retry = getService('retry'); + + describe('eventLog alerts', () => { + const objectRemover = new ObjectRemover(supertest); + + after(() => objectRemover.removeAll()); + + it('should generate expected alert events for normal operation', async () => { + // pattern of when the alert should fire + const pattern = { + instance: [false, true, true, false, false, true, true, true], + }; + + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + throttle: null, + params: { + pattern, + }, + actions: [], + }) + ); + + expect(response.status).to.eql(200); + const ruleId = response.body.id; + objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting'); + + // wait for the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: Spaces.space1.id, + type: 'alert', + id: ruleId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute', { gte: 9 }], + ['new-instance', { equal: 2 }], + ['active-instance', { gte: 4 }], + ['recovered-instance', { equal: 2 }], + ]), + }); + }); + + // filter out the execute event actions + const instanceEvents = events.filter( + (event: IValidatedEvent) => event?.event?.action !== 'execute' + ); + + const currentAlertSpan: { + alertId?: string; + start?: string; + durationToDate?: number; + } = {}; + for (let i = 0; i < instanceEvents.length; ++i) { + switch (instanceEvents[i]?.event?.action) { + case 'new-instance': + expect(instanceEvents[i]?.kibana?.alerting?.instance_id).to.equal('instance'); + // a new alert should generate a unique UUID for the duration of its activeness + expect(instanceEvents[i]?.event?.end).to.be(undefined); + + currentAlertSpan.alertId = instanceEvents[i]?.kibana?.alerting?.instance_id; + currentAlertSpan.start = instanceEvents[i]?.event?.start; + currentAlertSpan.durationToDate = instanceEvents[i]?.event?.duration; + break; + + case 'active-instance': + expect(instanceEvents[i]?.kibana?.alerting?.instance_id).to.equal('instance'); + expect(instanceEvents[i]?.event?.start).to.equal(currentAlertSpan.start); + expect(instanceEvents[i]?.event?.end).to.be(undefined); + + if (instanceEvents[i]?.event?.duration! !== 0) { + expect(instanceEvents[i]?.event?.duration! > currentAlertSpan.durationToDate!).to.be( + true + ); + } + currentAlertSpan.durationToDate = instanceEvents[i]?.event?.duration; + break; + + case 'recovered-instance': + expect(instanceEvents[i]?.kibana?.alerting?.instance_id).to.equal('instance'); + expect(instanceEvents[i]?.event?.start).to.equal(currentAlertSpan.start); + expect(instanceEvents[i]?.event?.end).not.to.be(undefined); + expect( + new Date(instanceEvents[i]?.event?.end!).valueOf() - + new Date(instanceEvents[i]?.event?.start!).valueOf() + ).to.equal(instanceEvents[i]?.event?.duration! / 1000 / 1000); + break; + } + } + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts index 9154c85af1bc7a..318dfdfe065dfe 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts @@ -76,7 +76,9 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont expect(alertInstances.length).to.eql(response.body.rule_type_state.runCount); alertInstances.forEach(([key, value], index) => { expect(key).to.eql(`instance-${index}`); - expect(value.state).to.eql({ instanceStateValue: true }); + expect(value.state.instanceStateValue).to.be(true); + expect(value.state.start).not.to.be(undefined); + expect(value.state.duration).not.to.be(undefined); }); }); @@ -131,7 +133,9 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont expect(alertInstances.length).to.eql(response.body.rule_type_state.runCount); alertInstances.forEach(([key, value], index) => { expect(key).to.eql(`instance-${index}`); - expect(value.state).to.eql({ instanceStateValue: true }); + expect(value.state.instanceStateValue).to.be(true); + expect(value.state.start).not.to.be(undefined); + expect(value.state.duration).not.to.be(undefined); }); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index e9aeec1717c968..5c3374a4d9c704 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -37,6 +37,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./builtin_alert_types')); loadTestFile(require.resolve('./mustache_templates.ts')); loadTestFile(require.resolve('./notify_when')); + loadTestFile(require.resolve('./event_log_alerts')); // note that this test will destroy existing spaces loadTestFile(require.resolve('./migrations')); diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts index 323b1b377e5555..0e92456b66c859 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts @@ -8,14 +8,16 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib'; +import { ConnectorTypes } from '../../../../../../plugins/cases/common/api'; import { - getConfigurationRequest, removeServerGeneratedPropertiesFromSavedObject, getConfigurationOutput, deleteConfiguration, createConfiguration, updateConfiguration, + getConfigurationRequest, + getConfiguration, } from '../../../../common/lib/utils'; import { secOnly, @@ -52,6 +54,39 @@ export default ({ getService }: FtrProviderContext): void => { expect(data).to.eql({ ...getConfigurationOutput(true), closure_type: 'close-by-pushing' }); }); + it('should update mapping when changing connector', async () => { + const configuration = await createConfiguration(supertest); + await updateConfiguration(supertest, configuration.id, { + connector: { + id: 'serviceNowITSM', + name: 'ServiceNow ITSM', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }, + version: configuration.version, + }); + const newConfiguration = await getConfiguration({ supertest }); + + expect(configuration.mappings).to.eql([]); + expect(newConfiguration[0].mappings).to.eql([ + { + action_type: 'overwrite', + source: 'title', + target: 'short_description', + }, + { + action_type: 'overwrite', + source: 'description', + target: 'description', + }, + { + action_type: 'append', + source: 'comments', + target: 'work_notes', + }, + ]); + }); + it('should not patch a configuration with unsupported connector type', async () => { const configuration = await createConfiguration(supertest); await updateConfiguration( diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/post_configure.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/post_configure.ts index 44ec24f688f201..fd9e8611db44ad 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/post_configure.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/configure/post_configure.ts @@ -60,20 +60,135 @@ export default ({ getService }: FtrProviderContext): void => { expect(configuration.length).to.be(1); }); - it('should return an error when failing to get mapping', async () => { + it('should return an empty mapping when they type is none', async () => { const postRes = await createConfiguration( supertest, getConfigurationRequest({ id: 'not-exists', name: 'not-exists', - type: ConnectorTypes.jira, + type: ConnectorTypes.none, }) ); - expect(postRes.error).to.not.be(null); expect(postRes.mappings).to.eql([]); }); + it('should return the correct mapping for Jira', async () => { + const postRes = await createConfiguration( + supertest, + getConfigurationRequest({ + id: 'jira', + name: 'Jira', + type: ConnectorTypes.jira, + }) + ); + + expect(postRes.mappings).to.eql([ + { + action_type: 'overwrite', + source: 'title', + target: 'summary', + }, + { + action_type: 'overwrite', + source: 'description', + target: 'description', + }, + { + action_type: 'append', + source: 'comments', + target: 'comments', + }, + ]); + }); + + it('should return the correct mapping for IBM Resilient', async () => { + const postRes = await createConfiguration( + supertest, + getConfigurationRequest({ + id: 'resilient', + name: 'Resilient', + type: ConnectorTypes.resilient, + }) + ); + + expect(postRes.mappings).to.eql([ + { + action_type: 'overwrite', + source: 'title', + target: 'name', + }, + { + action_type: 'overwrite', + source: 'description', + target: 'description', + }, + { + action_type: 'append', + source: 'comments', + target: 'comments', + }, + ]); + }); + + it('should return the correct mapping for ServiceNow ITSM', async () => { + const postRes = await createConfiguration( + supertest, + getConfigurationRequest({ + id: 'serviceNowITSM', + name: 'ServiceNow ITSM', + type: ConnectorTypes.serviceNowITSM, + }) + ); + + expect(postRes.mappings).to.eql([ + { + action_type: 'overwrite', + source: 'title', + target: 'short_description', + }, + { + action_type: 'overwrite', + source: 'description', + target: 'description', + }, + { + action_type: 'append', + source: 'comments', + target: 'work_notes', + }, + ]); + }); + + it('should return the correct mapping for ServiceNow SecOps', async () => { + const postRes = await createConfiguration( + supertest, + getConfigurationRequest({ + id: 'serviceNowSIR', + name: 'ServiceNow SecOps', + type: ConnectorTypes.serviceNowSIR, + }) + ); + + expect(postRes.mappings).to.eql([ + { + action_type: 'overwrite', + source: 'title', + target: 'short_description', + }, + { + action_type: 'overwrite', + source: 'description', + target: 'description', + }, + { + action_type: 'append', + source: 'comments', + target: 'work_notes', + }, + ]); + }); + it('should not create a configuration when missing connector.id', async () => { await createConfiguration( supertest, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts index 491f7bb9c417e5..2294d51537fb13 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts @@ -23,6 +23,7 @@ import { deleteListsIndex, importFile, } from '../../../lists_api_integration/utils'; +import { SIGNALS_TEMPLATE_VERSION } from '../../../../plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { @@ -127,7 +128,7 @@ export default ({ getService }: FtrProviderContext) => { host: { name: ['mothra'] }, event: { kind: 'signal' }, signal: { - _meta: { version: 35 }, + _meta: { version: SIGNALS_TEMPLATE_VERSION }, parents: [ { id: diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts index 755847e8b645d8..e6a835462619c1 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts @@ -27,6 +27,7 @@ import { import { getCreateThreatMatchRulesSchemaMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock'; import { getThreatMatchingSchemaPartialMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks'; +import { SIGNALS_TEMPLATE_VERSION } from '../../../../plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template'; const format = (value: unknown): string => JSON.stringify(value, null, 2); @@ -201,7 +202,7 @@ export default ({ getService }: FtrProviderContext) => { }, signal: { _meta: { - version: 35, + version: SIGNALS_TEMPLATE_VERSION, }, ancestors: [ { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts index 0c274a8f4678bb..d5f287e78268eb 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/finalize_signals_migrations.ts @@ -47,8 +47,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/99915 - describe.skip('Finalizing signals migrations', () => { + describe('Finalizing signals migrations', () => { let legacySignalsIndexName: string; let outdatedSignalsIndexName: string; let createdMigrations: CreateResponse[]; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index 0ba0749e75b08e..c3dbd24ae9f046 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -853,7 +853,7 @@ export default ({ getService }: FtrProviderContext) => { 'host.id': '8cc95778cce5407c809480e8e32ad76b', event: { kind: 'signal' }, signal: { - _meta: { version: 35 }, + _meta: { version: SIGNALS_TEMPLATE_VERSION }, parents: [ { depth: 0, @@ -1011,7 +1011,7 @@ export default ({ getService }: FtrProviderContext) => { 'host.id': '8cc95778cce5407c809480e8e32ad76b', event: { kind: 'signal' }, signal: { - _meta: { version: 35 }, + _meta: { version: SIGNALS_TEMPLATE_VERSION }, parents: [ { depth: 0, @@ -1101,7 +1101,7 @@ export default ({ getService }: FtrProviderContext) => { 'process.name': 'sshd', event: { kind: 'signal' }, signal: { - _meta: { version: 35 }, + _meta: { version: SIGNALS_TEMPLATE_VERSION }, parents: [ { depth: 0, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_signals_migration_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_signals_migration_status.ts index 6bb00c55f690e1..5c726a177bb601 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_signals_migration_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/get_signals_migration_status.ts @@ -19,8 +19,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/99915 - describe.skip('Signals migration status', () => { + describe('Signals migration status', () => { let legacySignalsIndexName: string; beforeEach(async () => { await createSignalsIndex(supertest); diff --git a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts index bbc7f5992506b3..18f4f6a38a7b10 100644 --- a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts @@ -35,14 +35,6 @@ export default function ({ getService }: FtrProviderContext) { await ml.securityUI.logout(); }); - it('should display the ML file data vis link on the Kibana home page', async () => { - await ml.testExecution.logTestStep('should load the Kibana home page'); - await ml.navigation.navigateToKibanaHome(); - - await ml.testExecution.logTestStep('should display the ML file data vis link'); - await ml.commonUI.assertKibanaHomeFileDataVisLinkExists(); - }); - it('should display the ML entry in Kibana app menu', async () => { await ml.testExecution.logTestStep('should open the Kibana app menu'); await ml.navigation.openKibanaNav(); diff --git a/x-pack/test/functional/apps/ml/permissions/no_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/no_ml_access.ts index 280801d1becb5b..431c0550b92718 100644 --- a/x-pack/test/functional/apps/ml/permissions/no_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/no_ml_access.ts @@ -41,16 +41,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.error.expectForbidden(); }); - it('should not display the ML file data vis link on the Kibana home page', async () => { - await ml.testExecution.logTestStep('should load the Kibana home page'); - await ml.navigation.navigateToKibanaHome(); - - await ml.testExecution.logTestStep('should not display the ML file data vis link'); - await ml.commonUI.assertKibanaHomeFileDataVisLinkNotExists(); - }); - it('should not display the ML entry in Kibana app menu', async () => { await ml.testExecution.logTestStep('should open the Kibana app menu'); + await ml.navigation.navigateToKibanaHome(); await ml.navigation.openKibanaNav(); await ml.testExecution.logTestStep('should not display the ML nav link'); diff --git a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts index dbf467e998f251..a53ed2fafe30c7 100644 --- a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts @@ -35,14 +35,6 @@ export default function ({ getService }: FtrProviderContext) { await ml.securityUI.logout(); }); - it('should not display the ML file data vis link on the Kibana home page', async () => { - await ml.testExecution.logTestStep('should load the Kibana home page'); - await ml.navigation.navigateToKibanaHome(); - - await ml.testExecution.logTestStep('should not display the ML file data vis link'); - await ml.commonUI.assertKibanaHomeFileDataVisLinkNotExists(); - }); - it('should display the ML entry in Kibana app menu', async () => { await ml.testExecution.logTestStep('should open the Kibana app menu'); await ml.navigation.openKibanaNav(); diff --git a/x-pack/test/functional/page_objects/reporting_page.ts b/x-pack/test/functional/page_objects/reporting_page.ts index e8999999ce50b6..742d41031004bc 100644 --- a/x-pack/test/functional/page_objects/reporting_page.ts +++ b/x-pack/test/functional/page_objects/reporting_page.ts @@ -91,7 +91,7 @@ export class ReportingPageObject extends FtrService { } async getQueueReportError() { - return await this.testSubjects.exists('queueReportError'); + return await this.testSubjects.exists('errorToastMessage'); } async getGenerateReportButton() { diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts index 31cf17575bdd9a..2de5d83714aeed 100644 --- a/x-pack/test/functional/services/ml/common_ui.ts +++ b/x-pack/test/functional/services/ml/common_ui.ts @@ -98,14 +98,6 @@ export function MachineLearningCommonUIProvider({ getService }: FtrProviderConte }); }, - async assertKibanaHomeFileDataVisLinkExists() { - await testSubjects.existOrFail('homeSynopsisLinkml_file_data_visualizer'); - }, - - async assertKibanaHomeFileDataVisLinkNotExists() { - await testSubjects.missingOrFail('homeSynopsisLinkml_file_data_visualizer'); - }, - async assertRadioGroupValue(testSubject: string, expectedValue: string) { const assertRadioGroupValue = await testSubjects.find(testSubject); const input = await assertRadioGroupValue.findByCssSelector(':checked'); diff --git a/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts index 7fdfbb45269c30..aff1402f5567ee 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts @@ -57,14 +57,6 @@ export default function ({ getService }: FtrProviderContext) { await ml.securityUI.logout(); }); - it('should display the ML file data vis link on the Kibana home page', async () => { - await ml.testExecution.logTestStep('should load the Kibana home page'); - await ml.navigation.navigateToKibanaHome(); - - await ml.testExecution.logTestStep('should display the ML file data vis link'); - await ml.commonUI.assertKibanaHomeFileDataVisLinkExists(); - }); - it('should display the ML entry in Kibana app menu', async () => { await ml.testExecution.logTestStep('should open the Kibana app menu'); await ml.navigation.openKibanaNav(); diff --git a/x-pack/test/functional_basic/apps/ml/permissions/no_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/no_ml_access.ts index 91a37d0d98cda3..8d3aa3c6b6ada7 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/no_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/no_ml_access.ts @@ -39,16 +39,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.error.expectForbidden(); }); - it('should not display the ML file data vis link on the Kibana home page', async () => { - await ml.testExecution.logTestStep('should load the Kibana home page'); - await ml.navigation.navigateToKibanaHome(); - - await ml.testExecution.logTestStep('should not display the ML file data vis link'); - await ml.commonUI.assertKibanaHomeFileDataVisLinkNotExists(); - }); - it('should not display the ML entry in Kibana app menu', async () => { await ml.testExecution.logTestStep('should open the Kibana app menu'); + await ml.navigation.navigateToKibanaHome(); await ml.navigation.openKibanaNav(); await ml.testExecution.logTestStep('should not display the ML nav link'); diff --git a/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts index e58e46e985fd9a..2e5216d7225186 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts @@ -58,14 +58,6 @@ export default function ({ getService }: FtrProviderContext) { await ml.securityUI.logout(); }); - it('should not display the ML file data vis link on the Kibana home page', async () => { - await ml.testExecution.logTestStep('should load the Kibana home page'); - await ml.navigation.navigateToKibanaHome(); - - await ml.testExecution.logTestStep('should not display the ML file data vis link'); - await ml.commonUI.assertKibanaHomeFileDataVisLinkNotExists(); - }); - it('should display the ML entry in Kibana app menu', async () => { await ml.testExecution.logTestStep('should open the Kibana app menu'); await ml.navigation.openKibanaNav(); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 34e08ad257f849..8cf92f77d939c7 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -136,7 +136,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('renders the alert details', async () => { const headingText = await pageObjects.alertDetailsUI.getHeadingText(); - expect(headingText).to.be(`test-alert-${testRunUuid}`); + expect(headingText.includes(`test-alert-${testRunUuid}`)).to.be(true); const alertType = await pageObjects.alertDetailsUI.getAlertType(); expect(alertType).to.be(`Always Firing`); @@ -279,7 +279,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(toastTitle).to.eql(`Updated '${updatedAlertName}'`); const headingText = await pageObjects.alertDetailsUI.getHeadingText(); - expect(headingText).to.be(updatedAlertName); + expect(headingText.includes(updatedAlertName)).to.be(true); }); it('should reset alert when canceling an edit', async () => { diff --git a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js index 83a759abe337d7..00fce236b5d172 100644 --- a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js +++ b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js @@ -227,7 +227,6 @@ export default ({ getService, getPageObjects }) => { url: process.env.TEST_KIBANA_URLDATA, certificateAuthorities: config.get('servers.kibana.certificateAuthorities'), uiSettingDefaults: kibanaServer.uiSettings, - importExportDir: config.get('kbnArchiver.directory'), }); const esArchiver = new EsArchiver({ diff --git a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js index d86debceb0d1c5..2b25d5ffea6e1a 100644 --- a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js +++ b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js @@ -15,7 +15,7 @@ export default function ({ getService, getPageObjects, updateBaselines }) { describe('check metricbeat Dashboard', function () { before(async function () { - await esArchiver.load('metricbeat'); + await esArchiver.load('../integration-test/test/es_archives/metricbeat'); // this navigateToActualURL takes the place of navigating to the dashboard landing page, // filtering on the dashboard name, selecting it, setting the timepicker, and going to full screen @@ -44,6 +44,10 @@ export default function ({ getService, getPageObjects, updateBaselines }) { await browser.setScreenshotSize(1000, 1000); }); + after(async function () { + await esArchiver.unload('../integration-test/test/es_archives/metricbeat'); + }); + it('[Metricbeat System] Overview ECS should match snapshot', async function () { try { const percentDifference = await screenshot.compareAgainstBaseline( diff --git a/yarn.lock b/yarn.lock index 3afbffbac6eb1b..bd34c0c4cb4b85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2759,7 +2759,7 @@ version "0.0.0" uid "" -"@kbn/server-route-repository@link:packages/kbn-server-route-repository": +"@kbn/server-route-repository@link:bazel-bin/packages/kbn-server-route-repository": version "0.0.0" uid "" @@ -29273,16 +29273,16 @@ write@1.0.3: mkdirp "^0.5.1" ws@^6.1.2, ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" ws@^7.2.3: - version "7.3.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" - integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== + version "7.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.2.tgz#782100048e54eb36fe9843363ab1c68672b261dd" + integrity sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA== x-is-function@^1.0.4: version "1.0.4"