From 68a2fdc7c3fb741c3dd6839b6e820be244533590 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 16 Oct 2019 08:08:08 +0100 Subject: [PATCH 1/7] [ML] Fixing overview page max anomaly score (#48110) * [ML] Fixing overview page max anomaly score * removing unnecessary copy of maxScore --- .../anomaly_detection_panel/anomaly_detection_panel.tsx | 6 +++--- .../ml/server/models/results_service/results_service.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 3517c86a6109fc..90b5be2c254064 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -98,13 +98,13 @@ export const AnomalyDetectionPanel: FC = () => { return ml.results.getMaxAnomalyScore(group.jobIds, twentyFourHoursAgo, latestTimestamp); }); - const results = await Promise.all(promises.map(p => p.catch(() => undefined))); + const results = await Promise.all(promises); const tempGroups = { ...groupsObject }; // Check results for each group's promise index and update state Object.keys(scores).forEach(groupId => { const resultsIndex = scores[groupId] && scores[groupId].index; - scores[groupId] = resultsIndex !== undefined && results[resultsIndex]; - tempGroups[groupId].max_anomaly_score = resultsIndex !== undefined && results[resultsIndex]; + const { maxScore } = resultsIndex !== undefined && results[resultsIndex]; + tempGroups[groupId].max_anomaly_score = maxScore || undefined; }); setGroups(tempGroups); diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js b/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js index 3fd20308b2f9be..9501389af195d5 100644 --- a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js +++ b/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js @@ -264,7 +264,7 @@ export function resultsServiceProvider(callWithRequest) { const resp = await callWithRequest('search', query); const maxScore = _.get(resp, ['aggregations', 'max_score', 'value'], null); - return maxScore; + return { maxScore }; } // Obtains the latest bucket result timestamp by job ID. From a42a767285f3991cff5be12c18f3c8b03200fb73 Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Wed, 16 Oct 2019 10:24:20 +0300 Subject: [PATCH 2/7] [Telemetry] Move to OSS (#45769) * update paths and licenses * localization collector in oss * node-crypto typings in oss * update telemetry i18n labels * update translation rc files * remove duplicate components in home for telemetry * update tests * finalize collection + move csp collector to oss * self review * use apm instead of beats * xpack collection * fix collection tests * remove space specific settings * mock npSetup and npStart * disable banner in tests * remove commented mock * monitoring np telemetry fixes * replace telemetryOptedIn in oss instead of xpack_main * fix telemetry OptIn test --- .i18nrc.json | 1 + docs/settings/monitoring-settings.asciidoc | 2 +- .../resources/bin/kibana-docker | 2 +- src/legacy/core_plugins/kibana/index.js | 2 + .../telemetry_opt_in_card.tsx | 2 +- .../kibana/public/home/kibana_services.js | 2 +- .../csp_collector.test.ts | 39 +++-- .../lib/csp_usage_collector/csp_collector.ts | 49 ++++++ .../server/lib/csp_usage_collector/index.ts | 20 +++ .../telemetry/common/constants.ts | 66 ++++++++ .../get_xpack_config_with_deprecated.ts | 41 +++++ .../legacy/core_plugins}/telemetry/index.ts | 49 ++++-- .../core_plugins}/telemetry/mappings.json | 0 .../core_plugins/telemetry/package.json | 4 + .../opt_in_details_component.test.tsx.snap | 4 +- .../__snapshots__/telemetry_form.test.js.snap | 6 +- .../telemetry/public/components/index.ts | 24 +++ .../components/opt_in_banner_component.tsx | 31 ++-- .../opt_in_details_component.test.tsx | 34 ++++ .../components}/opt_in_details_component.tsx | 12 +- .../public/components}/opt_in_message.tsx | 36 ++--- .../public/components/telemetry_form.js | 38 +++-- .../public/components/telemetry_form.test.js | 21 ++- .../public/hacks/__tests__/fetch_telemetry.js | 19 ++- .../public/hacks/__tests__/telemetry.js | 28 ++++ .../telemetry/public/hacks/fetch_telemetry.js | 39 +++++ .../telemetry/public/hacks/telemetry.js | 19 ++- .../telemetry/public/hacks/telemetry.test.js | 19 ++- .../telemetry/public/hacks/telemetry_init.ts | 45 ++++++ .../public/hacks/telemetry_opt_in.js | 24 +++ .../hacks/welcome_banner/click_banner.js | 25 ++- .../hacks/welcome_banner/click_banner.test.js | 19 ++- .../welcome_banner/handle_old_settings.js | 19 ++- .../handle_old_settings.test.js | 19 ++- .../public/hacks/welcome_banner/index.js | 20 +++ .../hacks/welcome_banner/inject_banner.js | 25 ++- .../hacks/welcome_banner/render_banner.js | 19 ++- .../welcome_banner/render_banner.test.js | 44 +++++ .../welcome_banner/should_show_banner.js | 33 ++++ .../welcome_banner/should_show_banner.test.js | 21 ++- .../telemetry/public/services/index.ts | 21 +++ .../telemetry/public/services/path.ts | 25 +++ .../public/services/telemetry_opt_in.test.js | 20 ++- .../services/telemetry_opt_in.test.mocks.js | 50 ++++++ .../public/services/telemetry_opt_in.ts} | 28 ++-- .../public/views/management/index.js | 20 +++ .../public/views/management/management.js | 42 +++++ .../telemetry/server/collection_manager.ts | 49 ++++++ .../collectors/encryption/encrypt.test.ts | 19 ++- .../server/collectors/encryption/encrypt.ts | 32 ++++ .../server/collectors/encryption/index.ts | 20 +++ .../collectors/encryption/telemetry_jwks.ts | 19 ++- .../telemetry/server/collectors/index.ts | 23 +++ .../localization/file_integrity.test.mocks.ts | 41 +++++ .../localization/file_integrity.test.ts | 19 ++- .../collectors/localization/file_integrity.ts | 19 ++- .../server/collectors/localization/index.ts | 20 +++ .../telemetry_localization_collector.test.ts | 19 ++- .../telemetry_localization_collector.ts | 19 ++- .../server/collectors/ui_metric/index.ts | 20 +++ .../telemetry_ui_metric_collector.ts | 19 ++- .../usage/ensure_deep_object.test.ts | 19 ++- .../collectors/usage/ensure_deep_object.ts | 19 ++- .../server/collectors/usage/index.ts | 20 +++ .../usage/telemetry_usage_collector.test.ts | 23 ++- .../usage/telemetry_usage_collector.ts | 19 ++- .../telemetry/server/get_telemetry_opt_in.ts | 19 ++- .../core_plugins/telemetry/server/index.ts | 29 ++++ .../core_plugins/telemetry/server/plugin.ts | 30 ++++ .../telemetry/server/routes/index.ts | 27 ++++ .../telemetry/server/routes/opt_in.ts | 19 ++- .../server/routes/telemetry_stats.ts | 28 +++- .../__tests__/get_cluster_info.js | 41 +++++ .../__tests__/get_cluster_stats.js | 49 ++++++ .../__tests__/get_local_stats.js | 52 +++--- .../server/telemetry_collection/constants.ts | 23 +++ .../telemetry_collection/get_cluster_info.js | 30 ++++ .../telemetry_collection/get_cluster_stats.js | 34 ++++ .../server/telemetry_collection/get_kibana.js | 58 +++++++ .../telemetry_collection}/get_local_stats.js | 44 ++--- .../server/telemetry_collection/get_stats.ts | 45 ++++++ .../server/telemetry_collection/index.ts | 24 +++ test/common/config.js | 1 + typings/elastic__node_crypto.d.ts | 20 +++ x-pack/.i18nrc.json | 1 - x-pack/index.js | 2 - .../public/lib/telemetry.js | 8 +- .../plugins/monitoring/common/constants.js | 49 +++++- .../__tests__/bulk_uploader.js | 3 - .../server/kibana_monitoring/bulk_uploader.js | 6 +- .../lib/send_bulk_payload.js | 3 +- .../plugins/monitoring/server/plugin.js | 6 +- .../__tests__/create_query.js | 0 .../fixtures/beats_stats_results.json | 0 .../__tests__/get_all_stats.js | 6 +- .../__tests__/get_beats_stats.js | 0 .../__tests__/get_cluster_uuids.js | 0 .../__tests__/get_es_stats.js | 0 .../__tests__/get_high_level_stats.js | 0 .../__tests__/get_kibana_stats.js | 0 .../telemetry_collection}/create_query.js | 0 .../telemetry_collection}/get_all_stats.js | 28 ++-- .../telemetry_collection}/get_beats_stats.js | 10 +- .../get_cluster_uuids.js | 2 +- .../telemetry_collection}/get_es_stats.js | 2 +- .../get_high_level_stats.js | 12 +- .../telemetry_collection}/get_kibana_stats.js | 2 +- .../get_stats_with_monitoring.ts} | 25 ++- .../server/telemetry_collection}/index.ts | 2 +- .../lib/collectors/csp/csp_collector.ts | 39 ----- .../server/lib/collectors/index.ts | 2 - .../plugins/telemetry/common/constants.ts | 89 ----------- .../get_xpack_config_with_deprecated.ts | 15 -- .../telemetry/public/components/index.ts | 11 -- .../opt_in_details_component.test.tsx | 21 --- .../components/opt_in_details_component.tsx | 150 ------------------ .../public/components/opt_in_message.tsx | 97 ----------- .../public/hacks/__tests__/telemetry.js | 15 -- .../telemetry/public/hacks/fetch_telemetry.js | 26 --- .../telemetry/public/hacks/telemetry_init.ts | 34 ---- .../public/hacks/telemetry_opt_in.js | 11 -- .../public/hacks/welcome_banner/index.js | 7 - .../welcome_banner/render_banner.test.js | 30 ---- .../welcome_banner/should_show_banner.js | 20 --- .../services/telemetry_opt_in.test.mocks.js | 27 ---- .../public/services/telemetry_opt_in.ts | 8 - .../public/views/management/index.js | 7 - .../public/views/management/management.js | 38 ----- .../server/collectors/encryption/encrypt.ts | 19 --- .../telemetry/server/collectors/index.ts | 15 -- .../local/__tests__/get_cluster_info.js | 28 ---- .../local/__tests__/get_cluster_stats.js | 36 ----- .../collectors/local/get_cluster_info.js | 17 -- .../collectors/local/get_cluster_stats.js | 21 --- .../server/collectors/local/get_kibana.js | 49 ------ .../server/collectors/local/index.js | 7 - .../localization/file_integrity.test.mocks.ts | 28 ---- .../server/collectors/localization/index.ts | 7 - .../server/collectors/monitoring/index.js | 7 - .../server/collectors/ui_metric/index.ts | 7 - .../server/collectors/usage/index.ts | 7 - .../legacy/plugins/telemetry/server/index.ts | 14 -- .../legacy/plugins/telemetry/server/plugin.ts | 14 -- .../plugins/telemetry/server/routes/index.ts | 14 -- x-pack/legacy/plugins/xpack_main/index.js | 34 ++-- .../lib/__tests__/replace_injected_vars.js | 18 +-- .../server/lib/replace_injected_vars.js | 2 - .../__tests__/get_xpack.js | 0 .../server/telemetry_collection/constants.ts} | 0 .../get_stats_with_xpack.ts | 42 +++++ .../server/telemetry_collection}/get_xpack.js | 1 - .../server/telemetry_collection}/index.ts | 2 +- .../translations/translations/ja-JP.json | 95 +++-------- .../translations/translations/zh-CN.json | 95 +++-------- x-pack/test/functional/config.js | 2 +- 155 files changed, 2128 insertions(+), 1406 deletions(-) rename {x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp => src/legacy/core_plugins/kibana/server/lib/csp_usage_collector}/csp_collector.test.ts (67%) create mode 100644 src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts create mode 100644 src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/index.ts create mode 100644 src/legacy/core_plugins/telemetry/common/constants.ts create mode 100644 src/legacy/core_plugins/telemetry/common/get_xpack_config_with_deprecated.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/index.ts (61%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/mappings.json (100%) create mode 100644 src/legacy/core_plugins/telemetry/package.json rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap (90%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap (89%) create mode 100644 src/legacy/core_plugins/telemetry/public/components/index.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/components/opt_in_banner_component.tsx (54%) create mode 100644 src/legacy/core_plugins/telemetry/public/components/opt_in_details_component.test.tsx rename src/legacy/core_plugins/{kibana/public/home/components/telemetry_opt_in => telemetry/public/components}/opt_in_details_component.tsx (90%) rename src/legacy/core_plugins/{kibana/public/home/components/telemetry_opt_in => telemetry/public/components}/opt_in_message.tsx (68%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/components/telemetry_form.js (77%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/components/telemetry_form.test.js (52%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/__tests__/fetch_telemetry.js (52%) create mode 100644 src/legacy/core_plugins/telemetry/public/hacks/__tests__/telemetry.js create mode 100644 src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry.js rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/telemetry.js (79%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/telemetry.test.js (92%) create mode 100644 src/legacy/core_plugins/telemetry/public/hacks/telemetry_init.ts create mode 100644 src/legacy/core_plugins/telemetry/public/hacks/telemetry_opt_in.js rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/welcome_banner/click_banner.js (60%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/welcome_banner/click_banner.test.js (80%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/welcome_banner/handle_old_settings.js (66%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js (89%) create mode 100644 src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/index.js rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/welcome_banner/inject_banner.js (64%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/welcome_banner/render_banner.js (51%) create mode 100644 src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js create mode 100644 src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/should_show_banner.js rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/hacks/welcome_banner/should_show_banner.test.js (73%) create mode 100644 src/legacy/core_plugins/telemetry/public/services/index.ts create mode 100644 src/legacy/core_plugins/telemetry/public/services/path.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/public/services/telemetry_opt_in.test.js (75%) create mode 100644 src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js rename src/legacy/core_plugins/{kibana/public/home/telemetry_opt_in.js => telemetry/public/services/telemetry_opt_in.ts} (75%) create mode 100644 src/legacy/core_plugins/telemetry/public/views/management/index.js create mode 100644 src/legacy/core_plugins/telemetry/public/views/management/management.js create mode 100644 src/legacy/core_plugins/telemetry/server/collection_manager.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/encryption/encrypt.test.ts (53%) create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/encryption/encrypt.ts create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/encryption/index.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/encryption/telemetry_jwks.ts (57%) create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/index.ts create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/localization/file_integrity.test.ts (56%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/localization/file_integrity.ts (52%) create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts (51%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/localization/telemetry_localization_collector.ts (64%) create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts (61%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/usage/ensure_deep_object.test.ts (72%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/usage/ensure_deep_object.ts (63%) create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts (80%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/collectors/usage/telemetry_usage_collector.ts (77%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/get_telemetry_opt_in.ts (52%) create mode 100644 src/legacy/core_plugins/telemetry/server/index.ts create mode 100644 src/legacy/core_plugins/telemetry/server/plugin.ts create mode 100644 src/legacy/core_plugins/telemetry/server/routes/index.ts rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/routes/opt_in.ts (52%) rename {x-pack/legacy/plugins => src/legacy/core_plugins}/telemetry/server/routes/telemetry_stats.ts (54%) create mode 100644 src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_info.js create mode 100644 src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js rename {x-pack/legacy/plugins/telemetry/server/collectors/local => src/legacy/core_plugins/telemetry/server/telemetry_collection}/__tests__/get_local_stats.js (86%) create mode 100644 src/legacy/core_plugins/telemetry/server/telemetry_collection/constants.ts create mode 100644 src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.js create mode 100644 src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.js create mode 100644 src/legacy/core_plugins/telemetry/server/telemetry_collection/get_kibana.js rename {x-pack/legacy/plugins/telemetry/server/collectors/local => src/legacy/core_plugins/telemetry/server/telemetry_collection}/get_local_stats.js (62%) create mode 100644 src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts create mode 100644 src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts create mode 100644 typings/elastic__node_crypto.d.ts rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/__tests__/create_query.js (100%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/__tests__/fixtures/beats_stats_results.json (100%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/__tests__/get_all_stats.js (95%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/__tests__/get_beats_stats.js (100%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/__tests__/get_cluster_uuids.js (100%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/__tests__/get_es_stats.js (100%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/__tests__/get_high_level_stats.js (100%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/__tests__/get_kibana_stats.js (100%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/create_query.js (100%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/get_all_stats.js (84%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/get_beats_stats.js (97%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/get_cluster_uuids.js (96%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/get_es_stats.js (96%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/get_high_level_stats.js (97%) rename x-pack/legacy/plugins/{telemetry/server/collectors/monitoring => monitoring/server/telemetry_collection}/get_kibana_stats.js (98%) rename x-pack/legacy/plugins/{telemetry/server/collectors/get_stats.ts => monitoring/server/telemetry_collection/get_stats_with_monitoring.ts} (58%) rename x-pack/legacy/plugins/{telemetry/server/collectors/encryption => monitoring/server/telemetry_collection}/index.ts (77%) delete mode 100644 x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/csp_collector.ts delete mode 100644 x-pack/legacy/plugins/telemetry/common/constants.ts delete mode 100644 x-pack/legacy/plugins/telemetry/common/get_xpack_config_with_deprecated.ts delete mode 100644 x-pack/legacy/plugins/telemetry/public/components/index.ts delete mode 100644 x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx delete mode 100644 x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx delete mode 100644 x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx delete mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/__tests__/telemetry.js delete mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/fetch_telemetry.js delete mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/telemetry_init.ts delete mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/telemetry_opt_in.js delete mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/index.js delete mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js delete mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.js delete mode 100644 x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js delete mode 100644 x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts delete mode 100644 x-pack/legacy/plugins/telemetry/public/views/management/index.js delete mode 100644 x-pack/legacy/plugins/telemetry/public/views/management/management.js delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/encryption/encrypt.ts delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/index.ts delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_cluster_info.js delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_cluster_stats.js delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/local/get_cluster_info.js delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/local/get_cluster_stats.js delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/local/get_kibana.js delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/local/index.js delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/localization/index.ts delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/monitoring/index.js delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/index.ts delete mode 100644 x-pack/legacy/plugins/telemetry/server/collectors/usage/index.ts delete mode 100644 x-pack/legacy/plugins/telemetry/server/index.ts delete mode 100644 x-pack/legacy/plugins/telemetry/server/plugin.ts delete mode 100644 x-pack/legacy/plugins/telemetry/server/routes/index.ts rename x-pack/legacy/plugins/{telemetry/server/collectors/local => xpack_main/server/telemetry_collection}/__tests__/get_xpack.js (100%) rename x-pack/legacy/plugins/{telemetry/server/collectors/local/constants.js => xpack_main/server/telemetry_collection/constants.ts} (100%) create mode 100644 x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.ts rename x-pack/legacy/plugins/{telemetry/server/collectors/local => xpack_main/server/telemetry_collection}/get_xpack.js (99%) rename x-pack/legacy/plugins/{oss_telemetry/server/lib/collectors/csp => xpack_main/server/telemetry_collection}/index.ts (80%) diff --git a/.i18nrc.json b/.i18nrc.json index 4a43d0a87a036d..b8bd8729139c0f 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -28,6 +28,7 @@ "kbnESQuery": "packages/kbn-es-query", "inspector": "src/plugins/inspector", "kibana-react": "src/plugins/kibana_react", + "telemetry": "src/legacy/core_plugins/telemetry", "esUi": "src/plugins/es_ui_shared", "uiActions": "src/plugins/ui_actions" }, diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 97fb891c95bdba..f08ae8e942f4aa 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -54,7 +54,7 @@ Specifies the password that {kib} uses for authentication when it retrieves data from the monitoring cluster. If not set, {kib} uses the value of the `elasticsearch.password` setting. -`xpack.telemetry.enabled`:: +`telemetry.enabled`:: Set to `true` (default) to send cluster statistics to Elastic. Reporting your cluster statistics helps us improve your user experience. Your data is never shared with anyone. Set to `false` to disable statistics reporting from any diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 60f94f7f380564..0926ef365c894c 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -180,7 +180,7 @@ kibana_vars=( xpack.security.encryptionKey xpack.security.secureCookies xpack.security.sessionTimeout - xpack.telemetry.enabled + telemetry.enabled ) longopts='' diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index bb041924215ddd..8b12f71660844e 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -38,6 +38,7 @@ import * as systemApi from './server/lib/system_api'; import mappings from './mappings.json'; import { getUiSettingDefaults } from './ui_setting_defaults'; import { makeKQLUsageCollector } from './server/lib/kql_usage_collector'; +import { registerCspCollector } from './server/lib/csp_usage_collector'; import { injectVars } from './inject_vars'; import { i18n } from '@kbn/i18n'; @@ -344,6 +345,7 @@ export default function (kibana) { registerFieldFormats(server); registerTutorials(server); makeKQLUsageCollector(server); + registerCspCollector(server); server.expose('systemApi', systemApi); server.injectUiAppVars('kibana', () => injectVars(server)); }, diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx index 5fa842291b028e..e0f37277dd2269 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx +++ b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx @@ -24,7 +24,7 @@ import { EuiCard, EuiButton, } from '@elastic/eui'; -import { OptInMessage } from './opt_in_message'; +import { OptInMessage } from '../../../../../telemetry/public/components/opt_in_message'; export interface Props { urlBasePath: string; diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.js b/src/legacy/core_plugins/kibana/public/home/kibana_services.js index c5480e16491a96..792c5e09435a49 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.js +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.js @@ -20,7 +20,7 @@ import { uiModules } from 'ui/modules'; import { npStart } from 'ui/new_platform'; import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { TelemetryOptInProvider } from './telemetry_opt_in'; +import { TelemetryOptInProvider } from '../../../telemetry/public/services'; export let indexPatternService; export let shouldShowTelemetryOptIn; diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/csp_collector.test.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts similarity index 67% rename from x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/csp_collector.test.ts rename to src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts index caea9fd49fed6e..36e7dc81d47084 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/csp_collector.test.ts +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts @@ -1,18 +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; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import sinon from 'sinon'; -import { DEFAULT_CSP_RULES } from '../../../../../../../../src/legacy/server/csp'; -import { - getMockCallWithInternal, - getMockKbnServer, - getMockTaskFetch, -} from '../../../../test_utils'; +import { Server } from 'hapi'; +import { DEFAULT_CSP_RULES } from '../../../../../server/csp'; import { createCspCollector } from './csp_collector'; +interface MockConfig { + get: (x: string) => any; +} + +const getMockKbnServer = (mockConfig: MockConfig) => ({ + config: () => mockConfig, +}); + test('fetches whether strict mode is enabled', async () => { const { collector, mockConfig } = setupCollector(); @@ -72,7 +89,7 @@ function setupCollector() { mockConfig.get.withArgs('csp.strict').returns(true); mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(true); - const mockKbnServer = getMockKbnServer(getMockCallWithInternal(), getMockTaskFetch(), mockConfig); + const mockKbnServer = getMockKbnServer(mockConfig); - return { mockConfig, collector: createCspCollector(mockKbnServer) }; + return { mockConfig, collector: createCspCollector(mockKbnServer as Server) }; } diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts new file mode 100644 index 00000000000000..3ff39c1a4eb8c4 --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Server } from 'hapi'; +import { createCSPRuleString, DEFAULT_CSP_RULES } from '../../../../../server/csp'; + +export function createCspCollector(server: Server) { + return { + type: 'csp', + isReady: () => true, + async fetch() { + const config = server.config(); + + // It's important that we do not send the value of csp.rules here as it + // can be customized with values that can be identifiable to given + // installs, such as URLs + const defaultRulesString = createCSPRuleString([...DEFAULT_CSP_RULES]); + const actualRulesString = createCSPRuleString(config.get('csp.rules')); + + return { + strict: config.get('csp.strict'), + warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'), + rulesChangedFromDefault: defaultRulesString !== actualRulesString, + }; + }, + }; +} + +export function registerCspCollector(server: Server): void { + const { collectorSet } = server.usage; + const collector = collectorSet.makeUsageCollector(createCspCollector(server)); + collectorSet.register(collector); +} diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/index.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/index.ts new file mode 100644 index 00000000000000..517b21d3d73ff8 --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { registerCspCollector } from './csp_collector'; diff --git a/src/legacy/core_plugins/telemetry/common/constants.ts b/src/legacy/core_plugins/telemetry/common/constants.ts new file mode 100644 index 00000000000000..ab1397b2cc232e --- /dev/null +++ b/src/legacy/core_plugins/telemetry/common/constants.ts @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +/* + * config options opt into telemetry + * @type {string} + */ +export const CONFIG_TELEMETRY = 'telemetry:optIn'; +/* + * config description for opting into telemetry + * @type {string} + */ +export const getConfigTelemetryDesc = () => { + return i18n.translate('telemetry.telemetryConfigDescription', { + defaultMessage: + 'Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.', + }); +}; + +/** + * The amount of time, in milliseconds, to wait between reports when enabled. + * + * Currently 24 hours. + * @type {Number} + */ +export const REPORT_INTERVAL_MS = 86400000; + +/* + * Key for the localStorage service + */ +export const LOCALSTORAGE_KEY = 'telemetry.data'; + +/** + * Link to the Elastic Telemetry privacy statement. + */ +export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/telemetry-privacy-statement`; + +/** + * The type name used within the Monitoring index to publish localization stats. + * @type {string} + */ +export const KIBANA_LOCALIZATION_STATS_TYPE = 'localization'; + +/** + * UI metric usage type + * @type {string} + */ +export const UI_METRIC_USAGE_TYPE = 'ui_metric'; diff --git a/src/legacy/core_plugins/telemetry/common/get_xpack_config_with_deprecated.ts b/src/legacy/core_plugins/telemetry/common/get_xpack_config_with_deprecated.ts new file mode 100644 index 00000000000000..3f7a8d3410993b --- /dev/null +++ b/src/legacy/core_plugins/telemetry/common/get_xpack_config_with_deprecated.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { KibanaConfig } from 'src/legacy/server/kbn_server'; + +export function getXpackConfigWithDeprecated(config: KibanaConfig, configPath: string) { + try { + const deprecatedXpackmainConfig = config.get(`xpack.xpack_main.${configPath}`); + if (typeof deprecatedXpackmainConfig !== 'undefined') { + return deprecatedXpackmainConfig; + } + } catch (err) { + // swallow error + } + try { + const deprecatedXpackConfig = config.get(`xpack.${configPath}`); + if (typeof deprecatedXpackConfig !== 'undefined') { + return deprecatedXpackConfig; + } + } catch (err) { + // swallow error + } + + return config.get(configPath); +} diff --git a/x-pack/legacy/plugins/telemetry/index.ts b/src/legacy/core_plugins/telemetry/index.ts similarity index 61% rename from x-pack/legacy/plugins/telemetry/index.ts rename to src/legacy/core_plugins/telemetry/index.ts index 71e80cb2bd02f2..3271373449eb3a 100644 --- a/x-pack/legacy/plugins/telemetry/index.ts +++ b/src/legacy/core_plugins/telemetry/index.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { resolve } from 'path'; @@ -9,10 +22,11 @@ import JoiNamespace from 'joi'; import { Server } from 'hapi'; import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { i18n } from '@kbn/i18n'; +// @ts-ignore import mappings from './mappings.json'; -import { CONFIG_TELEMETRY, getConfigTelemetryDesc, REPORT_INTERVAL_MS } from './common/constants'; +import { CONFIG_TELEMETRY, getConfigTelemetryDesc } from './common/constants'; import { getXpackConfigWithDeprecated } from './common/get_xpack_config_with_deprecated'; -import { telemetryPlugin } from './server'; +import { telemetryPlugin, getTelemetryOptIn } from './server'; import { createLocalizationUsageCollector, @@ -22,10 +36,10 @@ import { const ENDPOINT_VERSION = 'v2'; -export const telemetry = (kibana: any) => { +const telemetry = (kibana: any) => { return new kibana.Plugin({ id: 'telemetry', - configPrefix: 'xpack.telemetry', + configPrefix: 'telemetry', publicDir: resolve(__dirname, 'public'), require: ['elasticsearch'], config(Joi: typeof JoiNamespace) { @@ -49,7 +63,7 @@ export const telemetry = (kibana: any) => { managementSections: ['plugins/telemetry/views/management'], uiSettingDefaults: { [CONFIG_TELEMETRY]: { - name: i18n.translate('xpack.telemetry.telemetryConfigTitle', { + name: i18n.translate('telemetry.telemetryConfigTitle', { defaultMessage: 'Telemetry opt-in', }), description: getConfigTelemetryDesc(), @@ -62,11 +76,20 @@ export const telemetry = (kibana: any) => { isNamespaceAgnostic: true, }, }, + async replaceInjectedVars(originalInjectedVars: any, request: any) { + const telemetryOptedIn = await getTelemetryOptIn(request); + + return { + ...originalInjectedVars, + telemetryOptedIn, + }; + }, injectDefaultVars(server: Server) { const config = server.config(); return { + telemetryEnabled: getXpackConfigWithDeprecated(config, 'telemetry.enabled'), telemetryUrl: getXpackConfigWithDeprecated(config, 'telemetry.url'), - telemetryBanner: config.get('xpack.telemetry.banner'), + telemetryBanner: getXpackConfigWithDeprecated(config, 'telemetry.banner'), telemetryOptedIn: null, }; }, @@ -75,8 +98,10 @@ export const telemetry = (kibana: any) => { }, init(server: Server) { const initializerContext = {} as PluginInitializerContext; + const coreSetup = ({ http: { server }, + log: server.log, } as any) as CoreSetup; telemetryPlugin(initializerContext).setup(coreSetup); @@ -85,9 +110,9 @@ export const telemetry = (kibana: any) => { server.usage.collectorSet.register(createLocalizationUsageCollector(server)); server.usage.collectorSet.register(createTelemetryUsageCollector(server)); server.usage.collectorSet.register(createUiMetricUsageCollector(server)); - - // expose - server.expose('telemetryCollectionInterval', REPORT_INTERVAL_MS); }, }); }; + +// eslint-disable-next-line import/no-default-export +export default telemetry; diff --git a/x-pack/legacy/plugins/telemetry/mappings.json b/src/legacy/core_plugins/telemetry/mappings.json similarity index 100% rename from x-pack/legacy/plugins/telemetry/mappings.json rename to src/legacy/core_plugins/telemetry/mappings.json diff --git a/src/legacy/core_plugins/telemetry/package.json b/src/legacy/core_plugins/telemetry/package.json new file mode 100644 index 00000000000000..979e68cce742fd --- /dev/null +++ b/src/legacy/core_plugins/telemetry/package.json @@ -0,0 +1,4 @@ +{ + "name": "telemetry", + "version": "kibana" +} diff --git a/x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap similarity index 90% rename from x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap rename to src/legacy/core_plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap index 36f8c5ca5905e2..2601f691cd184d 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap +++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap @@ -15,7 +15,7 @@ exports[`OptInDetailsComponent renders as expected 1`] = `

@@ -26,7 +26,7 @@ exports[`OptInDetailsComponent renders as expected 1`] = ` diff --git a/x-pack/legacy/plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap similarity index 89% rename from x-pack/legacy/plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap rename to src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap index 68a1b92666ea9b..c1ad6276aee250 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap +++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap @@ -16,7 +16,7 @@ exports[`TelemetryForm renders as expected 1`] = `

@@ -43,7 +43,7 @@ exports[`TelemetryForm renders as expected 1`] = ` > @@ -55,7 +55,7 @@ exports[`TelemetryForm renders as expected 1`] = ` > diff --git a/src/legacy/core_plugins/telemetry/public/components/index.ts b/src/legacy/core_plugins/telemetry/public/components/index.ts new file mode 100644 index 00000000000000..1fc55eadd1e103 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/components/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-ignore +export { TelemetryForm } from './telemetry_form'; +export { OptInExampleFlyout } from './opt_in_details_component'; +export { OptInBanner } from './opt_in_banner_component'; +export { OptInMessage } from './opt_in_message'; diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.tsx b/src/legacy/core_plugins/telemetry/public/components/opt_in_banner_component.tsx similarity index 54% rename from x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.tsx rename to src/legacy/core_plugins/telemetry/public/components/opt_in_banner_component.tsx index 19754504c081e1..0029eaf666b2d6 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.tsx +++ b/src/legacy/core_plugins/telemetry/public/components/opt_in_banner_component.tsx @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import * as React from 'react'; @@ -21,7 +34,7 @@ export class OptInBanner extends React.PureComponent { render() { const title = ( ); @@ -32,18 +45,12 @@ export class OptInBanner extends React.PureComponent { this.props.optInClick(true)}> - + this.props.optInClick(false)}> - + diff --git a/src/legacy/core_plugins/telemetry/public/components/opt_in_details_component.test.tsx b/src/legacy/core_plugins/telemetry/public/components/opt_in_details_component.test.tsx new file mode 100644 index 00000000000000..3676430b55a414 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/components/opt_in_details_component.test.tsx @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { OptInExampleFlyout } from './opt_in_details_component'; + +describe('OptInDetailsComponent', () => { + it('renders as expected', () => { + expect( + shallowWithIntl( + ({ data: [] }))} + onClose={jest.fn()} + /> + ) + ).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx b/src/legacy/core_plugins/telemetry/public/components/opt_in_details_component.tsx similarity index 90% rename from src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx rename to src/legacy/core_plugins/telemetry/public/components/opt_in_details_component.tsx index d90f54b2bcb546..12ab780e759901 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx +++ b/src/legacy/core_plugins/telemetry/public/components/opt_in_details_component.tsx @@ -91,7 +91,7 @@ export class OptInExampleFlyout extends React.PureComponent { } @@ -99,7 +99,7 @@ export class OptInExampleFlyout extends React.PureComponent { iconType="cross" > @@ -111,7 +111,7 @@ export class OptInExampleFlyout extends React.PureComponent { } @@ -119,7 +119,7 @@ export class OptInExampleFlyout extends React.PureComponent { iconType="cross" > {

@@ -147,7 +147,7 @@ export class OptInExampleFlyout extends React.PureComponent { { }; render() { - const { fetchTelemetry } = this.props; const { showDetails, showExample } = this.state; const getDetails = () => ( ), telemetryPrivacyStatementLink: ( - + @@ -78,14 +78,14 @@ export class OptInMessage extends React.PureComponent { const getFlyoutDetails = () => ( this.setState({ showExample: false })} - fetchTelemetry={fetchTelemetry} + fetchTelemetry={this.props.fetchTelemetry} /> ); const getReadMore = () => ( this.setState({ showDetails: true })}> @@ -93,13 +93,13 @@ export class OptInMessage extends React.PureComponent { return ( - {' '} - {!showDetails && getReadMore()} - {showDetails && getDetails()} - {showDetails && showExample && getFlyoutDetails()} + {getConfigTelemetryDesc()} {!showDetails && getReadMore()} + {showDetails && ( + + {getDetails()} + {showExample && getFlyoutDetails()} + + )} ); } diff --git a/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.js b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js similarity index 77% rename from x-pack/legacy/plugins/telemetry/public/components/telemetry_form.js rename to src/legacy/core_plugins/telemetry/public/components/telemetry_form.js index dc3739bb7ae8e1..c2dcd48ee57da1 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.js +++ b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import React, { Component, Fragment } from 'react'; @@ -28,8 +41,7 @@ export class TelemetryForm extends Component { telemetryOptInProvider: PropTypes.object.isRequired, query: PropTypes.object, onQueryMatchChange: PropTypes.func.isRequired, - spacesEnabled: PropTypes.bool.isRequired, - activeSpace: PropTypes.object, + showAppliesSettingMessage: PropTypes.bool.isRequired, enableSaving: PropTypes.bool.isRequired, }; @@ -85,7 +97,7 @@ export class TelemetryForm extends Component {

@@ -93,7 +105,7 @@ export class TelemetryForm extends Component {
- {this.maybeGetSpacesWarning()} + {this.maybeGetAppliesSettingMessage()} { - if (!this.props.spacesEnabled) { + maybeGetAppliesSettingMessage = () => { + if (!this.props.showAppliesSettingMessage) { return null; } return ( @@ -123,13 +135,13 @@ export class TelemetryForm extends Component { title={

@@ -148,7 +160,7 @@ export class TelemetryForm extends Component {

@@ -156,7 +168,7 @@ export class TelemetryForm extends Component {

diff --git a/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.test.js similarity index 52% rename from x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js rename to src/legacy/core_plugins/telemetry/public/components/telemetry_form.test.js index 4df9cc0da1695c..4d2c1dec271761 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js +++ b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.test.js @@ -1,14 +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; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import '../services/telemetry_opt_in.test.mocks'; import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { TelemetryForm } from './telemetry_form'; -import { TelemetryOptInProvider } from '../services/telemetry_opt_in'; +import { TelemetryOptInProvider } from '../services'; const buildTelemetryOptInProvider = () => { const mockHttp = { diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/__tests__/fetch_telemetry.js b/src/legacy/core_plugins/telemetry/public/hacks/__tests__/fetch_telemetry.js similarity index 52% rename from x-pack/legacy/plugins/telemetry/public/hacks/__tests__/fetch_telemetry.js rename to src/legacy/core_plugins/telemetry/public/hacks/__tests__/fetch_telemetry.js index 26269ef97fa643..8fb8b6e44f0e35 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/__tests__/fetch_telemetry.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/__tests__/fetch_telemetry.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import expect from '@kbn/expect'; diff --git a/src/legacy/core_plugins/telemetry/public/hacks/__tests__/telemetry.js b/src/legacy/core_plugins/telemetry/public/hacks/__tests__/telemetry.js new file mode 100644 index 00000000000000..374927486867e1 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/hacks/__tests__/telemetry.js @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { uiModules } from 'ui/modules'; + +// This overrides settings for other UI tests +uiModules.get('kibana') + // disable stat reporting while running tests, + // MockInjector used in these tests is not impacted + .constant('telemetryEnabled', false) + .constant('telemetryOptedIn', null) + .constant('telemetryUrl', 'not.a.valid.url.0'); diff --git a/src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry.js b/src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry.js new file mode 100644 index 00000000000000..43ec0c2ce571fc --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import uiChrome from 'ui/chrome'; +import moment from 'moment'; + +/** + * Fetch Telemetry data by calling the Kibana API. + * + * @param {Object} $http The HTTP handler + * @param {String} basePath The base URI + * @param {Function} _moment moment.js, but injectable for tests + * @return {Promise} An array of cluster Telemetry objects. + */ +export function fetchTelemetry($http, { basePath = uiChrome.getBasePath(), _moment = moment, unencrypted = false } = { }) { + return $http.post(`${basePath}/api/telemetry/v2/clusters/_stats`, { + unencrypted, + timeRange: { + min: _moment().subtract(20, 'minutes').toISOString(), + max: _moment().toISOString() + } + }); +} diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry.js b/src/legacy/core_plugins/telemetry/public/hacks/telemetry.js similarity index 79% rename from x-pack/legacy/plugins/telemetry/public/hacks/telemetry.js rename to src/legacy/core_plugins/telemetry/public/hacks/telemetry.js index 168bb6d7eec971..4febd8cd9e9a8e 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/telemetry.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry.test.js b/src/legacy/core_plugins/telemetry/public/hacks/telemetry.test.js similarity index 92% rename from x-pack/legacy/plugins/telemetry/public/hacks/telemetry.test.js rename to src/legacy/core_plugins/telemetry/public/hacks/telemetry.test.js index d08d89d7380151..ffb72701f835d8 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry.test.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/telemetry.test.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { Telemetry } from './telemetry'; diff --git a/src/legacy/core_plugins/telemetry/public/hacks/telemetry_init.ts b/src/legacy/core_plugins/telemetry/public/hacks/telemetry_init.ts new file mode 100644 index 00000000000000..364871380a529c --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/hacks/telemetry_init.ts @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { npStart } from 'ui/new_platform'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +import { isUnauthenticated } from '../services'; +// @ts-ignore +import { Telemetry } from './telemetry'; +// @ts-ignore +import { fetchTelemetry } from './fetch_telemetry'; + +function telemetryInit($injector: any) { + const $http = $injector.get('$http'); + + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); + + if (telemetryEnabled) { + // no telemetry for non-logged in users + if (isUnauthenticated()) { + return; + } + + const sender = new Telemetry($injector, () => fetchTelemetry($http)); + sender.start(); + } +} + +uiModules.get('telemetry/hacks').run(telemetryInit); diff --git a/src/legacy/core_plugins/telemetry/public/hacks/telemetry_opt_in.js b/src/legacy/core_plugins/telemetry/public/hacks/telemetry_opt_in.js new file mode 100644 index 00000000000000..4e53c7ecd70302 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/hacks/telemetry_opt_in.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { uiModules } from 'ui/modules'; + +import { injectBanner } from './welcome_banner'; + +uiModules.get('telemetry/hacks').run(injectBanner); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.js similarity index 60% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js rename to src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.js index f337a8025e01d7..a20c99b7eee55d 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import React from 'react'; @@ -41,7 +54,7 @@ export async function clickBanner( _toastNotifications.addDanger({ title: ( ), @@ -49,13 +62,13 @@ export async function clickBanner(

diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js similarity index 80% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js rename to src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js index 751a8f5498ee58..9b17d51d6ea613 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks'; diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js similarity index 66% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js rename to src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js index 4188676bad1e0e..31091e19520533 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { CONFIG_TELEMETRY } from '../../../common/constants'; diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js similarity index 89% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js rename to src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js index 40e3bb042fa88f..fd21a5122b5946 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks'; diff --git a/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/index.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/index.js new file mode 100644 index 00000000000000..ffb0e88c60a0da --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/index.js @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { injectBanner } from './inject_banner'; diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/inject_banner.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/inject_banner.js similarity index 64% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/inject_banner.js rename to src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/inject_banner.js index 384086dfed3c79..13417a137e6c3c 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/inject_banner.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/inject_banner.js @@ -1,15 +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; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import chrome from 'ui/chrome'; -import { Path } from 'plugins/xpack_main/services/path'; + import { fetchTelemetry } from '../fetch_telemetry'; import { renderBanner } from './render_banner'; import { shouldShowBanner } from './should_show_banner'; -import { TelemetryOptInProvider } from '../../services/telemetry_opt_in'; +import { TelemetryOptInProvider, isUnauthenticated } from '../../services'; import { npStart } from 'ui/new_platform'; /** @@ -26,7 +39,7 @@ async function asyncInjectBanner($injector) { const config = $injector.get('config'); // and no banner for non-logged in users - if (Path.isUnauthenticated()) { + if (isUnauthenticated()) { return; } diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/render_banner.js similarity index 51% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js rename to src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/render_banner.js index 9143d13069316a..835d1a8cba62f7 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/render_banner.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import React from 'react'; diff --git a/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js new file mode 100644 index 00000000000000..9db5103cc9b2f9 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import '../../services/telemetry_opt_in.test.mocks'; +import { renderBanner } from './render_banner'; + +describe('render_banner', () => { + + it('adds a banner to banners with priority of 10000', () => { + const bannerID = 'brucer-banner'; + + const telemetryOptInProvider = { setBannerId: jest.fn() }; + const banners = { add: jest.fn().mockReturnValue(bannerID) }; + const fetchTelemetry = jest.fn(); + + renderBanner(telemetryOptInProvider, fetchTelemetry, { _banners: banners }); + + expect(banners.add).toBeCalledTimes(1); + expect(fetchTelemetry).toBeCalledTimes(0); + expect(telemetryOptInProvider.setBannerId).toBeCalledWith(bannerID); + + const bannerConfig = banners.add.mock.calls[0][0]; + + expect(bannerConfig.component).not.toBe(undefined); + expect(bannerConfig.priority).toBe(10000); + }); + +}); diff --git a/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/should_show_banner.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/should_show_banner.js new file mode 100644 index 00000000000000..47c6e58422e4c8 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/should_show_banner.js @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { handleOldSettings } from './handle_old_settings'; + +/** + * Determine if the banner should be displayed. + * + * This method can have side-effects related to deprecated config settings. + * + * @param {Object} config The advanced settings config object. + * @param {Object} _handleOldSettings handleOldSettings function, but overridable for tests. + * @return {Boolean} {@code true} if the banner should be displayed. {@code false} otherwise. + */ +export async function shouldShowBanner(telemetryOptInProvider, config, { _handleOldSettings = handleOldSettings } = {}) { + return telemetryOptInProvider.getOptIn() === null && await _handleOldSettings(config, telemetryOptInProvider); +} diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js similarity index 73% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js rename to src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js index 1bfe7e954738e9..19e7ccbe618661 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js +++ b/src/legacy/core_plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks'; @@ -10,7 +23,7 @@ import sinon from 'sinon'; import { CONFIG_TELEMETRY } from '../../../common/constants'; import { shouldShowBanner } from './should_show_banner'; -import { TelemetryOptInProvider } from '../../services/telemetry_opt_in'; +import { TelemetryOptInProvider } from '../../services'; const getMockInjector = () => { const get = sinon.stub(); diff --git a/src/legacy/core_plugins/telemetry/public/services/index.ts b/src/legacy/core_plugins/telemetry/public/services/index.ts new file mode 100644 index 00000000000000..8b02f8ce4c5b0a --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/services/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { TelemetryOptInProvider } from './telemetry_opt_in'; +export { isUnauthenticated } from './path'; diff --git a/src/legacy/core_plugins/telemetry/public/services/path.ts b/src/legacy/core_plugins/telemetry/public/services/path.ts new file mode 100644 index 00000000000000..4af545e982eaa0 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/services/path.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chrome from 'ui/chrome'; + +export function isUnauthenticated() { + const path = (chrome as any).removeBasePath(window.location.pathname); + return path === '/login' || path === '/logout' || path === '/logged_out' || path === '/status'; +} diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.js similarity index 75% rename from x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js rename to src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.js index 5b93f84eabf4a6..0034fa4438238a 100644 --- a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js +++ b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.js @@ -1,8 +1,22 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + import { mockInjectedMetadata } from './telemetry_opt_in.test.mocks'; import { TelemetryOptInProvider } from './telemetry_opt_in'; diff --git a/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js new file mode 100644 index 00000000000000..f98f5e16e00c3b --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + injectedMetadataServiceMock, + notificationServiceMock, + overlayServiceMock, +} from '../../../../../core/public/mocks'; +const injectedMetadataMock = injectedMetadataServiceMock.createStartContract(); + +export function mockInjectedMetadata({ telemetryOptedIn }) { + const mockGetInjectedVar = jest.fn().mockImplementation((key) => { + switch (key) { + case 'telemetryOptedIn': return telemetryOptedIn; + default: throw new Error(`unexpected injectedVar ${key}`); + } + }); + + injectedMetadataMock.getInjectedVar = mockGetInjectedVar; +} + +jest.doMock('ui/new_platform', () => ({ + npSetup: { + core: { + notifications: notificationServiceMock.createSetupContract(), + } + }, + npStart: { + core: { + injectedMetadata: injectedMetadataMock, + overlays: overlayServiceMock.createStartContract(), + }, + }, +})); diff --git a/src/legacy/core_plugins/kibana/public/home/telemetry_opt_in.js b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts similarity index 75% rename from src/legacy/core_plugins/kibana/public/home/telemetry_opt_in.js rename to src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts index 274820844da45a..f4462ffea7a337 100644 --- a/src/legacy/core_plugins/kibana/public/home/telemetry_opt_in.js +++ b/src/legacy/core_plugins/telemetry/public/services/telemetry_opt_in.ts @@ -23,16 +23,20 @@ import { toastNotifications } from 'ui/notify'; import { npStart } from 'ui/new_platform'; import { i18n } from '@kbn/i18n'; -export function TelemetryOptInProvider($injector, chrome) { - let currentOptInStatus = npStart.core.injectedMetadata.getInjectedVar('telemetryOptedIn'); - let bannerId = null; +let bannerId: string | null = null; +let currentOptInStatus = false; + +export function TelemetryOptInProvider($injector: any, chrome: any) { + currentOptInStatus = npStart.core.injectedMetadata.getInjectedVar('telemetryOptedIn') as boolean; setCanTrackUiMetrics(currentOptInStatus); const provider = { getBannerId: () => bannerId, getOptIn: () => currentOptInStatus, - setBannerId(id) { bannerId = id; }, - setOptIn: async (enabled) => { + setBannerId(id: string) { + bannerId = id; + }, + setOptIn: async (enabled: boolean) => { setCanTrackUiMetrics(enabled); const $http = $injector.get('$http'); @@ -41,10 +45,10 @@ export function TelemetryOptInProvider($injector, chrome) { currentOptInStatus = enabled; } catch (error) { toastNotifications.addError(error, { - title: i18n.translate('kbn.home.telemetry.optInErrorToastTitle', { + title: i18n.translate('telemetry.optInErrorToastTitle', { defaultMessage: 'Error', }), - toastMessage: i18n.translate('kbn.home.telemetry.optInErrorToastText', { + toastMessage: i18n.translate('telemetry.optInErrorToastText', { defaultMessage: 'An error occured while trying to set the usage statistics preference.', }), }); @@ -58,11 +62,13 @@ export function TelemetryOptInProvider($injector, chrome) { return $http.post(chrome.addBasePath(`/api/telemetry/v2/clusters/_stats`), { unencrypted: true, timeRange: { - min: moment().subtract(20, 'minutes').toISOString(), - max: moment().toISOString() - } + min: moment() + .subtract(20, 'minutes') + .toISOString(), + max: moment().toISOString(), + }, }); - } + }, }; return provider; diff --git a/src/legacy/core_plugins/telemetry/public/views/management/index.js b/src/legacy/core_plugins/telemetry/public/views/management/index.js new file mode 100644 index 00000000000000..2e9f064ec80d85 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/views/management/index.js @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import './management'; diff --git a/src/legacy/core_plugins/telemetry/public/views/management/management.js b/src/legacy/core_plugins/telemetry/public/views/management/management.js new file mode 100644 index 00000000000000..9f6e96c925cda8 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/public/views/management/management.js @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import routes from 'ui/routes'; + +import { registerSettingsComponent, PAGE_FOOTER_COMPONENT } from 'ui/management'; +import { TelemetryOptInProvider } from '../../services'; +import { TelemetryForm } from '../../components'; + +routes.defaults(/\/management/, { + resolve: { + telemetryManagementSection: function (Private) { + const telemetryOptInProvider = Private(TelemetryOptInProvider); + + const Component = (props) => ( + + ); + + registerSettingsComponent(PAGE_FOOTER_COMPONENT, Component, true); + } + } +}); diff --git a/src/legacy/core_plugins/telemetry/server/collection_manager.ts b/src/legacy/core_plugins/telemetry/server/collection_manager.ts new file mode 100644 index 00000000000000..fef0a9b0f9f40b --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collection_manager.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +class TelemetryCollectionManager { + private getterMethod?: any; + private collectionTitle?: string; + private getterMethodPriority = 0; + + public setStatsGetter = (statsGetter: any, title: string, priority = 0) => { + if (priority >= this.getterMethodPriority) { + this.getterMethod = statsGetter; + this.collectionTitle = title; + this.getterMethodPriority = priority; + } + }; + + getCollectionTitle = () => { + return this.collectionTitle; + }; + + public getStatsGetter = () => { + if (!this.getterMethod) { + throw Error('Stats getter method not set.'); + } + return { + getStats: this.getterMethod, + priority: this.getterMethodPriority, + title: this.collectionTitle, + }; + }; +} + +export const telemetryCollectionManager = new TelemetryCollectionManager(); diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/encryption/encrypt.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/encryption/encrypt.test.ts similarity index 53% rename from x-pack/legacy/plugins/telemetry/server/collectors/encryption/encrypt.test.ts rename to src/legacy/core_plugins/telemetry/server/collectors/encryption/encrypt.test.ts index ed13b5569d987e..4a4ba7aa1f3212 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/encryption/encrypt.test.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/encryption/encrypt.test.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { telemetryJWKS } from './telemetry_jwks'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/encryption/encrypt.ts b/src/legacy/core_plugins/telemetry/server/collectors/encryption/encrypt.ts new file mode 100644 index 00000000000000..c20f4b768b7dc0 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/encryption/encrypt.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createRequestEncryptor } from '@elastic/request-crypto'; +import { telemetryJWKS } from './telemetry_jwks'; + +export function getKID(isProd = false): string { + return isProd ? 'kibana' : 'kibana_dev'; +} + +export async function encryptTelemetry(payload: any, isProd = false): Promise { + const kid = getKID(isProd); + const encryptor = await createRequestEncryptor(telemetryJWKS); + const clusters = [].concat(payload); + return Promise.all(clusters.map((cluster: any) => encryptor.encrypt(kid, cluster))); +} diff --git a/src/legacy/core_plugins/telemetry/server/collectors/encryption/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/encryption/index.ts new file mode 100644 index 00000000000000..08636d45861e49 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/encryption/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { encryptTelemetry } from './encrypt'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/encryption/telemetry_jwks.ts b/src/legacy/core_plugins/telemetry/server/collectors/encryption/telemetry_jwks.ts similarity index 57% rename from x-pack/legacy/plugins/telemetry/server/collectors/encryption/telemetry_jwks.ts rename to src/legacy/core_plugins/telemetry/server/collectors/encryption/telemetry_jwks.ts index b4983ad2d4db7b..8615ba2f9d6055 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/encryption/telemetry_jwks.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/encryption/telemetry_jwks.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { PublicJWKS } from '@elastic/request-crypto'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/index.ts new file mode 100644 index 00000000000000..0bc1d50fab1be6 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { encryptTelemetry } from './encryption'; +export { createTelemetryUsageCollector } from './usage'; +export { createUiMetricUsageCollector } from './ui_metric'; +export { createLocalizationUsageCollector } from './localization'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts b/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts new file mode 100644 index 00000000000000..cb77ce581eff23 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Readable } from 'stream'; + +jest.doMock('fs', () => ({ + createReadStream(filepath: string): Readable { + if (filepath === 'ERROR') { + throw new Error('MOCK ERROR - Invalid Path'); + } + const readableStream = new Readable(); + const streamData = filepath.split(''); + let cursor = 0; + + readableStream._read = function(size) { + const current = streamData[cursor++]; + if (typeof current === 'undefined') { + return this.push(null); + } + this.push(current); + }; + + return readableStream; + }, +})); diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.ts similarity index 56% rename from x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.test.ts rename to src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.ts index 668b0d0c21088b..a0b566537f5b4f 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.test.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import './file_integrity.test.mocks'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.ts b/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.ts similarity index 52% rename from x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.ts rename to src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.ts index 1db3795828fffc..a852fba4a1c5a4 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { createHash } from 'crypto'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts new file mode 100644 index 00000000000000..3b289752ce39fc --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { createLocalizationUsageCollector } from './telemetry_localization_collector'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts similarity index 51% rename from x-pack/legacy/plugins/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts rename to src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts index 2cd3938e58ece2..eec5cc8a065e44 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ interface TranslationsMock { diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts similarity index 64% rename from x-pack/legacy/plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts rename to src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts index b66fe75e7af714..74c93931096b2e 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { i18nLoader } from '@kbn/i18n'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts new file mode 100644 index 00000000000000..e1ac7a1f5af122 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { createUiMetricUsageCollector } from './telemetry_ui_metric_collector'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts similarity index 61% rename from x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts rename to src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts index a931400399b443..fa3159669c33ca 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { UI_METRIC_USAGE_TYPE } from '../../../common/constants'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts similarity index 72% rename from x-pack/legacy/plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts rename to src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts index 4fad3923024446..5a520fbeef3169 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { ensureDeepObject } from './ensure_deep_object'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/usage/ensure_deep_object.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.ts similarity index 63% rename from x-pack/legacy/plugins/telemetry/server/collectors/usage/ensure_deep_object.ts rename to src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.ts index 5b00aab010d5c0..6594c7f8e7a6f0 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/usage/ensure_deep_object.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ // diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts new file mode 100644 index 00000000000000..a1b3d5a7b1982b --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { createTelemetryUsageCollector } from './telemetry_usage_collector'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts similarity index 80% rename from x-pack/legacy/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts rename to src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts index d99f9dba334c90..3806dfc77120f9 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { writeFileSync, unlinkSync } from 'fs'; @@ -28,8 +41,8 @@ const serverWithConfig = (configPath: string): KibanaHapiServer & Server => { ...getMockServer(), config: () => ({ get: (key: string) => { - if (key !== 'xpack.telemetry.config' && key !== 'xpack.xpack_main.telemetry.config') { - throw new Error('Expected `xpack.telemetry.config`'); + if (key !== 'telemetry.config' && key !== 'xpack.xpack_main.telemetry.config') { + throw new Error('Expected `telemetry.config`'); } return configPath; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts similarity index 77% rename from x-pack/legacy/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts rename to src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts index a20f4198b3ad33..c9274536411934 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { accessSync, constants, readFileSync, statSync } from 'fs'; diff --git a/x-pack/legacy/plugins/telemetry/server/get_telemetry_opt_in.ts b/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts similarity index 52% rename from x-pack/legacy/plugins/telemetry/server/get_telemetry_opt_in.ts rename to src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts index 8f5af83985f144..9b365d6dd7ae51 100644 --- a/x-pack/legacy/plugins/telemetry/server/get_telemetry_opt_in.ts +++ b/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ export async function getTelemetryOptIn(request: any) { diff --git a/src/legacy/core_plugins/telemetry/server/index.ts b/src/legacy/core_plugins/telemetry/server/index.ts new file mode 100644 index 00000000000000..b8ae5fc231fba6 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/index.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from 'src/core/server'; +import { TelemetryPlugin } from './plugin'; +import * as constants from '../common/constants'; + +export { getTelemetryOptIn } from './get_telemetry_opt_in'; +export { telemetryCollectionManager } from './collection_manager'; + +export const telemetryPlugin = (initializerContext: PluginInitializerContext) => + new TelemetryPlugin(); +export { constants }; diff --git a/src/legacy/core_plugins/telemetry/server/plugin.ts b/src/legacy/core_plugins/telemetry/server/plugin.ts new file mode 100644 index 00000000000000..70de51b2abe995 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/plugin.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreSetup } from 'src/core/server'; +import { registerRoutes } from './routes'; +import { telemetryCollectionManager } from './collection_manager'; +import { getStats } from './telemetry_collection'; + +export class TelemetryPlugin { + public setup(core: CoreSetup) { + telemetryCollectionManager.setStatsGetter(getStats, 'local'); + registerRoutes(core); + } +} diff --git a/src/legacy/core_plugins/telemetry/server/routes/index.ts b/src/legacy/core_plugins/telemetry/server/routes/index.ts new file mode 100644 index 00000000000000..12ba541d699f9f --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/routes/index.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreSetup } from 'src/core/server'; +import { registerOptInRoutes } from './opt_in'; +import { registerTelemetryDataRoutes } from './telemetry_stats'; + +export function registerRoutes(core: CoreSetup) { + registerOptInRoutes(core); + registerTelemetryDataRoutes(core); +} diff --git a/x-pack/legacy/plugins/telemetry/server/routes/opt_in.ts b/src/legacy/core_plugins/telemetry/server/routes/opt_in.ts similarity index 52% rename from x-pack/legacy/plugins/telemetry/server/routes/opt_in.ts rename to src/legacy/core_plugins/telemetry/server/routes/opt_in.ts index 72c602e84f90aa..aabc0259f08fc2 100644 --- a/x-pack/legacy/plugins/telemetry/server/routes/opt_in.ts +++ b/src/legacy/core_plugins/telemetry/server/routes/opt_in.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import Joi from 'joi'; diff --git a/x-pack/legacy/plugins/telemetry/server/routes/telemetry_stats.ts b/src/legacy/core_plugins/telemetry/server/routes/telemetry_stats.ts similarity index 54% rename from x-pack/legacy/plugins/telemetry/server/routes/telemetry_stats.ts rename to src/legacy/core_plugins/telemetry/server/routes/telemetry_stats.ts index 8ab4e68008e823..8a91d24b34ed21 100644 --- a/x-pack/legacy/plugins/telemetry/server/routes/telemetry_stats.ts +++ b/src/legacy/core_plugins/telemetry/server/routes/telemetry_stats.ts @@ -1,13 +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; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import Joi from 'joi'; import { boomify } from 'boom'; import { CoreSetup } from 'src/core/server'; -import { getStats, encryptTelemetry } from '../collectors'; +import { encryptTelemetry } from '../collectors'; +import { telemetryCollectionManager } from '../collection_manager'; export function registerTelemetryDataRoutes(core: CoreSetup) { const { server } = core.http as any; @@ -34,13 +48,17 @@ export function registerTelemetryDataRoutes(core: CoreSetup) { const isDev = config.get('env.dev'); try { + const { getStats, title } = telemetryCollectionManager.getStatsGetter(); + server.log(['debug', 'telemetry'], `Using Stats Getter: ${title}`); + const usageData = await getStats(req, config, start, end, unencrypted); + if (unencrypted) return usageData; return encryptTelemetry(usageData, isDev); } catch (err) { if (isDev) { // don't ignore errors when running in dev mode - return boomify(err, { statusCode: err.status }); + return boomify(err, { statusCode: err.status || 500 }); } else { const statusCode = unencrypted && err.status === 403 ? 403 : 200; // ignore errors and return empty set diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_info.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_info.js new file mode 100644 index 00000000000000..566e5b7019ddd0 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_info.js @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import sinon from 'sinon'; + +import { getClusterInfo } from '../get_cluster_info'; + +export function mockGetClusterInfo(callCluster, clusterInfo, req) { + callCluster.withArgs(req, 'info').returns(clusterInfo); + callCluster.withArgs('info').returns(clusterInfo); +} + +describe('get_cluster_info', () => { + + it('uses callCluster to get info API', () => { + const callCluster = sinon.stub(); + const response = Promise.resolve({}); + + mockGetClusterInfo(callCluster, response); + + expect(getClusterInfo(callCluster)).to.be(response); + }); + +}); diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js new file mode 100644 index 00000000000000..9ca609cd88778c --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_cluster_stats.js @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import sinon from 'sinon'; + +import { TIMEOUT } from '../constants'; +import { getClusterStats } from '../get_cluster_stats'; + +export function mockGetClusterStats(callCluster, clusterStats, req) { + callCluster.withArgs(req, 'cluster.stats', { + timeout: TIMEOUT + }) + .returns(clusterStats); + + callCluster.withArgs('cluster.stats', { + timeout: TIMEOUT + }) + .returns(clusterStats); +} + +describe('get_cluster_stats', () => { + + it('uses callCluster to get cluster.stats API', () => { + const callCluster = sinon.stub(); + const response = Promise.resolve({}); + + mockGetClusterStats(callCluster, response); + + expect(getClusterStats(callCluster)).to.be(response); + }); + +}); diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_local_stats.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js similarity index 86% rename from x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_local_stats.js rename to src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js index e0879d208d6f44..d0de9cc365a712 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_local_stats.js +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import expect from '@kbn/expect'; @@ -9,7 +22,6 @@ import sinon from 'sinon'; import { mockGetClusterInfo } from './get_cluster_info'; import { mockGetClusterStats } from './get_cluster_stats'; -import { mockGetXPack } from './get_xpack'; import { omit } from 'lodash'; import { @@ -22,17 +34,25 @@ const getMockServer = (getCluster = sinon.stub(), kibanaUsage = {}) => ({ log(tags, message) { console.log({ tags, message }); }, + config() { + return { + get(item) { + switch(item) { + case 'pkg.version': return '8675309-snapshot'; + default: throw Error(`unexpected config.get('${item}') received.`); + } + } + }; + }, usage: { collectorSet: { bulkFetch: () => kibanaUsage, toObject: data => data } }, plugins: { - xpack_main: { status: { plugin: { kbnServer: { version: '8675309-snapshot' } } } }, elasticsearch: { getCluster }, }, }); -function mockGetLocalStats(callCluster, clusterInfo, clusterStats, license, usage, req) { +function mockGetLocalStats(callCluster, clusterInfo, clusterStats, req) { mockGetClusterInfo(callCluster, clusterInfo, req); mockGetClusterStats(callCluster, clusterStats, req); - mockGetXPack(callCluster, license, usage, req); } describe('get_local_stats', () => { @@ -53,8 +73,6 @@ describe('get_local_stats', () => { nodes: { yup: 'abc' }, random: 123 }; - const license = { fancy: 'license' }; - const xpack = { also: 'fancy' }; const kibana = { kibana: { great: 'googlymoogly', @@ -81,9 +99,6 @@ describe('get_local_stats', () => { collection: 'local', cluster_uuid: clusterUuid, cluster_name: clusterName, - license: { - fancy: 'license' - }, version, cluster_stats: omit(clusterStats, '_nodes', 'cluster_name'), stack_stats: { @@ -108,7 +123,6 @@ describe('get_local_stats', () => { snow: { chances: 0 }, } }, - xpack: { also: 'fancy' }, } }; @@ -121,11 +135,11 @@ describe('get_local_stats', () => { expect(result.version).to.be('2.3.4'); expect(result.collection).to.be('local'); expect(result.license).to.be(undefined); - expect(result.stack_stats).to.eql({ kibana: undefined, xpack: undefined }); + expect(result.stack_stats).to.eql({ kibana: undefined }); }); it('returns expected object with xpack', () => { - const result = handleLocalStats(getMockServer(), clusterInfo, clusterStats, license, xpack); + const result = handleLocalStats(getMockServer(), clusterInfo, clusterStats); const { stack_stats: stack, ...cluster } = result; expect(cluster.collection).to.be(combinedStatsResult.collection); expect(cluster.cluster_uuid).to.be(combinedStatsResult.cluster_uuid); @@ -147,8 +161,6 @@ describe('get_local_stats', () => { callClusterUsageFailed, Promise.resolve(clusterInfo), Promise.resolve(clusterStats), - Promise.resolve(license), - Promise.reject('usage failed') ); const result = await getLocalStatsWithCaller(getMockServer(), callClusterUsageFailed); @@ -170,8 +182,6 @@ describe('get_local_stats', () => { callCluster, Promise.resolve(clusterInfo), Promise.resolve(clusterStats), - Promise.resolve(license), - Promise.resolve(xpack) ); const result = await getLocalStatsWithCaller(getMockServer(callCluster, kibana), callCluster); @@ -192,8 +202,6 @@ describe('get_local_stats', () => { callWithInternalUser, Promise.resolve(clusterInfo), Promise.resolve(clusterStats), - Promise.resolve(license), - Promise.resolve(xpack) ); const result = await getLocalStats(req, { useInternalUser: true }); @@ -213,8 +221,6 @@ describe('get_local_stats', () => { callWithRequest, Promise.resolve(clusterInfo), Promise.resolve(clusterStats), - Promise.resolve(license), - Promise.resolve(xpack), req ); diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/constants.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/constants.ts new file mode 100644 index 00000000000000..ad14bfe7430da4 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/constants.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * The timeout used by each request, whenever a timeout can be specified. + */ +export const TIMEOUT = '30s'; diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.js new file mode 100644 index 00000000000000..2e4ed0b36ed26d --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.js @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Get the cluster info from the connected cluster. + * + * This is the equivalent to GET / + * + * @param {function} callCluster The callWithInternalUser handler (exposed for testing) + * @return {Promise} The response from Elasticsearch. + */ +export function getClusterInfo(callCluster) { + return callCluster('info'); +} diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.js new file mode 100644 index 00000000000000..a840c39812e2c8 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_stats.js @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { TIMEOUT } from './constants'; + +/** + * Get the cluster stats from the connected cluster. + * + * This is the equivalent to GET /_cluster/stats?timeout=30s. + * + * @param {function} callCluster The callWithInternalUser handler (exposed for testing) + * @return {Promise} The response from Elasticsearch equivalent to GET /_cluster/stats. + */ +export function getClusterStats(callCluster) { + return callCluster('cluster.stats', { + timeout: TIMEOUT + }); +} diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_kibana.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_kibana.js new file mode 100644 index 00000000000000..051ef370fcde51 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_kibana.js @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { get, omit } from 'lodash'; + +export function handleKibanaStats(server, response) { + if (!response) { + server.log(['warning', 'telemetry', 'local-stats'], 'No Kibana stats returned from usage collectors'); + return; + } + + const { kibana, kibana_stats: kibanaStats, ...plugins } = response; + + const platform = get(kibanaStats, 'os.platform', 'unknown'); + const platformRelease = get(kibanaStats, 'os.platformRelease', 'unknown'); + + const version = server.config().get('pkg.version').replace(/-snapshot/i, ''); + + // combine core stats (os types, saved objects) with plugin usage stats + // organize the object into the same format as monitoring-enabled telemetry + return { + ...omit(kibana, 'index'), // discard index + count: 1, + indices: 1, + os: { + platforms: [{ platform, count: 1 }], + platformReleases: [{ platformRelease, count: 1 }], + }, + versions: [{ version, count: 1 }], + plugins, + }; +} + +/* + * Check user privileges for read access to monitoring + * Pass callWithInternalUser to bulkFetchUsage + */ +export async function getKibana(server, callWithInternalUser) { + const { collectorSet } = server.usage; + const usage = await collectorSet.bulkFetch(callWithInternalUser); + return collectorSet.toObject(usage); +} diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_local_stats.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.js similarity index 62% rename from x-pack/legacy/plugins/telemetry/server/collectors/local/get_local_stats.js rename to src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.js index 5f6f738b6e501d..67fc721306c213 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_local_stats.js +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.js @@ -1,13 +1,25 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { get, omit } from 'lodash'; import { getClusterInfo } from './get_cluster_info'; import { getClusterStats } from './get_cluster_stats'; -import { getXPack } from './get_xpack'; import { getKibana, handleKibanaStats } from './get_kibana'; /** @@ -16,10 +28,9 @@ import { getKibana, handleKibanaStats } from './get_kibana'; * * @param {Object} clusterInfo Cluster info (GET /) * @param {Object} clusterStats Cluster stats (GET /_cluster/stats) - * @param {Object} xpack License and X-Pack details * @return {Object} A combined object containing the different responses. */ -export function handleLocalStats(server, clusterInfo, clusterStats, license, xpack, kibana) { +export function handleLocalStats(server, clusterInfo, clusterStats, kibana) { return { timestamp: (new Date()).toISOString(), cluster_uuid: get(clusterInfo, 'cluster_uuid'), @@ -27,10 +38,8 @@ export function handleLocalStats(server, clusterInfo, clusterStats, license, xpa version: get(clusterInfo, 'version.number'), cluster_stats: omit(clusterStats, '_nodes', 'cluster_name'), collection: 'local', - license, stack_stats: { kibana: handleKibanaStats(server, kibana), - xpack, } }; } @@ -42,18 +51,17 @@ export function handleLocalStats(server, clusterInfo, clusterStats, license, xpa * @param {function} callCluster The callWithInternalUser handler (exposed for testing) * @return {Promise} The object containing the current Elasticsearch cluster's telemetry. */ -export function getLocalStatsWithCaller(server, callCluster) { - return Promise.all([ +export async function getLocalStatsWithCaller(server, callCluster) { + const [ clusterInfo, clusterStats, kibana ] = await Promise.all([ getClusterInfo(callCluster), // cluster info getClusterStats(callCluster), // cluster stats (not to be confused with cluster _state_) - getXPack(callCluster), // { license, xpack } - getKibana(server, callCluster) - ]).then(([clusterInfo, clusterStats, { license, xpack }, kibana]) => { - return handleLocalStats(server, clusterInfo, clusterStats, license, xpack, kibana); - } - ); + getKibana(server, callCluster), + ]); + + return handleLocalStats(server, clusterInfo, clusterStats, kibana); } + /** * Get statistics for the connected Elasticsearch cluster. * @@ -61,10 +69,10 @@ export function getLocalStatsWithCaller(server, callCluster) { * @param {Boolean} useRequestUser callWithRequest, otherwise callWithInternalUser * @return {Promise} The cluster object containing telemetry. */ -export function getLocalStats(req, { useInternalUser = false } = {}) { +export async function getLocalStats(req, { useInternalUser = false } = {}) { const { server } = req; const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster('data'); const callCluster = useInternalUser ? callWithInternalUser : (...args) => callWithRequest(req, ...args); - return getLocalStatsWithCaller(server, callCluster); + return await getLocalStatsWithCaller(server, callCluster); } diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts new file mode 100644 index 00000000000000..024272e0f805ca --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_stats.ts @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-ignore +import { getLocalStats } from './get_local_stats'; + +/** + * Get the telemetry data. + * + * @param {Object} req The incoming request. + * @param {Object} config Kibana config. + * @param {String} start The start time of the request (likely 20m ago). + * @param {String} end The end time of the request. + * @param {Boolean} unencrypted Is the request payload going to be unencrypted. + * @return {Promise} An array of telemetry objects. + */ +export async function getStats( + req: any, + config: any, + start: string, + end: string, + unencrypted: boolean +) { + return [ + await getLocalStats(req, { + useInternalUser: !unencrypted, + }), + ]; +} diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts new file mode 100644 index 00000000000000..f33727d82f44ce --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-ignore +export { getLocalStats } from './get_local_stats'; + +// @ts-ignore +export { getStats } from './get_stats'; diff --git a/test/common/config.js b/test/common/config.js index 452413fe4e4eb3..44e4bef99bf620 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -55,6 +55,7 @@ export default function () { `--elasticsearch.username=${servers.elasticsearch.username}`, `--elasticsearch.password=${servers.elasticsearch.password}`, `--kibana.disableWelcomeScreen=true`, + '--telemetry.banner=false', `--server.maxPayloadBytes=1679958`, ], }, diff --git a/typings/elastic__node_crypto.d.ts b/typings/elastic__node_crypto.d.ts new file mode 100644 index 00000000000000..8d4b47da96b738 --- /dev/null +++ b/typings/elastic__node_crypto.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +declare module '@elastic/node-crypto'; diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 872d4ed9c29d1b..e0ba4555529669 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -25,7 +25,6 @@ "xpack.ml": "legacy/plugins/ml", "xpack.logstash": "legacy/plugins/logstash", "xpack.main": "legacy/plugins/xpack_main", - "xpack.telemetry": "legacy/plugins/telemetry", "xpack.monitoring": "legacy/plugins/monitoring", "xpack.remoteClusters": "legacy/plugins/remote_clusters", "xpack.reporting": "legacy/plugins/reporting", diff --git a/x-pack/index.js b/x-pack/index.js index f2e602ec055869..756d9b4d3127a5 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -38,7 +38,6 @@ import { upgradeAssistant } from './legacy/plugins/upgrade_assistant'; import { uptime } from './legacy/plugins/uptime'; import { ossTelemetry } from './legacy/plugins/oss_telemetry'; import { fileUpload } from './legacy/plugins/file_upload'; -import { telemetry } from './legacy/plugins/telemetry'; import { encryptedSavedObjects } from './legacy/plugins/encrypted_saved_objects'; import { snapshotRestore } from './legacy/plugins/snapshot_restore'; import { transform } from './legacy/plugins/transform'; @@ -49,7 +48,6 @@ import { lens } from './legacy/plugins/lens'; module.exports = function (kibana) { return [ xpackMain(kibana), - telemetry(kibana), graph(kibana), monitoring(kibana), reporting(kibana), diff --git a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js b/x-pack/legacy/plugins/license_management/public/lib/telemetry.js index 08928549916eb3..bf8bed05aabed3 100644 --- a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js +++ b/x-pack/legacy/plugins/license_management/public/lib/telemetry.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fetchTelemetry } from '../../../telemetry/public/hacks/fetch_telemetry'; -export { PRIVACY_STATEMENT_URL } from '../../../telemetry/common/constants'; -export { TelemetryOptInProvider } from '../../../telemetry/public/services/telemetry_opt_in'; -export { OptInExampleFlyout } from '../../../telemetry/public/components'; +import { fetchTelemetry } from '../../../../../../src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry'; +export { PRIVACY_STATEMENT_URL } from '../../../../../../src/legacy/core_plugins/telemetry/common/constants'; +export { TelemetryOptInProvider } from '../../../../../../src/legacy/core_plugins/telemetry/public/services'; +export { OptInExampleFlyout } from '../../../../../../src/legacy/core_plugins/telemetry/public/components'; let telemetryEnabled; let httpClient; diff --git a/x-pack/legacy/plugins/monitoring/common/constants.js b/x-pack/legacy/plugins/monitoring/common/constants.js index f953741cd2e02d..e40941396b661c 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.js +++ b/x-pack/legacy/plugins/monitoring/common/constants.js @@ -155,10 +155,7 @@ export const METRICBEAT_INDEX_NAME_UNIQUE_TOKEN = '-mb-'; // We use this for metricbeat migration to identify specific products that we do not have constants for export const ELASTICSEARCH_SYSTEM_ID = 'elasticsearch'; -export const KIBANA_SYSTEM_ID = 'kibana'; -export const BEATS_SYSTEM_ID = 'beats'; -export const APM_SYSTEM_ID = 'apm'; -export const LOGSTASH_SYSTEM_ID = 'logstash'; + /** * The id of the infra source owned by the monitoring plugin. */ @@ -181,3 +178,47 @@ export const CODE_PATH_LOGSTASH = 'logstash'; export const CODE_PATH_APM = 'apm'; export const CODE_PATH_LICENSE = 'license'; export const CODE_PATH_LOGS = 'logs'; + +/** + * The header sent by telemetry service when hitting Elasticsearch to identify query source + * @type {string} + */ +export const TELEMETRY_QUERY_SOURCE = 'TELEMETRY'; + +/** + * The name of the Kibana System ID used to publish and look up Kibana stats through the Monitoring system. + * @type {string} + */ +export const KIBANA_SYSTEM_ID = 'kibana'; + +/** + * The name of the Beats System ID used to publish and look up Beats stats through the Monitoring system. + * @type {string} + */ +export const BEATS_SYSTEM_ID = 'beats'; + +/** + * The name of the Apm System ID used to publish and look up Apm stats through the Monitoring system. + * @type {string} + */ +export const APM_SYSTEM_ID = 'apm'; + +/** + * The name of the Kibana System ID used to look up Logstash stats through the Monitoring system. + * @type {string} + */ +export const LOGSTASH_SYSTEM_ID = 'logstash'; + +/** + * The name of the Kibana System ID used to look up Reporting stats through the Monitoring system. + * @type {string} + */ +export const REPORTING_SYSTEM_ID = 'reporting'; + +/** + * The amount of time, in milliseconds, to wait between collecting kibana stats from es. + * + * Currently 24 hours kept in sync with reporting interval. + * @type {Number} + */ +export const TELEMETRY_COLLECTION_INTERVAL = 86400000; diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js index d494963c4492de..3cacf00016f0dc 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js @@ -49,9 +49,6 @@ describe('BulkUploader', () => { server = { log: sinon.spy(), - xpackMainPlugin: { - telemetryCollectionInterval: 3000, - }, elasticsearchPlugin: { createCluster: () => cluster, getCluster: () => cluster, diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js index 015af8f3d633ae..da23d4b77a323e 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js @@ -10,7 +10,9 @@ import { callClusterFactory } from '../../../xpack_main'; import { LOGGING_TAG, KIBANA_MONITORING_LOGGING_TAG, + TELEMETRY_COLLECTION_INTERVAL, } from '../../common/constants'; + import { sendBulkPayload, monitoringBulk, @@ -36,7 +38,7 @@ const LOGGING_TAGS = [LOGGING_TAG, KIBANA_MONITORING_LOGGING_TAG]; * @param {Object} xpackInfo server.plugins.xpack_main.info object */ export class BulkUploader { - constructor({ config, log, interval, xpackMainPlugin, elasticsearchPlugin, kbnServerStatus, kbnServerVersion }) { + constructor({ config, log, interval, elasticsearchPlugin, kbnServerStatus, kbnServerVersion }) { if (typeof interval !== 'number') { throw new Error('interval number of milliseconds is required'); } @@ -44,7 +46,7 @@ export class BulkUploader { this._timer = null; this._interval = interval; this._lastFetchUsageTime = null; - this._usageInterval = xpackMainPlugin.telemetryCollectionInterval; + this._usageInterval = TELEMETRY_COLLECTION_INTERVAL; this._log = { debug: message => log(['debug', ...LOGGING_TAGS], message), diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/lib/send_bulk_payload.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/lib/send_bulk_payload.js index 208fb6305457b3..3a286e4d7a5139 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/lib/send_bulk_payload.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/lib/send_bulk_payload.js @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MONITORING_SYSTEM_API_VERSION } from '../../../common/constants'; -import { KIBANA_SYSTEM_ID } from '../../../../telemetry/common/constants'; +import { MONITORING_SYSTEM_API_VERSION, KIBANA_SYSTEM_ID } from '../../../common/constants'; /* * Send the Kibana usage data to the ES Monitoring Bulk endpoint diff --git a/x-pack/legacy/plugins/monitoring/server/plugin.js b/x-pack/legacy/plugins/monitoring/server/plugin.js index 349e705434bfa5..ab7813aa265664 100644 --- a/x-pack/legacy/plugins/monitoring/server/plugin.js +++ b/x-pack/legacy/plugins/monitoring/server/plugin.js @@ -10,6 +10,9 @@ import { requireUIRoutes } from './routes'; import { instantiateClient } from './es_client/instantiate_client'; import { initMonitoringXpackInfo } from './init_monitoring_xpack_info'; import { initBulkUploader } from './kibana_monitoring'; +import { telemetryCollectionManager } from '../../../../../src/legacy/core_plugins/telemetry/server'; +import { getStatsWithMonitoring } from './telemetry_collection'; + import { getKibanaUsageCollector, getOpsStatsCollector, @@ -36,6 +39,8 @@ export class Plugin { collectorSet.register(getKibanaUsageCollector({ collectorSet, config })); collectorSet.register(getSettingsCollector({ collectorSet, config })); + telemetryCollectionManager.setStatsGetter(getStatsWithMonitoring, 'monitoring', 2); + /* * Instantiate and start the internal background task that calls collector * fetch methods and uploads to the ES monitoring bulk endpoint @@ -90,7 +95,6 @@ export class Plugin { const bulkUploader = initBulkUploader({ elasticsearchPlugin: plugins.elasticsearch, - xpackMainPlugin: plugins.xpack_main, config, log: core.log, kbnServerStatus: kbnServer.status, diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/create_query.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/create_query.js similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/create_query.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/create_query.js diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/fixtures/beats_stats_results.json b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/fixtures/beats_stats_results.json similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/fixtures/beats_stats_results.json rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/fixtures/beats_stats_results.json diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_all_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js similarity index 95% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_all_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js index fff281a0d87ef6..0d147b747f2d04 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_all_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; -import { addStackStats, getAllStats, getAllStatsForServer, handleAllStats } from '../get_all_stats'; +import { addStackStats, getAllStats, handleAllStats } from '../get_all_stats'; describe('get_all_stats', () => { const size = 123; @@ -106,7 +106,7 @@ describe('get_all_stats', () => { } ]; - describe('getAllStats / getAllStatsForServer return the same results', () => { + describe('getAllStats', () => { it('returns clusters', async () => { const clusterUuidsResponse = { aggregations: { cluster_uuids: { buckets: [ { key: 'a' } ] } } @@ -201,7 +201,6 @@ describe('get_all_stats', () => { .onCall(3).returns(Promise.resolve(logstashStatsResponse)); expect(await getAllStats(req, start, end)).to.eql(allClusters); - expect(await getAllStatsForServer(server, start, end)).to.eql(allClusters); }); it('returns empty clusters', async () => { @@ -213,7 +212,6 @@ describe('get_all_stats', () => { callWithInternalUser.withArgs('search').returns(Promise.resolve(clusterUuidsResponse)); expect(await getAllStats(req, start, end)).to.eql([]); - expect(await getAllStatsForServer(server, start, end)).to.eql([]); }); }); diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_beats_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_beats_stats.js similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_beats_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_beats_stats.js diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_cluster_uuids.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_cluster_uuids.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_es_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_es_stats.js similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_es_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_es_stats.js diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_high_level_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_high_level_stats.js similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_high_level_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_high_level_stats.js diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_kibana_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_kibana_stats.js similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/__tests__/get_kibana_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_kibana_stats.js diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/create_query.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/create_query.js similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/create_query.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/create_query.js diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_all_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.js similarity index 84% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_all_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.js index bc93dad72214df..b1e8db1b96005d 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_all_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.js @@ -6,7 +6,11 @@ import { get, set, merge } from 'lodash'; -import { constants } from '../../'; +import { + LOGSTASH_SYSTEM_ID, + KIBANA_SYSTEM_ID, + BEATS_SYSTEM_ID, +} from '../../common/constants'; import { getClusterUuids } from './get_cluster_uuids'; import { getElasticsearchStats } from './get_es_stats'; import { getKibanaStats } from './get_kibana_stats'; @@ -30,20 +34,6 @@ export function getAllStats(req, start, end, { useInternalUser = false } = {}) { return getAllStatsWithCaller(server, callCluster, start, end); } -/** - * Get statistics for all products joined by Elasticsearch cluster. - * - * @param {Object} server The server instance used to call ES as the internal user - * @param {Date} start The starting range to request data - * @param {Date} end The ending range to request data - * @return {Promise} The array of clusters joined with the Kibana and Logstash instances. - */ -export function getAllStatsForServer(server, start, end) { - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('monitoring'); - - return getAllStatsWithCaller(server, callWithInternalUser, start, end); -} - /** * Get statistics for all products joined by Elasticsearch cluster. * @@ -64,7 +54,7 @@ function getAllStatsWithCaller(server, callCluster, start, end) { return Promise.all([ getElasticsearchStats(server, callCluster, clusterUuids), // cluster_stats, stack_stats.xpack, cluster_name/uuid, license, version getKibanaStats(server, callCluster, clusterUuids, start, end), // stack_stats.kibana - getHighLevelStats(server, callCluster, clusterUuids, start, end, constants.LOGSTASH_SYSTEM_ID), // stack_stats.logstash + getHighLevelStats(server, callCluster, clusterUuids, start, end, LOGSTASH_SYSTEM_ID), // stack_stats.logstash getBeatsStats(server, callCluster, clusterUuids, start, end), // stack_stats.beats ]) .then(([esClusters, kibana, logstash, beats]) => handleAllStats(esClusters, { kibana, logstash, beats })); @@ -83,9 +73,9 @@ function getAllStatsWithCaller(server, callCluster, start, end) { export function handleAllStats(clusters, { kibana, logstash, beats }) { return clusters.map(cluster => { // if they are using Kibana or Logstash, then add it to the cluster details under cluster.stack_stats - addStackStats(cluster, kibana, constants.KIBANA_SYSTEM_ID); - addStackStats(cluster, logstash, constants.LOGSTASH_SYSTEM_ID); - addStackStats(cluster, beats, constants.BEATS_SYSTEM_ID); + addStackStats(cluster, kibana, KIBANA_SYSTEM_ID); + addStackStats(cluster, logstash, LOGSTASH_SYSTEM_ID); + addStackStats(cluster, beats, BEATS_SYSTEM_ID); mergeXPackStats(cluster, kibana, 'graph_workspace', 'graph'); // copy graph_workspace info out of kibana, merge it into stack_stats.xpack.graph return cluster; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_beats_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.js similarity index 97% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_beats_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.js index b8688e8b7a55e1..247e4e991125c3 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_beats_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.js @@ -6,7 +6,7 @@ import { get } from 'lodash'; import { createQuery } from './create_query'; -import { INDEX_PATTERN_BEATS } from '../../../../monitoring/common/constants'; +import { INDEX_PATTERN_BEATS } from '../../common/constants'; const HITS_SIZE = 10000; // maximum hits to receive from ES with each search @@ -118,9 +118,13 @@ export function processResults(results = [], { clusters, clusterHostSets, cluste clusterHb.monitors += heartbeatState.monitors; clusterHb.endpoints += heartbeatState.endpoints; for (const proto in heartbeatState) { - if (!heartbeatState.hasOwnProperty(proto)) continue; + if (!heartbeatState.hasOwnProperty(proto)) { + continue; + } const val = heartbeatState[proto]; - if (typeof val !== 'object') continue; + if (typeof val !== 'object') { + continue; + } if (!clusterHb.hasOwnProperty(proto)) { clusterHb[proto] = { diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_cluster_uuids.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.js similarity index 96% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_cluster_uuids.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.js index 9ea8de80e05d40..9caf4cac50e09c 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_cluster_uuids.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.js @@ -6,7 +6,7 @@ import { get } from 'lodash'; import { createQuery } from './create_query'; -import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../monitoring/common/constants'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; /** * Get a list of Cluster UUIDs that exist within the specified timespan. diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_es_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_es_stats.js similarity index 96% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_es_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_es_stats.js index 0a548711653387..f12c337c306048 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_es_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_es_stats.js @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../monitoring/common/constants'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; /** * Get statistics for all selected Elasticsearch clusters. diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_high_level_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_high_level_stats.js similarity index 97% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_high_level_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_high_level_stats.js index ecd6ccc2d2404f..889efe37e7a8fc 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_high_level_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_high_level_stats.js @@ -6,8 +6,16 @@ import { get } from 'lodash'; import { createQuery } from './create_query'; -import { INDEX_PATTERN_KIBANA, INDEX_PATTERN_BEATS, INDEX_PATTERN_LOGSTASH } from '../../../../monitoring/common/constants'; -import { KIBANA_SYSTEM_ID, BEATS_SYSTEM_ID, APM_SYSTEM_ID, LOGSTASH_SYSTEM_ID, TELEMETRY_QUERY_SOURCE } from '../../../common/constants'; +import { + INDEX_PATTERN_KIBANA, + INDEX_PATTERN_BEATS, + INDEX_PATTERN_LOGSTASH, + KIBANA_SYSTEM_ID, + BEATS_SYSTEM_ID, + APM_SYSTEM_ID, + LOGSTASH_SYSTEM_ID, + TELEMETRY_QUERY_SOURCE, +} from '../../common/constants'; /** * Update a counter associated with the {@code key}. diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_kibana_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_kibana_stats.js similarity index 98% rename from x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_kibana_stats.js rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_kibana_stats.js index d92593811b4cd5..9364392ab33aed 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/get_kibana_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_kibana_stats.js @@ -5,7 +5,7 @@ */ import { get, isEmpty, omit } from 'lodash'; -import { KIBANA_SYSTEM_ID } from '../../../common/constants'; +import { KIBANA_SYSTEM_ID } from '../../common/constants'; import { fetchHighLevelStats, handleHighLevelStatsResponse } from './get_high_level_stats'; export function rollUpTotals(rolledUp, addOn, field) { diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/get_stats.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_stats_with_monitoring.ts similarity index 58% rename from x-pack/legacy/plugins/telemetry/server/collectors/get_stats.ts rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_stats_with_monitoring.ts index 7b1af9e06b14dd..fdf46122f13b76 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/get_stats.ts +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_stats_with_monitoring.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getAllStats, getLocalStats } from './'; +// @ts-ignore +import { getAllStats } from './get_all_stats'; +import { getStatsWithXpack } from '../../../xpack_main/server/telemetry_collection'; /** * Get the telemetry data. @@ -16,30 +18,25 @@ import { getAllStats, getLocalStats } from './'; * @param {Boolean} unencrypted Is the request payload going to be unencrypted. * @return {Promise} An array of telemetry objects. */ -export async function getStats( +export async function getStatsWithMonitoring( req: any, config: any, start: string, end: string, - unencrypted: boolean, - statsGetters: any = {} + unencrypted: boolean ) { - const { _getAllStats = getAllStats, _getLocalStats = getLocalStats } = statsGetters; let response = []; const useInternalUser = !unencrypted; - if (config.get('xpack.monitoring.enabled')) { - try { - // attempt to collect stats from multiple clusters in monitoring data - response = await _getAllStats(req, start, end, { useInternalUser }); - } catch (err) { - // no-op - } + try { + // attempt to collect stats from multiple clusters in monitoring data + response = await getAllStats(req, start, end, { useInternalUser }); + } catch (err) { + // no-op } if (!Array.isArray(response) || response.length === 0) { - // return it as an array for a consistent API response - response = [await _getLocalStats(req, { useInternalUser })]; + response = await getStatsWithXpack(req, config, start, end, unencrypted); } return response; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/encryption/index.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/index.ts similarity index 77% rename from x-pack/legacy/plugins/telemetry/server/collectors/encryption/index.ts rename to x-pack/legacy/plugins/monitoring/server/telemetry_collection/index.ts index 0c3ad5a5e30196..db2301932cfdca 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/encryption/index.ts +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { encryptTelemetry } from './encrypt'; +export { getStatsWithMonitoring } from './get_stats_with_monitoring'; diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/csp_collector.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/csp_collector.ts deleted file mode 100644 index 3bacde22bdbc4c..00000000000000 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/csp_collector.ts +++ /dev/null @@ -1,39 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - createCSPRuleString, - DEFAULT_CSP_RULES, -} from '../../../../../../../../src/legacy/server/csp'; -import { HapiServer } from '../../../../'; - -export function createCspCollector(server: HapiServer) { - return { - type: 'csp', - isReady: () => true, - async fetch() { - const config = server.config(); - - // It's important that we do not send the value of csp.rules here as it - // can be customized with values that can be identifiable to given - // installs, such as URLs - const defaultRulesString = createCSPRuleString([...DEFAULT_CSP_RULES]); - const actualRulesString = createCSPRuleString(config.get('csp.rules')); - - return { - strict: config.get('csp.strict'), - warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'), - rulesChangedFromDefault: defaultRulesString !== actualRulesString, - }; - }, - }; -} - -export function registerCspCollector(server: HapiServer): void { - const { usage } = server; - const collector = usage.collectorSet.makeUsageCollector(createCspCollector(server)); - usage.collectorSet.register(collector); -} diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts index abe96fe061faaa..8b825b13178f20 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts @@ -5,10 +5,8 @@ */ import { HapiServer } from '../../../'; -import { registerCspCollector } from './csp'; import { registerVisualizationsCollector } from './visualizations/register_usage_collector'; export function registerCollectors(server: HapiServer) { registerVisualizationsCollector(server); - registerCspCollector(server); } diff --git a/x-pack/legacy/plugins/telemetry/common/constants.ts b/x-pack/legacy/plugins/telemetry/common/constants.ts deleted file mode 100644 index c50f36ac94497a..00000000000000 --- a/x-pack/legacy/plugins/telemetry/common/constants.ts +++ /dev/null @@ -1,89 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -/* - * config options opt into telemetry - * @type {string} - */ -export const CONFIG_TELEMETRY = 'telemetry:optIn'; -/* - * config description for opting into telemetry - * @type {string} - */ -export const getConfigTelemetryDesc = () => { - return i18n.translate('xpack.telemetry.telemetryConfigDescription', { - defaultMessage: - 'Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.', - }); -}; - -/** - * The name of the Kibana System ID used to publish and look up Kibana stats through the Monitoring system. - * @type {string} - */ -export const KIBANA_SYSTEM_ID = 'kibana'; - -/** - * The name of the Beats System ID used to publish and look up Beats stats through the Monitoring system. - * @type {string} - */ -export const BEATS_SYSTEM_ID = 'beats'; - -/** - * The name of the Apm System ID used to publish and look up Apm stats through the Monitoring system. - * @type {string} - */ -export const APM_SYSTEM_ID = 'beats'; - -/** - * The name of the Kibana System ID used to look up Logstash stats through the Monitoring system. - * @type {string} - */ -export const LOGSTASH_SYSTEM_ID = 'logstash'; - -/** - * The name of the Kibana System ID used to look up Reporting stats through the Monitoring system. - * @type {string} - */ -export const REPORTING_SYSTEM_ID = 'reporting'; - -/** - * The amount of time, in milliseconds, to wait between reports when enabled. - * - * Currently 24 hours. - * @type {Number} - */ -export const REPORT_INTERVAL_MS = 86400000; - -/* - * Key for the localStorage service - */ -export const LOCALSTORAGE_KEY = 'xpack.data'; - -/** - * Link to the Elastic Telemetry privacy statement. - */ -export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/telemetry-privacy-statement`; - -/** - * The type name used within the Monitoring index to publish localization stats. - * @type {string} - */ -export const KIBANA_LOCALIZATION_STATS_TYPE = 'localization'; - -/** - * The header sent by telemetry service when hitting Elasticsearch to identify query source - * @type {string} - */ -export const TELEMETRY_QUERY_SOURCE = 'TELEMETRY'; - -/** - * UI metric usage type - * @type {string} - */ -export const UI_METRIC_USAGE_TYPE = 'ui_metric'; diff --git a/x-pack/legacy/plugins/telemetry/common/get_xpack_config_with_deprecated.ts b/x-pack/legacy/plugins/telemetry/common/get_xpack_config_with_deprecated.ts deleted file mode 100644 index 9fa8ead5c8a09b..00000000000000 --- a/x-pack/legacy/plugins/telemetry/common/get_xpack_config_with_deprecated.ts +++ /dev/null @@ -1,15 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { KibanaConfig } from 'src/legacy/server/kbn_server'; - -export function getXpackConfigWithDeprecated(config: KibanaConfig, configPath: string) { - const deprecatedConfig = config.get(`xpack.xpack_main.${configPath}`); - if (typeof deprecatedConfig !== 'undefined') { - return deprecatedConfig; - } - return config.get(`xpack.${configPath}`); -} diff --git a/x-pack/legacy/plugins/telemetry/public/components/index.ts b/x-pack/legacy/plugins/telemetry/public/components/index.ts deleted file mode 100644 index 9675bd997b1833..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/components/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// @ts-ignore -export { TelemetryForm } from './telemetry_form'; -export { OptInExampleFlyout } from './opt_in_details_component'; -export { OptInBanner } from './opt_in_banner_component'; -export { OptInMessage } from './opt_in_message'; diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx deleted file mode 100644 index c58927c66756b7..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx +++ /dev/null @@ -1,21 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { OptInExampleFlyout } from './opt_in_details_component'; - -describe('OptInDetailsComponent', () => { - it('renders as expected', () => { - expect( - shallowWithIntl( - ({ data: [] }))} - onClose={jest.fn()} - /> - ) - ).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx deleted file mode 100644 index 9cfb55af1dab3d..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx +++ /dev/null @@ -1,150 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as React from 'react'; - -import { - EuiCallOut, - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, - EuiFlyout, - EuiFlyoutHeader, - EuiFlyoutBody, - EuiLoadingSpinner, - EuiPortal, // EuiPortal is a temporary requirement to use EuiFlyout with "ownFocus" - EuiText, - EuiTextColor, - EuiTitle, -} from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -interface Props { - fetchTelemetry: () => Promise; - onClose: () => void; -} - -interface State { - isLoading: boolean; - hasPrivilegeToRead: boolean; - data: any[] | null; -} - -/** - * React component for displaying the example data associated with the Telemetry opt-in banner. - */ -export class OptInExampleFlyout extends React.PureComponent { - public readonly state: State = { - data: null, - isLoading: true, - hasPrivilegeToRead: false, - }; - - componentDidMount() { - this.props - .fetchTelemetry() - .then(response => - this.setState({ - data: Array.isArray(response.data) ? response.data : null, - isLoading: false, - hasPrivilegeToRead: true, - }) - ) - .catch(err => { - this.setState({ - isLoading: false, - hasPrivilegeToRead: err.status !== 403, - }); - }); - } - - renderBody({ data, isLoading, hasPrivilegeToRead }: State) { - if (isLoading) { - return ( - - - - - - ); - } - - if (!hasPrivilegeToRead) { - return ( - - } - color="danger" - iconType="cross" - > - - - ); - } - - if (data === null) { - return ( - - } - color="danger" - iconType="cross" - > - - - ); - } - - return {JSON.stringify(data, null, 2)}; - } - - render() { - return ( - - - - -

- -

-
- - - - - -
- {this.renderBody(this.state)} -
-
- ); - } -} diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx b/x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx deleted file mode 100644 index 5be20f8de32c1e..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx +++ /dev/null @@ -1,97 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as React from 'react'; -import { EuiLink, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/constants'; -import { OptInExampleFlyout } from './opt_in_details_component'; - -interface Props { - fetchTelemetry: () => Promise; -} - -interface State { - showDetails: boolean; - showExample: boolean; -} - -export class OptInMessage extends React.PureComponent { - public readonly state: State = { - showDetails: false, - showExample: false, - }; - - toggleShowExample = () => { - this.setState(prevState => ({ - showExample: !prevState.showExample, - })); - }; - - render() { - const { showDetails, showExample } = this.state; - - const getDetails = () => ( - -

- - - - ), - telemetryPrivacyStatementLink: ( - - - - ), - }} - /> -

-
- ); - - const getFlyoutDetails = () => ( - this.setState({ showExample: false })} - fetchTelemetry={this.props.fetchTelemetry} - /> - ); - - const getReadMore = () => ( - this.setState({ showDetails: true })}> - - - ); - - return ( - - -

- {getConfigTelemetryDesc()} {!showDetails && getReadMore()} -

-
- {showDetails && getDetails()} - {showDetails && showExample && getFlyoutDetails()} -
- ); - } -} diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/__tests__/telemetry.js b/x-pack/legacy/plugins/telemetry/public/hacks/__tests__/telemetry.js deleted file mode 100644 index 2c14538d773741..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/__tests__/telemetry.js +++ /dev/null @@ -1,15 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { uiModules } from 'ui/modules'; - -// This overrides settings for other UI tests -uiModules.get('kibana') - // disable stat reporting while running tests, - // MockInjector used in these tests is not impacted - .constant('telemetryEnabled', false) - .constant('telemetryOptedIn', null) - .constant('telemetryUrl', 'not.a.valid.url.0'); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/fetch_telemetry.js b/x-pack/legacy/plugins/telemetry/public/hacks/fetch_telemetry.js deleted file mode 100644 index ced23c58566b94..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/fetch_telemetry.js +++ /dev/null @@ -1,26 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import uiChrome from 'ui/chrome'; -import moment from 'moment'; - -/** - * Fetch Telemetry data by calling the Kibana API. - * - * @param {Object} $http The HTTP handler - * @param {String} basePath The base URI - * @param {Function} _moment moment.js, but injectable for tests - * @return {Promise} An array of cluster Telemetry objects. - */ -export function fetchTelemetry($http, { basePath = uiChrome.getBasePath(), _moment = moment, unencrypted = false } = { }) { - return $http.post(`${basePath}/api/telemetry/v2/clusters/_stats`, { - unencrypted, - timeRange: { - min: _moment().subtract(20, 'minutes').toISOString(), - max: _moment().toISOString() - } - }); -} diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_init.ts b/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_init.ts deleted file mode 100644 index c44da2b36bf1e2..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_init.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; - * you may not use this file except in compliance with the Elastic License. - */ - -// @ts-ignore -import { uiModules } from 'ui/modules'; -// @ts-ignore -import { Path } from 'plugins/xpack_main/services/path'; -// @ts-ignore -import { npStart } from 'ui/new_platform'; -// @ts-ignore -import { Telemetry } from './telemetry'; -// @ts-ignore -import { fetchTelemetry } from './fetch_telemetry'; - -function telemetryInit($injector: any) { - const $http = $injector.get('$http'); - - const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); - - if (telemetryEnabled) { - // no telemetry for non-logged in users - if (Path.isUnauthenticated()) { - return; - } - - const sender = new Telemetry($injector, () => fetchTelemetry($http)); - sender.start(); - } -} - -uiModules.get('telemetry/hacks').run(telemetryInit); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_opt_in.js b/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_opt_in.js deleted file mode 100644 index a7c36516d004c7..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_opt_in.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { uiModules } from 'ui/modules'; - -import { injectBanner } from './welcome_banner'; - -uiModules.get('telemetry/hacks').run(injectBanner); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/index.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/index.js deleted file mode 100644 index 2eabe7496b1f2a..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { injectBanner } from './inject_banner'; diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js deleted file mode 100644 index a55027703f9513..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js +++ /dev/null @@ -1,30 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { renderBanner } from './render_banner'; - -describe('render_banner', () => { - - it('adds a banner to banners with priority of 10000', () => { - const bannerID = 'brucer-banner'; - - const telemetryOptInProvider = { setBannerId: jest.fn() }; - const banners = { add: jest.fn().mockReturnValue(bannerID) }; - const fetchTelemetry = jest.fn(); - - renderBanner(telemetryOptInProvider, fetchTelemetry, { _banners: banners }); - - expect(banners.add).toBeCalledTimes(1); - expect(fetchTelemetry).toBeCalledTimes(0); - expect(telemetryOptInProvider.setBannerId).toBeCalledWith(bannerID); - - const bannerConfig = banners.add.mock.calls[0][0]; - - expect(bannerConfig.component).not.toBe(undefined); - expect(bannerConfig.priority).toBe(10000); - }); - -}); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.js deleted file mode 100644 index 5685132a95061f..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.js +++ /dev/null @@ -1,20 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { handleOldSettings } from './handle_old_settings'; - -/** - * Determine if the banner should be displayed. - * - * This method can have side-effects related to deprecated config settings. - * - * @param {Object} config The advanced settings config object. - * @param {Object} _handleOldSettings handleOldSettings function, but overridable for tests. - * @return {Boolean} {@code true} if the banner should be displayed. {@code false} otherwise. - */ -export async function shouldShowBanner(telemetryOptInProvider, config, { _handleOldSettings = handleOldSettings } = {}) { - return telemetryOptInProvider.getOptIn() === null && await _handleOldSettings(config, telemetryOptInProvider); -} diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js deleted file mode 100644 index 63bfcb4b2a3273..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js +++ /dev/null @@ -1,27 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { injectedMetadataServiceMock } from '../../../../../../src/core/public/mocks'; -const injectedMetadataMock = injectedMetadataServiceMock.createStartContract(); - -export function mockInjectedMetadata({ telemetryOptedIn }) { - const mockGetInjectedVar = jest.fn().mockImplementation((key) => { - switch (key) { - case 'telemetryOptedIn': return telemetryOptedIn; - default: throw new Error(`unexpected injectedVar ${key}`); - } - }); - - injectedMetadataMock.getInjectedVar = mockGetInjectedVar; -} - -jest.doMock('ui/new_platform', () => ({ - npStart: { - core: { - injectedMetadata: injectedMetadataMock - }, - }, -})); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts deleted file mode 100644 index 9c1bfde1d27b91..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// @ts-ignore -export { TelemetryOptInProvider } from '../../../../../../src/legacy/core_plugins/kibana/public/home/telemetry_opt_in'; // eslint-disable-line diff --git a/x-pack/legacy/plugins/telemetry/public/views/management/index.js b/x-pack/legacy/plugins/telemetry/public/views/management/index.js deleted file mode 100644 index 0ed6fe09ef80a4..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/views/management/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import './management'; diff --git a/x-pack/legacy/plugins/telemetry/public/views/management/management.js b/x-pack/legacy/plugins/telemetry/public/views/management/management.js deleted file mode 100644 index 9e580f109d5369..00000000000000 --- a/x-pack/legacy/plugins/telemetry/public/views/management/management.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import routes from 'ui/routes'; - -import { registerSettingsComponent, PAGE_FOOTER_COMPONENT } from 'ui/management'; -import { TelemetryOptInProvider } from '../../services/telemetry_opt_in'; -import { TelemetryForm } from '../../components'; - -routes.defaults(/\/management/, { - resolve: { - telemetryManagementSection: function (Private, spacesEnabled, activeSpace) { - const telemetryOptInProvider = Private(TelemetryOptInProvider); - - const spaceProps = { - spacesEnabled, - }; - - if (spacesEnabled) { - spaceProps.activeSpace = activeSpace ? activeSpace.space : null; - } - - const Component = (props) => ( - - ); - - registerSettingsComponent(PAGE_FOOTER_COMPONENT, Component, true); - } - } -}); diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/encryption/encrypt.ts b/x-pack/legacy/plugins/telemetry/server/collectors/encryption/encrypt.ts deleted file mode 100644 index 8907292df8445c..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/encryption/encrypt.ts +++ /dev/null @@ -1,19 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createRequestEncryptor } from '@elastic/request-crypto'; -import { telemetryJWKS } from './telemetry_jwks'; - -export function getKID(isProd = false): string { - return isProd ? 'kibana' : 'kibana_dev'; -} - -export async function encryptTelemetry(payload: any, isProd = false): Promise { - const kid = getKID(isProd); - const encryptor = await createRequestEncryptor(telemetryJWKS); - const clusters = [].concat(payload); - return Promise.all(clusters.map((cluster: any) => encryptor.encrypt(kid, cluster))); -} diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/index.ts b/x-pack/legacy/plugins/telemetry/server/collectors/index.ts deleted file mode 100644 index c9b94a8ea5d5eb..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/index.ts +++ /dev/null @@ -1,15 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -// @ts-ignore -export { getAllStats } from './monitoring'; -// @ts-ignore -export { getLocalStats } from './local'; -export { getStats } from './get_stats'; -export { encryptTelemetry } from './encryption'; -export { createTelemetryUsageCollector } from './usage'; -export { createUiMetricUsageCollector } from './ui_metric'; -export { createLocalizationUsageCollector } from './localization'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_cluster_info.js b/x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_cluster_info.js deleted file mode 100644 index dfe7998718a9ec..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_cluster_info.js +++ /dev/null @@ -1,28 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { getClusterInfo } from '../get_cluster_info'; - -export function mockGetClusterInfo(callCluster, clusterInfo, req) { - callCluster.withArgs(req, 'info').returns(clusterInfo); - callCluster.withArgs('info').returns(clusterInfo); -} - -describe('get_cluster_info', () => { - - it('uses callCluster to get info API', () => { - const callCluster = sinon.stub(); - const response = Promise.resolve({}); - - mockGetClusterInfo(callCluster, response); - - expect(getClusterInfo(callCluster)).to.be(response); - }); - -}); diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_cluster_stats.js b/x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_cluster_stats.js deleted file mode 100644 index 29e7e620e3313a..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_cluster_stats.js +++ /dev/null @@ -1,36 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { TIMEOUT } from '../constants'; -import { getClusterStats } from '../get_cluster_stats'; - -export function mockGetClusterStats(callCluster, clusterStats, req) { - callCluster.withArgs(req, 'cluster.stats', { - timeout: TIMEOUT - }) - .returns(clusterStats); - - callCluster.withArgs('cluster.stats', { - timeout: TIMEOUT - }) - .returns(clusterStats); -} - -describe('get_cluster_stats', () => { - - it('uses callCluster to get cluster.stats API', () => { - const callCluster = sinon.stub(); - const response = Promise.resolve({}); - - mockGetClusterStats(callCluster, response); - - expect(getClusterStats(callCluster)).to.be(response); - }); - -}); diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_cluster_info.js b/x-pack/legacy/plugins/telemetry/server/collectors/local/get_cluster_info.js deleted file mode 100644 index 550af3692b97fb..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_cluster_info.js +++ /dev/null @@ -1,17 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * Get the cluster info from the connected cluster. - * - * This is the equivalent to GET / - * - * @param {function} callCluster The callWithInternalUser handler (exposed for testing) - * @return {Promise} The response from Elasticsearch. - */ -export function getClusterInfo(callCluster) { - return callCluster('info'); -} diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_cluster_stats.js b/x-pack/legacy/plugins/telemetry/server/collectors/local/get_cluster_stats.js deleted file mode 100644 index 34152f8fea4e71..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_cluster_stats.js +++ /dev/null @@ -1,21 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { TIMEOUT } from './constants'; - -/** - * Get the cluster stats from the connected cluster. - * - * This is the equivalent to GET /_cluster/stats?timeout=30s. - * - * @param {function} callCluster The callWithInternalUser handler (exposed for testing) - * @return {Promise} The response from Elasticsearch equivalent to GET /_cluster/stats. - */ -export function getClusterStats(callCluster) { - return callCluster('cluster.stats', { - timeout: TIMEOUT - }); -} diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_kibana.js b/x-pack/legacy/plugins/telemetry/server/collectors/local/get_kibana.js deleted file mode 100644 index 50e68e2b74d687..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_kibana.js +++ /dev/null @@ -1,49 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get, omit } from 'lodash'; - -export function handleKibanaStats(server, response) { - if (!response) { - server.log(['warning', 'telemetry', 'local-stats'], 'No Kibana stats returned from usage collectors'); - return; - } - - const { kibana, kibana_stats: stats, ...plugins } = response; - - const platform = get(stats, 'os.platform', 'unknown'); - const platformRelease = get(stats, 'os.platformRelease', 'unknown'); - - let version; - const { kbnServer } = get(server, 'plugins.xpack_main.status.plugin'); - if (kbnServer) { - version = kbnServer.version.replace(/-snapshot/i, ''); - } - - // combine core stats (os types, saved objects) with plugin usage stats - // organize the object into the same format as monitoring-enabled telemetry - return { - ...omit(kibana, 'index'), // discard index - count: 1, - indices: 1, - os: { - platforms: [{ platform, count: 1 }], - platformReleases: [{ platformRelease, count: 1 }], - }, - versions: [{ version, count: 1 }], - plugins, - }; -} - -/* - * Check user privileges for read access to monitoring - * Pass callWithInternalUser to bulkFetchUsage - */ -export async function getKibana(server, callWithInternalUser) { - const { collectorSet } = server.usage; - const usage = await collectorSet.bulkFetch(callWithInternalUser); - return collectorSet.toObject(usage); -} diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/index.js b/x-pack/legacy/plugins/telemetry/server/collectors/local/index.js deleted file mode 100644 index b78fca22c1af74..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/local/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { getLocalStats } from './get_local_stats'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts b/x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts deleted file mode 100644 index 36f76516e188be..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts +++ /dev/null @@ -1,28 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Readable } from 'stream'; - -jest.doMock('fs', () => ({ - createReadStream(filepath: string): Readable { - if (filepath === 'ERROR') { - throw new Error('MOCK ERROR - Invalid Path'); - } - const readableStream = new Readable(); - const streamData = filepath.split(''); - let cursor = 0; - - readableStream._read = function(size) { - const current = streamData[cursor++]; - if (typeof current === 'undefined') { - return this.push(null); - } - this.push(current); - }; - - return readableStream; - }, -})); diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/localization/index.ts b/x-pack/legacy/plugins/telemetry/server/collectors/localization/index.ts deleted file mode 100644 index dd8a3a9a104001..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/localization/index.ts +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { createLocalizationUsageCollector } from './telemetry_localization_collector'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/index.js b/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/index.js deleted file mode 100644 index 4500385367d1d6..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/monitoring/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { getAllStats } from './get_all_stats'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/index.ts b/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/index.ts deleted file mode 100644 index f5a49587d49c8c..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/index.ts +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { createUiMetricUsageCollector } from './telemetry_ui_metric_collector'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/usage/index.ts b/x-pack/legacy/plugins/telemetry/server/collectors/usage/index.ts deleted file mode 100644 index 960fed00152b4f..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/collectors/usage/index.ts +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { createTelemetryUsageCollector } from './telemetry_usage_collector'; diff --git a/x-pack/legacy/plugins/telemetry/server/index.ts b/x-pack/legacy/plugins/telemetry/server/index.ts deleted file mode 100644 index 5748106be8b290..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { PluginInitializerContext } from 'src/core/server'; -import { TelemetryPlugin } from './plugin'; -import * as constants from '../common/constants'; - -export { getTelemetryOptIn } from './get_telemetry_opt_in'; -export const telemetryPlugin = (initializerContext: PluginInitializerContext) => - new TelemetryPlugin(); -export { constants }; diff --git a/x-pack/legacy/plugins/telemetry/server/plugin.ts b/x-pack/legacy/plugins/telemetry/server/plugin.ts deleted file mode 100644 index 13aeb774d4a68b..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/plugin.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CoreSetup } from 'src/core/server'; -import { registerRoutes } from './routes'; - -export class TelemetryPlugin { - public setup(core: CoreSetup) { - registerRoutes(core); - } -} diff --git a/x-pack/legacy/plugins/telemetry/server/routes/index.ts b/x-pack/legacy/plugins/telemetry/server/routes/index.ts deleted file mode 100644 index f6880b644699c5..00000000000000 --- a/x-pack/legacy/plugins/telemetry/server/routes/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CoreSetup } from 'src/core/server'; -import { registerOptInRoutes } from './opt_in'; -import { registerTelemetryDataRoutes } from './telemetry_stats'; - -export function registerRoutes(core: CoreSetup) { - registerOptInRoutes(core); - registerTelemetryDataRoutes(core); -} diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js index e589caa2819e6e..9af4448a8d6151 100644 --- a/x-pack/legacy/plugins/xpack_main/index.js +++ b/x-pack/legacy/plugins/xpack_main/index.js @@ -9,7 +9,6 @@ import dedent from 'dedent'; import { XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS } from '../../server/lib/constants'; -import { getXpackConfigWithDeprecated } from '../telemetry/common/get_xpack_config_with_deprecated'; import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; import { replaceInjectedVars } from './server/lib/replace_injected_vars'; import { setupXPackMain } from './server/lib/setup_xpack_main'; @@ -17,15 +16,10 @@ import { xpackInfoRoute, settingsRoute } from './server/routes/api/v1'; import { has } from 'lodash'; -function movedToTelemetry(configPath) { - return (settings, log) => { - if (has(settings, configPath)) { - log(`Config key ${configPath} is deprecated. Use "xpack.telemetry.${configPath}" instead.`); - } - }; -} - export { callClusterFactory } from './server/lib/call_cluster_factory'; +import { getStatsWithXpack } from './server/telemetry_collection'; +import { telemetryCollectionManager } from '../../../../src/legacy/core_plugins/telemetry/server'; + export const xpackMain = (kibana) => { return new kibana.Plugin({ id: 'xpack_main', @@ -62,7 +56,6 @@ export const xpackMain = (kibana) => { const config = server.config(); return { - telemetryEnabled: getXpackConfigWithDeprecated(config, 'telemetry.enabled'), activeSpace: null, spacesEnabled: config.get('xpack.spaces.enabled'), }; @@ -86,6 +79,8 @@ export const xpackMain = (kibana) => { mirrorPluginStatus(server.plugins.elasticsearch, this, 'yellow', 'red'); + telemetryCollectionManager.setStatsGetter(getStatsWithXpack, 'local_xpack', 1); + featuresPlugin.registerLegacyAPI({ xpackInfo: setupXPackMain(server), savedObjectTypes: server.savedObjects.types @@ -95,10 +90,19 @@ export const xpackMain = (kibana) => { xpackInfoRoute(server); settingsRoute(server, this.kbnServer); }, - deprecations: () => [ - movedToTelemetry('telemetry.config'), - movedToTelemetry('telemetry.url'), - movedToTelemetry('telemetry.enabled'), - ], + deprecations: () => { + function movedToTelemetry(configPath) { + return (settings, log) => { + if (has(settings, configPath)) { + log(`Config key "xpack.xpack_main.${configPath}" is deprecated. Use "telemetry.${configPath}" instead.`); + } + }; + } + return [ + movedToTelemetry('telemetry.config'), + movedToTelemetry('telemetry.url'), + movedToTelemetry('telemetry.enabled'), + ]; + }, }); }; diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js index c926170a2a4de6..f094f588cf8581 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js +++ b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js @@ -10,13 +10,8 @@ import expect from '@kbn/expect'; import { replaceInjectedVars } from '../replace_injected_vars'; import { KibanaRequest } from '../../../../../../../src/core/server'; -const buildRequest = (telemetryOptedIn = null, path = '/app/kibana') => { +const buildRequest = (path = '/app/kibana') => { const get = sinon.stub(); - if (telemetryOptedIn === null) { - get.withArgs('telemetry', 'telemetry').rejects(new Error('not found exception')); - } else { - get.withArgs('telemetry', 'telemetry').resolves({ attributes: { enabled: telemetryOptedIn } }); - } return { path, @@ -50,7 +45,6 @@ describe('replaceInjectedVars uiExport', () => { const newVars = await replaceInjectedVars(originalInjectedVars, request, server); expect(newVars).to.eql({ a: 1, - telemetryOptedIn: null, xpackInitialInfo: { b: 1 }, @@ -73,7 +67,6 @@ describe('replaceInjectedVars uiExport', () => { const newVars = await replaceInjectedVars(originalInjectedVars, request, server); expect(newVars).to.eql({ a: 1, - telemetryOptedIn: null, xpackInitialInfo: { b: 1 }, @@ -89,7 +82,6 @@ describe('replaceInjectedVars uiExport', () => { const newVars = await replaceInjectedVars(originalInjectedVars, request, server); expect(newVars).to.eql({ a: 1, - telemetryOptedIn: null, xpackInitialInfo: { b: 1 }, @@ -98,14 +90,13 @@ describe('replaceInjectedVars uiExport', () => { it('respects the telemetry opt-in document when opted-out', async () => { const originalInjectedVars = { a: 1 }; - const request = buildRequest(false); + const request = buildRequest(); const server = mockServer(); server.plugins.xpack_main.info.license.isOneOf.returns(true); const newVars = await replaceInjectedVars(originalInjectedVars, request, server); expect(newVars).to.eql({ a: 1, - telemetryOptedIn: false, xpackInitialInfo: { b: 1 }, @@ -114,14 +105,13 @@ describe('replaceInjectedVars uiExport', () => { it('respects the telemetry opt-in document when opted-in', async () => { const originalInjectedVars = { a: 1 }; - const request = buildRequest(true); + const request = buildRequest(); const server = mockServer(); server.plugins.xpack_main.info.license.isOneOf.returns(true); const newVars = await replaceInjectedVars(originalInjectedVars, request, server); expect(newVars).to.eql({ a: 1, - telemetryOptedIn: true, xpackInitialInfo: { b: 1 }, @@ -137,7 +127,6 @@ describe('replaceInjectedVars uiExport', () => { const newVars = await replaceInjectedVars(originalInjectedVars, request, server); expect(newVars).to.eql({ a: 1, - telemetryOptedIn: false, xpackInitialInfo: { b: 1 }, @@ -174,7 +163,6 @@ describe('replaceInjectedVars uiExport', () => { const newVars = await replaceInjectedVars(originalInjectedVars, request, server); expect(newVars).to.eql({ a: 1, - telemetryOptedIn: null, xpackInitialInfo: undefined, uiCapabilities: { navLinks: { foo: true }, diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/replace_injected_vars.js b/x-pack/legacy/plugins/xpack_main/server/lib/replace_injected_vars.js index 9def7da5e7e4f6..cd111240261c49 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/replace_injected_vars.js +++ b/x-pack/legacy/plugins/xpack_main/server/lib/replace_injected_vars.js @@ -5,14 +5,12 @@ */ import { KibanaRequest } from '../../../../../../src/core/server'; -import { getTelemetryOptIn } from '../../../telemetry/server'; export async function replaceInjectedVars(originalInjectedVars, request, server) { const xpackInfo = server.plugins.xpack_main.info; const withXpackInfo = async () => ({ ...originalInjectedVars, - telemetryOptedIn: await getTelemetryOptIn(request), xpackInitialInfo: xpackInfo.isAvailable() ? xpackInfo.toJSON() : undefined, }); diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_xpack.js b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/local/__tests__/get_xpack.js rename to x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/constants.js b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/constants.ts similarity index 100% rename from x-pack/legacy/plugins/telemetry/server/collectors/local/constants.js rename to x-pack/legacy/plugins/xpack_main/server/telemetry_collection/constants.ts diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.ts b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.ts new file mode 100644 index 00000000000000..f19695ca065257 --- /dev/null +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore +import { getXPack } from './get_xpack'; +import { getLocalStats } from '../../../../../../src/legacy/core_plugins/telemetry/server/telemetry_collection'; + +/** + * Get the telemetry data. + * + * @param {Object} req The incoming request. + * @param {Object} config Kibana config. + * @param {String} start The start time of the request (likely 20m ago). + * @param {String} end The end time of the request. + * @param {Boolean} unencrypted Is the request payload going to be unencrypted. + * @return {Promise} An array of telemetry objects. + */ +export async function getStatsWithXpack( + req: any, + config: any, + start: string, + end: string, + unencrypted: boolean +) { + const useInternalUser = !unencrypted; + const { server } = req; + const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster('data'); + const callCluster = useInternalUser + ? callWithInternalUser + : (...args: any[]) => callWithRequest(req, ...args); + + const localStats = await getLocalStats(req, { useInternalUser }); + const { license, xpack } = await getXPack(callCluster); + + localStats.license = license; + localStats.stack_stats.xpack = xpack; + + return [localStats]; +} diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_xpack.js b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js similarity index 99% rename from x-pack/legacy/plugins/telemetry/server/collectors/local/get_xpack.js rename to x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js index 3909741c8c5561..07d635ef52e22a 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/local/get_xpack.js +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js @@ -83,4 +83,3 @@ export function getXPack(callCluster) { }) .catch(() => { return {}; }); } - diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/index.ts b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/index.ts similarity index 80% rename from x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/index.ts rename to x-pack/legacy/plugins/xpack_main/server/telemetry_collection/index.ts index a7c1088f9961b2..553f8dc0c41880 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/csp/index.ts +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { registerCspCollector } from './csp_collector'; +export { getStatsWithXpack } from './get_stats_with_xpack'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f3253132604c7c..59ac561065fa08 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2464,8 +2464,6 @@ "kbn.visualize.wizard.step1Breadcrumb": "作成", "kbn.visualize.wizard.step2Breadcrumb": "作成", "kbn.visualizeTitle": "可視化", - "kbn.home.telemetry.optInErrorToastText": "使用状況統計設定の設定中にエラーが発生しました。", - "kbn.home.telemetry.optInErrorToastTitle": "エラー", "kbn.advancedSettings.discover.searchOnPageLoadText": "ディスカバリの最初の読み込み時に検索を実行するかを制御します。この設定は、保存された検索の読み込み時には影響しません。", "kbn.advancedSettings.discover.searchOnPageLoadTitle": "ページの読み込み時の検索", "kbn.advancedSettings.visualization.heatmap.maxBucketsText": "1 つのデータソースが返せるバケットの最大数です。値が大きいとブラウザのレンダリング速度が下がる可能性があります。", @@ -2480,17 +2478,6 @@ "kbn.home.addData.siem.addSiemEventsButtonLabel": "セキュリティイベントを追加", "kbn.home.addData.siem.nameDescription": "即利用可能なビジュアライゼーションで、セキュリティイベントをまとめてインタラクティブな調査を可能にします。", "kbn.home.addData.siem.nameTitle": "SIEM", - "kbn.home.telemetry.callout.clusterStatisticsDescription": "これは収集される基本的なクラスター統計の例です。インデックス、チャート、ノードの数が含まれます。監視がオンになっているかどうかなどのハイレベルの使用統計も含まれます。", - "kbn.home.telemetry.callout.clusterStatisticsTitle": "クラスター統計", - "kbn.home.telemetry.callout.errorLoadingClusterStatisticsDescription": "クラスター統計の取得中に予期せぬエラーが発生しました。Elasticsearch、Kibana、またはネットワークのエラーが原因の可能性があります。Kibana を確認し、ページを再読み込みして再試行してください。", - "kbn.home.telemetry.callout.errorLoadingClusterStatisticsTitle": "クラスター統計の読み込みエラー", - "kbn.home.telemetry.callout.errorUnprivilegedUserDescription": "暗号化されていないクラスター統計を表示するアクセス権がありません。", - "kbn.home.telemetry.callout.errorUnprivilegedUserTitle": "クラスター統計の表示エラー", - "kbn.home.telemetry.optInMessage.detailsDescription": "ユーザーが処理したり保管したりするデータに関する情報は一切送信されません。この機能は定期的に基本的な機能利用に関する統計情報を送信します。{exampleLink} をご覧いただくか、{telemetryPrivacyStatementLink} をお読みください。この機能はいつでも無効にできます。", - "kbn.home.telemetry.optInMessage.detailsExampleLinkText": "例", - "kbn.home.telemetry.optInMessage.detailsTelemetryPrivacyStatementLinkText": "遠隔測定に関するプライバシーステートメント", - "kbn.home.telemetry.optInMessage.readMoreLinkText": "さらに詳しく", - "kbn.home.telemetry.optInMessageDescription": "基本的な機能利用に関する統計情報を提供して Elastic Stack の改善にご協力ください。このデータは Elastic 社外と共有されません。", "kbn.home.telemtery.optInCardConfirmButtonLabel": "はい", "kbn.home.telemtery.optInCardDeclineButtonLabel": "いいえ", "kbn.home.telemtery.optInCardTitle": "Elastic Stack の改善にご協力ください", @@ -10127,66 +10114,28 @@ "xpack.spaces.spaceSelector.noSpacesMatchSearchCriteriaDescription": "検索条件に一致するスペースがありません", "xpack.spaces.spaceSelector.selectSpacesTitle": "スペースの選択", "xpack.spaces.spacesTitle": "スペース", - "xpack.spaces.management.copyToSpace.actionDescription": "この保存されたオブジェクトを 1 つまたは複数のスペースにコピーします。", - "xpack.spaces.management.copyToSpace.actionTitle": "スペースにコピー", - "xpack.spaces.management.copyToSpace.automaticallyOverwrite": "すべての保存されたオブジェクトを自動的に上書き", - "xpack.spaces.management.copyToSpace.copyDetail.overwriteButton": "上書き", - "xpack.spaces.management.copyToSpace.copyDetail.skipOverwriteButton": "スキップ", - "xpack.spaces.management.copyToSpace.copyErrorTitle": "保存されたオブジェクトのコピー中にエラーが発生", - "xpack.spaces.management.copyToSpace.copyResultsLabel": "コピー結果", - "xpack.spaces.management.copyToSpace.copyStatus.conflictsMessage": "このスペースには同じ ID 「{id}」の保存されたオブジェクトが既に存在します。", - "xpack.spaces.management.copyToSpace.copyStatus.conflictsOverwriteMessage": "「上書き」をクリックしてこのバージョンをコピーされたバージョンに置き換えます。", - "xpack.spaces.management.copyToSpace.copyStatus.pendingOverwriteMessage": "保存されたオブジェクトは上書きされます。「スキップ」をクリックしてこの操作をキャンセルします。", - "xpack.spaces.management.copyToSpace.copyStatus.successMessage": "保存されたオブジェクトがコピーされました。", - "xpack.spaces.management.copyToSpace.copyStatus.unresolvableErrorMessage": "この保存されたオブジェクトのコピー中にエラーが発生しました。", - "xpack.spaces.management.copyToSpace.copyStatusSummary.conflictsMessage": "{space} スペースに 1 つまたは複数の矛盾が検出されました。解決するにはこのセクションを拡張してください。", - "xpack.spaces.management.copyToSpace.copyStatusSummary.failedMessage": "{space} スペースへのコピーに失敗しました。詳細はこのセクションを拡張してください。", - "xpack.spaces.management.copyToSpace.copyStatusSummary.successMessage": "{space} スペースにコピーされました。", - "xpack.spaces.management.copyToSpace.copyToSpacesButton": "{spaceCount} 個の{spaceCount, plural, one {スペース} other {スペース}}にコピー", - "xpack.spaces.management.copyToSpace.disabledCopyToSpacesButton": "コピー", - "xpack.spaces.management.copyToSpace.dontIncludeRelatedLabel": "関連性のある保存されたオブジェクトを含みません", - "xpack.spaces.management.copyToSpace.dontOverwriteLabel": "保存されたオブジェクトを上書きしません", - "xpack.spaces.management.copyToSpace.finishCopyToSpacesButton": "終了", - "xpack.spaces.management.copyToSpace.finishedButtonLabel": "コピーが完了しました。", - "xpack.spaces.management.copyToSpace.finishPendingOverwritesCopyToSpacesButton": "{overwriteCount} 件のオブジェクトを上書き", - "xpack.spaces.management.copyToSpace.includeRelatedFormLabel": "関連性のある保存されたオブジェクトを含みます", - "xpack.spaces.management.copyToSpace.includeRelatedLabel": "関連性のある保存されたオブジェクトを含みます", - "xpack.spaces.management.copyToSpace.inProgressButtonLabel": "コピーが進行中です。お待ちください。", - "xpack.spaces.management.copyToSpace.noSpacesBody": "コピーできるスペースがありません。", - "xpack.spaces.management.copyToSpace.noSpacesTitle": "スペースがありません", - "xpack.spaces.management.copyToSpace.overwriteLabel": "保存されたオブジェクトを自動的に上書きしています", - "xpack.spaces.management.copyToSpace.resolveCopyErrorTitle": "保存されたオブジェクトの矛盾の解決中にエラーが発生", - "xpack.spaces.management.copyToSpace.resolveCopySuccessTitle": "上書き成功", - "xpack.spaces.management.copyToSpace.selectSpacesLabel": "コピー先のスペースを選択してください", - "xpack.spaces.management.copyToSpace.spacesLoadErrorTitle": "利用可能なスペースを読み込み中にエラーが発生", - "xpack.spaces.management.copyToSpaceFlyoutFooter.conflictCount": "スキップ", - "xpack.spaces.management.copyToSpaceFlyoutFooter.errorCount": "エラー", - "xpack.spaces.management.copyToSpaceFlyoutFooter.pendingCount": "保留中", - "xpack.spaces.management.copyToSpaceFlyoutFooter.successCount": "コピー完了", - "xpack.spaces.management.copyToSpaceFlyoutHeader": "保存されたオブジェクトのスペースへのコピー", - "xpack.telemetry.callout.appliesSettingTitle": "この設定は {allOfKibanaText} に適用されます", - "xpack.telemetry.callout.appliesSettingTitle.allOfKibanaText": "Kibana のすべて", - "xpack.telemetry.callout.clusterStatisticsDescription": "これは収集される基本的なクラスター統計の例です。インデックス、チャート、ノードの数が含まれます。監視がオンになっているかどうかなどのハイレベルの使用統計も含まれます。", - "xpack.telemetry.callout.clusterStatisticsTitle": "クラスター統計", - "xpack.telemetry.callout.errorLoadingClusterStatisticsDescription": "クラスター統計の取得中に予期せぬエラーが発生しました。Elasticsearch、Kibana、またはネットワークのエラーが原因の可能性があります。Kibana を確認し、ページを再読み込みして再試行してください。", - "xpack.telemetry.callout.errorLoadingClusterStatisticsTitle": "クラスター統計の読み込みエラー", - "xpack.telemetry.callout.errorUnprivilegedUserDescription": "暗号化されていないクラスター統計を表示するアクセス権がありません。", - "xpack.telemetry.callout.errorUnprivilegedUserTitle": "クラスター統計の表示エラー", - "xpack.telemetry.readOurUsageDataPrivacyStatementLinkText": "使用データのプライバシーステートメントをお読みください", - "xpack.telemetry.seeExampleOfWhatWeCollectLinkText": "収集されるデータの例を見る", - "xpack.telemetry.telemetryConfigDescription": "基本的な機能利用に関する統計情報を提供して Elastic Stack の改善にご協力ください。このデータは Elastic 社外と共有されません。", - "xpack.telemetry.telemetryConfigTitle": "遠隔測定オプトイン", - "xpack.telemetry.telemetryErrorNotificationMessageDescription.tryAgainText": "Kibana と Elasticsearch が現在も実行中であることを確認し、再試行してください。", - "xpack.telemetry.telemetryErrorNotificationMessageDescription.unableToSaveTelemetryPreferenceText": "遠隔測定設定を保存できません。", - "xpack.telemetry.telemetryErrorNotificationMessageTitle": "遠隔測定エラー", - "xpack.telemetry.usageDataTitle": "使用データ", - "xpack.telemetry.welcomeBanner.noButtonLabel": "いいえ", - "xpack.telemetry.welcomeBanner.telemetryConfigDescription.readMoreLinkText": "続きを読む", - "xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription": "ユーザーが処理したり保管したりするデータに関する情報は一切送信されません。この機能は定期的に基本的な機能利用に関する統計情報を送信します。{exampleLink} をご覧いただくか、{telemetryPrivacyStatementLink} をお読みください。この機能はいつでも無効にできます。", - "xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription.exampleLinkText": "例", - "xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "遠隔測定に関するプライバシーステートメント", - "xpack.telemetry.welcomeBanner.yesButtonLabel": "はい", - "xpack.telemetry.welcomeBanner.title": "Elastic Stack の改善にご協力ください!", + "telemetry.callout.appliesSettingTitle": "この設定は {allOfKibanaText} に適用されます", + "telemetry.callout.appliesSettingTitle.allOfKibanaText": "Kibana のすべて", + "telemetry.callout.clusterStatisticsDescription": "これは収集される基本的なクラスター統計の例です。インデックス、チャート、ノードの数が含まれます。監視がオンになっているかどうかなどのハイレベルの使用統計も含まれます。", + "telemetry.callout.clusterStatisticsTitle": "クラスター統計", + "telemetry.callout.errorLoadingClusterStatisticsDescription": "クラスター統計の取得中に予期せぬエラーが発生しました。Elasticsearch、Kibana、またはネットワークのエラーが原因の可能性があります。Kibana を確認し、ページを再読み込みして再試行してください。", + "telemetry.callout.errorLoadingClusterStatisticsTitle": "クラスター統計の読み込みエラー", + "telemetry.callout.errorUnprivilegedUserDescription": "暗号化されていないクラスター統計を表示するアクセス権がありません。", + "telemetry.callout.errorUnprivilegedUserTitle": "クラスター統計の表示エラー", + "telemetry.readOurUsageDataPrivacyStatementLinkText": "使用データのプライバシーステートメントをお読みください", + "telemetry.seeExampleOfWhatWeCollectLinkText": "収集されるデータの例を見る", + "telemetry.telemetryConfigDescription": "基本的な機能利用に関する統計情報を提供して Elastic Stack の改善にご協力ください。このデータは Elastic 社外と共有されません。", + "telemetry.telemetryConfigTitle": "遠隔測定オプトイン", + "telemetry.telemetryErrorNotificationMessageDescription.tryAgainText": "Kibana と Elasticsearch が現在も実行中であることを確認し、再試行してください。", + "telemetry.telemetryErrorNotificationMessageDescription.unableToSaveTelemetryPreferenceText": "遠隔測定設定を保存できません。", + "telemetry.telemetryErrorNotificationMessageTitle": "遠隔測定エラー", + "telemetry.usageDataTitle": "使用データ", + "telemetry.welcomeBanner.noButtonLabel": "いいえ", + "telemetry.welcomeBanner.telemetryConfigDescription.readMoreLinkText": "続きを読む", + "telemetry.welcomeBanner.telemetryConfigDetailsDescription": "ユーザーが処理したり保管したりするデータに関する情報は一切送信されません。この機能は定期的に基本的な機能利用に関する統計情報を送信します。{exampleLink} をご覧いただくか、{telemetryPrivacyStatementLink} をお読みください。この機能はいつでも無効にできます。", + "telemetry.welcomeBanner.telemetryConfigDetailsDescription.exampleLinkText": "例", + "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "遠隔測定に関するプライバシーステートメント", + "telemetry.welcomeBanner.yesButtonLabel": "はい", "xpack.upgradeAssistant.appTitle": "{version} アップグレードアシスタント", "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail": "{snapshotRestoreDocsButton} でデータをバックアップします。", "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel": "API のスナップショットと復元", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4e8a81028aa889..cf40c5cb6e9737 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2465,8 +2465,6 @@ "kbn.visualize.wizard.step1Breadcrumb": "创建", "kbn.visualize.wizard.step2Breadcrumb": "创建", "kbn.visualizeTitle": "可视化", - "kbn.home.telemetry.optInErrorToastText": "尝试设置使用统计信息首选项时发生错误。", - "kbn.home.telemetry.optInErrorToastTitle": "错误", "kbn.advancedSettings.discover.searchOnPageLoadText": "控制在 Discover 首次加载时是否执行搜索。加载已保存搜索时,此设置无效。", "kbn.advancedSettings.discover.searchOnPageLoadTitle": "在页面加载时搜索", "kbn.advancedSettings.visualization.heatmap.maxBucketsText": "单个数据源可以返回的最大存储桶数目。较高的数目可能对浏览器呈现性能有负面影响", @@ -2481,17 +2479,6 @@ "kbn.home.addData.siem.addSiemEventsButtonLabel": "添加安全事件", "kbn.home.addData.siem.nameDescription": "集中安全事件,以通过即用型可视化实现交互式调查。", "kbn.home.addData.siem.nameTitle": "SIEM", - "kbn.home.telemetry.callout.clusterStatisticsDescription": "这是我们将收集的基本集群统计信息的示例。其包括索引、分片和节点的数目。还包括高级使用情况统计信息,例如监测是否打开。", - "kbn.home.telemetry.callout.clusterStatisticsTitle": "集群统计信息", - "kbn.home.telemetry.callout.errorLoadingClusterStatisticsDescription": "尝试提取集群统计信息时发生意外错误。发生此问题的原因可能是 Elasticsearch 出故障、Kibana 出故障,或者有网络错误。检查 Kibana,然后重新加载页面并重试。", - "kbn.home.telemetry.callout.errorLoadingClusterStatisticsTitle": "加载集群统计信息时出错", - "kbn.home.telemetry.callout.errorUnprivilegedUserDescription": "您无权查看未加密的集群统计信息。", - "kbn.home.telemetry.callout.errorUnprivilegedUserTitle": "显示集群统计信息时出错", - "kbn.home.telemetry.optInMessage.detailsDescription": "不会发送有关您处理或存储的数据的信息。此功能将定期发送基本功能使用情况统计信息。请参阅{exampleLink}或阅读我们的{telemetryPrivacyStatementLink}。您可以随时禁用此功能。", - "kbn.home.telemetry.optInMessage.detailsExampleLinkText": "示例", - "kbn.home.telemetry.optInMessage.detailsTelemetryPrivacyStatementLinkText": "遥测隐私声明", - "kbn.home.telemetry.optInMessage.readMoreLinkText": "阅读更多内容", - "kbn.home.telemetry.optInMessageDescription": "通过提供基本功能的使用情况统计信息,来帮助我们改进 Elastic Stack。我们不会在 Elastic 之外共享此数据。", "kbn.home.telemtery.optInCardConfirmButtonLabel": "是", "kbn.home.telemtery.optInCardDeclineButtonLabel": "否", "kbn.home.telemtery.optInCardTitle": "帮助我们改进 Elastic Stack", @@ -10283,66 +10270,28 @@ "xpack.spaces.spaceSelector.noSpacesMatchSearchCriteriaDescription": "没有匹配搜索条件的空间", "xpack.spaces.spaceSelector.selectSpacesTitle": "选择您的空间", "xpack.spaces.spacesTitle": "工作区", - "xpack.spaces.management.copyToSpace.actionDescription": "将此已保存对象复制到一个或多个工作区", - "xpack.spaces.management.copyToSpace.actionTitle": "复制到工作区", - "xpack.spaces.management.copyToSpace.automaticallyOverwrite": "自动覆盖所有已保存对象", - "xpack.spaces.management.copyToSpace.copyDetail.overwriteButton": "覆盖", - "xpack.spaces.management.copyToSpace.copyDetail.skipOverwriteButton": "跳过", - "xpack.spaces.management.copyToSpace.copyErrorTitle": "复制已保存对象时出错", - "xpack.spaces.management.copyToSpace.copyResultsLabel": "复制结果", - "xpack.spaces.management.copyToSpace.copyStatus.conflictsMessage": "具有匹配 ID ({id}) 的已保存对象在此工作区中已存在。", - "xpack.spaces.management.copyToSpace.copyStatus.conflictsOverwriteMessage": "单击“覆盖”可将此版本替换为复制的版本。", - "xpack.spaces.management.copyToSpace.copyStatus.pendingOverwriteMessage": "已保存对象将被覆盖。单击“跳过”可取消此操作。", - "xpack.spaces.management.copyToSpace.copyStatus.successMessage": "已保存对象成功复制。", - "xpack.spaces.management.copyToSpace.copyStatus.unresolvableErrorMessage": "复制此已保存对象时出错。", - "xpack.spaces.management.copyToSpace.copyStatusSummary.conflictsMessage": "在 {space} 工作区中检测到一个或多个冲突。展开此部分以进行解决。", - "xpack.spaces.management.copyToSpace.copyStatusSummary.failedMessage": "复制到 {space} 工作区失败。展开此部分以获取详情。", - "xpack.spaces.management.copyToSpace.copyStatusSummary.successMessage": "已成功复制到 {space} 工作区。", - "xpack.spaces.management.copyToSpace.copyToSpacesButton": "复制到 {spaceCount} 个 {spaceCount, plural, one {工作区} other {工作区}}", - "xpack.spaces.management.copyToSpace.disabledCopyToSpacesButton": "复制", - "xpack.spaces.management.copyToSpace.dontIncludeRelatedLabel": "不包括相关已保存对象", - "xpack.spaces.management.copyToSpace.dontOverwriteLabel": "未覆盖已保存对象", - "xpack.spaces.management.copyToSpace.finishCopyToSpacesButton": "完成", - "xpack.spaces.management.copyToSpace.finishedButtonLabel": "复制已完成。", - "xpack.spaces.management.copyToSpace.finishPendingOverwritesCopyToSpacesButton": "覆盖 {overwriteCount} 个对象", - "xpack.spaces.management.copyToSpace.includeRelatedFormLabel": "包括相关已保存对象", - "xpack.spaces.management.copyToSpace.includeRelatedLabel": "包括相关已保存对象", - "xpack.spaces.management.copyToSpace.inProgressButtonLabel": "复制正在进行中。请稍候。", - "xpack.spaces.management.copyToSpace.noSpacesBody": "没有要复制到的合格工作区。", - "xpack.spaces.management.copyToSpace.noSpacesTitle": "没有可用的工作区", - "xpack.spaces.management.copyToSpace.overwriteLabel": "正在自动覆盖已保存对象", - "xpack.spaces.management.copyToSpace.resolveCopyErrorTitle": "解决已保存对象冲突时出错", - "xpack.spaces.management.copyToSpace.resolveCopySuccessTitle": "覆盖成功", - "xpack.spaces.management.copyToSpace.selectSpacesLabel": "选择要复制到的工作区", - "xpack.spaces.management.copyToSpace.spacesLoadErrorTitle": "加载可用工作区时出错", - "xpack.spaces.management.copyToSpaceFlyoutFooter.conflictCount": "已跳过", - "xpack.spaces.management.copyToSpaceFlyoutFooter.errorCount": "错误", - "xpack.spaces.management.copyToSpaceFlyoutFooter.pendingCount": "待处理", - "xpack.spaces.management.copyToSpaceFlyoutFooter.successCount": "已复制", - "xpack.spaces.management.copyToSpaceFlyoutHeader": "将已保存对象复制到工作区", - "xpack.telemetry.callout.appliesSettingTitle": "此设置适用于{allOfKibanaText}", - "xpack.telemetry.callout.appliesSettingTitle.allOfKibanaText": "所有 Kibana。", - "xpack.telemetry.callout.clusterStatisticsDescription": "这是我们将收集的基本集群统计信息的示例。其包括索引、分片和节点的数目。还包括高级使用情况统计信息,例如监测是否打开。", - "xpack.telemetry.callout.clusterStatisticsTitle": "集群统计信息", - "xpack.telemetry.callout.errorLoadingClusterStatisticsDescription": "尝试提取集群统计信息时发生意外错误。发生此问题的原因可能是 Elasticsearch 出故障、Kibana 出故障,或者有网络错误。检查 Kibana,然后重新加载页面并重试。", - "xpack.telemetry.callout.errorLoadingClusterStatisticsTitle": "加载集群统计信息时出错", - "xpack.telemetry.callout.errorUnprivilegedUserDescription": "您无权查看未加密的集群统计信息。", - "xpack.telemetry.callout.errorUnprivilegedUserTitle": "显示集群统计信息时出错", - "xpack.telemetry.readOurUsageDataPrivacyStatementLinkText": "阅读我们的使用情况数据隐私声明", - "xpack.telemetry.seeExampleOfWhatWeCollectLinkText": "查看我们收集的内容示例", - "xpack.telemetry.telemetryConfigDescription": "通过提供基本功能的使用情况统计信息,来帮助我们改进 Elastic Stack。我们不会在 Elastic 之外共享此数据。", - "xpack.telemetry.telemetryConfigTitle": "遥测选择加入", - "xpack.telemetry.telemetryErrorNotificationMessageDescription.tryAgainText": "确认 Kibana 和 Elasticsearch 仍在运行,然后重试。", - "xpack.telemetry.telemetryErrorNotificationMessageDescription.unableToSaveTelemetryPreferenceText": "无法保存遥测首选项。", - "xpack.telemetry.telemetryErrorNotificationMessageTitle": "遥测错误", - "xpack.telemetry.usageDataTitle": "使用情况数据", - "xpack.telemetry.welcomeBanner.noButtonLabel": "否", - "xpack.telemetry.welcomeBanner.telemetryConfigDescription.readMoreLinkText": "阅读更多内容", - "xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription": "不会发送有关您处理或存储的数据的信息。此功能将定期发送基本功能使用情况统计信息。请参阅{exampleLink}或阅读我们的{telemetryPrivacyStatementLink}。您可以随时禁用此功能。", - "xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription.exampleLinkText": "示例", - "xpack.telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "遥测隐私声明", - "xpack.telemetry.welcomeBanner.yesButtonLabel": "是", - "xpack.telemetry.welcomeBanner.title": "帮助我们改进 Elastic Stack!", + "telemetry.callout.appliesSettingTitle": "此设置适用于{allOfKibanaText}", + "telemetry.callout.appliesSettingTitle.allOfKibanaText": "所有 Kibana。", + "telemetry.callout.clusterStatisticsDescription": "这是我们将收集的基本集群统计信息的示例。其包括索引、分片和节点的数目。还包括高级使用情况统计信息,例如监测是否打开。", + "telemetry.callout.clusterStatisticsTitle": "集群统计信息", + "telemetry.callout.errorLoadingClusterStatisticsDescription": "尝试提取集群统计信息时发生意外错误。发生此问题的原因可能是 Elasticsearch 出故障、Kibana 出故障,或者有网络错误。检查 Kibana,然后重新加载页面并重试。", + "telemetry.callout.errorLoadingClusterStatisticsTitle": "加载集群统计信息时出错", + "telemetry.callout.errorUnprivilegedUserDescription": "您无权查看未加密的集群统计信息。", + "telemetry.callout.errorUnprivilegedUserTitle": "显示集群统计信息时出错", + "telemetry.readOurUsageDataPrivacyStatementLinkText": "阅读我们的使用情况数据隐私声明", + "telemetry.seeExampleOfWhatWeCollectLinkText": "查看我们收集的内容示例", + "telemetry.telemetryConfigDescription": "通过提供基本功能的使用情况统计信息,来帮助我们改进 Elastic Stack。我们不会在 Elastic 之外共享此数据。", + "telemetry.telemetryConfigTitle": "遥测选择加入", + "telemetry.telemetryErrorNotificationMessageDescription.tryAgainText": "确认 Kibana 和 Elasticsearch 仍在运行,然后重试。", + "telemetry.telemetryErrorNotificationMessageDescription.unableToSaveTelemetryPreferenceText": "无法保存遥测首选项。", + "telemetry.telemetryErrorNotificationMessageTitle": "遥测错误", + "telemetry.usageDataTitle": "使用情况数据", + "telemetry.welcomeBanner.noButtonLabel": "否", + "telemetry.welcomeBanner.telemetryConfigDescription.readMoreLinkText": "阅读更多内容", + "telemetry.welcomeBanner.telemetryConfigDetailsDescription": "不会发送有关您处理或存储的数据的信息。此功能将定期发送基本功能使用情况统计信息。请参阅{exampleLink}或阅读我们的{telemetryPrivacyStatementLink}。您可以随时禁用此功能。", + "telemetry.welcomeBanner.telemetryConfigDetailsDescription.exampleLinkText": "示例", + "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "遥测隐私声明", + "telemetry.welcomeBanner.yesButtonLabel": "是", "xpack.upgradeAssistant.appTitle": "{version} 升级助手", "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail": "使用 {snapshotRestoreDocsButton} 备份您的数据。", "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel": "快照和还原 API", diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index d920b368922cd5..3ec86a7fc17220 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -79,12 +79,12 @@ export default async function ({ readConfigFile }) { '--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d', '--xpack.maps.showMapsInspectorAdapter=true', '--xpack.maps.preserveDrawingBuffer=true', - '--xpack.telemetry.banner=false', '--xpack.reporting.queue.pollInterval=3000', // make it explicitly the default '--xpack.reporting.csv.maxSizeBytes=2850', // small-ish limit for cutting off a 1999 byte report '--stats.maximumWaitTimeForAllCollectorsInS=1', '--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions '--xpack.encrypted_saved_objects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"', + '--telemetry.banner=false', '--timelion.ui.enabled=true', ], }, From c39eee8eea4a5c1365bd9795e459ca1852127a11 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 16 Oct 2019 09:35:53 +0200 Subject: [PATCH 3/7] Fix saved query in app state in discvoer (#47849) --- .../kibana/public/dashboard/dashboard_app_controller.tsx | 2 +- .../kibana/public/discover/controllers/discover.js | 8 ++++++-- .../core_plugins/kibana/public/visualize/editor/editor.js | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 60004374eb33ee..eb49277fbc7b47 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -503,7 +503,7 @@ export class DashboardAppController { $scope.savedQuery = undefined; return; } - if ($scope.savedQuery && newSavedQueryId !== $scope.savedQuery.id) { + if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery: SavedQuery) => { $scope.$evalAsync(() => { $scope.savedQuery = savedQuery; diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index 8a6570781e87db..e517b2a02a31ca 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -220,7 +220,6 @@ function discoverController( $scope.minimumVisibleRows = 50; $scope.fetchStatus = fetchStatuses.UNINITIALIZED; $scope.refreshInterval = timefilter.getRefreshInterval(); - $scope.savedQuery = $route.current.locals.savedQuery; $scope.showSaveQuery = uiCapabilities.discover.saveQuery; $scope.$watch(() => uiCapabilities.discover.saveQuery, (newCapability) => { @@ -549,6 +548,11 @@ function discoverController( }; const shouldSearchOnPageLoad = () => { + // If a saved query is referenced in the app state, omit the initial load because the saved query will + // be fetched separately and trigger a reload + if ($scope.state.savedQuery) { + return false; + } // A saved search is created on every page load, so we check the ID to see if we're loading a // previously saved search or if it is just transient return config.get('discover:searchOnPageLoad') @@ -980,7 +984,7 @@ function discoverController( return; } - if ($scope.savedQuery && newSavedQueryId !== $scope.savedQuery.id) { + if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => { $scope.$evalAsync(() => { $scope.savedQuery = savedQuery; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 85604448dc8d07..2b6636165169f1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -547,7 +547,7 @@ function VisEditor( $scope.savedQuery = undefined; return; } - if ($scope.savedQuery && newSavedQueryId !== $scope.savedQuery.id) { + if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => { $scope.$evalAsync(() => { $scope.savedQuery = savedQuery; From 5dda3b1d6abbbf5e15fc85c0afa94ba9d58ba7e1 Mon Sep 17 00:00:00 2001 From: Sebastian Grodzicki Date: Wed, 16 Oct 2019 10:10:42 +0200 Subject: [PATCH 4/7] Update Logs & Metrics UI team name (#47942) --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ccfc58bd084f12..4c06d2aee017bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -27,8 +27,8 @@ /x-pack/test/functional/apps/code/ @teams/code /x-pack/test/api_integration/apis/code/ @teams/code -# Infrastructure and Logs UI -/x-pack/legacy/plugins/infra/ @elastic/infra-logs-ui +# Logs & Metrics UI +/x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui # Machine Learning /x-pack/legacy/plugins/ml/ @elastic/ml-ui From 314ba8269cea1e2836c48fec918d5c5e39400cc7 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 16 Oct 2019 10:23:58 +0200 Subject: [PATCH 5/7] Remove unused console app file (#48001) --- .../console/public/quarantined/src/app.js | 94 ------------------- 1 file changed, 94 deletions(-) delete mode 100644 src/legacy/core_plugins/console/public/quarantined/src/app.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/app.js b/src/legacy/core_plugins/console/public/quarantined/src/app.js deleted file mode 100644 index e6bb96726dad9a..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/app.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const $ = require('jquery'); - -const DEFAULT_INPUT_VALUE = `GET _search -{ - "query": { - "match_all": {} - } -}`; - -export default function init(input, output, history, sourceLocation = 'stored') { - $(document.body).removeClass('fouc'); - - // set the value of the input and clear the output - function resetToValues(content) { - input.update(content != null ? content : DEFAULT_INPUT_VALUE); - output.update(''); - } - - function setupAutosave() { - let timer; - const saveDelay = 500; - - input.getSession().on('change', function onChange() { - if (timer) { - timer = clearTimeout(timer); - } - timer = setTimeout(saveCurrentState, saveDelay); - }); - } - - function saveCurrentState() { - try { - const content = input.getValue(); - history.updateCurrentState(content); - } catch (e) { - console.log('Ignoring saving error: ' + e); - } - } - function loadSavedState() { - const previousSaveState = history.getSavedEditorState(); - - if (sourceLocation === 'stored') { - if (previousSaveState) { - resetToValues(previousSaveState.content); - } else { - resetToValues(); - } - } else if (/^https?:\/\//.test(sourceLocation)) { - const loadFrom = { - url: sourceLocation, - // Having dataType here is required as it doesn't allow jQuery to `eval` content - // coming from the external source thereby preventing XSS attack. - dataType: 'text', - kbnXsrfToken: false, - }; - - if (/https?:\/\/api.github.com/.test(sourceLocation)) { - loadFrom.headers = { Accept: 'application/vnd.github.v3.raw' }; - } - - $.ajax(loadFrom).done(data => { - resetToValues(data); - input.moveToNextRequestEdge(true); - input.highlightCurrentRequestsAndUpdateActionBar(); - input.updateActionsBar(); - }); - } else { - resetToValues(); - } - input.moveToNextRequestEdge(true); - } - - setupAutosave(); - loadSavedState(); -} From 730ba21ed4d0758cb4d6cb6fb179568f11b4aaf7 Mon Sep 17 00:00:00 2001 From: Andrew Goldstein Date: Wed, 16 Oct 2019 02:30:06 -0600 Subject: [PATCH 6/7] [SIEM] Endgame Row Renderers: DNS, File (FIM), Network, Security (Authentication), Process (#48277) ## [SIEM] Endgame Row Renderers: DNS, File (FIM), Network, Security (Authentication), Process This PR renders Endgame events via _row renderers_ in the Timeline, per the following screenshot: ![endgame-row-renderers](https://user-images.githubusercontent.com/4459398/66854649-fa6d7900-ef3e-11e9-97cc-5b229041f186.png) The following Endgame event types / subtypes will be rendered via row renderers in the Timeline: * DNS (`dns_event`) - [X] `request_event` * File (FIM) (`file_event`) - [X] `file_create_event` - [X] `file_delete_event` * Network (`network_event`) - [X] `ipv4_connection_accept_event` - [X] `ipv6_connection_accept_event` - [X] `ipv4_disconnect_received_event` - [X] `ipv6_disconnect_received_event` * Security (Authentication) (`security_event`) - [X] `user_logon` - [X] `admin_logon` - [X] `explicit_user_logon` - [X] `user_logoff` * Process (`process_event`) - [X] `creation_event` - [X] `termination_event` This PR also adds row rendering support for some non-Endgame events that conform to the [Elastic Common Schema](https://www.elastic.co/guide/en/ecs/current/index.html) (ECS): * DNS requests * FIM file creation events * FIM file deletion events RELEASE NOTE: To view Endgame events in existing SIEM deployments, you must manually add `endgame-*` to the SIEM index pattern in `Kibana Management > Advanced Settings > SIEM > Elasticsearch indices`. ## DNS Request events Endgame DNS events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` endgame.event_type_full: dns_event and endgame.event_subtype_full: request_event ``` _To view these Endgame DNS events in a timeline, add `endgame-*` to the `SIEM` > `Elasticsearch indices` setting in Kibana `Advanced Settings`, then paste the query above into a timeline to view events._ ### Runtime matching criteria All DNS events, including Endgame and non-Endgame DNS events matching the following criteria will be rendered: ``` dns.question.type: * and dns.question.name: * ``` _The query above can be executed in a timeline to view all data that will be rendered via the (new) DNS event row renderer._ ### Sample rendered DNS event ![endgame-dns-event](https://user-images.githubusercontent.com/4459398/66856414-643b5200-ef42-11e9-8d50-894b7f7abf3d.png) Each field with `this formatting` will be draggable (to pivot a search) in the row-rendered event: `Arun` \ `Anvi-Acer` @ `HD-obe-8bf77f54` asked for `clients4.google.com` with question type `A`, which resolved to `10.58.197.78` (response code: `NOERROR`) via `chrome.exe` `(11620)` [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `3008`] ### Fields in a DNS event The following fields will be used to render a DNS event: `user.name` \ `user.domain` @ `host.name` asked for `dns.question.name` with question type `dns.question.type`, which resolved to `dns.resolved_ip` (resp code: `dns.response_code`) via `process.name` `(process.pid)` [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `event.code | winlog.event_id`] Note: At the time of this writing, Endgame DNS events do not populate `dns.response_code`. Row renderers are designed to still render partial results when fields are missing. In this case the following text: > (resp code: `dns.response_code`) will NOT be rendered, but the other (populated) fields in the DNS event will be rendered. ### Additional Rendering of DNS events by the Netflow row renderer In addition to being rendered by the new DNS renderer described above, DNS events will also be rendered by the Netflow row renderer. The Neflow row renderer shows the directionality, protocol, and flow of data between a source and destination ### Non-Endgame DNS events The following screenshot shows a DNS event from `packetbeat` rendered by the new DNS row renderer: ![non-endgame-dns-event](https://user-images.githubusercontent.com/4459398/66857061-b7fa6b00-ef43-11e9-894a-d717539db96c.png) _A non-Endgame DNS event that conforms to ECS_ ## File (FIM) Creation events Endgame File (FIM) Creation events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` endgame.event_type_full: file_event and endgame.event_subtype_full: file_create_event ``` ### Runtime matching criteria All file creation events, including Endgame and non-Endgame events matching the following criteria will be rendered: ``` (event.category: file and event.action: file_create_event) or (event.dataset: file and event.action: created) ``` ### Sample rendered File (FIM) Creation event ![file-create-event](https://user-images.githubusercontent.com/4459398/66857794-3f94a980-ef45-11e9-9030-fff35403e8f4.png) `Arun` \ `Anvi-Acer` @ `HD-obe-8bf77f54` created file `the-real-index~RFa99cd75.TMP` in `C:\Users\Arun\AppData\Local\Google\Chrome\User Data\Default\Service Worker\CacheStorage\579544fd7d0441717f082c9eb123588966aa57ac\d81a98b1-59b9-43b2-a228-b3daf7da56df\index-dir\the-real-index~RFa99cd75.TMP` via `chrome.exe` `(11620)` ### Fields in a File (FIM) Creation event `user.name` \ `user.domain` @ `host.name` created file `file.name | endgame.file_name` in `file.path | endgame.file_path` via `process.name | endgame.process_name` `(process.pid | endgame.pid)` ## File (FIM) Deletion events Endgame File (FIM) Deletion events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` endgame.event_type_full: file_event and endgame.event_subtype_full: file_delete_event ``` ### Runtime matching criteria All file deletion events, including Endgame and non-Endgame events matching the following criteria will be rendered: ``` (event.category: file and event.action: file_delete_event) or (event.dataset: file and event.action: deleted) ``` ### Sample rendered File (FIM) Deletion event ![file-delete-event](https://user-images.githubusercontent.com/4459398/66857970-9a2e0580-ef45-11e9-97bb-219c8673a2f2.png) `SYSTEM` \ `NT AUTHORITY` @ `HD-v1s-d2118419` deleted file `tmp0000031a` in `C:\Windows\TEMP\tmp00000404\tmp0000031a` via `AmSvc.exe` `(1084)` ### Fields in a File (FIM) Deletion event `user.name` \ `user.domain` @ `host.name` deleted file `file.name | endgame.file_name` in `file.path | endgame.file_path` via `process.name | endgame.process_name` `(process.pid | endgame.pid)` ## Network Connection Accepted events Endgame Network Connection Accepted events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` (endgame.event_type_full: network_event and endgame.event_subtype_full: ipv4_connection_accept_event) or (endgame.event_type_full: network_event and endgame.event_subtype_full: ipv6_connection_accept_event) ```` ### Runtime matching criteria All Endgame Connection Accepted events, and existing "socket opened" events matching the following criteria will be rendered: ``` event.action: ipv4_connection_accept_event or event.action: ipv6_connection_accept_event or event.action: socket_opened ``` ### Sample rendered Network Connection Accepted event ![ipv4-connection-accept-event](https://user-images.githubusercontent.com/4459398/66858241-16c0e400-ef46-11e9-9fa8-f8b852490bd8.png) `SYSTEM` \ `NT AUTHORITY` @ `HD-gqf-0af7b4fe` accepted a connection via `AmSvc.exe` `(1084)` Network Connection Accepted events are also be rendered with the Netflow row renderer, like the `event.action: socket_opened` events are rendered today. The Network Connection Accepted row renderer displays information about the principal actors in the event (i.e. `user.name`, `host.name`, `process.name`), and the Netflow row renderer displays information about the directionality, source / destination, protocol, etc. ### Fields in a Network Connection Accepted event `user.name` \ `user.domain` @ `host.name` accepted a connection via `process.name` `(process.pid)` ## Network Disconnect Received events Endgame Network Disconnect Received events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` (endgame.event_type_full: network_event and endgame.event_subtype_full: ipv4_disconnect_received_event) or (endgame.event_type_full: network_event and endgame.event_subtype_full: ipv6_disconnect_received_event) ```` ### Runtime matching criteria All Endgame Network Disconnect Received events, and existing "socket closed" events matching the following criteria will be rendered: ``` event.action: ipv4_disconnect_received_event or event.action: ipv6_disconnect_received_event or event.action: socket_closed ``` ### Sample rendered Network Disconnect Received event ![ipv4-disconnect-received-event](https://user-images.githubusercontent.com/4459398/66859155-fa25ab80-ef47-11e9-995c-7628fc0885bf.png) `SYSTEM` \ `NT AUTHORITY` @ `HD-gqf-0af7b4fe` disconnected via `AmSvc.exe` `(1084)` The existing row renderer for `event.action: socket_closed` will be enhanced to display additional fields: - `user.domain` - `process.pid` Network Disconnect Received events will also be rendered with the Netflow row renderer, like the `event.action: socket_closed` events are rendered today. The Network Connection Accepted row renderer displays information about the principal actors in the event (i.e. `user.name`, `host.name`, `process.name`), and the Netflow row renderer displays information about the directionality, source / destination, protocol, etc. ### Fields in a Network Disconnect Received event `user.name` \ `user.domain` @ `host.name` disconnected via `process.name` `(process.pid)` ## Security (Authentication) User Logon events Endgame Security (Authentication) User Logon events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` endgame.event_type_full: security_event and endgame.event_subtype_full: user_logon ``` ### Runtime matching criteria Security (Authentication) User Logon events matching the following criteria will be rendered: ``` event.category: authentication and event.action: user_logon ``` ### Sample rendered Security (Authentication) User Logon event ![user-logon](https://user-images.githubusercontent.com/4459398/66859339-525cad80-ef48-11e9-851c-c08c302df0fc.png) `SYSTEM` \ `NT AUTHORITY` @ `HD-v1s-d2118419` successfully logged in using logon type `5 - Service` (target logon ID `0x3e7`) via `C:\Windows\System32\services.exe` (`432`) as requested by subject `WIN-Q3DOP1UKA81$` \ `WORKGROUP` (source logon ID `0x3e7`) [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `4624`] ### Fields in an Security (Authentication) User Logon event `user.name` \ `user.domain` @ `host.name` successfully logged in using logon type `endgame.logon_type` (target logon ID `endgame.target_logon_id`) via `process.name | process.executable` (`process.pid`) as requested by subject `endgame.subject_user_name` \ `endgame.subject_domain_name` (subject logon ID `endgame.subject_logon_id`) [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `event.code | winlog.event_id`] ### Reference: LogonType Enumerations The following enumerated values will humanize the numeric `endgame.logon_type` field: ``` 2 - Interactive 3 - Network 4 - Batch 5 - Service 7 - Unlock 8 - Network Cleartext 9 - New Credentials 10 - Remote Interactive 11 - Cached Interactive ``` ## Security (Authentication) Admin Logon events Endgame Security (Authentication) Admin Logon events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` endgame.event_type_full: security_event and endgame.event_subtype_full: admin_logon ``` ### Runtime matching criteria Security (Authentication) Admin Logon events matching the following criteria will be rendered: ``` event.category: authentication and event.action: admin_logon ``` ### Sample rendered Security (Authentication) Admin Logon event ![admin-logon](https://user-images.githubusercontent.com/4459398/66860598-bc765200-ef4a-11e9-9e58-a96c2b97f4e1.png) With special privileges, `SYSTEM` \ `NT AUTHORITY` @ `HD-v1s-d2118419` successfully logged in via `C:\Windows\System32\services.exe` (`964`) as requested by subject `SYSTEM` \ `NT AUTHORITY` (subject logon ID `0x3e7`) [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `4672`] ### Fields in a Security (Authentication) Admin Logon event With special privileges, `user.name` \ `user.domain` @ `host.name` successfully logged in via `process.name | process.executable` (`process.pid`) as requested by subject `endgame.subject_user_name` \ `endgame.subject_domain_name` (subject logon ID `endgame.subject_logon_id`) [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `event.code | winlog.event_id`] ## Security (Authentication) Explicit User Logon events Endgame Security (Authentication) Explicit User Logon events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` endgame.event_type_full: security_event and endgame.event_subtype_full: explicit_user_logon ``` ### Runtime matching criteria Security (Authentication) Explicit User Logon events matching the following criteria will be rendered: ``` event.category: authentication and event.action: explicit_user_logon ``` ### Sample rendered Security (Authentication) Explicit User Logon event ![explicit-user-logon](https://user-images.githubusercontent.com/4459398/66860797-170fae00-ef4b-11e9-88c5-befd3dcab070.png) A login was attempted using explicit credentials `Arun` \ `Anvi-Acer` to `HD-v1s-d2118419` via `C:\Windows\System32\services.exe` (`1736`) as requested by subject `ANVI-ACER$` \ `WORKGROUP` (subject logon ID `0x3e7`) [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `4648`] ### Fields in an Security (Authentication) Explicit User Logon event A login was attempted using explicit credentials `endgame.target_user_name` \ `endgame.target_domain_name` to `host.name` via `process.name | process.executable` (`process.pid`) as requested by subject `endgame.subject_user_name` \ `endgame.subject_domain_name` (subject logon ID `endgame.subject_logon_id`) [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `event.code | winlog.event_id`] ## Security (Authentication) User Logoff events Endgame Security (Authentication) User Logoff events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` endgame.event_type_full: security_event and endgame.event_subtype_full: user_logoff ``` ### Runtime matching criteria Security (Authentication) User Logoff events matching the following criteria will be rendered: ``` event.category: authentication and event.action: user_logoff ``` ### Sample rendered Security (Authentication) User Logoff event ![user-logoff](https://user-images.githubusercontent.com/4459398/66861089-9a310400-ef4b-11e9-9f71-b148409c75a7.png) `Arun` \ `Anvi-Acer` @ `HD-55b-3ec87f66` logged off using logon type `2 - Interactive` (target logon ID `0x16db41e`) via `C:\Windows\System32\services.exe` (`964`) [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `4634` ] ### Fields in Security (Authentication) User Logoff event `endgame.target_user_name` \ `endgame.target_domain_name` @ `host.name` logged off using logon type `endgame.logon_type` (target logon ID `endgame.target_logon_id`) via `process.name | process.executable` (`process.pid`) [![windows-logo](https://user-images.githubusercontent.com/4459398/66249835-e3d15180-e6f6-11e9-89c3-5517c5ed1596.png) `event.code | winlog.event_id`] ## Process Creation events Endgame Process Creation events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` endgame.event_type_full: process_event and endgame.event_subtype_full: creation_event ``` ### Runtime matching criteria Process Creation events matching the following criteria will be rendered: ``` event.category: process and event.action: creation_event ``` ### Sample rendered Process Creation event ![creation-event](https://user-images.githubusercontent.com/4459398/66861295-fbf16e00-ef4b-11e9-9455-8a1f13463974.png) `Arun` \ `Anvi-Acer` @ `HD-obe-8bf77f54` started process `Microsoft.Photos.exe` (`441684`) `-ServerName:App.AppXzst44mncqdg84v7sv6p7yznqwssy6f7f.mca` via parent process `svchost.exe` (`8`) `sha256 d4c97ed46046893141652e2ec0056a698f6445109949d7fcabbce331146889ee` `sha1 12563599116157778a22600d2a163d8112aed845` `md5 62d06d7235b37895b68de56687895743` ### Fields in a Process Creation event The following fields will be used to render a Process Creation event: `user.name` \ `user.domain` @ `host.name` started process `process.name` (`process.pid`) `process.args` via parent process `endgame.parent_process_name` (`process.ppid`) `process.hash.sha256` `process.hash.sha1` `process.hash.md5` ## Process Termination events Endgame Process Termination events with the following event type and subtype will be rendered in the Timeline via row renderers: ``` endgame.event_type_full: process_event and endgame.event_subtype_full: termination_event ``` ### Runtime matching criteria Process Termination events matching the following criteria will be rendered: ``` event.category: process and event.action: termination_event ``` ### Sample rendered Process Termination event ![termination-event](https://user-images.githubusercontent.com/4459398/66861495-57bbf700-ef4c-11e9-8e6e-923e9c6bab3e.png) `Arun` \ `Anvi-Acer` @ `HD-obe-8bf77f54` terminated process `RuntimeBroker.exe` (`442384`) with exit code `0` `sha256 87976f3430cc99bc939e0694247c0759961a49832b87218f4313d6fc0bc3a776` `sha1 797255e72d5ed5c058d4785950eba7abaa057653` `md5 bd4401441a21bf1abce6404f4231db4d` ### Fields in a Process Termination event The following fields will be used to render a Process Termination event: `user.name` \ `user.domain` @ `host.name` terminated process `process.name` (`process.pid`) with exit code `endgame.exit_code` `process.hash.sha256` `process.hash.sha1` `process.hash.md5` ## Testing Desk tested in: * Dark / light mode * Chrome `77.0.3865.90` * Firefox `69.0.3` * Safari `13.0.1` * NOT tested in IE11 (due to current blocker) https://github.com/elastic/ecs-dev/issues/178 --- .../__snapshots__/args.test.tsx.snap | 55 +- .../process_draggable.test.tsx.snap | 2 + .../user_host_working_dir.test.tsx.snap | 1 + .../timeline/body/renderers/args.test.tsx | 51 +- .../timeline/body/renderers/args.tsx | 52 +- .../renderers/auditd/generic_details.test.tsx | 46 +- .../body/renderers/auditd/generic_details.tsx | 9 +- .../auditd/generic_file_details.test.tsx | 46 +- .../renderers/auditd/generic_file_details.tsx | 9 +- .../auditd/generic_row_renderer.test.tsx | 4 +- .../dns/dns_request_event_details.test.tsx | 37 ++ .../dns/dns_request_event_details.tsx | 64 ++ .../dns_request_event_details_line.test.tsx | 383 ++++++++++++ .../dns/dns_request_event_details_line.tsx | 179 ++++++ .../body/renderers/dns/translations.ts | 39 ++ .../endgame_security_event_details.test.tsx | 90 +++ .../endgame_security_event_details.tsx | 81 +++ ...dgame_security_event_details_line.test.tsx | 589 ++++++++++++++++++ .../endgame_security_event_details_line.tsx | 255 ++++++++ .../body/renderers/endgame/helpers.test.tsx | 208 +++++++ .../body/renderers/endgame/helpers.ts | 61 ++ .../body/renderers/endgame/translations.ts | 134 ++++ .../body/renderers/exit_code_draggable.tsx | 47 ++ .../body/renderers/file_draggable.tsx | 93 +++ .../body/renderers/get_row_renderer.test.tsx | 4 +- .../timeline/body/renderers/helpers.test.tsx | 68 +- .../timeline/body/renderers/helpers.tsx | 24 + .../renderers/parent_process_draggable.tsx | 74 +++ .../timeline/body/renderers/process.hash.tsx | 79 +++ .../body/renderers/process_draggable.test.tsx | 168 ++++- .../body/renderers/process_draggable.tsx | 138 ++-- .../renderers/system/generic_details.test.tsx | 56 +- .../body/renderers/system/generic_details.tsx | 9 +- .../system/generic_file_details.test.tsx | 516 ++++++++++++++- .../renderers/system/generic_file_details.tsx | 120 +++- .../system/generic_row_renderer.test.tsx | 4 +- .../renderers/system/generic_row_renderer.tsx | 237 ++++++- .../body/renderers/system/translations.ts | 35 ++ .../timeline/body/renderers/translations.ts | 2 +- .../renderers/user_host_working_dir.test.tsx | 35 +- .../body/renderers/user_host_working_dir.tsx | 43 +- .../siem/public/mock/mock_endgame_ecs_data.ts | 579 +++++++++++++++++ 42 files changed, 4478 insertions(+), 248 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/helpers.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/helpers.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process.hash.tsx create mode 100644 x-pack/legacy/plugins/siem/public/mock/mock_endgame_ecs_data.ts diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/args.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/args.test.tsx.snap index 8b5996b06e4e26..7017ffa6fd9f14 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/args.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/args.test.tsx.snap @@ -1,10 +1,53 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Args rendering it renders against shallow snapshot 1`] = ` - + + + + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/process_draggable.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/process_draggable.test.tsx.snap index 807c945e13747c..0e1634821fb92a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/process_draggable.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/process_draggable.test.tsx.snap @@ -3,6 +3,8 @@ exports[`ProcessDraggable rendering it renders against shallow snapshot 1`] = ` diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx index 9583fef3a57374..284cd0b49cb584 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx @@ -10,7 +10,6 @@ import * as React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TestProviders } from '../../../../mock'; -import { getEmptyString } from '../../../empty_value'; import { Args } from './args'; describe('Args', () => { @@ -20,28 +19,60 @@ describe('Args', () => { ); expect(toJson(wrapper)).toMatchSnapshot(); }); - test('it returns null if args is undefined', () => { + test('it returns an empty string when both args and process title are undefined', () => { const wrapper = mountWithIntl( + + ); + expect(wrapper.text()).toEqual(''); + }); + + test('it returns an empty string when both args and process title are null', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual(''); + }); + + test('it returns an empty string when args is an empty array, and title is an empty string', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual(''); + }); + + test('it returns args when args are provided, and process title is NOT provided', () => { + const wrapper = mountWithIntl( + + ); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect(wrapper.text()).toEqual('arg1arg2arg3'); }); - test('it returns null if args is null', () => { + test('it returns process title when process title is provided, and args is NOT provided', () => { const wrapper = mountWithIntl( { /> ); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect(wrapper.text()).toEqual('process-title-1'); }); - test('it returns empty string if args happens to be an empty string', () => { + test('it returns both args and process title, when both are provided', () => { const wrapper = mountWithIntl( ); - expect(wrapper.text()).toEqual(getEmptyString()); + expect(wrapper.text()).toEqual('arg1arg2arg3process-title-1'); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx index b7c19c3f7639dd..aad8ef52feeea8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx @@ -5,30 +5,48 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { DraggableBadge } from '../../../draggables'; -import { TokensFlexItem } from './helpers'; +import { isNillEmptyOrNotFinite, TokensFlexItem } from './helpers'; interface Props { - eventId: string; + args: string[] | null | undefined; contextId: string; - args: string | null | undefined; + eventId: string; processTitle: string | null | undefined; } -export const Args = pure(({ eventId, contextId, args, processTitle }) => - args != null ? ( - - - - ) : null -); +export const Args = React.memo(({ args, contextId, eventId, processTitle }) => { + if (isNillEmptyOrNotFinite(args) && isNillEmptyOrNotFinite(processTitle)) { + return null; + } + + return ( + <> + {args != null && + args.map((arg, i) => ( + + + + ))} + + {!isNillEmptyOrNotFinite(processTitle) && ( + + + + )} + + ); +}); Args.displayName = 'Args'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx index 3270aca83c4cbe..90698cc3bf5c95 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx @@ -44,7 +44,7 @@ describe('GenericDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionalice@zeek-sanfranin/generic-text-123gpgconf--list-dirs agent-socket' + 'Sessionalice@zeek-sanfranin/generic-text-123gpgconf(5402)gpgconf--list-dirsagent-socketgpgconf --list-dirs agent-socket' ); }); @@ -82,13 +82,13 @@ describe('GenericDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} result="success" /> ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -109,13 +109,13 @@ describe('GenericDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} result="success" /> ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -136,13 +136,13 @@ describe('GenericDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} result="success" /> ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -163,13 +163,13 @@ describe('GenericDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} result="success" /> ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -190,13 +190,13 @@ describe('GenericDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} result="success" /> ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123process-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -217,13 +217,13 @@ describe('GenericDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} result="success" /> ); expect(wrapper.text()).toEqual( - 'Sessionsession-1[username-2]as[username-3]@host-1inworking-directory-1generic-text-123process-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1[username-2]as[username-3]@host-1inworking-directory-1generic-text-123process-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -244,13 +244,13 @@ describe('GenericDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} result="success" /> ); expect(wrapper.text()).toEqual( - 'Sessionsession-1[username-1]as[username-2]@host-1inworking-directory-1generic-text-123process-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1[username-1]as[username-2]@host-1inworking-directory-1generic-text-123process-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -271,13 +271,13 @@ describe('GenericDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} result="success" /> ); expect(wrapper.text()).toEqual( - 'Sessionsession-1[username-primary]@host-1inworking-directory-1generic-text-123process-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1[username-primary]@host-1inworking-directory-1generic-text-123process-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -298,13 +298,13 @@ describe('GenericDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} result="success" /> ); expect(wrapper.text()).toEqual( - 'Sessionsession-1[username-primary]@host-1inworking-directory-1generic-text-123process-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1[username-primary]@host-1inworking-directory-1generic-text-123process-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -408,7 +408,7 @@ describe('GenericDetails', () => { expect(wrapper.text()).toEqual('Sessiongeneric-text-123some-process-name'); }); - test('it returns only session and user name if process title with id is given', () => { + test('it returns session, user name, and process title if process title with id is given', () => { const wrapper = mountWithIntl( { /> ); - expect(wrapper.text()).toEqual('Sessionsome-user-name'); + expect(wrapper.text()).toEqual('Sessionsome-user-namesome-process-title'); }); test('it returns only a working directory if that is all that is given with a id', () => { @@ -465,7 +465,7 @@ describe('GenericDetails', () => { id="hello-i-am-an-id" contextId="contextid-123" text="generic-text-123" - args="arg1 arg2 arg 3" + args={['arg1', 'arg2', 'arg 3']} userName={undefined} secondary={undefined} session={undefined} @@ -480,7 +480,7 @@ describe('GenericDetails', () => { /> ); - expect(wrapper.text()).toEqual('Sessionarg1 arg2 arg 3'); + expect(wrapper.text()).toEqual('Sessionarg1arg2arg 3'); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx index f6fa3ea63de0b8..127d88a2a2c6d6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx @@ -34,7 +34,7 @@ interface Props { processExecutable: string | null | undefined; processTitle: string | null | undefined; workingDirectory: string | null | undefined; - args: string | null | undefined; + args: string[] | null | undefined; session: string | null | undefined; } @@ -56,7 +56,7 @@ export const AuditdGenericLine = pure( session, text, }) => ( - + ( ( const workingDirectory: string | null | undefined = get('process.working_directory[0]', data); const primary: string | null | undefined = get('auditd.summary.actor.primary[0]', data); const secondary: string | null | undefined = get('auditd.summary.actor.secondary[0]', data); - const rawArgs: string[] | null | undefined = get('process.args', data); - const args: string | null = rawArgs != null ? rawArgs.slice(1).join(' ') : null; + const args: string[] | null | undefined = get('process.args', data); if (data.process != null) { return (
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx index 629f58ee781839..7630202293f8fe 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx @@ -48,7 +48,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionalice@zeek-sanfranin/generic-text-123usinggpgconf--list-dirs agent-socket' + 'Sessionalice@zeek-sanfranin/generic-text-123usinggpgconf(5402)gpgconf--list-dirsagent-socketgpgconf --list-dirs agent-socket' ); }); @@ -87,7 +87,7 @@ describe('GenericFileDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} filePath="/somepath" fileIcon="document" result="success" @@ -95,7 +95,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -116,7 +116,7 @@ describe('GenericFileDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} filePath="/somepath" fileIcon="document" result="success" @@ -124,7 +124,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -145,7 +145,7 @@ describe('GenericFileDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} filePath="/somepath" fileIcon="document" result="success" @@ -153,7 +153,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -174,7 +174,7 @@ describe('GenericFileDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} filePath="/somepath" fileIcon="document" result="success" @@ -182,7 +182,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -203,7 +203,7 @@ describe('GenericFileDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} filePath="/somepath" fileIcon="document" result="success" @@ -211,7 +211,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1username-1@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -232,7 +232,7 @@ describe('GenericFileDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} filePath="/somepath" fileIcon="document" result="success" @@ -240,7 +240,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionsession-1[username-2]as[username-3]@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1[username-2]as[username-3]@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -261,7 +261,7 @@ describe('GenericFileDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} filePath="/somepath" fileIcon="document" result="success" @@ -269,7 +269,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionsession-1[username-1]as[username-2]@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1[username-1]as[username-2]@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -290,7 +290,7 @@ describe('GenericFileDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} filePath="/somepath" fileIcon="document" result="success" @@ -298,7 +298,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionsession-1[username-primary]@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1[username-primary]@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -319,7 +319,7 @@ describe('GenericFileDetails', () => { processExecutable="process-1" processTitle="process-title-1" workingDirectory="working-directory-1" - args="arg1 arg2 arg3" + args={['arg1', 'arg2', 'arg3']} filePath="/somepath" fileIcon="document" result="success" @@ -327,7 +327,7 @@ describe('GenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Sessionsession-1[username-primary]@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1arg1 arg2 arg3with resultsuccess' + 'Sessionsession-1[username-primary]@host-1inworking-directory-1generic-text-123/somepathusingprocess-name-1(123)arg1arg2arg3process-title-1with resultsuccess' ); }); @@ -439,7 +439,7 @@ describe('GenericFileDetails', () => { expect(wrapper.text()).toEqual('Sessiongeneric-text-123usingsome-process-name'); }); - test('it returns only session and user name if process title with id is given', () => { + test('it returns session user name and title if process title with id is given', () => { const wrapper = mountWithIntl( { /> ); - expect(wrapper.text()).toEqual('Sessionsome-user-name'); + expect(wrapper.text()).toEqual('Sessionsome-user-namesome-process-title'); }); test('it returns only a working directory if that is all that is given with a id', () => { @@ -500,7 +500,7 @@ describe('GenericFileDetails', () => { id="hello-i-am-an-id" contextId="contextid-123" text="generic-text-123" - args="arg1 arg2 arg 3" + args={['arg1', 'arg2', 'arg 3']} fileIcon="document" userName={undefined} secondary={undefined} @@ -517,7 +517,7 @@ describe('GenericFileDetails', () => { /> ); - expect(wrapper.text()).toEqual('Sessionarg1 arg2 arg 3'); + expect(wrapper.text()).toEqual('Sessionarg1arg2arg 3'); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx index 318a3abe9c00f1..076c605ecb89fa 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx @@ -36,7 +36,7 @@ interface Props { processExecutable: string | null | undefined; processTitle: string | null | undefined; workingDirectory: string | null | undefined; - args: string | null | undefined; + args: string[] | null | undefined; session: string | null | undefined; } @@ -60,7 +60,7 @@ export const AuditdGenericFileLine = pure( text, fileIcon, }) => ( - + ( ( const filePath: string | null | undefined = get('file.path[0]', data); const primary: string | null | undefined = get('auditd.summary.actor.primary[0]', data); const secondary: string | null | undefined = get('auditd.summary.actor.secondary[0]', data); - const rawArgs: string[] | null | undefined = get('process.args', data); - const args: string | null = rawArgs != null ? rawArgs.slice(1).join(' ') : null; + const args: string[] | null | undefined = get('process.args', data); if (data.process != null) { return ( diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx index cb2f14eeaa2653..cf5f57f031da7e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx @@ -92,7 +92,7 @@ describe('GenericRowRenderer', () => { ); expect(wrapper.text()).toContain( - 'some children Session246alice@zeek-londonsome textwgetwith resultsuccessDestination93.184.216.34:80' + 'some children Session246alice@zeek-londonsome textwget(1490)wget www.example.comwith resultsuccessDestination93.184.216.34:80' ); }); }); @@ -171,7 +171,7 @@ describe('GenericRowRenderer', () => { ); expect(wrapper.text()).toContain( - 'some children Sessionunsetroot@zeek-londonin/some text/proc/15990/attr/currentusingsystemd-journalwith resultsuccess' + 'some children Sessionunsetroot@zeek-londonin/some text/proc/15990/attr/currentusingsystemd-journal(27244)/lib/systemd/systemd-journaldwith resultsuccess' ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx new file mode 100644 index 00000000000000..805888afd29648 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +import { TestProviders } from '../../../../../mock'; +import { mockBrowserFields } from '../../../../../../public/containers/source/mock'; +import { mockEndgameDnsRequest } from '../../../../../../public/mock/mock_endgame_ecs_data'; + +import { DnsRequestEventDetails } from './dns_request_event_details'; + +describe('DnsRequestEventDetails', () => { + test('it renders the expected text given an Endgame DNS request_event', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'SYSTEM\\NT AUTHORITY@HD-obe-8bf77f54asked forupdate.googleapis.comwith question typeA, which resolved to10.100.197.67viaGoogleUpdate.exe(443192)3008dns' + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx new file mode 100644 index 00000000000000..752163901de2e2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiSpacer } from '@elastic/eui'; +import { get } from 'lodash/fp'; +import * as React from 'react'; + +import { BrowserFields } from '../../../../../containers/source'; +import { Details } from '../helpers'; +import { Ecs } from '../../../../../graphql/types'; +import { NetflowRenderer } from '../netflow'; + +import { DnsRequestEventDetailsLine } from './dns_request_event_details_line'; + +interface Props { + browserFields: BrowserFields; + contextId: string; + data: Ecs; + timelineId: string; +} + +export const DnsRequestEventDetails = React.memo(({ data, contextId, timelineId }) => { + const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', data); + const dnsQuestionType: string | null | undefined = get('dns.question.type[0]', data); + const dnsResolvedIp: string | null | undefined = get('dns.resolved_ip[0]', data); + const dnsResponseCode: string | null | undefined = get('dns.response_code[0]', data); + const eventCode: string | null | undefined = get('event.code[0]', data); + const hostName: string | null | undefined = get('host.name[0]', data); + const id = data._id; + const processExecutable: string | null | undefined = get('process.executable[0]', data); + const processName: string | null | undefined = get('process.name[0]', data); + const processPid: number | null | undefined = get('process.pid[0]', data); + const userDomain: string | null | undefined = get('user.domain[0]', data); + const userName: string | null | undefined = get('user.name[0]', data); + const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data); + + return ( +
+ + + +
+ ); +}); + +DnsRequestEventDetails.displayName = 'DnsRequestEventDetails'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx new file mode 100644 index 00000000000000..4c45846574d7c7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx @@ -0,0 +1,383 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +import { TestProviders } from '../../../../../mock'; + +import { DnsRequestEventDetailsLine } from './dns_request_event_details_line'; + +describe('DnsRequestEventDetailsLine', () => { + test('it renders the expected text when all properties are provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when dnsQuestionName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when dnsQuestionType is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when dnsResolvedIp is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType](response code:[dnsResponseCode])via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when dnsResponseCode is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp]via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when eventCode is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)[winlogEventId]' + ); + }); + + test('it renders the expected text when hostName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when processExecutable is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when processName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processExecutable](123)[eventCode]' + ); + }); + + test('it renders the expected text when processPid is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName][eventCode]' + ); + }); + + test('it renders the expected text when userDomain is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when userName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '\\[userDomain][hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when winlogEventId is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)[eventCode]' + ); + }); + + test('it renders the expected text when both eventCode and winlogEventId are NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]asked for[dnsQuestionName]with question type[dnsQuestionType], which resolved to[dnsResolvedIp](response code:[dnsResponseCode])via[processName](123)' + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx new file mode 100644 index 00000000000000..fd49395379e240 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup } from '@elastic/eui'; +import * as React from 'react'; + +import { DraggableBadge } from '../../../../draggables'; +import { isNillEmptyOrNotFinite, TokensFlexItem } from '../helpers'; +import { ProcessDraggableWithNonExistentProcess } from '../process_draggable'; +import { UserHostWorkingDir } from '../user_host_working_dir'; + +import * as i18n from './translations'; + +interface Props { + contextId: string; + dnsQuestionName: string | null | undefined; + dnsQuestionType: string | null | undefined; + dnsResolvedIp: string | null | undefined; + dnsResponseCode: string | null | undefined; + eventCode: string | null | undefined; + hostName: string | null | undefined; + id: string; + processExecutable: string | null | undefined; + processName: string | null | undefined; + processPid: number | null | undefined; + userDomain: string | null | undefined; + userName: string | null | undefined; + winlogEventId: string | null | undefined; +} + +export const DnsRequestEventDetailsLine = React.memo( + ({ + contextId, + dnsQuestionName, + dnsQuestionType, + dnsResolvedIp, + dnsResponseCode, + eventCode, + hostName, + id, + processExecutable, + processName, + processPid, + userDomain, + userName, + winlogEventId, + }) => { + return ( + <> + + + + {!isNillEmptyOrNotFinite(dnsQuestionName) && ( + <> + + {i18n.ASKED_FOR} + + + + + + )} + + {!isNillEmptyOrNotFinite(dnsQuestionType) && ( + <> + + {i18n.WITH_QUESTION_TYPE} + + + + + + )} + + {!isNillEmptyOrNotFinite(dnsResolvedIp) && ( + <> + + {i18n.WHICH_RESOLVED_TO} + + + + + + )} + + {!isNillEmptyOrNotFinite(dnsResponseCode) && ( + <> + + {'('} + + + {i18n.RESPONSE_CODE} + + + + + + {')'} + + + )} + + + {i18n.VIA} + + + + + + + {(!isNillEmptyOrNotFinite(eventCode) || !isNillEmptyOrNotFinite(winlogEventId)) && ( + <> + {!isNillEmptyOrNotFinite(eventCode) ? ( + + + + ) : ( + + + + )} + + )} + + + ); + } +); + +DnsRequestEventDetailsLine.displayName = 'DnsRequestEventDetailsLine'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/translations.ts new file mode 100644 index 00000000000000..b86b88370cdf5f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/translations.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ASKED_FOR = i18n.translate( + 'xpack.siem.timeline.body.renderers.dns.askedForDescription', + { + defaultMessage: 'asked for', + } +); + +export const RESPONSE_CODE = i18n.translate( + 'xpack.siem.timeline.body.renderers.dns.responseCodeDescription', + { + defaultMessage: 'response code:', + } +); + +export const VIA = i18n.translate('xpack.siem.timeline.body.renderers.dns.viaDescription', { + defaultMessage: 'via', +}); + +export const WHICH_RESOLVED_TO = i18n.translate( + 'xpack.siem.timeline.body.renderers.dns.whichResolvedToDescription', + { + defaultMessage: ', which resolved to', + } +); + +export const WITH_QUESTION_TYPE = i18n.translate( + 'xpack.siem.timeline.body.renderers.dns.withQuestionTypeDescription', + { + defaultMessage: 'with question type', + } +); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx new file mode 100644 index 00000000000000..e1da17ad2904b3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +import { TestProviders } from '../../../../../mock'; +import { mockBrowserFields } from '../../../../../../public/containers/source/mock'; +import { + mockEndgameAdminLogon, + mockEndgameExplicitUserLogon, + mockEndgameUserLogon, + mockEndgameUserLogoff, +} from '../../../../../../public/mock/mock_endgame_ecs_data'; + +import { EndgameSecurityEventDetails } from './endgame_security_event_details'; + +describe('EndgameSecurityEventDetails', () => { + test('it renders the expected text given an Endgame Security user_logon event', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'SYSTEM\\NT AUTHORITY@HD-v1s-d2118419successfully logged inusing logon type5 - Service(target logon ID0x3e7)viaC:\\Windows\\System32\\services.exe(432)as requested by subjectWIN-Q3DOP1UKA81$(subject logon ID0x3e7)4624' + ); + }); + + test('it renders the expected text given an Endgame Security admin_logon event', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'With special privileges,SYSTEM\\NT AUTHORITY@HD-obe-8bf77f54successfully logged inviaC:\\Windows\\System32\\lsass.exe(964)as requested by subjectSYSTEM\\NT AUTHORITY4672' + ); + }); + + test('it renders the expected text given an Endgame Security explicit_user_logon event', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentialsArun\\Anvi-AcertoHD-55b-3ec87f66viaC:\\Windows\\System32\\svchost.exe(1736)as requested by subjectANVI-ACER$\\WORKGROUP(subject logon ID0x3e7)4648' + ); + }); + + test('it renders the expected text given an Endgame Security user_logoff event', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'Arun\\Anvi-Acer@HD-55b-3ec87f66logged offusing logon type2 - Interactive(target logon ID0x16db41e)viaC:\\Windows\\System32\\lsass.exe(964)4634' + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx new file mode 100644 index 00000000000000..10f9c4ad9e5455 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiSpacer } from '@elastic/eui'; +import { get } from 'lodash/fp'; +import * as React from 'react'; + +import { BrowserFields } from '../../../../../containers/source'; +import { Ecs } from '../../../../../graphql/types'; +import { NetflowRenderer } from '../netflow'; + +import { EndgameSecurityEventDetailsLine } from './endgame_security_event_details_line'; +import { Details } from '../helpers'; + +interface Props { + browserFields: BrowserFields; + contextId: string; + data: Ecs; + timelineId: string; +} + +export const EndgameSecurityEventDetails = React.memo(({ data, contextId, timelineId }) => { + const endgameLogonType: number | null | undefined = get('endgame.logon_type[0]', data); + const endgameSubjectDomainName: string | null | undefined = get( + 'endgame.subject_domain_name[0]', + data + ); + const endgameSubjectLogonId: string | null | undefined = get('endgame.subject_logon_id[0]', data); + const endgameSubjectUserName: string | null | undefined = get( + 'endgame.subject_user_name[0]', + data + ); + const endgameTargetLogonId: string | null | undefined = get('endgame.target_logon_id[0]', data); + const endgameTargetDomainName: string | null | undefined = get( + 'endgame.target_domain_name[0]', + data + ); + const endgameTargetUserName: string | null | undefined = get('endgame.target_user_name[0]', data); + const eventAction: string | null | undefined = get('event.action[0]', data); + const eventCode: string | null | undefined = get('event.code[0]', data); + const hostName: string | null | undefined = get('host.name[0]', data); + const id = data._id; + const processExecutable: string | null | undefined = get('process.executable[0]', data); + const processName: string | null | undefined = get('process.name[0]', data); + const processPid: number | null | undefined = get('process.pid[0]', data); + const userDomain: string | null | undefined = get('user.domain[0]', data); + const userName: string | null | undefined = get('user.name[0]', data); + const winlogEventId: string | null | undefined = get('winlog.event_id[0]', data); + + return ( +
+ + + +
+ ); +}); + +EndgameSecurityEventDetails.displayName = 'EndgameSecurityEventDetails'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx new file mode 100644 index 00000000000000..80635e9f63ac7a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx @@ -0,0 +1,589 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +import { TestProviders } from '../../../../../mock'; + +import { EndgameSecurityEventDetailsLine } from './endgame_security_event_details_line'; + +describe('EndgameSecurityEventDetailsLine', () => { + test('it renders the expected text when all properties are provided and event action is admin_logon', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'With special privileges,[userName]\\[userDomain]@[hostName]successfully logged inusing logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when all properties are provided and event action is explicit_user_logon', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when endgameLogonType is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName](target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when endgameSubjectDomainName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when endgameSubjectLogonId is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName][eventCode]' + ); + }); + + test('it renders the expected text when when endgameSubjectUserName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when endgameTargetDomainName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]to[hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when endgameTargetLogonId is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName]using logon type2 - Interactivevia[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when endgameTargetUserName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials\\[endgameTargetDomainName][hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when eventAction is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + '[userName]\\[userDomain]@[hostName]successfully logged inusing logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when eventCode is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[winlogEventId]' + ); + }); + + test('it renders the expected text when hostName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when processExecutable is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when processName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processExecutable](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when processPid is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'A login was attempted using explicit credentials[endgameTargetUserName]\\[endgameTargetDomainName]to[hostName]using logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName]as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when userDomain is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'With special privileges,[userName]@[hostName]successfully logged inusing logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when userName is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'With special privileges,\\[userDomain][hostName]successfully logged inusing logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when winlogEventId is NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'With special privileges,[userName]\\[userDomain]@[hostName]successfully logged inusing logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])[eventCode]' + ); + }); + + test('it renders the expected text when BOTH eventCode and winlogEventId are NOT provided', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual( + 'With special privileges,[userName]\\[userDomain]@[hostName]successfully logged inusing logon type2 - Interactive(target logon ID[endgameTargetLogonId])via[processName](123)as requested by subject[endgameSubjectUserName]\\[endgameSubjectDomainName](subject logon ID[endgameSubjectLogonId])' + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx new file mode 100644 index 00000000000000..97138580618ba2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx @@ -0,0 +1,255 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup } from '@elastic/eui'; +import * as React from 'react'; + +import { DraggableBadge } from '../../../../draggables'; +import { isNillEmptyOrNotFinite, TokensFlexItem } from '../helpers'; +import { ProcessDraggableWithNonExistentProcess } from '../process_draggable'; +import { UserHostWorkingDir } from '../user_host_working_dir'; + +import { + getEventDetails, + getHostNameSeparator, + getHumanReadableLogonType, + getUserDomainField, + getUserNameField, + useTargetUserAndTargetDomain, +} from './helpers'; + +import * as i18n from './translations'; + +interface Props { + contextId: string; + endgameLogonType: number | null | undefined; + endgameSubjectDomainName: string | null | undefined; + endgameSubjectLogonId: string | null | undefined; + endgameSubjectUserName: string | null | undefined; + endgameTargetDomainName: string | null | undefined; + endgameTargetLogonId: string | null | undefined; + endgameTargetUserName: string | null | undefined; + eventAction: string | null | undefined; + eventCode: string | null | undefined; + hostName: string | null | undefined; + id: string; + processExecutable: string | null | undefined; + processName: string | null | undefined; + processPid: number | null | undefined; + userDomain: string | null | undefined; + userName: string | null | undefined; + winlogEventId: string | null | undefined; +} + +export const EndgameSecurityEventDetailsLine = React.memo( + ({ + contextId, + endgameLogonType, + endgameSubjectDomainName, + endgameSubjectLogonId, + endgameSubjectUserName, + endgameTargetDomainName, + endgameTargetLogonId, + endgameTargetUserName, + eventAction, + eventCode, + hostName, + id, + processExecutable, + processName, + processPid, + userDomain, + userName, + winlogEventId, + }) => { + const domain = useTargetUserAndTargetDomain(eventAction) ? endgameTargetDomainName : userDomain; + const eventDetails = getEventDetails(eventAction); + const hostNameSeparator = getHostNameSeparator(eventAction); + const user = useTargetUserAndTargetDomain(eventAction) ? endgameTargetUserName : userName; + const userDomainField = getUserDomainField(eventAction); + const userNameField = getUserNameField(eventAction); + + return ( + <> + + {eventAction === 'admin_logon' && ( + + {i18n.WITH_SPECIAL_PRIVILEGES} + + )} + + {eventAction === 'explicit_user_logon' && ( + + {i18n.A_LOGIN_WAS_ATTEMPTED_USING_EXPLICIT_CREDENTIALS} + + )} + + + + + {eventDetails} + + + {!isNillEmptyOrNotFinite(endgameLogonType) && ( + <> + + {i18n.USING_LOGON_TYPE} + + + + + + )} + + {!isNillEmptyOrNotFinite(endgameTargetLogonId) && ( + <> + + {'('} + + + {i18n.TARGET_LOGON_ID} + + + + + + {')'} + + + )} + + + {i18n.VIA} + + + + + + + {!isNillEmptyOrNotFinite(endgameSubjectUserName) && ( + <> + + {i18n.AS_REQUESTED_BY_SUBJECT} + + + + + + + )} + + {endgameSubjectDomainName != null && ( + <> + + {'\\'} + + + + + + )} + + {!isNillEmptyOrNotFinite(endgameSubjectLogonId) && ( + <> + + {'('} + + + {i18n.SUBJECT_LOGON_ID} + + + + + + {')'} + + + )} + + {(!isNillEmptyOrNotFinite(eventCode) || !isNillEmptyOrNotFinite(winlogEventId)) && ( + <> + {!isNillEmptyOrNotFinite(eventCode) ? ( + + + + ) : ( + + + + )} + + )} + + + ); + } +); + +EndgameSecurityEventDetailsLine.displayName = 'EndgameSecurityEventDetailsLine'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/helpers.test.tsx new file mode 100644 index 00000000000000..06a24730242a8d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/helpers.test.tsx @@ -0,0 +1,208 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + getHostNameSeparator, + getHumanReadableLogonType, + useTargetUserAndTargetDomain, + getUserDomainField, + getUserNameField, + getEventDetails, +} from './helpers'; + +describe('helpers', () => { + describe('#getHumanReadableLogonType', () => { + test('it returns an empty string when endgameLogonType is undefined', () => { + expect(getHumanReadableLogonType(undefined)).toEqual(''); + }); + + test('it returns an empty string when endgameLogonType is null', () => { + expect(getHumanReadableLogonType(null)).toEqual(''); + }); + + test('it returns an empty string when endgameLogonType is NaN', () => { + expect(getHumanReadableLogonType(NaN)).toEqual(''); + }); + + test('it returns an empty string when endgameLogonType is Infinity', () => { + expect(getHumanReadableLogonType(Infinity)).toEqual(''); + }); + + test('it returns a string "0" given 0, an unknown logon type', () => { + expect(getHumanReadableLogonType(0)).toEqual('0'); + }); + + test('it returns a string "-1" given -1, an unknown logon type', () => { + expect(getHumanReadableLogonType(-1)).toEqual('-1'); + }); + + test('it returns "Interactive" given 2', () => { + expect(getHumanReadableLogonType(2)).toEqual('Interactive'); + }); + + test('it returns "Network" given 3', () => { + expect(getHumanReadableLogonType(3)).toEqual('Network'); + }); + + test('it returns "Batch" given 4', () => { + expect(getHumanReadableLogonType(4)).toEqual('Batch'); + }); + + test('it returns "Service" given 5', () => { + expect(getHumanReadableLogonType(5)).toEqual('Service'); + }); + + test('it returns "Unlock" given 7', () => { + expect(getHumanReadableLogonType(7)).toEqual('Unlock'); + }); + + test('it returns "Network Cleartext" given 8', () => { + expect(getHumanReadableLogonType(8)).toEqual('Network Cleartext'); + }); + + test('it returns "New Credentials" given 9', () => { + expect(getHumanReadableLogonType(9)).toEqual('New Credentials'); + }); + + test('it returns "Remote Interactive" given 10', () => { + expect(getHumanReadableLogonType(10)).toEqual('Remote Interactive'); + }); + + test('it returns "Cached Interactive" given 11', () => { + expect(getHumanReadableLogonType(11)).toEqual('Cached Interactive'); + }); + }); + + describe('#getHostNameSeparator', () => { + test('it returns "@" when eventAction is undefined', () => { + expect(getHostNameSeparator(undefined)).toEqual('@'); + }); + + test('it returns "@" when eventAction is null', () => { + expect(getHostNameSeparator(null)).toEqual('@'); + }); + + test('it returns "@" when eventAction is an empty string', () => { + expect(getHostNameSeparator('')).toEqual('@'); + }); + + test('it returns "@" when eventAction is a random value', () => { + expect(getHostNameSeparator('a random value')).toEqual('@'); + }); + + test('it returns "@" when eventAction is "user_logoff"', () => { + expect(getHostNameSeparator('user_logoff')).toEqual('@'); + }); + + test('it returns "to" when eventAction is "explicit_user_logon"', () => { + expect(getHostNameSeparator('explicit_user_logon')).toEqual('to'); + }); + }); + + describe('#useTargetUserAndTargetDomain', () => { + test('it returns false when eventAction is undefined', () => { + expect(useTargetUserAndTargetDomain(undefined)).toEqual(false); + }); + + test('it returns false when eventAction is null', () => { + expect(useTargetUserAndTargetDomain(null)).toEqual(false); + }); + + test('it returns false when eventAction is an empty string', () => { + expect(useTargetUserAndTargetDomain('')).toEqual(false); + }); + + test('it returns false when eventAction is a random value', () => { + expect(useTargetUserAndTargetDomain('a random value')).toEqual(false); + }); + + test('it returns true when eventAction is "explicit_user_logon"', () => { + expect(useTargetUserAndTargetDomain('explicit_user_logon')).toEqual(true); + }); + + test('it returns true when eventAction is "user_logoff"', () => { + expect(useTargetUserAndTargetDomain('user_logoff')).toEqual(true); + }); + }); + + describe('#getUserDomainField', () => { + test('it returns user.domain when eventAction is undefined', () => { + expect(getUserDomainField(undefined)).toEqual('user.domain'); + }); + + test('it returns user.domain when eventAction is null', () => { + expect(getUserDomainField(null)).toEqual('user.domain'); + }); + + test('it returns user.domain when eventAction is an empty string', () => { + expect(getUserDomainField('')).toEqual('user.domain'); + }); + + test('it returns user.domain when eventAction is a random value', () => { + expect(getUserDomainField('a random value')).toEqual('user.domain'); + }); + + test('it returns endgame.target_domain_name when eventAction is "explicit_user_logon"', () => { + expect(getUserDomainField('explicit_user_logon')).toEqual('endgame.target_domain_name'); + }); + + test('it returns endgame.target_domain_name when eventAction is "user_logoff"', () => { + expect(getUserDomainField('user_logoff')).toEqual('endgame.target_domain_name'); + }); + }); + + describe('#getUserNameField', () => { + test('it returns user.name when eventAction is undefined', () => { + expect(getUserNameField(undefined)).toEqual('user.name'); + }); + + test('it returns user.name when eventAction is null', () => { + expect(getUserNameField(null)).toEqual('user.name'); + }); + + test('it returns user.name when eventAction is an empty string', () => { + expect(getUserNameField('')).toEqual('user.name'); + }); + + test('it returns user.name when eventAction is a random value', () => { + expect(getUserNameField('a random value')).toEqual('user.name'); + }); + + test('it returns endgame.target_user_name when eventAction is "explicit_user_logon"', () => { + expect(getUserNameField('explicit_user_logon')).toEqual('endgame.target_user_name'); + }); + + test('it returns endgame.target_user_name when eventAction is "user_logoff"', () => { + expect(getUserNameField('user_logoff')).toEqual('endgame.target_user_name'); + }); + }); + + describe('#getEventDetails', () => { + test('it returns successfully logged in when eventAction is undefined', () => { + expect(getEventDetails(undefined)).toEqual('successfully logged in'); + }); + + test('it returns successfully logged in when eventAction is null', () => { + expect(getEventDetails(null)).toEqual('successfully logged in'); + }); + + test('it returns successfully logged in when eventAction is an empty string', () => { + expect(getEventDetails('')).toEqual('successfully logged in'); + }); + + test('it returns successfully logged in when eventAction is a random value', () => { + expect(getEventDetails('a random value')).toEqual('successfully logged in'); + }); + + test('it returns an empty string when eventAction is "explicit_user_logon"', () => { + expect(getEventDetails('explicit_user_logon')).toEqual(''); + }); + + test('it returns logged off when eventAction is "user_logoff"', () => { + expect(getEventDetails('user_logoff')).toEqual('logged off'); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/helpers.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/helpers.ts new file mode 100644 index 00000000000000..7470c3618fe5c3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/helpers.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isNillEmptyOrNotFinite } from '../helpers'; + +import * as i18n from './translations'; + +export const getHumanReadableLogonType = (endgameLogonType: number | null | undefined): string => { + if (isNillEmptyOrNotFinite(endgameLogonType)) { + return ''; + } + + switch (endgameLogonType) { + case 2: + return i18n.LOGON_TYPE_INTERACTIVE; + case 3: + return i18n.LOGON_TYPE_NETWORK; + case 4: + return i18n.LOGON_TYPE_BATCH; + case 5: + return i18n.LOGON_TYPE_SERVICE; + case 7: + return i18n.LOGON_TYPE_UNLOCK; + case 8: + return i18n.LOGON_TYPE_NETWORK_CLEARTEXT; + case 9: + return i18n.LOGON_TYPE_NEW_CREDENTIALS; + case 10: + return i18n.LOGON_TYPE_REMOTE_INTERACTIVE; + case 11: + return i18n.LOGON_TYPE_CACHED_INTERACTIVE; + default: + return `${endgameLogonType}`; + } +}; + +export const getHostNameSeparator = (eventAction: string | null | undefined): string => + eventAction === 'explicit_user_logon' ? i18n.TO : '@'; + +export const useTargetUserAndTargetDomain = (eventAction: string | null | undefined): boolean => + eventAction === 'explicit_user_logon' || eventAction === 'user_logoff'; + +export const getUserDomainField = (eventAction: string | null | undefined): string => + useTargetUserAndTargetDomain(eventAction) ? 'endgame.target_domain_name' : 'user.domain'; + +export const getUserNameField = (eventAction: string | null | undefined): string => + useTargetUserAndTargetDomain(eventAction) ? 'endgame.target_user_name' : 'user.name'; + +export const getEventDetails = (eventAction: string | null | undefined): string => { + switch (eventAction) { + case 'explicit_user_logon': + return ''; // no details + case 'user_logoff': + return i18n.LOGGED_OFF; + default: + return i18n.SUCCESSFULLY_LOGGED_IN; + } +}; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/translations.ts new file mode 100644 index 00000000000000..b8c534bc1356c8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/translations.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const A_LOGIN_WAS_ATTEMPTED_USING_EXPLICIT_CREDENTIALS = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.aLoginWasAttemptedUsingExplicitCredentialsDescription', + { + defaultMessage: 'A login was attempted using explicit credentials', + } +); + +export const AS_REQUESTED_BY_SUBJECT = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.asRequestedBySubjectDescription', + { + defaultMessage: 'as requested by subject', + } +); + +export const LOGGED_OFF = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.loggedOffDescription', + { + defaultMessage: 'logged off', + } +); + +export const LOGON_TYPE_BATCH = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.logonTypeBatchDescription', + { + defaultMessage: 'Batch', + } +); + +export const LOGON_TYPE_CACHED_INTERACTIVE = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.logonTypeCachedInteractiveDescription', + { + defaultMessage: 'Cached Interactive', + } +); + +export const LOGON_TYPE_INTERACTIVE = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.logonTypeInteractiveDescription', + { + defaultMessage: 'Interactive', + } +); + +export const LOGON_TYPE_NETWORK = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.logonTypeNetworkDescription', + { + defaultMessage: 'Network', + } +); + +export const LOGON_TYPE_NETWORK_CLEARTEXT = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.logonTypeNetworkCleartextDescription', + { + defaultMessage: 'Network Cleartext', + } +); + +export const LOGON_TYPE_NEW_CREDENTIALS = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.logonTypeNewCredentialsDescription', + { + defaultMessage: 'New Credentials', + } +); + +export const LOGON_TYPE_REMOTE_INTERACTIVE = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.logonTypeRemoteInteractiveDescription', + { + defaultMessage: 'Remote Interactive', + } +); + +export const LOGON_TYPE_SERVICE = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.logonTypeServiceDescription', + { + defaultMessage: 'Service', + } +); + +export const LOGON_TYPE_UNLOCK = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.logonTypeUnlockDescription', + { + defaultMessage: 'Unlock', + } +); + +export const SUBJECT_LOGON_ID = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.subjectLogonIdDescription', + { + defaultMessage: 'subject logon ID', + } +); + +export const SUCCESSFULLY_LOGGED_IN = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.successfullyLoggedInDescription', + { + defaultMessage: 'successfully logged in', + } +); + +export const TARGET_LOGON_ID = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.targetLogonIdDescription', + { + defaultMessage: 'target logon ID', + } +); + +export const TO = i18n.translate('xpack.siem.timeline.body.renderers.endgame.toDescription', { + defaultMessage: 'to', +}); + +export const USING_LOGON_TYPE = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.usingLogonTypeDescription', + { + defaultMessage: 'using logon type', + } +); + +export const VIA = i18n.translate('xpack.siem.timeline.body.renderers.endgame.viaDescription', { + defaultMessage: 'via', +}); + +export const WITH_SPECIAL_PRIVILEGES = i18n.translate( + 'xpack.siem.timeline.body.renderers.endgame.withSpecialPrivilegesDescription', + { + defaultMessage: 'With special privileges,', + } +); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx new file mode 100644 index 00000000000000..9ea5f2cdd99fa9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; + +import { DraggableBadge } from '../../../draggables'; + +import { isNillEmptyOrNotFinite, TokensFlexItem } from './helpers'; + +interface Props { + contextId: string; + endgameExitCode: string | null | undefined; + eventId: string; + text: string | null | undefined; +} + +export const ExitCodeDraggable = React.memo( + ({ contextId, endgameExitCode, eventId, text }) => { + if (isNillEmptyOrNotFinite(endgameExitCode)) { + return null; + } + + return ( + <> + {!isNillEmptyOrNotFinite(text) && ( + + {text} + + )} + + + + + + ); + } +); + +ExitCodeDraggable.displayName = 'ExitCodeDraggable'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx new file mode 100644 index 00000000000000..af8876927d2359 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; + +import { DraggableBadge } from '../../../draggables'; + +import { isNillEmptyOrNotFinite, TokensFlexItem } from './helpers'; +import * as i18n from './translations'; + +interface Props { + contextId: string; + endgameFileName: string | null | undefined; + endgameFilePath: string | null | undefined; + eventId: string; + fileName: string | null | undefined; + filePath: string | null | undefined; +} + +export const FileDraggable = React.memo( + ({ contextId, endgameFileName, endgameFilePath, eventId, fileName, filePath }) => { + if ( + isNillEmptyOrNotFinite(fileName) && + isNillEmptyOrNotFinite(endgameFileName) && + isNillEmptyOrNotFinite(filePath) && + isNillEmptyOrNotFinite(endgameFilePath) + ) { + return null; + } + + const fileNameIsKnown = + !isNillEmptyOrNotFinite(fileName) || !isNillEmptyOrNotFinite(endgameFileName); + + return ( + <> + {!isNillEmptyOrNotFinite(fileName) ? ( + + + + ) : !isNillEmptyOrNotFinite(endgameFileName) ? ( + + + + ) : null} + + {fileNameIsKnown && ( + + {i18n.IN} + + )} + + {!isNillEmptyOrNotFinite(filePath) ? ( + + + + ) : !isNillEmptyOrNotFinite(endgameFilePath) ? ( + + + + ) : null} + + ); + } +); + +FileDraggable.displayName = 'FileDraggable'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx index 61b4910b935e22..f7a5462be6e8fc 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -131,7 +131,7 @@ describe('get_column_renderer', () => { ); expect(wrapper.text()).toContain( - 'some child Braden@zeek-londonattempted a login via6278with resultfailureSource128.199.212.120' + 'some child Braden@zeek-londonattempted a login via(6278)with resultfailureSource128.199.212.120' ); }); @@ -150,7 +150,7 @@ describe('get_column_renderer', () => { ); expect(wrapper.text()).toContain( - 'some child Sessionalice@zeek-sanfranin/executedgpgconf--list-dirs agent-socket' + 'some child Sessionalice@zeek-sanfranin/executedgpgconf(5402)gpgconf--list-dirsagent-socketgpgconf --list-dirs agent-socket' ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/helpers.test.tsx index faaba7b92d962d..2cd75344deaf67 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/helpers.test.tsx @@ -8,7 +8,7 @@ import { cloneDeep } from 'lodash/fp'; import { TimelineNonEcsData } from '../../../../graphql/types'; import { mockTimelineData } from '../../../../mock'; -import { deleteItemIdx, findItem, getValues } from './helpers'; +import { deleteItemIdx, findItem, getValues, isNillEmptyOrNotFinite, showVia } from './helpers'; describe('helpers', () => { describe('#deleteItemIdx', () => { @@ -123,4 +123,70 @@ describe('helpers', () => { expect(getValues('user.name', nullValue)).toBeUndefined(); }); }); + + describe('#isNillEmptyOrNotFinite', () => { + test('undefined returns true', () => { + expect(isNillEmptyOrNotFinite(undefined)).toEqual(true); + }); + + test('null returns true', () => { + expect(isNillEmptyOrNotFinite(null)).toEqual(true); + }); + + test('empty string returns true', () => { + expect(isNillEmptyOrNotFinite('')).toEqual(true); + }); + + test('empty array returns true', () => { + expect(isNillEmptyOrNotFinite([])).toEqual(true); + }); + + test('NaN returns true', () => { + expect(isNillEmptyOrNotFinite(NaN)).toEqual(true); + }); + + test('Infinity returns true', () => { + expect(isNillEmptyOrNotFinite(Infinity)).toEqual(true); + }); + + test('a single space string returns false', () => { + expect(isNillEmptyOrNotFinite(' ')).toEqual(false); + }); + + test('a simple string returns false', () => { + expect(isNillEmptyOrNotFinite('a simple string')).toEqual(false); + }); + + test('the number 0 returns false', () => { + expect(isNillEmptyOrNotFinite(0)).toEqual(false); + }); + + test('a non-empty array return false', () => { + expect(isNillEmptyOrNotFinite(['non empty array'])).toEqual(false); + }); + }); + + describe('#showVia', () => { + test('undefined returns false', () => { + expect(showVia(undefined)).toEqual(false); + }); + + test('null returns false', () => { + expect(showVia(undefined)).toEqual(false); + }); + + test('empty string false', () => { + expect(showVia('')).toEqual(false); + }); + + test('a random string returns false', () => { + expect(showVia('a random string')).toEqual(false); + }); + + ['file_create_event', 'created', 'file_delete_event', 'deleted'].forEach(eventAction => { + test(`${eventAction} returns true`, () => { + expect(showVia(eventAction)).toEqual(true); + }); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/helpers.tsx index 92c7207257a147..b8e55fd29930e5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/helpers.tsx @@ -5,6 +5,7 @@ */ import { EuiFlexItem } from '@elastic/eui'; +import { isNumber, isEmpty } from 'lodash/fp'; import styled from 'styled-components'; import { TimelineNonEcsData } from '../../../../graphql/types'; @@ -27,6 +28,9 @@ export const getValues = (field: string, data: TimelineNonEcsData[]): string[] | export const Details = styled.div` margin: 5px 0 5px 10px; + & .euiBadge { + margin: 2px 0 2px 0; + } `; Details.displayName = 'Details'; @@ -34,3 +38,23 @@ export const TokensFlexItem = styled(EuiFlexItem)` margin-left: 3px; `; TokensFlexItem.displayName = 'TokensFlexItem'; + +export function isNillEmptyOrNotFinite(value: string | number | T[] | null | undefined) { + return isNumber(value) ? !isFinite(value) : isEmpty(value); +} + +export const isFimEvent = ({ + eventCategory, + eventDataset, +}: { + eventCategory: string | null | undefined; + eventDataset: string | null | undefined; +}) => + (eventCategory != null && eventCategory.toLowerCase() === 'file') || + (eventDataset != null && eventDataset.toLowerCase() === 'file'); + +export const isProcessStoppedOrTerminationEvent = (eventAction: string | null | undefined) => + eventAction === 'process_stopped' || eventAction === 'termination_event'; + +export const showVia = (eventAction: string | null | undefined) => + ['file_create_event', 'created', 'file_delete_event', 'deleted'].includes(`${eventAction}`); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx new file mode 100644 index 00000000000000..1423ccad7211ec --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; + +import { DraggableBadge } from '../../../draggables'; + +import { + isNillEmptyOrNotFinite, + isProcessStoppedOrTerminationEvent, + TokensFlexItem, +} from './helpers'; + +interface Props { + contextId: string; + endgameParentProcessName: string | null | undefined; + eventAction: string | null | undefined; + eventId: string; + processPpid: number | undefined | null; + text: string | null | undefined; +} + +export const ParentProcessDraggable = React.memo( + ({ contextId, endgameParentProcessName, eventAction, eventId, processPpid, text }) => { + if ( + (isNillEmptyOrNotFinite(endgameParentProcessName) && isNillEmptyOrNotFinite(processPpid)) || + isProcessStoppedOrTerminationEvent(eventAction) + ) { + return null; + } + + return ( + <> + {!isNillEmptyOrNotFinite(text) && ( + + {text} + + )} + + {!isNillEmptyOrNotFinite(endgameParentProcessName) && ( + + + + )} + + {!isNillEmptyOrNotFinite(processPpid) && ( + + + + )} + + ); + } +); + +ParentProcessDraggable.displayName = 'ParentProcessDraggable'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process.hash.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process.hash.tsx new file mode 100644 index 00000000000000..cb0b40bdd8fcae --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process.hash.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup } from '@elastic/eui'; +import * as React from 'react'; +import styled from 'styled-components'; + +import { DraggableBadge } from '../../../draggables'; + +import { isNillEmptyOrNotFinite, TokensFlexItem } from './helpers'; + +const HashFlexGroup = styled(EuiFlexGroup)` + margin: ${({ theme }) => theme.eui.euiSizeXS}; +`; + +interface Props { + contextId: string; + eventId: string; + processHashMd5: string | null | undefined; + processHashSha1: string | null | undefined; + processHashSha256: string | null | undefined; +} + +export const ProcessHash = React.memo( + ({ contextId, eventId, processHashMd5, processHashSha1, processHashSha256 }) => { + if ( + isNillEmptyOrNotFinite(processHashSha256) && + isNillEmptyOrNotFinite(processHashSha1) && + isNillEmptyOrNotFinite(processHashMd5) + ) { + return null; + } + + return ( + + {!isNillEmptyOrNotFinite(processHashSha256) && ( + + + + )} + + {!isNillEmptyOrNotFinite(processHashSha1) && ( + + + + )} + + {!isNillEmptyOrNotFinite(processHashMd5) && ( + + + + )} + + ); + } +); + +ProcessHash.displayName = 'ProcessHash'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx index 3cbe5b50f2cb20..826af00c390118 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx @@ -10,7 +10,7 @@ import * as React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TestProviders } from '../../../../mock'; -import { ProcessDraggable, isNillOrEmptyString } from './process_draggable'; +import { ProcessDraggable } from './process_draggable'; describe('ProcessDraggable', () => { describe('rendering', () => { @@ -18,6 +18,8 @@ describe('ProcessDraggable', () => { const wrapper = shallow( { { { { { { /> ); - expect(wrapper.text()).toEqual('123'); + expect(wrapper.text()).toEqual('(123)'); }); - test('it returns process name if everything else is an empty string', () => { + test('it returns just process name if process.pid and endgame.pid are NaN', () => { const wrapper = mountWithIntl( { expect(wrapper.text()).toEqual('[process-name]'); }); - test('it returns process executable if everything else is an empty string', () => { + test('it returns just process executable if process.pid and endgame.pid are NaN', () => { const wrapper = mountWithIntl( { expect(wrapper.text()).toEqual('[process-executable]'); }); - test('it returns process pid if everything else is an empty string', () => { + test('it returns process executable if everything else is an empty string or NaN', () => { const wrapper = mountWithIntl( + + ); + expect(wrapper.text()).toEqual('[process-executable]'); + }); + + test('it returns endgame.process_name if everything else is an empty string or NaN', () => { + const wrapper = mountWithIntl( + + ); - expect(wrapper.text()).toEqual('123'); + expect(wrapper.text()).toEqual('[endgame-process_name]'); }); - test('it returns process name if everything is filled', () => { + test('it returns endgame.process_name and endgame.pid if everything else is an empty string or undefined', () => { const wrapper = mountWithIntl( ); - expect(wrapper.text()).toEqual('[process-name]'); + expect(wrapper.text()).toEqual('[endgame-process_name](456)'); }); - test('it returns process executable if process name is undefined', () => { + test('it returns process pid if everything else is an empty string', () => { const wrapper = mountWithIntl( ); - expect(wrapper.text()).toEqual('[process-executable]'); + expect(wrapper.text()).toEqual('(123)'); }); - test('it returns process executable if process name is an empty string', () => { + test('it returns endgame.pid if everything else is an empty string', () => { const wrapper = mountWithIntl( ); - expect(wrapper.text()).toEqual('[process-executable]'); + expect(wrapper.text()).toEqual('(456)'); }); - }); - describe('isNillOrEmptyString', () => { - test('undefined returns true', () => { - expect(isNillOrEmptyString(undefined)).toEqual(true); + test('it returns pid and process name if everything is filled', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual('[process-name](123)'); }); - test('null returns true', () => { - expect(isNillOrEmptyString(null)).toEqual(true); + test('it returns process pid and executable and if process name and endgame process name are null', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual('[process-executable](123)'); }); - test('empty string returns true', () => { - expect(isNillOrEmptyString('')).toEqual(true); + test('it returns endgame pid and executable and if process name and endgame process name are null', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual('[process-executable](456)'); }); - test('single space string returns false', () => { - expect(isNillOrEmptyString(' ')).toEqual(false); + test('it returns process pid and executable and if process name is undefined', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual('[process-executable](123)'); }); - test('regular value returns false', () => { - expect(isNillOrEmptyString('[process-name-1]')).toEqual(false); + test('it returns process pid and executable if process name is an empty string', () => { + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.text()).toEqual('[process-executable](123)'); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx index a02b5099d36089..440feca8f3e56e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx @@ -4,88 +4,120 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isNumber, isString } from 'lodash/fp'; import * as React from 'react'; import { pure } from 'recompose'; import { DraggableBadge } from '../../../draggables'; +import { isNillEmptyOrNotFinite } from './helpers'; import * as i18n from './translations'; -export const isNillOrEmptyString = (value: string | number | null | undefined) => { - if (value == null) { - return true; - } else if (isString(value)) { - return value === ''; - } else if (isNumber(value)) { - return !isFinite(value); - } -}; - interface Props { contextId: string; + endgamePid: number | null | undefined; + endgameProcessName: string | null | undefined; eventId: string; processExecutable: string | undefined | null; - processPid?: number | undefined | null; - processName?: string | undefined | null; + processPid: number | undefined | null; + processName: string | undefined | null; } export const ProcessDraggable = pure( - ({ contextId, eventId, processExecutable, processName, processPid }) => { + ({ + contextId, + endgamePid, + endgameProcessName, + eventId, + processExecutable, + processName, + processPid, + }) => { if ( - !isNillOrEmptyString(processName) || - (processName === '' && - isNillOrEmptyString(processExecutable) && - isNillOrEmptyString(processPid)) - ) { - return ( - - ); - } else if ( - !isNillOrEmptyString(processExecutable) || - (processExecutable === '' && isNillOrEmptyString(processPid)) + isNillEmptyOrNotFinite(processName) && + isNillEmptyOrNotFinite(processExecutable) && + isNillEmptyOrNotFinite(endgameProcessName) && + isNillEmptyOrNotFinite(processPid) && + isNillEmptyOrNotFinite(endgamePid) ) { - return ( - - ); - } else if (processPid != null) { - return ( - - ); - } else { return null; } + + return ( +
+ {!isNillEmptyOrNotFinite(processName) ? ( + + ) : !isNillEmptyOrNotFinite(processExecutable) ? ( + + ) : !isNillEmptyOrNotFinite(endgameProcessName) ? ( + + ) : null} + + {!isNillEmptyOrNotFinite(processPid) ? ( + + ) : !isNillEmptyOrNotFinite(endgamePid) ? ( + + ) : null} +
+ ); } ); ProcessDraggable.displayName = 'ProcessDraggable'; export const ProcessDraggableWithNonExistentProcess = pure( - ({ contextId, eventId, processExecutable, processName, processPid }) => { - if (processExecutable == null && processName == null && processPid == null) { + ({ + contextId, + endgamePid, + endgameProcessName, + eventId, + processExecutable, + processName, + processPid, + }) => { + if ( + endgamePid == null && + endgameProcessName == null && + processExecutable == null && + processName == null && + processPid == null + ) { return <>{i18n.NON_EXISTENT}; } else { return ( {
); expect(wrapper.text()).toEqual( - 'Braden@zeek-london[generic-text-123]6278with resultfailureSource128.199.212.120' + 'Braden@zeek-london[generic-text-123](6278)with resultfailureSource128.199.212.120' ); }); }); @@ -69,6 +69,7 @@ describe('SystemGenericDetails', () => { sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text="[generic-text-123]" + userDomain="[userDomain-123]" userName="[username-123]" workingDirectory="[working-directory-123]" /> @@ -76,7 +77,7 @@ describe('SystemGenericDetails', () => {
); expect(wrapper.text()).toEqual( - '[username-123]@[hostname-123]in[working-directory-123][generic-text-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][generic-text-123][processName-123](123)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); @@ -99,6 +100,7 @@ describe('SystemGenericDetails', () => { sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -127,6 +129,7 @@ describe('SystemGenericDetails', () => { sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -155,6 +158,7 @@ describe('SystemGenericDetails', () => { sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -183,6 +187,7 @@ describe('SystemGenericDetails', () => { sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -211,6 +216,7 @@ describe('SystemGenericDetails', () => { sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -241,6 +247,7 @@ describe('SystemGenericDetails', () => { sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -271,6 +278,7 @@ describe('SystemGenericDetails', () => { sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -295,12 +303,13 @@ describe('SystemGenericDetails', () => { packageName="[packageName-123]" packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" - processExecutable="[packageVersion-123]" + processExecutable="[processExecutable-123]" processPid={null} processName={null} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -308,7 +317,7 @@ describe('SystemGenericDetails', () => {
); expect(wrapper.text()).toEqual( - '[hostname-123][packageVersion-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][processExecutable-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); @@ -325,12 +334,13 @@ describe('SystemGenericDetails', () => { packageName="[packageName-123]" packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" - processExecutable="[packageVersion-123]" + processExecutable="[processExecutable-123]" processPid={123} processName={null} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -338,7 +348,7 @@ describe('SystemGenericDetails', () => {
); expect(wrapper.text()).toEqual( - '[hostname-123][packageVersion-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][processExecutable-123](123)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); @@ -355,12 +365,13 @@ describe('SystemGenericDetails', () => { packageName="[packageName-123]" packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" - processExecutable="[packageVersion-123]" + processExecutable="[processExecutable-123]" processPid={123} processName="[processName-123]" sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -368,7 +379,7 @@ describe('SystemGenericDetails', () => {
); expect(wrapper.text()).toEqual( - '[hostname-123][processName-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][processName-123](123)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); @@ -385,12 +396,13 @@ describe('SystemGenericDetails', () => { packageName="[packageName-123]" packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" - processExecutable="[packageVersion-123]" + processExecutable="[processExecutable-123]" processPid={123} processName="[processName-123]" sshMethod="[sshMethod-123]" sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -398,7 +410,7 @@ describe('SystemGenericDetails', () => {
); expect(wrapper.text()).toEqual( - '[hostname-123][processName-123]with result[outcome-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][processName-123](123)with result[outcome-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); @@ -415,12 +427,13 @@ describe('SystemGenericDetails', () => { packageName="[packageName-123]" packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" - processExecutable="[packageVersion-123]" + processExecutable="[processExecutable-123]" processPid={123} processName="[processName-123]" sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text={null} + userDomain={null} userName={null} workingDirectory={null} /> @@ -428,7 +441,7 @@ describe('SystemGenericDetails', () => { ); expect(wrapper.text()).toEqual( - '[hostname-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][processName-123](123)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); @@ -445,12 +458,13 @@ describe('SystemGenericDetails', () => { packageName="[packageName-123]" packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" - processExecutable="[packageVersion-123]" + processExecutable="[processExecutable-123]" processPid={123} processName="[processName-123]" sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text="[text-123]" + userDomain={null} userName={null} workingDirectory={null} /> @@ -458,11 +472,11 @@ describe('SystemGenericDetails', () => { ); expect(wrapper.text()).toEqual( - '[hostname-123][text-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][text-123][processName-123](123)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature, text, username', () => { + test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature, text, userDomain, username', () => { const wrapper = mountWithIntl(
@@ -475,12 +489,13 @@ describe('SystemGenericDetails', () => { packageName="[packageName-123]" packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" - processExecutable="[packageVersion-123]" + processExecutable="[processExecutable-123]" processPid={123} processName="[processName-123]" sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text="[text-123]" + userDomain="[userDomain-123]" userName="[username-123]" workingDirectory={null} /> @@ -488,11 +503,11 @@ describe('SystemGenericDetails', () => { ); expect(wrapper.text()).toEqual( - '[username-123]@[hostname-123][text-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[username-123]\\[userDomain-123]@[hostname-123][text-123][processName-123](123)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature, text, username, working-directory', () => { + test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory', () => { const wrapper = mountWithIntl(
@@ -505,12 +520,13 @@ describe('SystemGenericDetails', () => { packageName="[packageName-123]" packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" - processExecutable="[packageVersion-123]" + processExecutable="[processExecutable-123]" processPid={123} processName="[processName-123]" sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text="[text-123]" + userDomain="[userDomain-123]" userName="[username-123]" workingDirectory="[working-directory-123]" /> @@ -518,7 +534,7 @@ describe('SystemGenericDetails', () => { ); expect(wrapper.text()).toEqual( - '[username-123]@[hostname-123]in[working-directory-123][text-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx index 3284519f984fe4..764a93a7294f4c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx @@ -38,6 +38,7 @@ interface Props { sshMethod: string | null | undefined; sshSignature: string | null | undefined; text: string | null | undefined; + userDomain: string | null | undefined; userName: string | null | undefined; workingDirectory: string | null | undefined; } @@ -58,14 +59,16 @@ export const SystemGenericLine = pure( sshSignature, sshMethod, text, + userDomain, userName, workingDirectory, }) => ( <> - + ( ( const id = data._id; const message: string | null = data.message != null ? data.message[0] : null; const hostName: string | null | undefined = get('host.name[0]', data); + const userDomain: string | null | undefined = get('user.domain[0]', data); const userName: string | null | undefined = get('user.name[0]', data); const outcome: string | null | undefined = get('event.outcome[0]', data); const packageName: string | null | undefined = get('system.audit.package.name[0]', data); @@ -170,6 +176,7 @@ export const SystemGenericDetails = pure( sshMethod={sshMethod} sshSignature={sshSignature} text={text} + userDomain={userDomain} userName={userName} workingDirectory={workingDirectory} /> diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx index 57657614c4373e..91a848c8e02ed7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx @@ -46,7 +46,7 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - 'Evan@zeek-london[generic-text-123]6278with resultfailureSource128.199.212.120' + 'Evan@zeek-london[generic-text-123](6278)with resultfailureSource128.199.212.120' ); }); }); @@ -59,20 +59,35 @@ describe('SystemGenericFileDetails', () => { @@ -80,7 +95,7 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[username-123]@[hostname-123]in[working-directory-123][generic-text-123][processName-123][arg-1] [arg-2] [arg-3]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][generic-text-123][fileName-123]in[filePath-123][processName-123](123)[arg-1][arg-2][arg-3][some-title-123]with exit code[endgameExitCode-123]via parent process[endgameParentProcessName-123](456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); @@ -90,6 +105,15 @@ describe('SystemGenericFileDetails', () => {
{ packageSummary={null} packageVersion={null} processExecutable={null} + processHashMd5={null} + processHashSha1={null} + processHashSha256={null} processPid={null} + processPpid={null} processName={null} + showMessage={true} sshMethod={null} processTitle={null} args={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} />
); - expect(wrapper.text()).toEqual('to an unknown process'); + expect(wrapper.text()).toEqual('an unknown process'); }); test('it can return only the host name', () => { @@ -120,6 +150,15 @@ describe('SystemGenericFileDetails', () => {
{ packageSummary={null} packageVersion={null} processExecutable={null} + processHashMd5={null} + processHashSha1={null} + processHashSha256={null} processPid={null} + processPpid={null} processName={null} + showMessage={true} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} processTitle={null} @@ -141,7 +186,7 @@ describe('SystemGenericFileDetails', () => {
); - expect(wrapper.text()).toEqual('[hostname-123]to an unknown process'); + expect(wrapper.text()).toEqual('[hostname-123]an unknown process'); }); test('it can return the host, message', () => { @@ -150,6 +195,15 @@ describe('SystemGenericFileDetails', () => {
{ packageSummary={null} packageVersion={null} processExecutable={null} + processHashMd5={null} + processHashSha1={null} + processHashSha256={null} processPid={null} + processPpid={null} processName={null} + showMessage={true} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} processTitle={null} @@ -171,7 +231,7 @@ describe('SystemGenericFileDetails', () => {
); - expect(wrapper.text()).toEqual('[hostname-123]to an unknown process[message-123]'); + expect(wrapper.text()).toEqual('[hostname-123]an unknown process[message-123]'); }); test('it can return the host, message, outcome', () => { @@ -180,6 +240,15 @@ describe('SystemGenericFileDetails', () => {
{ packageSummary={null} packageVersion={null} processExecutable={null} + processHashMd5={null} + processHashSha1={null} + processHashSha256={null} processPid={null} + processPpid={null} processName={null} + showMessage={true} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} processTitle={null} @@ -202,7 +277,7 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[hostname-123]to an unknown processwith result[outcome-123][message-123]' + '[hostname-123]an unknown processwith result[outcome-123][message-123]' ); }); @@ -212,6 +287,15 @@ describe('SystemGenericFileDetails', () => {
{ packageSummary={null} packageVersion={null} processExecutable={null} + processHashMd5={null} + processHashSha1={null} + processHashSha256={null} processPid={null} + processPpid={null} processName={null} + showMessage={true} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} processTitle={null} @@ -234,7 +324,7 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[hostname-123]to an unknown processwith result[outcome-123][packageName-123][message-123]' + '[hostname-123]an unknown processwith result[outcome-123][packageName-123][message-123]' ); }); @@ -244,6 +334,15 @@ describe('SystemGenericFileDetails', () => {
{ packageSummary="[packageSummary-123]" packageVersion={null} processExecutable={null} + processHashMd5={null} + processHashSha1={null} + processHashSha256={null} processPid={null} + processPpid={null} processName={null} + showMessage={true} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} processTitle={null} @@ -266,7 +371,7 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[hostname-123]to an unknown processwith result[outcome-123][packageName-123][packageSummary-123][message-123]' + '[hostname-123]an unknown processwith result[outcome-123][packageName-123][packageSummary-123][message-123]' ); }); @@ -276,6 +381,15 @@ describe('SystemGenericFileDetails', () => {
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable={null} + processHashMd5={null} + processHashSha1={null} + processHashSha256={null} processPid={null} + processPpid={null} processName={null} + showMessage={true} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} processTitle={null} @@ -298,7 +418,7 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[hostname-123]to an unknown processwith result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123]an unknown processwith result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' ); }); @@ -308,6 +428,15 @@ describe('SystemGenericFileDetails', () => {
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5={null} + processHashSha1={null} + processHashSha256={null} processPid={null} + processPpid={null} processName={null} + showMessage={true} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} processTitle={null} @@ -334,12 +469,21 @@ describe('SystemGenericFileDetails', () => { ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid', () => { + test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5', () => { const wrapper = mountWithIntl(
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5="[processHashMd5-123]" + processHashSha1={null} + processHashSha256={null} + processPid={null} + processPpid={null} + processName={null} + showMessage={true} + sshMethod={null} + sshSignature={null} + text={null} + userDomain={null} + userName={null} + workingDirectory={null} + processTitle={null} + args={null} + /> +
+
+ ); + expect(wrapper.text()).toEqual( + '[hostname-123][packageVersion-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashMd5-123][message-123]' + ); + }); + + test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1', () => { + const wrapper = mountWithIntl( + +
+ +
+
+ ); + expect(wrapper.text()).toEqual( + '[hostname-123][packageVersion-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha1-123][processHashMd5-123][message-123]' + ); + }); + + test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256', () => { + const wrapper = mountWithIntl( + +
+ +
+
+ ); + expect(wrapper.text()).toEqual( + '[hostname-123][packageVersion-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' + ); + }); + + test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid', () => { + const wrapper = mountWithIntl( + +
+ { ); expect(wrapper.text()).toEqual( - '[hostname-123][packageVersion-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][processExecutable-123](123)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName', () => { + test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid, processPpid, processName', () => { const wrapper = mountWithIntl(
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5="[processHashMd5-123]" + processHashSha1="[processHashSha1-123]" + processHashSha256="[processHashSha256-123]" processPid={123} + processPpid={456} processName="[processName-123]" + showMessage={true} sshMethod={null} sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} processTitle={null} @@ -394,16 +700,25 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[hostname-123][processName-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][processName-123](123)via parent process(456)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod', () => { + test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid, processPpid, processName, sshMethod', () => { const wrapper = mountWithIntl(
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5="[processHashMd5-123]" + processHashSha1="[processHashSha1-123]" + processHashSha256="[processHashSha256-123]" processPid={123} + processPpid={456} processName="[processName-123]" + showMessage={true} sshMethod="[sshMethod-123]" sshSignature={null} text={null} + userDomain={null} userName={null} workingDirectory={null} processTitle={null} @@ -426,16 +747,25 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[hostname-123][processName-123]with result[outcome-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][processName-123](123)with exit code[endgameExitCode-123]via parent process[endgameParentProcessName-123](456)with result[outcome-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature', () => { + test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid, processPpid, processName, sshMethod, sshSignature', () => { const wrapper = mountWithIntl(
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5="[processHashMd5-123]" + processHashSha1="[processHashSha1-123]" + processHashSha256="[processHashSha256-123]" processPid={123} + processPpid={456} processName="[processName-123]" + showMessage={true} sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text={null} + userDomain={null} + userName={null} + workingDirectory={null} + processTitle={null} + args={null} + /> +
+
+ ); + expect(wrapper.text()).toEqual( + '[hostname-123][processName-123](123)with exit code[endgameExitCode-123]via parent process[endgameParentProcessName-123](456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' + ); + }); + + test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid, processPpid, processName, sshMethod, sshSignature, text', () => { + const wrapper = mountWithIntl( + +
+ { ); expect(wrapper.text()).toEqual( - '[hostname-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[hostname-123][text-123][processName-123](123)with exit code[endgameExitCode-123]via parent process[endgameParentProcessName-123](456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature, text', () => { + test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain', () => { const wrapper = mountWithIntl(
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5="[processHashMd5-123]" + processHashSha1="[processHashSha1-123]" + processHashSha256="[processHashSha256-123]" processPid={123} + processPpid={456} processName="[processName-123]" + showMessage={true} sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text="[text-123]" + userDomain="[userDomain-123]" userName={null} workingDirectory={null} processTitle={null} @@ -490,16 +888,25 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[hostname-123][text-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '\\[userDomain-123][hostname-123][text-123][processName-123](123)with exit code[endgameExitCode-123]via parent process[endgameParentProcessName-123](456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature, text, username', () => { + test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username', () => { const wrapper = mountWithIntl(
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5="[processHashMd5-123]" + processHashSha1="[processHashSha1-123]" + processHashSha256="[processHashSha256-123]" processPid={123} + processPpid={456} processName="[processName-123]" + showMessage={true} sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text="[text-123]" + userDomain="[userDomain-123]" userName="[username-123]" workingDirectory={null} processTitle={null} @@ -522,16 +935,25 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[username-123]@[hostname-123][text-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[username-123]\\[userDomain-123]@[hostname-123][text-123][processName-123](123)with exit code[endgameExitCode-123]via parent process[endgameParentProcessName-123](456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature, text, username, working-directory', () => { + test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory', () => { const wrapper = mountWithIntl(
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5="[processHashMd5-123]" + processHashSha1="[processHashSha1-123]" + processHashSha256="[processHashSha256-123]" processPid={123} + processPpid={456} processName="[processName-123]" + showMessage={true} sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text="[text-123]" + userDomain="[userDomain-123]" userName="[username-123]" workingDirectory="[working-directory-123]" processTitle={null} @@ -554,16 +982,25 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[username-123]@[hostname-123]in[working-directory-123][text-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)with exit code[endgameExitCode-123]via parent process[endgameParentProcessName-123](456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature, text, username, working-directory, process-title', () => { + test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title', () => { const wrapper = mountWithIntl(
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5="[processHashMd5-123]" + processHashSha1="[processHashSha1-123]" + processHashSha256="[processHashSha256-123]" processPid={123} + processPpid={456} processName="[processName-123]" + showMessage={true} sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text="[text-123]" + userDomain="[userDomain-123]" userName="[username-123]" workingDirectory="[working-directory-123]" processTitle="[process-title-123]" @@ -586,16 +1029,25 @@ describe('SystemGenericFileDetails', () => { ); expect(wrapper.text()).toEqual( - '[username-123]@[hostname-123]in[working-directory-123][text-123][processName-123]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[process-title-123]with exit code[endgameExitCode-123]via parent process[endgameParentProcessName-123](456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); - test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processPid, processName, sshMethod, sshSignature, text, username, working-directory, process-title, args', () => { + test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, packageExecutable, processHashMd5, processHashSha1, processHashSha256, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title, args', () => { const wrapper = mountWithIntl(
{ packageSummary="[packageSummary-123]" packageVersion="[packageVersion-123]" processExecutable="[packageVersion-123]" + processHashMd5="[processHashMd5-123]" + processHashSha1="[processHashSha1-123]" + processHashSha256="[processHashSha256-123]" processPid={123} + processPpid={456} processName="[processName-123]" + showMessage={true} sshMethod="[sshMethod-123]" sshSignature="[sshSignature-123]" text="[text-123]" + userDomain="[userDomain-123]" userName="[username-123]" workingDirectory="[working-directory-123]" processTitle="[process-title-123]" - args="[args-1] [args-2] [args-3]" + args={['[arg-1]', '[arg-2]', '[arg-3]']} />
); expect(wrapper.text()).toEqual( - '[username-123]@[hostname-123]in[working-directory-123][text-123][processName-123][args-1] [args-2] [args-3]with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][message-123]' + '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[arg-1][arg-2][arg-3][process-title-123]with exit code[endgameExitCode-123]via parent process[endgameParentProcessName-123](456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]' ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx index e2884205a7b604..4d87543dd64f2b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx @@ -17,16 +17,29 @@ import { OverflowField } from '../../../../tables/helpers'; import * as i18n from './translations'; import { NetflowRenderer } from '../netflow'; import { UserHostWorkingDir } from '../user_host_working_dir'; -import { Details, TokensFlexItem } from '../helpers'; +import { Details, showVia, TokensFlexItem } from '../helpers'; import { ProcessDraggableWithNonExistentProcess } from '../process_draggable'; import { Args } from '../args'; import { AuthSsh } from './auth_ssh'; +import { ExitCodeDraggable } from '../exit_code_draggable'; +import { FileDraggable } from '../file_draggable'; import { Package } from './package'; import { Badge } from '../../../../page'; +import { ParentProcessDraggable } from '../parent_process_draggable'; +import { ProcessHash } from '../process.hash'; interface Props { - args: string | null | undefined; + args: string[] | null | undefined; contextId: string; + endgameExitCode: string | null | undefined; + endgameFileName: string | null | undefined; + endgameFilePath: string | null | undefined; + endgameParentProcessName: string | null | undefined; + endgamePid: number | null | undefined; + endgameProcessName: string | null | undefined; + eventAction: string | null | undefined; + fileName: string | null | undefined; + filePath: string | null | undefined; hostName: string | null | undefined; id: string; message: string | null | undefined; @@ -36,11 +49,17 @@ interface Props { packageVersion: string | null | undefined; processName: string | null | undefined; processPid: number | null | undefined; + processPpid: number | null | undefined; processExecutable: string | null | undefined; + processHashMd5: string | null | undefined; + processHashSha1: string | null | undefined; + processHashSha256: string | null | undefined; processTitle: string | null | undefined; + showMessage: boolean; sshSignature: string | null | undefined; sshMethod: string | null | undefined; text: string | null | undefined; + userDomain: string | null | undefined; userName: string | null | undefined; workingDirectory: string | null | undefined; } @@ -49,6 +68,15 @@ export const SystemGenericFileLine = pure( ({ args, contextId, + endgameExitCode, + endgameFileName, + endgameFilePath, + endgameParentProcessName, + endgamePid, + endgameProcessName, + eventAction, + fileName, + filePath, hostName, id, message, @@ -57,20 +85,27 @@ export const SystemGenericFileLine = pure( packageSummary, packageVersion, processExecutable, + processHashMd5, + processHashSha1, + processHashSha256, processName, processPid, + processPpid, processTitle, + showMessage, sshSignature, sshMethod, text, + userDomain, userName, workingDirectory, }) => ( <> - + ( {text} + + {showVia(eventAction) && ( + + {i18n.VIA} + + )} - + + + {outcome != null && ( {i18n.WITH_RESULT} @@ -116,7 +180,15 @@ export const SystemGenericFileLine = pure( packageVersion={packageVersion} /> - {message != null && ( + + + {message != null && showMessage && ( <> @@ -138,29 +210,46 @@ interface GenericDetailsProps { browserFields: BrowserFields; data: Ecs; contextId: string; + showMessage?: boolean; text: string; timelineId: string; } export const SystemGenericFileDetails = pure( - ({ data, contextId, text, timelineId }) => { + ({ data, contextId, showMessage = true, text, timelineId }) => { const id = data._id; const message: string | null = data.message != null ? data.message[0] : null; const hostName: string | null | undefined = get('host.name[0]', data); + const endgameExitCode: string | null | undefined = get('endgame.exit_code[0]', data); + const endgameFileName: string | null | undefined = get('endgame.file_name[0]', data); + const endgameFilePath: string | null | undefined = get('endgame.file_path[0]', data); + const endgameParentProcessName: string | null | undefined = get( + 'endgame.parent_process_name[0]', + data + ); + const endgamePid: number | null | undefined = get('endgame.pid[0]', data); + const endgameProcessName: string | null | undefined = get('endgame.process_name[0]', data); + const eventAction: string | null | undefined = get('event.action[0]', data); + const fileName: string | null | undefined = get('file.name[0]', data); + const filePath: string | null | undefined = get('file.path[0]', data); + const userDomain: string | null | undefined = get('user.domain[0]', data); const userName: string | null | undefined = get('user.name[0]', data); const outcome: string | null | undefined = get('event.outcome[0]', data); const packageName: string | null | undefined = get('system.audit.package.name[0]', data); const packageSummary: string | null | undefined = get('system.audit.package.summary[0]', data); const packageVersion: string | null | undefined = get('system.audit.package.version[0]', data); + const processHashMd5: string | null | undefined = get('process.hash.md5[0]', data); + const processHashSha1: string | null | undefined = get('process.hash.sha1[0]', data); + const processHashSha256: string | null | undefined = get('process.hash.sha256', data); const processPid: number | null | undefined = get('process.pid[0]', data); + const processPpid: number | null | undefined = get('process.ppid[0]', data); const processName: string | null | undefined = get('process.name[0]', data); const sshSignature: string | null | undefined = get('system.auth.ssh.signature[0]', data); const sshMethod: string | null | undefined = get('system.auth.ssh.method[0]', data); const processExecutable: string | null | undefined = get('process.executable[0]', data); const processTitle: string | null | undefined = get('process.title[0]', data); const workingDirectory: string | null | undefined = get('process.working_directory[0]', data); - const rawArgs: string[] | null | undefined = get('process.args', data); - const args: string | null = rawArgs != null ? rawArgs.slice(1).join(' ') : null; + const args: string[] | null | undefined = get('process.args', data); return (
@@ -169,6 +258,16 @@ export const SystemGenericFileDetails = pure( contextId={contextId} text={text} hostName={hostName} + endgameExitCode={endgameExitCode} + endgameFileName={endgameFileName} + endgameFilePath={endgameFilePath} + endgameParentProcessName={endgameParentProcessName} + endgamePid={endgamePid} + endgameProcessName={endgameProcessName} + eventAction={eventAction} + fileName={fileName} + filePath={filePath} + userDomain={userDomain} userName={userName} message={message} processTitle={processTitle} @@ -177,9 +276,14 @@ export const SystemGenericFileDetails = pure( packageName={packageName} packageSummary={packageSummary} packageVersion={packageVersion} + processHashMd5={processHashMd5} + processHashSha1={processHashSha1} + processHashSha256={processHashSha256} processName={processName} processPid={processPid} + processPpid={processPpid} processExecutable={processExecutable} + showMessage={showMessage} sshSignature={sshSignature} sshMethod={sshMethod} outcome={outcome} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx index fe25c7b62a27d1..595e8f0327db84 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx @@ -77,7 +77,7 @@ describe('GenericRowRenderer', () => { ); expect(wrapper.text()).toContain( - 'some children Evan@zeek-londonsome text6278with resultfailureSource128.199.212.120' + 'some children Evan@zeek-londonsome text(6278)with resultfailureSource128.199.212.120' ); }); }); @@ -140,7 +140,7 @@ describe('GenericRowRenderer', () => { ); expect(wrapper.text()).toContain( - 'some children Braden@zeek-londonsome text6278with resultfailureSource128.199.212.120' + 'some children Braden@zeek-londonsome text(6278)with resultfailureSource128.199.212.120' ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx index 1b3e415fa4ccb2..9000e23ee0d3a2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx @@ -7,7 +7,11 @@ import { get } from 'lodash/fp'; import React from 'react'; +import { DnsRequestEventDetails } from '../dns/dns_request_event_details'; +import { EndgameSecurityEventDetails } from '../endgame/endgame_security_event_details'; +import { isFimEvent, isNillEmptyOrNotFinite } from '../helpers'; import { RowRenderer, RowRendererContainer } from '../row_renderer'; + import { SystemGenericDetails } from './generic_details'; import { SystemGenericFileDetails } from './generic_file_details'; import * as i18n from './translations'; @@ -45,6 +49,74 @@ export const createGenericSystemRowRenderer = ({ ), }); +export const createEndgameProcessRowRenderer = ({ + actionName, + text, +}: { + actionName: string; + text: string; +}): RowRenderer => ({ + isInstance: ecs => { + const action: string | null | undefined = get('event.action[0]', ecs); + const category: string | null | undefined = get('event.category[0]', ecs); + return ( + category != null && + category.toLowerCase() === 'process' && + action != null && + action.toLowerCase() === actionName + ); + }, + renderRow: ({ browserFields, data, children, timelineId }) => ( + <> + {children} + + + + + ), +}); + +export const createFimRowRenderer = ({ + actionName, + text, +}: { + actionName: string; + text: string; +}): RowRenderer => ({ + isInstance: ecs => { + const action: string | null | undefined = get('event.action[0]', ecs); + const category: string | null | undefined = get('event.category[0]', ecs); + const dataset: string | null | undefined = get('event.dataset[0]', ecs); + return ( + isFimEvent({ eventCategory: category, eventDataset: dataset }) && + action != null && + action.toLowerCase() === actionName + ); + }, + renderRow: ({ browserFields, data, children, timelineId }) => ( + <> + {children} + + + + + ), +}); + export const createGenericFileRowRenderer = ({ actionName, text, @@ -78,6 +150,84 @@ export const createGenericFileRowRenderer = ({ ), }); +export const createSocketRowRenderer = ({ + actionName, + text, +}: { + actionName: string; + text: string; +}): RowRenderer => ({ + isInstance: ecs => { + const action: string | null | undefined = get('event.action[0]', ecs); + return action != null && action.toLowerCase() === actionName; + }, + renderRow: ({ browserFields, data, children, timelineId }) => ( + <> + {children} + + + + + ), +}); + +export const createSecurityEventRowRenderer = ({ + actionName, +}: { + actionName: string; +}): RowRenderer => ({ + isInstance: ecs => { + const category: string | null | undefined = get('event.category[0]', ecs); + const action: string | null | undefined = get('event.action[0]', ecs); + return ( + category != null && + category.toLowerCase() === 'authentication' && + action != null && + action.toLowerCase() === actionName + ); + }, + renderRow: ({ browserFields, data, children, timelineId }) => ( + <> + {children} + + + + + ), +}); + +export const createDnsRowRenderer = (): RowRenderer => ({ + isInstance: ecs => { + const dnsQuestionType: string | null | undefined = get('dns.question.type[0]', ecs); + const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', ecs); + return !isNillEmptyOrNotFinite(dnsQuestionType) && !isNillEmptyOrNotFinite(dnsQuestionName); + }, + renderRow: ({ browserFields, data, children, timelineId }) => ( + <> + {children} + + + + + ), +}); + const systemLoginRowRenderer = createGenericSystemRowRenderer({ actionName: 'user_login', text: i18n.ATTEMPTED_LOGIN, @@ -88,26 +238,94 @@ const systemProcessStartedRowRenderer = createGenericFileRowRenderer({ text: i18n.PROCESS_STARTED, }); +const endgameProcessStartedRowRenderer = createEndgameProcessRowRenderer({ + actionName: 'creation_event', + text: i18n.PROCESS_STARTED, +}); + const systemProcessStoppedRowRenderer = createGenericFileRowRenderer({ actionName: 'process_stopped', text: i18n.PROCESS_STOPPED, }); +const endgameProcessTerminationRowRenderer = createEndgameProcessRowRenderer({ + actionName: 'termination_event', + text: i18n.TERMINATED_PROCESS, +}); + +const endgameFileCreateEventRowRenderer = createFimRowRenderer({ + actionName: 'file_create_event', + text: i18n.CREATED_FILE, +}); + +const fimFileCreateEventRowRenderer = createFimRowRenderer({ + actionName: 'created', + text: i18n.CREATED_FILE, +}); + +const endgameFileDeleteEventRowRenderer = createFimRowRenderer({ + actionName: 'file_delete_event', + text: i18n.DELETED_FILE, +}); + +const fimFileDeletedEventRowRenderer = createFimRowRenderer({ + actionName: 'deleted', + text: i18n.DELETED_FILE, +}); + const systemExistingRowRenderer = createGenericFileRowRenderer({ actionName: 'existing_process', text: i18n.EXISTING_PROCESS, }); -const systemSocketOpenedRowRenderer = createGenericFileRowRenderer({ +const systemSocketOpenedRowRenderer = createSocketRowRenderer({ actionName: 'socket_opened', text: i18n.SOCKET_OPENED, }); -const systemSocketClosedRowRenderer = createGenericFileRowRenderer({ +const systemSocketClosedRowRenderer = createSocketRowRenderer({ actionName: 'socket_closed', text: i18n.SOCKET_CLOSED, }); +const endgameIpv4ConnectionAcceptEventRowRenderer = createSocketRowRenderer({ + actionName: 'ipv4_connection_accept_event', + text: i18n.ACCEPTED_A_CONNECTION_VIA, +}); + +const endgameIpv6ConnectionAcceptEventRowRenderer = createSocketRowRenderer({ + actionName: 'ipv6_connection_accept_event', + text: i18n.ACCEPTED_A_CONNECTION_VIA, +}); + +const endgameIpv4DisconnectReceivedEventRowRenderer = createSocketRowRenderer({ + actionName: 'ipv4_disconnect_received_event', + text: i18n.DISCONNECTED_VIA, +}); + +const endgameIpv6DisconnectReceivedEventRowRenderer = createSocketRowRenderer({ + actionName: 'ipv6_disconnect_received_event', + text: i18n.DISCONNECTED_VIA, +}); + +const endgameAdminLogonRowRenderer = createSecurityEventRowRenderer({ + actionName: 'admin_logon', +}); + +const endgameExplicitUserLogonRowRenderer = createSecurityEventRowRenderer({ + actionName: 'explicit_user_logon', +}); + +const endgameUserLogoffRowRenderer = createSecurityEventRowRenderer({ + actionName: 'user_logoff', +}); + +const endgameUserLogonRowRenderer = createSecurityEventRowRenderer({ + actionName: 'user_logon', +}); + +const dnsRowRenderer = createDnsRowRenderer(); + const systemExistingUserRowRenderer = createGenericSystemRowRenderer({ actionName: 'existing_user', text: i18n.EXISTING_USER, @@ -195,6 +413,21 @@ const systemUserRemovedRowRenderer = createGenericSystemRowRenderer({ }); export const systemRowRenderers: RowRenderer[] = [ + dnsRowRenderer, + endgameAdminLogonRowRenderer, + endgameExplicitUserLogonRowRenderer, + endgameFileCreateEventRowRenderer, + endgameFileDeleteEventRowRenderer, + endgameIpv4ConnectionAcceptEventRowRenderer, + endgameIpv6ConnectionAcceptEventRowRenderer, + endgameIpv4DisconnectReceivedEventRowRenderer, + endgameIpv6DisconnectReceivedEventRowRenderer, + endgameProcessStartedRowRenderer, + endgameProcessTerminationRowRenderer, + endgameUserLogoffRowRenderer, + endgameUserLogonRowRenderer, + fimFileCreateEventRowRenderer, + fimFileDeletedEventRowRenderer, systemAcceptedRowRenderer, systemBootRowRenderer, systemErrorRowRenderer, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/translations.ts index be4ce4a16af87f..25a23fcfea7837 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/translations.ts @@ -32,10 +32,21 @@ export const WAS_AUTHORIZED_TO_USE = i18n.translate( } ); +export const ACCEPTED_A_CONNECTION_VIA = i18n.translate( + 'xpack.siem.system.acceptedAConnectionViaDescription', + { + defaultMessage: 'accepted a connection via', + } +); + export const ATTEMPTED_LOGIN = i18n.translate('xpack.siem.system.attemptedLoginDescription', { defaultMessage: 'attempted a login via', }); +export const DISCONNECTED_VIA = i18n.translate('xpack.siem.system.disconnectedViaDescription', { + defaultMessage: 'disconnected via', +}); + export const LOGGED_OUT = i18n.translate('xpack.siem.system.loggedOutDescription', { defaultMessage: 'logged out via', }); @@ -52,6 +63,18 @@ export const PROCESS_STOPPED = i18n.translate('xpack.siem.system.processStoppedD defaultMessage: 'stopped process', }); +export const TERMINATED_PROCESS = i18n.translate('xpack.siem.system.terminatedProcessDescription', { + defaultMessage: 'terminated process', +}); + +export const CREATED_FILE = i18n.translate('xpack.siem.system.createdFileDescription', { + defaultMessage: 'created a file', +}); + +export const DELETED_FILE = i18n.translate('xpack.siem.system.deletedFileDescription', { + defaultMessage: 'deleted a file', +}); + export const EXISTING_PROCESS = i18n.translate('xpack.siem.system.existingProcessDescription', { defaultMessage: 'is running process', }); @@ -123,3 +146,15 @@ export const PACKAGE_REMOVED = i18n.translate('xpack.siem.system.packageRemovedD export const USER_REMOVED = i18n.translate('xpack.siem.system.userRemovedDescription', { defaultMessage: 'was removed', }); + +export const VIA = i18n.translate('xpack.siem.system.viaDescription', { + defaultMessage: 'via', +}); + +export const VIA_PARENT_PROCESS = i18n.translate('xpack.siem.system.viaParentProcessDescription', { + defaultMessage: 'via parent process', +}); + +export const WITH_EXIT_CODE = i18n.translate('xpack.siem.system.withExitCodeDescription', { + defaultMessage: 'with exit code', +}); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/translations.ts index 304b8a23aa23bd..2c3c3efdb29935 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/translations.ts @@ -27,5 +27,5 @@ export const IN = i18n.translate('xpack.siem.auditd.inDescription', { }); export const NON_EXISTENT = i18n.translate('xpack.siem.auditd.nonExistentDescription', { - defaultMessage: 'to an unknown process', + defaultMessage: 'an unknown process', }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx index 8b0df5c64d45ab..cf03213fc34f3b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx @@ -19,6 +19,7 @@ describe('UserHostWorkingDir', () => { { expect(toJson(wrapper)).toMatchSnapshot(); }); - test('it returns null if userName, hostName, and workingDirectory are all null', () => { + test('it returns null if userDomain, userName, hostName, and workingDirectory are all null', () => { const wrapper = mountWithIntl( { expect(wrapper.isEmptyRender()).toBeTruthy(); }); - test('it returns null if userName, hostName, and workingDirectory are all undefined', () => { + test('it returns null if userDomain, userName, hostName, and workingDirectory are all undefined', () => { const wrapper = mountWithIntl( { expect(wrapper.isEmptyRender()).toBeTruthy(); }); + test('it returns userDomain if that is the only attribute defined', () => { + const wrapper = mountWithIntl( + +
+ +
+
+ ); + expect(wrapper.text()).toEqual('\\[user-domain-123]'); + }); + test('it returns userName if that is the only attribute defined', () => { const wrapper = mountWithIntl( @@ -64,6 +85,7 @@ describe('UserHostWorkingDir', () => { { { { { { expect(wrapper.text()).toEqual('[host-name-123]in[working-directory-123]'); }); - test('it returns userName, hostName', () => { + test('it returns userName, userDomain, hostName', () => { const wrapper = mountWithIntl(
{
); - expect(wrapper.text()).toEqual('[user-name-123]@[host-name-123]'); + expect(wrapper.text()).toEqual('[user-name-123]\\[user-domain-123]@[host-name-123]'); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx index 076dbbf324766b..f9ee78aaa5bdee 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx @@ -14,27 +14,62 @@ import { HostWorkingDir } from './host_working_dir'; interface Props { contextId: string; eventId: string; + userDomain: string | null | undefined; + userDomainField?: string; userName: string | null | undefined; + userNameField?: string; hostName: string | null | undefined; + hostNameSeparator?: string; workingDirectory: string | null | undefined; } export const UserHostWorkingDir = pure( - ({ contextId, eventId, userName, hostName, workingDirectory }) => - userName != null || hostName != null || workingDirectory != null ? ( + ({ + contextId, + eventId, + hostName, + hostNameSeparator = '@', + userDomain, + userDomainField = 'user.domain', + userName, + userNameField = 'user.name', + workingDirectory, + }) => + userName != null || userDomain != null || hostName != null || workingDirectory != null ? ( <> + + {userDomain != null && ( + <> + + {'\\'} + + + + + + )} + {hostName != null && userName != null && ( - {'@'} + {hostNameSeparator} )} Date: Wed, 16 Oct 2019 10:36:40 +0200 Subject: [PATCH 7/7] Server saved objects client through request context (#44143) * Expose Saved Objects client in request context * API Integration test for savedobjects in req context * SavedObjectsClient docs * SavedObjectsClient#find remove dependency on indexPatterns And use the saved objects mappings instead * Review comments * Review comments, fixes and tests * Use correct type for KQL syntax check --- ...a-plugin-public.savedobjectsclient.find.md | 2 +- ...kibana-plugin-public.savedobjectsclient.md | 2 +- ...gin-server.callapioptions.wrap401errors.md | 2 +- .../core/server/kibana-plugin-server.md | 5 +- ...lugin-server.requesthandlercontext.core.md | 3 + ...ana-plugin-server.requesthandlercontext.md | 4 +- ...server.savedobjectsclient._constructor_.md | 20 +++ ...in-server.savedobjectsclient.bulkcreate.md | 25 +++ ...lugin-server.savedobjectsclient.bulkget.md | 29 ++++ ...plugin-server.savedobjectsclient.create.md | 26 ++++ ...plugin-server.savedobjectsclient.delete.md | 26 ++++ ...plugin-server.savedobjectsclient.errors.md | 11 ++ ...a-plugin-server.savedobjectsclient.find.md | 24 +++ ...na-plugin-server.savedobjectsclient.get.md | 26 ++++ ...kibana-plugin-server.savedobjectsclient.md | 38 +++++ ...plugin-server.savedobjectsclient.update.md | 27 ++++ ...lugin-server.savedobjectsclientcontract.md | 2 +- packages/kbn-es-query/src/kuery/ast/ast.d.ts | 2 +- src/core/public/public.api.md | 2 +- src/core/server/elasticsearch/api_types.ts | 2 +- .../integration_tests/core_services.test.ts | 6 - src/core/server/index.ts | 13 ++ src/core/server/legacy/legacy_service.test.ts | 11 +- src/core/server/legacy/legacy_service.ts | 1 + .../kibana/__mocks__/kibana_migrator.ts | 26 ++++ .../migrations/kibana/kibana_migrator.mock.ts | 18 ++- .../saved_objects_service.mock.ts | 4 +- .../saved_objects_service.test.ts | 34 +++-- .../saved_objects/saved_objects_service.ts | 59 ++++++- .../server/saved_objects/service/index.ts | 10 +- .../service/lib/cache_index_patterns.test.ts | 108 ------------- .../service/lib/cache_index_patterns.ts | 82 ---------- .../service/lib/filter_utils.test.ts | 134 ++++++---------- .../saved_objects/service/lib/filter_utils.ts | 76 +++++---- .../server/saved_objects/service/lib/index.ts | 5 +- .../service/lib/repository.test.js | 144 +++++++++++------- .../saved_objects/service/lib/repository.ts | 34 ++--- .../lib/scoped_client_provider.mock.ts | 30 ++++ .../lib/scoped_client_provider.test.js | 16 +- .../service/lib/scoped_client_provider.ts | 19 ++- .../lib/search_dsl/query_params.test.ts | 39 ----- .../service/lib/search_dsl/query_params.ts | 5 +- .../service/lib/search_dsl/search_dsl.ts | 4 - .../service/saved_objects_client.ts | 2 +- src/core/server/saved_objects/types.ts | 1 + src/core/server/server.api.md | 16 +- src/core/server/server.ts | 45 ++++-- .../saved_objects/saved_objects_mixin.js | 27 +--- .../saved_objects/saved_objects_mixin.test.js | 25 +-- src/plugins/data/server/search/routes.test.ts | 6 +- src/plugins/testbed/server/index.ts | 19 ++- test/api_integration/apis/core/index.js | 14 +- test/api_integration/apis/index.js | 1 + .../apis/saved_objects/find.js | 16 ++ 54 files changed, 743 insertions(+), 585 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkcreate.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkget.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient.create.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient.errors.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient.find.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient.get.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclient.update.md create mode 100644 src/core/server/saved_objects/migrations/kibana/__mocks__/kibana_migrator.ts delete mode 100644 src/core/server/saved_objects/service/lib/cache_index_patterns.test.ts delete mode 100644 src/core/server/saved_objects/service/lib/cache_index_patterns.ts create mode 100644 src/core/server/saved_objects/service/lib/scoped_client_provider.mock.ts diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md index a4fa3f17d0d94f..866755e78648af 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md @@ -9,5 +9,5 @@ Search for objects Signature: ```typescript -find: (options: Pick) => Promise>; +find: (options: Pick) => Promise>; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md index 00a71d25cea38a..1d0d942a24c0ab 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md @@ -20,7 +20,7 @@ export declare class SavedObjectsClient | [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | (objects?: {
id: string;
type: string;
}[]) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>> | Returns an array of objects by id | | [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>> | Persists an object | | [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | (type: string, id: string) => Promise<{}> | Deletes an object | -| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "perPage" | "sortField" | "fields" | "searchFields" | "hasReference" | "defaultSearchOperator">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | +| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "page" | "perPage" | "fields">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | | [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>> | Fetches a single object | ## Methods diff --git a/docs/development/core/server/kibana-plugin-server.callapioptions.wrap401errors.md b/docs/development/core/server/kibana-plugin-server.callapioptions.wrap401errors.md index 4436a07d0b28b6..296d7695339508 100644 --- a/docs/development/core/server/kibana-plugin-server.callapioptions.wrap401errors.md +++ b/docs/development/core/server/kibana-plugin-server.callapioptions.wrap401errors.md @@ -9,5 +9,5 @@ Indicates whether `401 Unauthorized` errors returned from the Elasticsearch API Signature: ```typescript -wrap401Errors: boolean; +wrap401Errors?: boolean; ``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 8a259f68706288..c085d4cdf0d427 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -20,6 +20,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | +| [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | | [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | | [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | @@ -77,7 +78,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginManifest](./kibana-plugin-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | | [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | | | [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | | -| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler. | +| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request | | [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | | [SavedObject](./kibana-plugin-server.savedobject.md) | | @@ -159,6 +160,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | | [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | -| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | +| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md index d06b3b9ea637c5..e1ea358320b9a6 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md @@ -8,6 +8,9 @@ ```typescript core: { + savedObjects: { + client: SavedObjectsClientContract; + }; elasticsearch: { dataClient: IScopedClusterClient; adminClient: IScopedClusterClient; diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md index 85c7989c6e8637..37a40f98adef3b 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md @@ -6,6 +6,8 @@ Plugin specific context passed to a route handler. +Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request + Signature: ```typescript @@ -16,5 +18,5 @@ export interface RequestHandlerContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-server.requesthandlercontext.core.md) | {
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
} | | +| [core](./kibana-plugin-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
} | | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md new file mode 100644 index 00000000000000..0bcca3ec57b546 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [(constructor)](./kibana-plugin-server.savedobjectsclient._constructor_.md) + +## SavedObjectsClient.(constructor) + +Constructs a new instance of the `SavedObjectsClient` class + +Signature: + +```typescript +constructor(repository: SavedObjectsRepository); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| repository | SavedObjectsRepository | | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkcreate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkcreate.md new file mode 100644 index 00000000000000..1081a91f927624 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkcreate.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [bulkCreate](./kibana-plugin-server.savedobjectsclient.bulkcreate.md) + +## SavedObjectsClient.bulkCreate() method + +Persists multiple documents batched together as a single request + +Signature: + +```typescript +bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | Array<SavedObjectsBulkCreateObject<T>> | | +| options | SavedObjectsCreateOptions | | + +Returns: + +`Promise>` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkget.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkget.md new file mode 100644 index 00000000000000..6fbeadd4ce67c4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkget.md @@ -0,0 +1,29 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [bulkGet](./kibana-plugin-server.savedobjectsclient.bulkget.md) + +## SavedObjectsClient.bulkGet() method + +Returns an array of objects by id + +Signature: + +```typescript +bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | SavedObjectsBulkGetObject[] | an array of ids, or an array of objects containing id, type and optionally fields | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + +## Example + +bulkGet(\[ { id: 'one', type: 'config' }, { id: 'foo', type: 'index-pattern' } \]) + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.create.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.create.md new file mode 100644 index 00000000000000..68b97ccdf2aef4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.create.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [create](./kibana-plugin-server.savedobjectsclient.create.md) + +## SavedObjectsClient.create() method + +Persists a SavedObject + +Signature: + +```typescript +create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| attributes | T | | +| options | SavedObjectsCreateOptions | | + +Returns: + +`Promise>` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md new file mode 100644 index 00000000000000..657f56d591e772 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [delete](./kibana-plugin-server.savedobjectsclient.delete.md) + +## SavedObjectsClient.delete() method + +Deletes a SavedObject + +Signature: + +```typescript +delete(type: string, id: string, options?: SavedObjectsBaseOptions): Promise<{}>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise<{}>` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.errors.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.errors.md new file mode 100644 index 00000000000000..f08440485c63c5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.errors.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [errors](./kibana-plugin-server.savedobjectsclient.errors.md) + +## SavedObjectsClient.errors property + +Signature: + +```typescript +static errors: typeof SavedObjectsErrorHelpers; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.find.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.find.md new file mode 100644 index 00000000000000..a590cc4c4b663d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.find.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [find](./kibana-plugin-server.savedobjectsclient.find.md) + +## SavedObjectsClient.find() method + +Find all SavedObjects matching the search query + +Signature: + +```typescript +find(options: SavedObjectsFindOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | SavedObjectsFindOptions | | + +Returns: + +`Promise>` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.get.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.get.md new file mode 100644 index 00000000000000..bde16a134f5b59 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.get.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [get](./kibana-plugin-server.savedobjectsclient.get.md) + +## SavedObjectsClient.get() method + +Retrieves a single object + +Signature: + +```typescript +get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | The type of SavedObject to retrieve | +| id | string | The ID of the SavedObject to retrieve | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md new file mode 100644 index 00000000000000..0081c729fe10a5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md @@ -0,0 +1,38 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) + +## SavedObjectsClient class + + +Signature: + +```typescript +export declare class SavedObjectsClient +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(repository)](./kibana-plugin-server.savedobjectsclient._constructor_.md) | | Constructs a new instance of the SavedObjectsClient class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [errors](./kibana-plugin-server.savedobjectsclient.errors.md) | | typeof SavedObjectsErrorHelpers | | +| [errors](./kibana-plugin-server.savedobjectsclient.errors.md) | static | typeof SavedObjectsErrorHelpers | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [bulkCreate(objects, options)](./kibana-plugin-server.savedobjectsclient.bulkcreate.md) | | Persists multiple documents batched together as a single request | +| [bulkGet(objects, options)](./kibana-plugin-server.savedobjectsclient.bulkget.md) | | Returns an array of objects by id | +| [create(type, attributes, options)](./kibana-plugin-server.savedobjectsclient.create.md) | | Persists a SavedObject | +| [delete(type, id, options)](./kibana-plugin-server.savedobjectsclient.delete.md) | | Deletes a SavedObject | +| [find(options)](./kibana-plugin-server.savedobjectsclient.find.md) | | Find all SavedObjects matching the search query | +| [get(type, id, options)](./kibana-plugin-server.savedobjectsclient.get.md) | | Retrieves a single object | +| [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsclient.update.md) | | Updates an SavedObject | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.update.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.update.md new file mode 100644 index 00000000000000..16454c98bb55b3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.update.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [update](./kibana-plugin-server.savedobjectsclient.update.md) + +## SavedObjectsClient.update() method + +Updates an SavedObject + +Signature: + +```typescript +update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| attributes | Partial<T> | | +| options | SavedObjectsUpdateOptions | | + +Returns: + +`Promise>` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientcontract.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientcontract.md index ae948b090146b9..bc5b11dd21b785 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclientcontract.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientcontract.md @@ -34,7 +34,7 @@ From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The obje Unlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's `action.auto_create_index` setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated. -See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) +See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) Signature: diff --git a/packages/kbn-es-query/src/kuery/ast/ast.d.ts b/packages/kbn-es-query/src/kuery/ast/ast.d.ts index 448ef0e9cca750..06f4940e8ed3b5 100644 --- a/packages/kbn-es-query/src/kuery/ast/ast.d.ts +++ b/packages/kbn-es-query/src/kuery/ast/ast.d.ts @@ -37,6 +37,6 @@ export function fromKueryExpression( parseOptions?: KueryParseOptions ): KueryNode; -export function toElasticsearchQuery(node: KueryNode, indexPattern: any): JsonObject; +export function toElasticsearchQuery(node: KueryNode, indexPattern?: any): JsonObject; export function doesKueryExpressionHaveLuceneSyntaxError(expression: string): boolean; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 911b87e2bedf20..0db6e74c7e8044 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -768,7 +768,7 @@ export class SavedObjectsClient { }[]) => Promise>; create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; delete: (type: string, id: string) => Promise<{}>; - find: (options: Pick) => Promise>; + find: (options: Pick) => Promise>; get: (type: string, id: string) => Promise>; update(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise>; } diff --git a/src/core/server/elasticsearch/api_types.ts b/src/core/server/elasticsearch/api_types.ts index 8444f97b0c7f02..5f2b61eb4935fd 100644 --- a/src/core/server/elasticsearch/api_types.ts +++ b/src/core/server/elasticsearch/api_types.ts @@ -158,7 +158,7 @@ export interface CallAPIOptions { * header that could have been returned by the API itself. If API didn't specify that * then `Basic realm="Authorization Required"` is used as `WWW-Authenticate`. */ - wrap401Errors: boolean; + wrap401Errors?: boolean; /** * A signal object that allows you to abort the request via an AbortController object. */ diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index 1dfa8e3c66d108..00629b811b28fc 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -233,9 +233,6 @@ describe('http service', () => { await kbnTestServer.request.get(root, '/new-platform/').expect(200); - // called twice by elasticsearch service in http route handler context provider - expect(clusterClientMock).toBeCalledTimes(2); - // admin client contains authHeaders for BWC with legacy platform. const [adminClient, dataClient] = clusterClientMock.mock.calls; const [, , adminClientHeaders] = adminClient; @@ -259,9 +256,6 @@ describe('http service', () => { .set('Authorization', authorizationHeader) .expect(200); - // called twice by elasticsearch service in http route handler context provider - expect(clusterClientMock).toBeCalledTimes(2); - const [adminClient, dataClient] = clusterClientMock.mock.calls; const [, , adminClientHeaders] = adminClient; expect(adminClientHeaders).toEqual({ authorization: authorizationHeader }); diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 0941153fee87d6..d92c92841bb484 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -48,6 +48,7 @@ import { InternalHttpServiceSetup, HttpServiceSetup } from './http'; import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins'; import { ContextSetup } from './context'; import { SavedObjectsServiceStart } from './saved_objects'; +import { SavedObjectsClientContract } from './saved_objects/types'; export { bootstrap } from './bootstrap'; export { ConfigPath, ConfigService, EnvironmentMode, PackageInfo } from './config'; @@ -180,10 +181,22 @@ export { LegacyServiceSetupDeps, LegacyServiceStartDeps } from './legacy'; /** * Plugin specific context passed to a route handler. + * + * Provides the following clients: + * - {@link SavedObjectsClient | savedObjects.client} - Saved Objects client + * which uses the credentials of the incoming request + * - {@link ScopedClusterClient | elasticsearch.dataClient} - Elasticsearch + * data client which uses the credentials of the incoming request + * - {@link ScopedClusterClient | elasticsearch.adminClient} - Elasticsearch + * admin client which uses the credentials of the incoming request + * * @public */ export interface RequestHandlerContext { core: { + savedObjects: { + client: SavedObjectsClientContract; + }; elasticsearch: { dataClient: IScopedClusterClient; adminClient: IScopedClusterClient; diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 9b64d7c811aa7a..bdb1499e065ca9 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -44,8 +44,12 @@ import { HttpServiceStart, BasePathProxyServer } from '../http'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { DiscoveredPlugin, DiscoveredPluginInternal } from '../plugins'; import { PluginsServiceSetup, PluginsServiceStart } from '../plugins/plugins_service'; -import { SavedObjectsServiceStart } from 'src/core/server/saved_objects/saved_objects_service'; +import { + SavedObjectsServiceStart, + SavedObjectsServiceSetup, +} from 'src/core/server/saved_objects/saved_objects_service'; import { KibanaMigrator } from '../saved_objects/migrations'; +import { ISavedObjectsClientProvider } from '../saved_objects'; import { httpServiceMock } from '../http/http_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -59,6 +63,7 @@ let setupDeps: { elasticsearch: InternalElasticsearchServiceSetup; http: any; plugins: PluginsServiceSetup; + savedObjects: SavedObjectsServiceSetup; }; plugins: Record; }; @@ -99,6 +104,9 @@ beforeEach(() => { internal: new Map([['plugin-id', {} as DiscoveredPluginInternal]]), }, }, + savedObjects: { + clientProvider: {} as ISavedObjectsClientProvider, + }, }, plugins: { 'plugin-id': 'plugin-value' }, }; @@ -110,6 +118,7 @@ beforeEach(() => { }, savedObjects: { migrator: {} as KibanaMigrator, + clientProvider: {} as ISavedObjectsClientProvider, }, plugins: { contracts: new Map() }, }, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 52e999f5e03a75..edd913fe16a63d 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -275,6 +275,7 @@ export class LegacyService implements CoreService { kibanaMigrator: startDeps.core.savedObjects.migrator, uiPlugins: setupDeps.core.plugins.uiPlugins, elasticsearch: setupDeps.core.elasticsearch, + savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider, }, logger: this.coreContext.logger, }, diff --git a/src/core/server/saved_objects/migrations/kibana/__mocks__/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/__mocks__/kibana_migrator.ts new file mode 100644 index 00000000000000..d9f745810412cb --- /dev/null +++ b/src/core/server/saved_objects/migrations/kibana/__mocks__/kibana_migrator.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { mockKibanaMigrator } from '../kibana_migrator.mock'; + +export const mockKibanaMigratorInstance = mockKibanaMigrator.create(); + +const mockConstructor = jest.fn().mockImplementation(() => mockKibanaMigratorInstance); + +export const KibanaMigrator = mockConstructor; diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts index ca732f4f150282..c1021018538d53 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts @@ -17,16 +17,30 @@ * under the License. */ -import { KibanaMigrator, mergeProperties } from './kibana_migrator'; +import { KibanaMigrator } from './kibana_migrator'; import { buildActiveMappings } from '../core'; import { SavedObjectsMapping } from '../../mappings'; +const { mergeProperties } = jest.requireActual('./kibana_migrator'); + +const defaultSavedObjectMappings = [ + { + pluginId: 'testplugin', + properties: { + testtype: { + properties: { + name: { type: 'keyword' }, + }, + }, + }, + }, +]; const createMigrator = ( { savedObjectMappings, }: { savedObjectMappings: SavedObjectsMapping[]; - } = { savedObjectMappings: [] } + } = { savedObjectMappings: defaultSavedObjectMappings } ) => { const mockMigrator: jest.Mocked> = { runMigrations: jest.fn(), diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index 5561031d820ec0..0a021ee97e26ad 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -19,11 +19,13 @@ import { SavedObjectsService, SavedObjectsServiceStart } from './saved_objects_service'; import { mockKibanaMigrator } from './migrations/kibana/kibana_migrator.mock'; +import { savedObjectsClientProviderMock } from './service/lib/scoped_client_provider.mock'; type SavedObjectsServiceContract = PublicMethodsOf; const createStartContractMock = () => { const startContract: jest.Mocked = { + clientProvider: savedObjectsClientProviderMock.create(), migrator: mockKibanaMigrator.create(), }; @@ -37,7 +39,7 @@ const createsavedObjectsServiceMock = () => { stop: jest.fn(), }; - mocked.setup.mockResolvedValue({}); + mocked.setup.mockResolvedValue({ clientProvider: savedObjectsClientProviderMock.create() }); mocked.start.mockResolvedValue(createStartContractMock()); mocked.stop.mockResolvedValue(); return mocked; diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index ffcfa247f756fb..07bb4342c754a0 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -21,11 +21,13 @@ jest.mock('./migrations/kibana/kibana_migrator'); import { SavedObjectsService, SavedObjectsSetupDeps } from './saved_objects_service'; import { mockCoreContext } from '../core_context.mock'; -import { KibanaMigrator } from './migrations/kibana/kibana_migrator'; +// @ts-ignore Typescript doesn't know about the jest mock +import { KibanaMigrator, mockKibanaMigratorInstance } from './migrations/kibana/kibana_migrator'; import { of } from 'rxjs'; import * as legacyElasticsearch from 'elasticsearch'; import { Env } from '../config'; import { configServiceMock } from '../mocks'; +import { SavedObjectsClientProvider } from '.'; afterEach(() => { jest.clearAllMocks(); @@ -49,7 +51,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ elasticsearch: { adminClient$: of(clusterClient) }, - legacy: { uiExports: {}, pluginExtendedConfig: {} }, + legacy: { uiExports: { savedObjectMappings: [] }, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); @@ -58,6 +60,18 @@ describe('SavedObjectsService', () => { 'success' ); }); + + it('resolves with clientProvider', async () => { + const coreContext = mockCoreContext.create(); + const soService = new SavedObjectsService(coreContext); + const coreSetup = ({ + elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, + legacy: { uiExports: {}, pluginExtendedConfig: {} }, + } as unknown) as SavedObjectsSetupDeps; + + const savedObjectsSetup = await soService.setup(coreSetup); + expect(savedObjectsSetup.clientProvider).toBeInstanceOf(SavedObjectsClientProvider); + }); }); describe('#start()', () => { @@ -72,9 +86,8 @@ describe('SavedObjectsService', () => { } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); - const migrator = (KibanaMigrator as jest.Mock).mock.instances[0]; await soService.start({}); - expect(migrator.runMigrations).toHaveBeenCalledWith(true); + expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledWith(true); }); it('skips KibanaMigrator migrations when migrations.skip=true', async () => { @@ -87,9 +100,8 @@ describe('SavedObjectsService', () => { } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); - const migrator = (KibanaMigrator as jest.Mock).mock.instances[0]; await soService.start({}); - expect(migrator.runMigrations).toHaveBeenCalledWith(true); + expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledWith(true); }); it('resolves with KibanaMigrator after waiting for migrations to complete', async () => { @@ -102,12 +114,12 @@ describe('SavedObjectsService', () => { } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); - const migrator = (KibanaMigrator as jest.Mock).mock.instances[0]; - expect(migrator.runMigrations).toHaveBeenCalledTimes(0); + expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledTimes(0); + const startContract = await soService.start({}); - expect(startContract.migrator).toBeInstanceOf(KibanaMigrator); - expect(migrator.runMigrations).toHaveBeenCalledWith(false); - expect(migrator.runMigrations).toHaveBeenCalledTimes(1); + expect(startContract.migrator).toBe(mockKibanaMigratorInstance); + expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledWith(false); + expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index ebcea8dc3b2750..5ccb02414d043b 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -19,6 +19,15 @@ import { CoreService } from 'src/core/types'; import { first } from 'rxjs/operators'; +import { + SavedObjectsClient, + SavedObjectsSchema, + SavedObjectsRepository, + SavedObjectsSerializer, + SavedObjectsClientProvider, + ISavedObjectsClientProvider, +} from './'; +import { getRootPropertiesObjects } from './mappings'; import { KibanaMigrator, IKibanaMigrator } from './migrations'; import { CoreContext } from '../core_context'; import { LegacyServiceSetup } from '../legacy/legacy_service'; @@ -26,19 +35,22 @@ import { ElasticsearchServiceSetup } from '../elasticsearch'; import { KibanaConfigType } from '../kibana_config'; import { retryCallCluster } from '../elasticsearch/retry_call_cluster'; import { SavedObjectsConfigType } from './saved_objects_config'; +import { KibanaRequest } from '../http'; import { Logger } from '..'; /** * @public */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface SavedObjectsServiceSetup {} +export interface SavedObjectsServiceSetup { + clientProvider: ISavedObjectsClientProvider; +} /** * @public */ export interface SavedObjectsServiceStart { migrator: IKibanaMigrator; + clientProvider: ISavedObjectsClientProvider; } /** @internal */ @@ -54,13 +66,14 @@ export interface SavedObjectsStartDeps {} export class SavedObjectsService implements CoreService { private migrator: KibanaMigrator | undefined; - logger: Logger; + private logger: Logger; + private clientProvider: ISavedObjectsClientProvider | undefined; constructor(private readonly coreContext: CoreContext) { this.logger = coreContext.logger.get('savedobjects-service'); } - public async setup(coreSetup: SavedObjectsSetupDeps) { + public async setup(coreSetup: SavedObjectsSetupDeps): Promise { this.logger.debug('Setting up SavedObjects service'); const { @@ -82,7 +95,7 @@ export class SavedObjectsService .pipe(first()) .toPromise(); - this.migrator = new KibanaMigrator({ + const migrator = (this.migrator = new KibanaMigrator({ savedObjectSchemas, savedObjectMappings, savedObjectMigrations, @@ -93,12 +106,41 @@ export class SavedObjectsService savedObjectsConfig, kibanaConfig, callCluster: retryCallCluster(adminClient.callAsInternalUser), + })); + + const mappings = this.migrator.getActiveMappings(); + const allTypes = Object.keys(getRootPropertiesObjects(mappings)); + const schema = new SavedObjectsSchema(savedObjectSchemas); + const serializer = new SavedObjectsSerializer(schema); + const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type)); + + this.clientProvider = new SavedObjectsClientProvider({ + defaultClientFactory({ request }) { + const repository = new SavedObjectsRepository({ + index: kibanaConfig.index, + config: coreSetup.legacy.pluginExtendedConfig, + migrator, + mappings, + schema, + serializer, + allowedTypes: visibleTypes, + callCluster: retryCallCluster(adminClient.asScoped(request).callAsCurrentUser), + }); + + return new SavedObjectsClient(repository); + }, }); - return ({} as any) as Promise; + return { + clientProvider: this.clientProvider, + }; } public async start(core: SavedObjectsStartDeps): Promise { + if (!this.clientProvider) { + throw new Error('#setup() needs to be run first'); + } + this.logger.debug('Starting SavedObjects service'); /** @@ -119,7 +161,10 @@ export class SavedObjectsService const skipMigrations = cliArgs.optimize || savedObjectsConfig.skip; await this.migrator!.runMigrations(skipMigrations); - return { migrator: this.migrator! }; + return { + migrator: this.migrator!, + clientProvider: this.clientProvider, + }; } public async stop() {} diff --git a/src/core/server/saved_objects/service/index.ts b/src/core/server/saved_objects/service/index.ts index dbf35ff4e134d7..15f46711fc94b5 100644 --- a/src/core/server/saved_objects/service/index.ts +++ b/src/core/server/saved_objects/service/index.ts @@ -18,7 +18,7 @@ */ import { Readable } from 'stream'; -import { ScopedSavedObjectsClientProvider } from './lib'; +import { SavedObjectsClientProvider } from './lib'; import { SavedObjectsClient } from './saved_objects_client'; import { SavedObjectsExportOptions } from '../export'; import { SavedObjectsImportOptions, SavedObjectsImportResponse } from '../import'; @@ -31,10 +31,10 @@ import { SavedObjectsResolveImportErrorsOptions } from '../import/types'; */ export interface SavedObjectsLegacyService { // ATTENTION: these types are incomplete - addScopedSavedObjectsClientWrapperFactory: ScopedSavedObjectsClientProvider< + addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider< Request >['addClientWrapperFactory']; - getScopedSavedObjectsClient: ScopedSavedObjectsClientProvider['getClient']; + getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; SavedObjectsClient: typeof SavedObjectsClient; types: string[]; schema: SavedObjectsSchema; @@ -51,12 +51,12 @@ export interface SavedObjectsLegacyService { export { SavedObjectsRepository, - ScopedSavedObjectsClientProvider, + SavedObjectsClientProvider, + ISavedObjectsClientProvider, SavedObjectsClientProviderOptions, SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, SavedObjectsErrorHelpers, - SavedObjectsCacheIndexPatterns, } from './lib'; export * from './saved_objects_client'; diff --git a/src/core/server/saved_objects/service/lib/cache_index_patterns.test.ts b/src/core/server/saved_objects/service/lib/cache_index_patterns.test.ts deleted file mode 100644 index e3aeca42d1cf07..00000000000000 --- a/src/core/server/saved_objects/service/lib/cache_index_patterns.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { SavedObjectsCacheIndexPatterns } from './cache_index_patterns'; - -const mockGetFieldsForWildcard = jest.fn(); -const mockIndexPatternsService: jest.Mock = jest.fn().mockImplementation(() => ({ - getFieldsForWildcard: mockGetFieldsForWildcard, - getFieldsForTimePattern: jest.fn(), -})); - -describe('SavedObjectsRepository', () => { - let cacheIndexPatterns: SavedObjectsCacheIndexPatterns; - - const fields = [ - { - aggregatable: true, - name: 'config.type', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'foo.type', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'bar.type', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'baz.type', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'dashboard.otherField', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'hiddenType.someField', - searchable: true, - type: 'string', - }, - ]; - - beforeEach(() => { - cacheIndexPatterns = new SavedObjectsCacheIndexPatterns(); - jest.clearAllMocks(); - }); - - it('setIndexPatterns should return an error object when indexPatternsService is undefined', async () => { - try { - await cacheIndexPatterns.setIndexPatterns('test-index'); - } catch (error) { - expect(error.message).toMatch('indexPatternsService is not defined'); - } - }); - - it('setIndexPatterns should return an error object if getFieldsForWildcard is not defined', async () => { - mockGetFieldsForWildcard.mockImplementation(() => { - throw new Error('something happen'); - }); - try { - cacheIndexPatterns.setIndexPatternsService(new mockIndexPatternsService()); - await cacheIndexPatterns.setIndexPatterns('test-index'); - } catch (error) { - expect(error.message).toMatch('Index Pattern Error - something happen'); - } - }); - - it('setIndexPatterns should return empty array when getFieldsForWildcard is returning null or undefined', async () => { - mockGetFieldsForWildcard.mockImplementation(() => null); - cacheIndexPatterns.setIndexPatternsService(new mockIndexPatternsService()); - await cacheIndexPatterns.setIndexPatterns('test-index'); - expect(cacheIndexPatterns.getIndexPatterns()).toEqual(undefined); - }); - - it('setIndexPatterns should return index pattern when getFieldsForWildcard is returning fields', async () => { - mockGetFieldsForWildcard.mockImplementation(() => fields); - cacheIndexPatterns.setIndexPatternsService(new mockIndexPatternsService()); - await cacheIndexPatterns.setIndexPatterns('test-index'); - expect(cacheIndexPatterns.getIndexPatterns()).toEqual({ fields, title: 'test-index' }); - }); -}); diff --git a/src/core/server/saved_objects/service/lib/cache_index_patterns.ts b/src/core/server/saved_objects/service/lib/cache_index_patterns.ts deleted file mode 100644 index e96cf996f504c3..00000000000000 --- a/src/core/server/saved_objects/service/lib/cache_index_patterns.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { FieldDescriptor } from 'src/legacy/server/index_patterns/service/index_patterns_service'; -import { IndexPatternsService } from 'src/legacy/server/index_patterns'; - -export interface SavedObjectsIndexPatternField { - name: string; - type: string; - aggregatable: boolean; - searchable: boolean; -} - -export interface SavedObjectsIndexPattern { - fields: SavedObjectsIndexPatternField[]; - title: string; -} - -export class SavedObjectsCacheIndexPatterns { - private _indexPatterns: SavedObjectsIndexPattern | undefined = undefined; - private _indexPatternsService: IndexPatternsService | undefined = undefined; - - public setIndexPatternsService(indexPatternsService: IndexPatternsService) { - this._indexPatternsService = indexPatternsService; - } - - public getIndexPatternsService() { - return this._indexPatternsService; - } - - public getIndexPatterns(): SavedObjectsIndexPattern | undefined { - return this._indexPatterns; - } - - public async setIndexPatterns(index: string) { - await this._getIndexPattern(index); - } - - private async _getIndexPattern(index: string) { - try { - if (this._indexPatternsService == null) { - throw new TypeError('indexPatternsService is not defined'); - } - const fieldsDescriptor: FieldDescriptor[] = await this._indexPatternsService.getFieldsForWildcard( - { - pattern: index, - } - ); - - this._indexPatterns = - fieldsDescriptor && Array.isArray(fieldsDescriptor) && fieldsDescriptor.length > 0 - ? { - fields: fieldsDescriptor.map(field => ({ - aggregatable: field.aggregatable, - name: field.name, - searchable: field.searchable, - type: field.type, - })), - title: index, - } - : undefined; - } catch (err) { - throw new Error(`Index Pattern Error - ${err.message}`); - } - } -} diff --git a/src/core/server/saved_objects/service/lib/filter_utils.test.ts b/src/core/server/saved_objects/service/lib/filter_utils.test.ts index 73a0804512ed10..80a6a96aeaf2bf 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.test.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.test.ts @@ -19,66 +19,51 @@ import { fromKueryExpression } from '@kbn/es-query'; -import { - validateFilterKueryNode, - getSavedObjectTypeIndexPatterns, - validateConvertFilterToKueryNode, -} from './filter_utils'; -import { SavedObjectsIndexPattern } from './cache_index_patterns'; +import { validateFilterKueryNode, validateConvertFilterToKueryNode } from './filter_utils'; -const mockIndexPatterns: SavedObjectsIndexPattern = { - fields: [ - { - name: 'updatedAt', +const mockMappings = { + properties: { + updatedAt: { type: 'date', - aggregatable: true, - searchable: true, }, - { - name: 'foo.title', - type: 'text', - aggregatable: true, - searchable: true, - }, - { - name: 'foo.description', - type: 'text', - aggregatable: true, - searchable: true, - }, - { - name: 'foo.bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'bar.foo', - type: 'text', - aggregatable: true, - searchable: true, + foo: { + properties: { + title: { + type: 'text', + }, + description: { + type: 'text', + }, + bytes: { + type: 'number', + }, + }, }, - { - name: 'bar.description', - type: 'text', - aggregatable: true, - searchable: true, + bar: { + properties: { + foo: { + type: 'text', + }, + description: { + type: 'text', + }, + }, }, - { - name: 'hiddentype.description', - type: 'text', - aggregatable: true, - searchable: true, + hiddenType: { + properties: { + description: { + type: 'text', + }, + }, }, - ], - title: 'mock', + }, }; describe('Filter Utils', () => { describe('#validateConvertFilterToKueryNode', () => { test('Validate a simple filter', () => { expect( - validateConvertFilterToKueryNode(['foo'], 'foo.attributes.title: "best"', mockIndexPatterns) + validateConvertFilterToKueryNode(['foo'], 'foo.attributes.title: "best"', mockMappings) ).toEqual(fromKueryExpression('foo.title: "best"')); }); test('Assemble filter kuery node saved object attributes with one saved object type', () => { @@ -86,7 +71,7 @@ describe('Filter Utils', () => { validateConvertFilterToKueryNode( ['foo'], 'foo.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)', - mockIndexPatterns + mockMappings ) ).toEqual( fromKueryExpression( @@ -100,7 +85,7 @@ describe('Filter Utils', () => { validateConvertFilterToKueryNode( ['foo', 'bar'], 'foo.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)', - mockIndexPatterns + mockMappings ) ).toEqual( fromKueryExpression( @@ -114,7 +99,7 @@ describe('Filter Utils', () => { validateConvertFilterToKueryNode( ['foo', 'bar'], '(bar.updatedAt: 5678654567 OR foo.updatedAt: 5678654567) and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or bar.attributes.description :*)', - mockIndexPatterns + mockMappings ) ).toEqual( fromKueryExpression( @@ -128,7 +113,7 @@ describe('Filter Utils', () => { validateConvertFilterToKueryNode( ['foo', 'bar'], 'updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)', - mockIndexPatterns + mockMappings ); }).toThrowErrorMatchingInlineSnapshot( `"This key 'updatedAt' need to be wrapped by a saved object type like foo,bar: Bad Request"` @@ -137,7 +122,7 @@ describe('Filter Utils', () => { test('Lets make sure that we are throwing an exception if we are using hiddentype with types', () => { expect(() => { - validateConvertFilterToKueryNode([], 'hiddentype.title: "title"', mockIndexPatterns); + validateConvertFilterToKueryNode([], 'hiddentype.title: "title"', mockMappings); }).toThrowErrorMatchingInlineSnapshot(`"This type hiddentype is not allowed: Bad Request"`); }); }); @@ -149,7 +134,7 @@ describe('Filter Utils', () => { 'foo.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)' ), ['foo'], - getSavedObjectTypeIndexPatterns(['foo'], mockIndexPatterns) + mockMappings ); expect(validationObject).toEqual([ @@ -204,7 +189,7 @@ describe('Filter Utils', () => { 'updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)' ), ['foo'], - getSavedObjectTypeIndexPatterns(['foo'], mockIndexPatterns) + mockMappings ); expect(validationObject).toEqual([ @@ -259,7 +244,7 @@ describe('Filter Utils', () => { 'foo.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.description :*)' ), ['foo'], - getSavedObjectTypeIndexPatterns(['foo'], mockIndexPatterns) + mockMappings ); expect(validationObject).toEqual([ @@ -316,7 +301,7 @@ describe('Filter Utils', () => { 'bar.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)' ), ['foo'], - getSavedObjectTypeIndexPatterns(['foo'], mockIndexPatterns) + mockMappings ); expect(validationObject).toEqual([ @@ -371,7 +356,7 @@ describe('Filter Utils', () => { 'foo.updatedAt33: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.header: "best" and (foo.attributes.description: t* or foo.attributes.description :*)' ), ['foo'], - getSavedObjectTypeIndexPatterns(['foo'], mockIndexPatterns) + mockMappings ); expect(validationObject).toEqual([ @@ -421,37 +406,4 @@ describe('Filter Utils', () => { ]); }); }); - - describe('#getSavedObjectTypeIndexPatterns', () => { - test('Get index patterns related to your type', () => { - const indexPatternsFilterByType = getSavedObjectTypeIndexPatterns(['foo'], mockIndexPatterns); - - expect(indexPatternsFilterByType).toEqual([ - { - name: 'updatedAt', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'foo.title', - type: 'text', - aggregatable: true, - searchable: true, - }, - { - name: 'foo.description', - type: 'text', - aggregatable: true, - searchable: true, - }, - { - name: 'foo.bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ]); - }); - }); }); diff --git a/src/core/server/saved_objects/service/lib/filter_utils.ts b/src/core/server/saved_objects/service/lib/filter_utils.ts index 2397971e66966f..64abf268cacd6e 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.ts @@ -19,23 +19,21 @@ import { fromKueryExpression, KueryNode, nodeTypes } from '@kbn/es-query'; import { get, set } from 'lodash'; - -import { SavedObjectsIndexPattern, SavedObjectsIndexPatternField } from './cache_index_patterns'; import { SavedObjectsErrorHelpers } from './errors'; +import { IndexMapping } from '../../mappings'; export const validateConvertFilterToKueryNode = ( - types: string[], + allowedTypes: string[], filter: string, - indexPattern: SavedObjectsIndexPattern | undefined + indexMapping: IndexMapping ): KueryNode => { - if (filter && filter.length > 0 && indexPattern) { + if (filter && filter.length > 0 && indexMapping) { const filterKueryNode = fromKueryExpression(filter); - const typeIndexPatterns = getSavedObjectTypeIndexPatterns(types, indexPattern); const validationFilterKuery = validateFilterKueryNode( filterKueryNode, - types, - typeIndexPatterns, + allowedTypes, + indexMapping, filterKueryNode.type === 'function' && ['is', 'range'].includes(filterKueryNode.function) ); @@ -60,7 +58,7 @@ export const validateConvertFilterToKueryNode = ( path.length === 0 ? filterKueryNode : get(filterKueryNode, path); if (item.isSavedObjectAttr) { existingKueryNode.arguments[0].value = existingKueryNode.arguments[0].value.split('.')[1]; - const itemType = types.filter(t => t === item.type); + const itemType = allowedTypes.filter(t => t === item.type); if (itemType.length === 1) { set( filterKueryNode, @@ -84,18 +82,6 @@ export const validateConvertFilterToKueryNode = ( return null; }; -export const getSavedObjectTypeIndexPatterns = ( - types: string[], - indexPattern: SavedObjectsIndexPattern | undefined -): SavedObjectsIndexPatternField[] => { - return indexPattern != null - ? indexPattern.fields.filter( - ip => - !ip.name.includes('.') || (ip.name.includes('.') && types.includes(ip.name.split('.')[0])) - ) - : []; -}; - interface ValidateFilterKueryNode { astPath: string; error: string; @@ -107,7 +93,7 @@ interface ValidateFilterKueryNode { export const validateFilterKueryNode = ( astFilter: KueryNode, types: string[], - typeIndexPatterns: SavedObjectsIndexPatternField[], + indexMapping: IndexMapping, storeValue: boolean = false, path: string = 'arguments' ): ValidateFilterKueryNode[] => { @@ -119,7 +105,7 @@ export const validateFilterKueryNode = ( ...validateFilterKueryNode( ast, types, - typeIndexPatterns, + indexMapping, ast.type === 'function' && ['is', 'range'].includes(ast.function), `${myPath}.arguments` ), @@ -131,8 +117,8 @@ export const validateFilterKueryNode = ( ...kueryNode, { astPath: splitPath.slice(0, splitPath.length - 1).join('.'), - error: hasFilterKeyError(ast.value, types, typeIndexPatterns), - isSavedObjectAttr: isSavedObjectAttr(ast.value, typeIndexPatterns), + error: hasFilterKeyError(ast.value, types, indexMapping), + isSavedObjectAttr: isSavedObjectAttr(ast.value, indexMapping), key: ast.value, type: getType(ast.value), }, @@ -144,47 +130,55 @@ export const validateFilterKueryNode = ( const getType = (key: string) => (key.includes('.') ? key.split('.')[0] : null); -export const isSavedObjectAttr = ( - key: string, - typeIndexPatterns: SavedObjectsIndexPatternField[] -) => { - const splitKey = key.split('.'); - if (splitKey.length === 1 && typeIndexPatterns.some(tip => tip.name === splitKey[0])) { +/** + * Is this filter key referring to a a top-level SavedObject attribute such as + * `updated_at` or `references`. + * + * @param key + * @param indexMapping + */ +export const isSavedObjectAttr = (key: string, indexMapping: IndexMapping) => { + const keySplit = key.split('.'); + if (keySplit.length === 1 && fieldDefined(indexMapping, keySplit[0])) { return true; - } else if (splitKey.length > 1 && typeIndexPatterns.some(tip => tip.name === splitKey[1])) { + } else if (keySplit.length === 2 && fieldDefined(indexMapping, keySplit[1])) { return true; + } else { + return false; } - return false; }; export const hasFilterKeyError = ( key: string, types: string[], - typeIndexPatterns: SavedObjectsIndexPatternField[] + indexMapping: IndexMapping ): string | null => { if (!key.includes('.')) { return `This key '${key}' need to be wrapped by a saved object type like ${types.join()}`; } else if (key.includes('.')) { const keySplit = key.split('.'); + if (keySplit.length <= 1 || !types.includes(keySplit[0])) { return `This type ${keySplit[0]} is not allowed`; } if ( - (keySplit.length === 2 && typeIndexPatterns.some(tip => tip.name === key)) || - (keySplit.length > 2 && types.includes(keySplit[0]) && keySplit[1] !== 'attributes') + (keySplit.length === 2 && fieldDefined(indexMapping, key)) || + (keySplit.length > 2 && keySplit[1] !== 'attributes') ) { return `This key '${key}' does NOT match the filter proposition SavedObjectType.attributes.key`; } if ( - (keySplit.length === 2 && !typeIndexPatterns.some(tip => tip.name === keySplit[1])) || + (keySplit.length === 2 && !fieldDefined(indexMapping, keySplit[1])) || (keySplit.length > 2 && - !typeIndexPatterns.some( - tip => - tip.name === [...keySplit.slice(0, 1), ...keySplit.slice(2, keySplit.length)].join('.') - )) + !fieldDefined(indexMapping, keySplit[0] + '.' + keySplit.slice(2, keySplit.length))) ) { return `This key '${key}' does NOT exist in ${types.join()} saved object index patterns`; } } return null; }; + +const fieldDefined = (indexMappings: IndexMapping, key: string) => { + const mappingKey = 'properties.' + key.split('.').join('.properties.'); + return get(indexMappings, mappingKey) != null; +}; diff --git a/src/core/server/saved_objects/service/lib/index.ts b/src/core/server/saved_objects/service/lib/index.ts index be78fdde762106..4bc159e17ec0fb 100644 --- a/src/core/server/saved_objects/service/lib/index.ts +++ b/src/core/server/saved_objects/service/lib/index.ts @@ -21,10 +21,9 @@ export { SavedObjectsRepository, SavedObjectsRepositoryOptions } from './reposit export { SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, - ScopedSavedObjectsClientProvider, + ISavedObjectsClientProvider, + SavedObjectsClientProvider, SavedObjectsClientProviderOptions, } from './scoped_client_provider'; export { SavedObjectsErrorHelpers } from './errors'; - -export { SavedObjectsCacheIndexPatterns } from './cache_index_patterns'; diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index dcabb90c0a13df..7e89b8f3c08208 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -273,10 +273,6 @@ describe('SavedObjectsRepository', () => { savedObjectsRepository = new SavedObjectsRepository({ index: '.kibana-test', - cacheIndexPatterns: { - setIndexPatterns: jest.fn(), - getIndexPatterns: () => undefined, - }, mappings, callCluster: callAdminCluster, migrator, @@ -290,8 +286,6 @@ describe('SavedObjectsRepository', () => { getSearchDslNS.getSearchDsl.mockReset(); }); - afterEach(() => { }); - describe('#create', () => { beforeEach(() => { callAdminCluster.mockImplementation((method, params) => ({ @@ -302,9 +296,7 @@ describe('SavedObjectsRepository', () => { }); it('waits until migrations are complete before proceeding', async () => { - migrator.runMigrations = jest.fn(async () => - expect(callAdminCluster).not.toHaveBeenCalled() - ); + migrator.runMigrations = jest.fn(async () => expect(callAdminCluster).not.toHaveBeenCalled()); await expect( savedObjectsRepository.create( @@ -557,9 +549,7 @@ describe('SavedObjectsRepository', () => { describe('#bulkCreate', () => { it('waits until migrations are complete before proceeding', async () => { - migrator.runMigrations = jest.fn(async () => - expect(callAdminCluster).not.toHaveBeenCalled() - ); + migrator.runMigrations = jest.fn(async () => expect(callAdminCluster).not.toHaveBeenCalled()); callAdminCluster.mockReturnValue({ items: [ { create: { type: 'config', id: 'config:one', _primary_term: 1, _seq_no: 1 } }, @@ -998,14 +988,12 @@ describe('SavedObjectsRepository', () => { expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); - it('should return objects in the same order regardless of type', () => { }); + it('should return objects in the same order regardless of type', () => {}); }); describe('#delete', () => { it('waits until migrations are complete before proceeding', async () => { - migrator.runMigrations = jest.fn(async () => - expect(callAdminCluster).not.toHaveBeenCalled() - ); + migrator.runMigrations = jest.fn(async () => expect(callAdminCluster).not.toHaveBeenCalled()); callAdminCluster.mockReturnValue({ result: 'deleted' }); await expect( savedObjectsRepository.delete('index-pattern', 'logstash-*', { @@ -1119,9 +1107,7 @@ describe('SavedObjectsRepository', () => { describe('#find', () => { it('waits until migrations are complete before proceeding', async () => { - migrator.runMigrations = jest.fn(async () => - expect(callAdminCluster).not.toHaveBeenCalled() - ); + migrator.runMigrations = jest.fn(async () => expect(callAdminCluster).not.toHaveBeenCalled()); callAdminCluster.mockReturnValue(noNamespaceSearchResults); await expect(savedObjectsRepository.find({ type: 'foo' })).resolves.toBeDefined(); @@ -1159,13 +1145,6 @@ describe('SavedObjectsRepository', () => { } }); - it('requires index pattern to be defined if filter is defined', async () => { - callAdminCluster.mockReturnValue(noNamespaceSearchResults); - expect(savedObjectsRepository.find({ type: 'foo', filter: 'foo.type: hello' })) - .rejects - .toThrowErrorMatchingInlineSnapshot('"options.filter is missing index pattern to work correctly: Bad Request"'); - }); - it('passes mappings, schema, search, defaultSearchOperator, searchFields, type, sortField, sortOrder and hasReference to getSearchDsl', async () => { callAdminCluster.mockReturnValue(namespacedSearchResults); @@ -1190,6 +1169,75 @@ describe('SavedObjectsRepository', () => { expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, schema, relevantOpts); }); + it('accepts KQL filter and passes keuryNode to getSearchDsl', async () => { + callAdminCluster.mockReturnValue(namespacedSearchResults); + const findOpts = { + namespace: 'foo-namespace', + search: 'foo*', + searchFields: ['foo'], + type: ['dashboard'], + sortField: 'name', + sortOrder: 'desc', + defaultSearchOperator: 'AND', + hasReference: { + type: 'foo', + id: '1', + }, + indexPattern: undefined, + filter: 'dashboard.attributes.otherField: *', + }; + + await savedObjectsRepository.find(findOpts); + expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledTimes(1); + const { kueryNode } = getSearchDslNS.getSearchDsl.mock.calls[0][2]; + expect(kueryNode).toMatchInlineSnapshot(` + Object { + "arguments": Array [ + Object { + "type": "literal", + "value": "dashboard.otherField", + }, + Object { + "type": "wildcard", + "value": "@kuery-wildcard@", + }, + Object { + "type": "literal", + "value": false, + }, + ], + "function": "is", + "type": "function", + } + `); + }); + + it('KQL filter syntax errors rejects with bad request', async () => { + callAdminCluster.mockReturnValue(namespacedSearchResults); + const findOpts = { + namespace: 'foo-namespace', + search: 'foo*', + searchFields: ['foo'], + type: ['dashboard'], + sortField: 'name', + sortOrder: 'desc', + defaultSearchOperator: 'AND', + hasReference: { + type: 'foo', + id: '1', + }, + indexPattern: undefined, + filter: 'dashboard.attributes.otherField:<', + }; + + await expect(savedObjectsRepository.find(findOpts)).rejects.toMatchInlineSnapshot(` + [Error: KQLSyntaxError: Expected "(", value, whitespace but "<" found. + dashboard.attributes.otherField:< + --------------------------------^: Bad Request] + `); + expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledTimes(0); + }); + it('merges output of getSearchDsl into es request body', async () => { callAdminCluster.mockReturnValue(noNamespaceSearchResults); getSearchDslNS.getSearchDsl.mockReturnValue({ query: 1, aggregations: 2 }); @@ -1329,9 +1377,7 @@ describe('SavedObjectsRepository', () => { }; it('waits until migrations are complete before proceeding', async () => { - migrator.runMigrations = jest.fn(async () => - expect(callAdminCluster).not.toHaveBeenCalled() - ); + migrator.runMigrations = jest.fn(async () => expect(callAdminCluster).not.toHaveBeenCalled()); callAdminCluster.mockResolvedValue(noNamespaceResult); await expect( @@ -1422,9 +1468,7 @@ describe('SavedObjectsRepository', () => { describe('#bulkGet', () => { it('waits until migrations are complete before proceeding', async () => { - migrator.runMigrations = jest.fn(async () => - expect(callAdminCluster).not.toHaveBeenCalled() - ); + migrator.runMigrations = jest.fn(async () => expect(callAdminCluster).not.toHaveBeenCalled()); callAdminCluster.mockReturnValue({ docs: [] }); await expect( @@ -1676,9 +1720,7 @@ describe('SavedObjectsRepository', () => { }); it('waits until migrations are complete before proceeding', async () => { - migrator.runMigrations = jest.fn(async () => - expect(callAdminCluster).not.toHaveBeenCalled() - ); + migrator.runMigrations = jest.fn(async () => expect(callAdminCluster).not.toHaveBeenCalled()); await expect( savedObjectsRepository.update('index-pattern', 'logstash-*', attributes, { @@ -1745,11 +1787,7 @@ describe('SavedObjectsRepository', () => { }); it('does not pass references if omitted', async () => { - await savedObjectsRepository.update( - type, - id, - { title: 'Testing' } - ); + await savedObjectsRepository.update(type, id, { title: 'Testing' }); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).not.toHaveBeenCalledWith( @@ -1758,19 +1796,14 @@ describe('SavedObjectsRepository', () => { body: { doc: expect.objectContaining({ references: [], - }) - } + }), + }, }) ); }); it('passes references if they are provided', async () => { - await savedObjectsRepository.update( - type, - id, - { title: 'Testing' }, - { references: ['foo'] } - ); + await savedObjectsRepository.update(type, id, { title: 'Testing' }, { references: ['foo'] }); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith( @@ -1779,19 +1812,14 @@ describe('SavedObjectsRepository', () => { body: { doc: expect.objectContaining({ references: ['foo'], - }) - } + }), + }, }) ); }); it('passes empty references array if empty references array is provided', async () => { - await savedObjectsRepository.update( - type, - id, - { title: 'Testing' }, - { references: [] } - ); + await savedObjectsRepository.update(type, id, { title: 'Testing' }, { references: [] }); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith( @@ -1800,8 +1828,8 @@ describe('SavedObjectsRepository', () => { body: { doc: expect.objectContaining({ references: [], - }) - } + }), + }, }) ); }); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index cc5b9804769dd7..b4723f35b1efc0 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -25,7 +25,6 @@ import { getSearchDsl } from './search_dsl'; import { includedFields } from './included_fields'; import { decorateEsError } from './decorate_es_error'; import { SavedObjectsErrorHelpers } from './errors'; -import { SavedObjectsCacheIndexPatterns } from './cache_index_patterns'; import { decodeRequestVersion, encodeVersion, encodeHitVersion } from '../../version'; import { SavedObjectsSchema } from '../../schema'; import { KibanaMigrator } from '../../migrations'; @@ -77,7 +76,6 @@ export interface SavedObjectsRepositoryOptions { serializer: SavedObjectsSerializer; migrator: KibanaMigrator; allowedTypes: string[]; - cacheIndexPatterns: SavedObjectsCacheIndexPatterns; onBeforeWrite?: (...args: Parameters) => Promise; } @@ -95,13 +93,11 @@ export class SavedObjectsRepository { private _onBeforeWrite: (...args: Parameters) => Promise; private _unwrappedCallCluster: CallCluster; private _serializer: SavedObjectsSerializer; - private _cacheIndexPatterns: SavedObjectsCacheIndexPatterns; constructor(options: SavedObjectsRepositoryOptions) { const { index, config, - cacheIndexPatterns, mappings, callCluster, schema, @@ -123,7 +119,6 @@ export class SavedObjectsRepository { this._config = config; this._mappings = mappings; this._schema = schema; - this._cacheIndexPatterns = cacheIndexPatterns; if (allowedTypes.length === 0) { throw new Error('Empty or missing types for saved object repository!'); } @@ -133,9 +128,6 @@ export class SavedObjectsRepository { this._unwrappedCallCluster = async (...args: Parameters) => { await migrator.runMigrations(); - if (this._cacheIndexPatterns.getIndexPatterns() == null) { - await this._cacheIndexPatterns.setIndexPatterns(index); - } return callCluster(...args); }; this._schema = schema; @@ -441,21 +433,20 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createBadRequestError('options.fields must be an array'); } - if (filter && filter !== '' && this._cacheIndexPatterns.getIndexPatterns() == null) { - throw SavedObjectsErrorHelpers.createBadRequestError( - 'options.filter is missing index pattern to work correctly' - ); + let kueryNode; + try { + kueryNode = + filter && filter !== '' + ? validateConvertFilterToKueryNode(allowedTypes, filter, this._mappings) + : null; + } catch (e) { + if (e.name === 'KQLSyntaxError') { + throw SavedObjectsErrorHelpers.createBadRequestError('KQLSyntaxError: ' + e.message); + } else { + throw e; + } } - const kueryNode = - filter && filter !== '' - ? validateConvertFilterToKueryNode( - allowedTypes, - filter, - this._cacheIndexPatterns.getIndexPatterns() - ) - : null; - const esOptions = { index: this.getIndicesForTypes(allowedTypes), size: perPage, @@ -474,7 +465,6 @@ export class SavedObjectsRepository { sortOrder, namespace, hasReference, - indexPattern: kueryNode != null ? this._cacheIndexPatterns.getIndexPatterns() : undefined, kueryNode, }), }, diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.mock.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.mock.ts new file mode 100644 index 00000000000000..5de234f4f93beb --- /dev/null +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.mock.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ISavedObjectsClientProvider } from './scoped_client_provider'; + +const create = (): jest.Mocked => ({ + addClientWrapperFactory: jest.fn(), + getClient: jest.fn(), + setClientFactory: jest.fn(), +}); + +export const savedObjectsClientProviderMock = { + create, +}; diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js b/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js index bce76bc7841e68..eb210b6843de01 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js @@ -17,14 +17,14 @@ * under the License. */ -import { ScopedSavedObjectsClientProvider } from './scoped_client_provider'; +import { SavedObjectsClientProvider } from './scoped_client_provider'; test(`uses default client factory when one isn't set`, () => { const returnValue = Symbol(); const defaultClientFactoryMock = jest.fn().mockReturnValue(returnValue); const request = Symbol(); - const clientProvider = new ScopedSavedObjectsClientProvider({ + const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, }); const result = clientProvider.getClient(request); @@ -42,7 +42,7 @@ test(`uses custom client factory when one is set`, () => { const returnValue = Symbol(); const customClientFactoryMock = jest.fn().mockReturnValue(returnValue); - const clientProvider = new ScopedSavedObjectsClientProvider({ + const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, }); clientProvider.setClientFactory(customClientFactoryMock); @@ -57,7 +57,7 @@ test(`uses custom client factory when one is set`, () => { }); test(`throws error when more than one scoped saved objects client factory is set`, () => { - const clientProvider = new ScopedSavedObjectsClientProvider({}); + const clientProvider = new SavedObjectsClientProvider({}); clientProvider.setClientFactory(() => {}); expect(() => { clientProvider.setClientFactory(() => {}); @@ -66,7 +66,7 @@ test(`throws error when more than one scoped saved objects client factory is set test(`throws error when registering a wrapper with a duplicate id`, () => { const defaultClientFactoryMock = jest.fn(); - const clientProvider = new ScopedSavedObjectsClientProvider({ + const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, }); const firstClientWrapperFactoryMock = jest.fn(); @@ -81,7 +81,7 @@ test(`throws error when registering a wrapper with a duplicate id`, () => { test(`invokes and uses wrappers in specified order`, () => { const defaultClient = Symbol(); const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); - const clientProvider = new ScopedSavedObjectsClientProvider({ + const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, }); const firstWrappedClient = Symbol('first client'); @@ -108,7 +108,7 @@ test(`invokes and uses wrappers in specified order`, () => { test(`does not invoke or use excluded wrappers`, () => { const defaultClient = Symbol(); const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); - const clientProvider = new ScopedSavedObjectsClientProvider({ + const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, }); const firstWrappedClient = Symbol('first client'); @@ -135,7 +135,7 @@ test(`does not invoke or use excluded wrappers`, () => { test(`allows all wrappers to be excluded`, () => { const defaultClient = Symbol(); const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); - const clientProvider = new ScopedSavedObjectsClientProvider({ + const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, }); const firstWrappedClient = Symbol('first client'); diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts index 0e93f9c443f144..ad1ceb60cdb86a 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts @@ -55,9 +55,24 @@ export interface SavedObjectsClientProviderOptions { } /** - * Provider for the Scoped Saved Object Client. + * @public + * See {@link SavedObjectsClientProvider} + */ +export type ISavedObjectsClientProvider = Pick< + SavedObjectsClientProvider, + keyof SavedObjectsClientProvider +>; + +/** + * Provider for the Scoped Saved Objects Client. + * + * @internalRemarks Because `getClient` is synchronous the Client Provider does + * not support creating factories that react to new ES clients emitted from + * elasticsearch.adminClient$. The Client Provider therefore doesn't support + * configuration changes to the Elasticsearch client. TODO: revisit once we've + * closed https://github.com/elastic/kibana/pull/45796 */ -export class ScopedSavedObjectsClientProvider { +export class SavedObjectsClientProvider { private readonly _wrapperFactories = new PriorityCollection<{ id: string; factory: SavedObjectsClientWrapperFactory; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index 75b30580292279..e585953109d2c0 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -18,7 +18,6 @@ */ import { schemaMock } from '../../../schema/schema.mock'; -import { SavedObjectsIndexPattern } from '../cache_index_patterns'; import { getQueryParams } from './query_params'; const SCHEMA = schemaMock.create(); @@ -62,41 +61,6 @@ const MAPPINGS = { }, }, }; -const INDEX_PATTERN: SavedObjectsIndexPattern = { - fields: [ - { - aggregatable: true, - name: 'type', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'pending.title', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'saved.title', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'saved.obj.key1', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'global.name', - searchable: true, - type: 'string', - }, - ], - title: 'test', -}; // create a type clause to be used within the "should", if a namespace is specified // the clause will ensure the namespace matches; otherwise, the clause will ensure @@ -1005,7 +969,6 @@ describe('searchDsl/queryParams', () => { { type: 'literal', value: false }, ], }, - indexPattern: INDEX_PATTERN, }) ).toEqual({ query: { @@ -1121,7 +1084,6 @@ describe('searchDsl/queryParams', () => { }, ], }, - indexPattern: INDEX_PATTERN, }) ).toEqual({ query: { @@ -1240,7 +1202,6 @@ describe('searchDsl/queryParams', () => { { type: 'literal', value: false }, ], }, - indexPattern: INDEX_PATTERN, }) ).toEqual({ query: { diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index 125b0c40af9e41..bee35b899d83c5 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -20,7 +20,6 @@ import { toElasticsearchQuery, KueryNode } from '@kbn/es-query'; import { getRootPropertiesObjects, IndexMapping } from '../../../mappings'; import { SavedObjectsSchema } from '../../../schema'; -import { SavedObjectsIndexPattern } from '../cache_index_patterns'; /** * Gets the types based on the type. Uses mappings to support @@ -93,7 +92,6 @@ interface QueryParams { defaultSearchOperator?: string; hasReference?: HasReferenceQueryParams; kueryNode?: KueryNode; - indexPattern?: SavedObjectsIndexPattern; } /** @@ -109,12 +107,11 @@ export function getQueryParams({ defaultSearchOperator, hasReference, kueryNode, - indexPattern, }: QueryParams) { const types = getTypes(mappings, type); const bool: any = { filter: [ - ...(kueryNode != null ? [toElasticsearchQuery(kueryNode, indexPattern)] : []), + ...(kueryNode != null ? [toElasticsearchQuery(kueryNode)] : []), { bool: { must: hasReference diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index 68f60607025053..868ca51a76eab8 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -24,7 +24,6 @@ import { IndexMapping } from '../../../mappings'; import { SavedObjectsSchema } from '../../../schema'; import { getQueryParams } from './query_params'; import { getSortingParams } from './sorting_params'; -import { SavedObjectsIndexPattern } from '../cache_index_patterns'; interface GetSearchDslOptions { type: string | string[]; @@ -39,7 +38,6 @@ interface GetSearchDslOptions { id: string; }; kueryNode?: KueryNode; - indexPattern?: SavedObjectsIndexPattern; } export function getSearchDsl( @@ -57,7 +55,6 @@ export function getSearchDsl( namespace, hasReference, kueryNode, - indexPattern, } = options; if (!type) { @@ -79,7 +76,6 @@ export function getSearchDsl( defaultSearchOperator, hasReference, kueryNode, - indexPattern, }), ...getSortingParams(mappings, type, sortField, sortOrder), }; diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 634f5ba007397b..b83d72f9ad46df 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -119,7 +119,7 @@ export interface SavedObjectsUpdateResponse saved_objects: Array>; } -// @internal (undocumented) +// @public (undocumented) export class SavedObjectsClient { // Warning: (ae-forgotten-export) The symbol "SavedObjectsRepository" needs to be exported by the entry point index.d.ts constructor(repository: SavedObjectsRepository); @@ -1180,8 +1182,6 @@ export class SavedObjectsClient { update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; } -// Warning: (ae-incompatible-release-tags) The symbol "SavedObjectsClientContract" is marked as @public, but its signature references "SavedObjectsClient" which is marked as @internal -// // @public export type SavedObjectsClientContract = Pick; @@ -1407,14 +1407,14 @@ export interface SavedObjectsImportUnsupportedTypeError { // @internal @deprecated (undocumented) export interface SavedObjectsLegacyService { - // Warning: (ae-forgotten-export) The symbol "ScopedSavedObjectsClientProvider" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts // // (undocumented) - addScopedSavedObjectsClientWrapperFactory: ScopedSavedObjectsClientProvider['addClientWrapperFactory']; + addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; // (undocumented) getSavedObjectsRepository(...rest: any[]): any; // (undocumented) - getScopedSavedObjectsClient: ScopedSavedObjectsClientProvider['getClient']; + getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; // (undocumented) importExport: { objectLimit: number; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 81d30a367261d7..455bb97d47b57e 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -21,7 +21,7 @@ import { take } from 'rxjs/operators'; import { Type } from '@kbn/config-schema'; import { ConfigService, Env, Config, ConfigPath } from './config'; -import { ElasticsearchService } from './elasticsearch'; +import { ElasticsearchService, ElasticsearchServiceSetup } from './elasticsearch'; import { HttpService, InternalHttpServiceSetup } from './http'; import { LegacyService } from './legacy'; import { Logger, LoggerFactory } from './logging'; @@ -36,7 +36,8 @@ import { config as kibanaConfig } from './kibana_config'; import { config as savedObjectsConfig } from './saved_objects'; import { mapToObject } from '../utils/'; import { ContextService } from './context'; -import { InternalCoreSetup } from './index'; +import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service'; +import { RequestHandlerContext } from '.'; const coreId = Symbol('core'); @@ -94,7 +95,6 @@ export class Server { http: httpSetup, }; - this.registerCoreContext(coreSetup); const pluginsSetup = await this.plugins.setup(coreSetup); const legacySetup = await this.legacy.setup({ @@ -102,11 +102,13 @@ export class Server { plugins: mapToObject(pluginsSetup.contracts), }); - await this.savedObjects.setup({ + const savedObjectsSetup = await this.savedObjects.setup({ elasticsearch: elasticsearchServiceSetup, legacy: legacySetup, }); + this.registerCoreContext({ ...coreSetup, savedObjects: savedObjectsSetup }); + return coreSetup; } @@ -147,17 +149,30 @@ export class Server { ); } - private registerCoreContext(coreSetup: InternalCoreSetup) { - coreSetup.http.registerRouteHandlerContext(coreId, 'core', async (context, req) => { - const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); - const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise(); - return { - elasticsearch: { - adminClient: adminClient.asScoped(req), - dataClient: dataClient.asScoped(req), - }, - }; - }); + private registerCoreContext(coreSetup: { + http: InternalHttpServiceSetup; + elasticsearch: ElasticsearchServiceSetup; + savedObjects: SavedObjectsServiceSetup; + }) { + coreSetup.http.registerRouteHandlerContext( + coreId, + 'core', + async (context, req): Promise => { + const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); + const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise(); + return { + savedObjects: { + // Note: the client provider doesn't support new ES clients + // emitted from adminClient$ + client: coreSetup.savedObjects.clientProvider.getClient(req), + }, + elasticsearch: { + adminClient: adminClient.asScoped(req), + dataClient: dataClient.asScoped(req), + }, + }; + } + ); } public async setupConfigSchemas() { diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.js b/src/legacy/server/saved_objects/saved_objects_mixin.js index 0de0f34e28a844..0583dea333c7d1 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.js @@ -19,15 +19,11 @@ // Disable lint errors for imports from src/core/server/saved_objects until SavedObjects migration is complete /* eslint-disable @kbn/eslint/no-restricted-paths */ - -import { first } from 'rxjs/operators'; import { SavedObjectsSchema } from '../../../core/server/saved_objects/schema'; import { SavedObjectsSerializer } from '../../../core/server/saved_objects/serialization'; import { SavedObjectsClient, SavedObjectsRepository, - ScopedSavedObjectsClientProvider, - SavedObjectsCacheIndexPatterns, getSortedObjectsForExport, importSavedObjects, resolveImportErrors, @@ -65,7 +61,6 @@ export async function savedObjectsMixin(kbnServer, server) { const schema = new SavedObjectsSchema(kbnServer.uiExports.savedObjectSchemas); const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type)); const importableAndExportableTypes = getImportableAndExportableTypes({ kbnServer, visibleTypes }); - const cacheIndexPatterns = new SavedObjectsCacheIndexPatterns(); server.decorate('server', 'kibanaMigrator', migrator); server.decorate( @@ -104,15 +99,6 @@ export async function savedObjectsMixin(kbnServer, server) { const serializer = new SavedObjectsSerializer(schema); - if (cacheIndexPatterns.getIndexPatternsService() == null) { - const adminClient = await server.newPlatform.__internals.elasticsearch.adminClient$ - .pipe(first()) - .toPromise(); - cacheIndexPatterns.setIndexPatternsService( - server.indexPatternsServiceFactory({ callCluster: adminClient.callAsInternalUser }) - ); - } - const createRepository = (callCluster, extraTypes = []) => { if (typeof callCluster !== 'function') { throw new TypeError('Repository requires a "callCluster" function to be provided.'); @@ -131,7 +117,6 @@ export async function savedObjectsMixin(kbnServer, server) { return new SavedObjectsRepository({ index: config.get('kibana.index'), config, - cacheIndexPatterns, migrator, mappings, schema, @@ -141,17 +126,7 @@ export async function savedObjectsMixin(kbnServer, server) { }); }; - const provider = new ScopedSavedObjectsClientProvider({ - index: server.config().get('kibana.index'), - mappings, - defaultClientFactory({ request }) { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); - const callCluster = (...args) => callWithRequest(request, ...args); - const repository = createRepository(callCluster); - - return new SavedObjectsClient(repository); - }, - }); + const provider = kbnServer.newPlatform.__internals.savedObjectsClientProvider; const service = { types: visibleTypes, diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.test.js b/src/legacy/server/saved_objects/saved_objects_mixin.test.js index fe21b0c529b3ff..45ac31ab7fc615 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.test.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.test.js @@ -20,6 +20,8 @@ import { savedObjectsMixin } from './saved_objects_mixin'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { mockKibanaMigrator } from '../../../core/server/saved_objects/migrations/kibana/kibana_migrator.mock'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { savedObjectsClientProviderMock } from '../../../core/server/saved_objects/service/lib/scoped_client_provider.mock'; const savedObjectMappings = [ { @@ -75,6 +77,7 @@ describe('Saved Objects Mixin', () => { }); beforeEach(() => { + const clientProvider = savedObjectsClientProviderMock.create(); mockServer = { log: jest.fn(), route: jest.fn(), @@ -115,7 +118,7 @@ describe('Saved Objects Mixin', () => { }; mockKbnServer = { newPlatform: { - __internals: { kibanaMigrator: migrator }, + __internals: { kibanaMigrator: migrator, savedObjectsClientProvider: clientProvider }, }, server: mockServer, ready: () => {}, @@ -290,9 +293,8 @@ describe('Saved Objects Mixin', () => { }); describe('get client', () => { - it('should return a valid client object', () => { - const client = service.getScopedSavedObjectsClient(); - expect(client).toBeDefined(); + it('should have a method to get the client', () => { + expect(service).toHaveProperty('getScopedSavedObjectsClient'); }); it('should have a method to set the client factory', () => { @@ -314,21 +316,6 @@ describe('Saved Objects Mixin', () => { service.addScopedSavedObjectsClientWrapperFactory({}); }).not.toThrowError(); }); - - it('should call underlining callCluster', async () => { - mockCallCluster.mockImplementation(method => { - if (method === 'indices.get') { - return { status: 404 }; - } else if (method === 'indices.getAlias') { - return { status: 404 }; - } else if (method === 'cat.templates') { - return []; - } - }); - const client = await service.getScopedSavedObjectsClient(); - await client.create('testtype'); - expect(mockCallCluster).toHaveBeenCalled(); - }); }); describe('#getSavedObjectsClient', () => { diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts index 0923b525650974..ebdcf48f608b93 100644 --- a/src/plugins/data/server/search/routes.test.ts +++ b/src/plugins/data/server/search/routes.test.ts @@ -19,7 +19,7 @@ import { httpServiceMock, httpServerMock } from '../../../../../src/core/server/mocks'; import { registerSearchRoute } from './routes'; -import { IRouter, ScopedClusterClient } from 'kibana/server'; +import { IRouter, ScopedClusterClient, RequestHandlerContext } from 'kibana/server'; describe('Search service', () => { let routerMock: jest.Mocked; @@ -56,7 +56,7 @@ describe('Search service', () => { registerSearchRoute(routerMock); const handler = routerMock.post.mock.calls[0][1]; - await handler(mockContext, mockRequest, mockResponse); + await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); expect(mockSearch).toBeCalled(); expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody); @@ -88,7 +88,7 @@ describe('Search service', () => { registerSearchRoute(routerMock); const handler = routerMock.post.mock.calls[0][1]; - await handler(mockContext, mockRequest, mockResponse); + await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); expect(mockSearch).toBeCalled(); expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody); diff --git a/src/plugins/testbed/server/index.ts b/src/plugins/testbed/server/index.ts index f9f6b3cf97e911..4dd22d3dce1efc 100644 --- a/src/plugins/testbed/server/index.ts +++ b/src/plugins/testbed/server/index.ts @@ -43,10 +43,21 @@ class Plugin { ); const router = core.http.createRouter(); - router.get({ path: '/ping', validate: false }, async (context, req, res) => { - const response = await context.core.elasticsearch.adminClient.callAsInternalUser('ping'); - return res.ok({ body: `Pong: ${response}` }); - }); + router.get( + { path: '/requestcontext/elasticsearch', validate: false }, + async (context, req, res) => { + const response = await context.core.elasticsearch.adminClient.callAsInternalUser('ping'); + return res.ok({ body: `Elasticsearch: ${response}` }); + } + ); + + router.get( + { path: '/requestcontext/savedobjectsclient', validate: false }, + async (context, req, res) => { + const response = await context.core.savedObjects.client.find({ type: 'TYPE' }); + return res.ok({ body: `SavedObjects client: ${JSON.stringify(response)}` }); + } + ); return { data$: this.initializerContext.config.create().pipe( diff --git a/test/api_integration/apis/core/index.js b/test/api_integration/apis/core/index.js index d12a4a9d37b39b..d617b2ad073511 100644 --- a/test/api_integration/apis/core/index.js +++ b/test/api_integration/apis/core/index.js @@ -20,11 +20,17 @@ export default function ({ getService }) { const supertest = getService('supertest'); - describe('core', () => { - it('provides access to request context', async () => ( + describe('core request context', () => { + it('provides access to elasticsearch', async () => ( await supertest - .get('/testbed/ping') - .expect(200, 'Pong: true') + .get('/requestcontext/elasticsearch') + .expect(200, 'Elasticsearch: true') + )); + + it('provides access to SavedObjects client', async () => ( + await supertest + .get('/requestcontext/savedobjectsclient') + .expect(200, 'SavedObjects client: {"page":1,"per_page":20,"total":0,"saved_objects":[]}') )); }); } diff --git a/test/api_integration/apis/index.js b/test/api_integration/apis/index.js index 09d1896da5c80e..9f2672959390ce 100644 --- a/test/api_integration/apis/index.js +++ b/test/api_integration/apis/index.js @@ -19,6 +19,7 @@ export default function ({ loadTestFile }) { describe('apis', () => { + loadTestFile(require.resolve('./core')); loadTestFile(require.resolve('./elasticsearch')); loadTestFile(require.resolve('./general')); loadTestFile(require.resolve('./home')); diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index a41df24ea7a418..a1c84a766a1167 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -165,6 +165,22 @@ export default function ({ getService }) { }); }) )); + + it('KQL syntax error should return 400 with Bad Request', async () => ( + await supertest + .get('/api/saved_objects/_find?type=dashboard&filter=dashboard.attributes.title:foo { + console.log('body', JSON.stringify(resp.body)); + expect(resp.body).to.eql({ + error: 'Bad Request', + message: 'KQLSyntaxError: Expected AND, OR, end of input, ' + + 'whitespace but \"<\" found.\ndashboard.attributes.title:foo' + + '