From c2571c7faf10b93d14ccbc150849ed498ff7ac02 Mon Sep 17 00:00:00 2001
From: Jason Stoltzfus
Date: Mon, 18 Oct 2021 12:20:19 -0400
Subject: [PATCH 001/204] [App Search] Added a History tab to the Automated
Curation detail view (#115090)
---
.../curation/automated_curation.test.tsx | 37 ++++++++++--
.../curations/curation/automated_curation.tsx | 32 +++++++++--
.../curations/curation/history.test.tsx | 23 ++++++++
.../components/curations/curation/history.tsx | 57 +++++++++++++++++++
4 files changed, 140 insertions(+), 9 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.tsx
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx
index 2cee5bbbec80b..944d8315452b0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx
@@ -8,6 +8,7 @@
import '../../../../__mocks__/shallow_useeffect.mock';
import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
import { mockUseParams } from '../../../../__mocks__/react_router';
+
import '../../../__mocks__/engine_logic.mock';
import React from 'react';
@@ -27,6 +28,7 @@ import { CurationLogic } from './curation_logic';
import { DeleteCurationButton } from './delete_curation_button';
import { PromotedDocuments, OrganicDocuments } from './documents';
+import { History } from './history';
describe('AutomatedCuration', () => {
const values = {
@@ -39,6 +41,7 @@ describe('AutomatedCuration', () => {
suggestion: {
status: 'applied',
},
+ queries: ['foo'],
},
activeQuery: 'query A',
isAutomated: true,
@@ -61,20 +64,46 @@ describe('AutomatedCuration', () => {
expect(wrapper.is(AppSearchPageTemplate));
expect(wrapper.find(PromotedDocuments)).toHaveLength(1);
expect(wrapper.find(OrganicDocuments)).toHaveLength(1);
+ expect(wrapper.find(History)).toHaveLength(0);
});
- it('includes a static tab group', () => {
+ it('includes tabs', () => {
const wrapper = shallow();
- const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
+ let tabs = getPageHeaderTabs(wrapper).find(EuiTab);
- expect(tabs).toHaveLength(2);
+ expect(tabs).toHaveLength(3);
- expect(tabs.at(0).prop('onClick')).toBeUndefined();
expect(tabs.at(0).prop('isSelected')).toBe(true);
expect(tabs.at(1).prop('onClick')).toBeUndefined();
expect(tabs.at(1).prop('isSelected')).toBe(false);
expect(tabs.at(1).prop('disabled')).toBe(true);
+
+ expect(tabs.at(2).prop('isSelected')).toBe(false);
+
+ // Clicking on the History tab shows the history view
+ tabs.at(2).simulate('click');
+
+ tabs = getPageHeaderTabs(wrapper).find(EuiTab);
+
+ expect(tabs.at(0).prop('isSelected')).toBe(false);
+ expect(tabs.at(2).prop('isSelected')).toBe(true);
+
+ expect(wrapper.find(PromotedDocuments)).toHaveLength(0);
+ expect(wrapper.find(OrganicDocuments)).toHaveLength(0);
+ expect(wrapper.find(History)).toHaveLength(1);
+
+ // Clicking back to the Promoted tab shows promoted documents
+ tabs.at(0).simulate('click');
+
+ tabs = getPageHeaderTabs(wrapper).find(EuiTab);
+
+ expect(tabs.at(0).prop('isSelected')).toBe(true);
+ expect(tabs.at(2).prop('isSelected')).toBe(false);
+
+ expect(wrapper.find(PromotedDocuments)).toHaveLength(1);
+ expect(wrapper.find(OrganicDocuments)).toHaveLength(1);
+ expect(wrapper.find(History)).toHaveLength(0);
});
it('initializes CurationLogic with a curationId prop from URL param', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx
index fa34fa071b855..276b40ba88677 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx
@@ -5,15 +5,18 @@
* 2.0.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import { useValues, useActions } from 'kea';
import { EuiButton, EuiBadge, EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { EngineLogic } from '../../engine';
import { AppSearchPageTemplate } from '../../layout';
import { AutomatedIcon } from '../components/automated_icon';
+
import {
AUTOMATED_LABEL,
COVERT_TO_MANUAL_BUTTON_LABEL,
@@ -26,19 +29,25 @@ import { HIDDEN_DOCUMENTS_TITLE, PROMOTED_DOCUMENTS_TITLE } from './constants';
import { CurationLogic } from './curation_logic';
import { DeleteCurationButton } from './delete_curation_button';
import { PromotedDocuments, OrganicDocuments } from './documents';
+import { History } from './history';
+
+const PROMOTED = 'promoted';
+const HISTORY = 'history';
export const AutomatedCuration: React.FC = () => {
const { curationId } = useParams<{ curationId: string }>();
const logic = CurationLogic({ curationId });
const { convertToManual } = useActions(logic);
const { activeQuery, dataLoading, queries, curation } = useValues(logic);
+ const { engineName } = useValues(EngineLogic);
+ const [selectedPageTab, setSelectedPageTab] = useState(PROMOTED);
- // This tab group is meant to visually mirror the dynamic group of tags in the ManualCuration component
const pageTabs = [
{
label: PROMOTED_DOCUMENTS_TITLE,
append: {curation.promoted.length},
- isSelected: true,
+ isSelected: selectedPageTab === PROMOTED,
+ onClick: () => setSelectedPageTab(PROMOTED),
},
{
label: HIDDEN_DOCUMENTS_TITLE,
@@ -46,6 +55,16 @@ export const AutomatedCuration: React.FC = () => {
isSelected: false,
disabled: true,
},
+ {
+ label: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyButtonLabel',
+ {
+ defaultMessage: 'History',
+ }
+ ),
+ isSelected: selectedPageTab === HISTORY,
+ onClick: () => setSelectedPageTab(HISTORY),
+ },
];
return (
@@ -83,8 +102,11 @@ export const AutomatedCuration: React.FC = () => {
}}
isLoading={dataLoading}
>
-
-
+ {selectedPageTab === PROMOTED && }
+ {selectedPageTab === PROMOTED && }
+ {selectedPageTab === HISTORY && (
+
+ )}
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.test.tsx
new file mode 100644
index 0000000000000..a7f83fb0c61d9
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.test.tsx
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EntSearchLogStream } from '../../../../shared/log_stream';
+
+import { History } from './history';
+
+describe('History', () => {
+ it('renders', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(EntSearchLogStream).prop('query')).toEqual(
+ 'appsearch.search_relevance_suggestions.query: some text and event.kind: event and event.dataset: search-relevance-suggestions and appsearch.search_relevance_suggestions.engine: foo and event.action: curation_suggestion'
+ );
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.tsx
new file mode 100644
index 0000000000000..744141372469c
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { i18n } from '@kbn/i18n';
+
+import { EntSearchLogStream } from '../../../../shared/log_stream';
+import { DataPanel } from '../../data_panel';
+
+interface Props {
+ query: string;
+ engineName: string;
+}
+
+export const History: React.FC = ({ query, engineName }) => {
+ const filters = [
+ `appsearch.search_relevance_suggestions.query: ${query}`,
+ 'event.kind: event',
+ 'event.dataset: search-relevance-suggestions',
+ `appsearch.search_relevance_suggestions.engine: ${engineName}`,
+ 'event.action: curation_suggestion',
+ ];
+
+ return (
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableTitle',
+ {
+ defaultMessage: 'Automated curation changes',
+ }
+ )}
+
+ }
+ subtitle={i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableDescription',
+ {
+ defaultMessage: 'A detailed log of recent changes to your automated curation.',
+ }
+ )}
+ hasBorder
+ >
+
+
+ );
+};
From 3ebfb029a2b7a774bd777602fbdb193748e2a379 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?=
Date: Mon, 18 Oct 2021 18:24:01 +0200
Subject: [PATCH 002/204] [Stack monitoring] Remove angular (#115063)
* Remove angular
* Fix translations
* convert insetupmode to boolean
* remove license service
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/monitoring/kibana.json | 1 -
.../monitoring/public/alerts/badge.tsx | 2 +-
.../monitoring/public/angular/app_modules.ts | 246 -------------
.../public/angular/helpers/routes.ts | 39 ---
.../public/angular/helpers/utils.ts | 45 ---
.../monitoring/public/angular/index.ts | 83 -----
.../public/angular/providers/private.js | 193 -----------
.../application/pages/logstash/pipelines.tsx | 8 +-
.../pages/no_data/no_data_page.tsx | 2 +-
.../application/pages/page_template.tsx | 2 +-
.../public/application/setup_mode/index.ts | 2 +-
.../application/setup_mode/setup_mode.tsx | 203 -----------
.../setup_mode/setup_mode_renderer.js | 2 +-
.../elasticsearch/ml_job_listing/index.js | 171 ----------
.../public/directives/main/index.html | 323 ------------------
.../public/directives/main/index.js | 275 ---------------
.../public/directives/main/index.scss | 3 -
.../main/monitoring_main_controller.test.js | 286 ----------------
.../monitoring/public/lib/setup_mode.test.js | 2 +-
.../monitoring/public/lib/setup_mode.tsx | 113 +++---
x-pack/plugins/monitoring/public/plugin.ts | 48 +--
.../monitoring/public/services/breadcrumbs.js | 214 ------------
.../public/services/breadcrumbs.test.js | 166 ---------
.../monitoring/public/services/clusters.js | 59 ----
.../public/services/enable_alerts_modal.js | 51 ---
.../monitoring/public/services/executor.js | 130 -------
.../monitoring/public/services/features.js | 47 ---
.../monitoring/public/services/license.js | 52 ---
.../monitoring/public/services/title.js | 26 --
.../public/views/access_denied/index.html | 44 ---
.../public/views/access_denied/index.js | 44 ---
x-pack/plugins/monitoring/public/views/all.js | 39 ---
.../public/views/apm/instance/index.html | 8 -
.../public/views/apm/instance/index.js | 74 ----
.../public/views/apm/instances/index.html | 7 -
.../public/views/apm/instances/index.js | 92 -----
.../public/views/apm/overview/index.html | 7 -
.../public/views/apm/overview/index.js | 58 ----
.../public/views/base_controller.js | 271 ---------------
.../public/views/base_eui_table_controller.js | 135 --------
.../public/views/base_table_controller.js | 53 ---
.../public/views/beats/beat/get_page_data.js | 32 --
.../public/views/beats/beat/index.html | 11 -
.../public/views/beats/beat/index.js | 75 ----
.../views/beats/listing/get_page_data.js | 31 --
.../public/views/beats/listing/index.html | 7 -
.../public/views/beats/listing/index.js | 89 -----
.../views/beats/overview/get_page_data.js | 31 --
.../public/views/beats/overview/index.html | 7 -
.../public/views/beats/overview/index.js | 62 ----
.../public/views/cluster/listing/index.html | 3 -
.../public/views/cluster/listing/index.js | 100 ------
.../public/views/cluster/overview/index.html | 3 -
.../public/views/cluster/overview/index.js | 96 ------
.../views/elasticsearch/ccr/get_page_data.js | 31 --
.../public/views/elasticsearch/ccr/index.html | 7 -
.../public/views/elasticsearch/ccr/index.js | 79 -----
.../elasticsearch/ccr/shard/get_page_data.js | 32 --
.../views/elasticsearch/ccr/shard/index.html | 8 -
.../views/elasticsearch/ccr/shard/index.js | 110 ------
.../elasticsearch/index/advanced/index.html | 8 -
.../elasticsearch/index/advanced/index.js | 124 -------
.../views/elasticsearch/index/index.html | 9 -
.../public/views/elasticsearch/index/index.js | 148 --------
.../views/elasticsearch/indices/index.html | 8 -
.../views/elasticsearch/indices/index.js | 120 -------
.../elasticsearch/ml_jobs/get_page_data.js | 30 --
.../views/elasticsearch/ml_jobs/index.html | 9 -
.../views/elasticsearch/ml_jobs/index.js | 51 ---
.../elasticsearch/node/advanced/index.html | 11 -
.../elasticsearch/node/advanced/index.js | 135 --------
.../views/elasticsearch/node/get_page_data.js | 36 --
.../views/elasticsearch/node/index.html | 11 -
.../public/views/elasticsearch/node/index.js | 155 ---------
.../views/elasticsearch/nodes/index.html | 8 -
.../public/views/elasticsearch/nodes/index.js | 149 --------
.../elasticsearch/overview/controller.js | 100 ------
.../views/elasticsearch/overview/index.html | 8 -
.../views/elasticsearch/overview/index.js | 24 --
.../plugins/monitoring/public/views/index.js | 10 -
.../public/views/kibana/instance/index.html | 8 -
.../public/views/kibana/instance/index.js | 168 ---------
.../views/kibana/instances/get_page_data.js | 31 --
.../public/views/kibana/instances/index.html | 7 -
.../public/views/kibana/instances/index.js | 95 ------
.../public/views/kibana/overview/index.html | 7 -
.../public/views/kibana/overview/index.js | 117 -------
.../public/views/license/controller.js | 79 -----
.../public/views/license/index.html | 3 -
.../monitoring/public/views/license/index.js | 24 --
.../public/views/loading/index.html | 5 -
.../monitoring/public/views/loading/index.js | 78 -----
.../views/logstash/node/advanced/index.html | 9 -
.../views/logstash/node/advanced/index.js | 149 --------
.../public/views/logstash/node/index.html | 9 -
.../public/views/logstash/node/index.js | 147 --------
.../views/logstash/node/pipelines/index.html | 8 -
.../views/logstash/node/pipelines/index.js | 135 --------
.../views/logstash/nodes/get_page_data.js | 31 --
.../public/views/logstash/nodes/index.html | 3 -
.../public/views/logstash/nodes/index.js | 89 -----
.../public/views/logstash/overview/index.html | 3 -
.../public/views/logstash/overview/index.js | 81 -----
.../public/views/logstash/pipeline/index.html | 12 -
.../public/views/logstash/pipeline/index.js | 181 ----------
.../views/logstash/pipelines/index.html | 7 -
.../public/views/logstash/pipelines/index.js | 130 -------
.../public/views/no_data/controller.js | 102 ------
.../public/views/no_data/index.html | 5 -
.../monitoring/public/views/no_data/index.js | 15 -
.../public/views/no_data/model_updater.js | 38 ---
.../views/no_data/model_updater.test.js | 55 ---
.../translations/translations/ja-JP.json | 21 +-
.../translations/translations/zh-CN.json | 21 +-
114 files changed, 68 insertions(+), 7399 deletions(-)
delete mode 100644 x-pack/plugins/monitoring/public/angular/app_modules.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/helpers/routes.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/helpers/utils.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/index.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/providers/private.js
delete mode 100644 x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx
delete mode 100644 x-pack/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js
delete mode 100644 x-pack/plugins/monitoring/public/directives/main/index.html
delete mode 100644 x-pack/plugins/monitoring/public/directives/main/index.js
delete mode 100644 x-pack/plugins/monitoring/public/directives/main/index.scss
delete mode 100644 x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js
delete mode 100644 x-pack/plugins/monitoring/public/services/breadcrumbs.js
delete mode 100644 x-pack/plugins/monitoring/public/services/breadcrumbs.test.js
delete mode 100644 x-pack/plugins/monitoring/public/services/clusters.js
delete mode 100644 x-pack/plugins/monitoring/public/services/enable_alerts_modal.js
delete mode 100644 x-pack/plugins/monitoring/public/services/executor.js
delete mode 100644 x-pack/plugins/monitoring/public/services/features.js
delete mode 100644 x-pack/plugins/monitoring/public/services/license.js
delete mode 100644 x-pack/plugins/monitoring/public/services/title.js
delete mode 100644 x-pack/plugins/monitoring/public/views/access_denied/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/access_denied/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/all.js
delete mode 100644 x-pack/plugins/monitoring/public/views/apm/instance/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/apm/instance/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/apm/instances/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/apm/instances/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/apm/overview/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/apm/overview/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/base_controller.js
delete mode 100644 x-pack/plugins/monitoring/public/views/base_eui_table_controller.js
delete mode 100644 x-pack/plugins/monitoring/public/views/base_table_controller.js
delete mode 100644 x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js
delete mode 100644 x-pack/plugins/monitoring/public/views/beats/beat/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/beats/beat/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js
delete mode 100644 x-pack/plugins/monitoring/public/views/beats/listing/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/beats/listing/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js
delete mode 100644 x-pack/plugins/monitoring/public/views/beats/overview/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/beats/overview/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/cluster/listing/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/cluster/listing/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/cluster/overview/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/cluster/overview/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/index/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/node/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/kibana/instance/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/kibana/instance/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js
delete mode 100644 x-pack/plugins/monitoring/public/views/kibana/instances/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/kibana/instances/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/kibana/overview/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/kibana/overview/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/license/controller.js
delete mode 100644 x-pack/plugins/monitoring/public/views/license/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/license/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/loading/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/loading/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/node/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/node/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/nodes/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/nodes/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/overview/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/overview/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/pipeline/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/pipeline/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/pipelines/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/no_data/controller.js
delete mode 100644 x-pack/plugins/monitoring/public/views/no_data/index.html
delete mode 100644 x-pack/plugins/monitoring/public/views/no_data/index.js
delete mode 100644 x-pack/plugins/monitoring/public/views/no_data/model_updater.js
delete mode 100644 x-pack/plugins/monitoring/public/views/no_data/model_updater.test.js
diff --git a/x-pack/plugins/monitoring/kibana.json b/x-pack/plugins/monitoring/kibana.json
index 4f8e1c0bdbae4..bc0cf47181585 100644
--- a/x-pack/plugins/monitoring/kibana.json
+++ b/x-pack/plugins/monitoring/kibana.json
@@ -25,7 +25,6 @@
"home",
"alerting",
"kibanaReact",
- "licenseManagement",
"kibanaLegacy"
]
}
diff --git a/x-pack/plugins/monitoring/public/alerts/badge.tsx b/x-pack/plugins/monitoring/public/alerts/badge.tsx
index 6b1c8c5085565..22bffb5d62b19 100644
--- a/x-pack/plugins/monitoring/public/alerts/badge.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/badge.tsx
@@ -73,7 +73,7 @@ export const AlertsBadge: React.FC = (props: Props) => {
const groupByType = GROUP_BY_NODE;
const panels = showByNode
? getAlertPanelsByNode(PANEL_TITLE, alerts, stateFilter)
- : getAlertPanelsByCategory(PANEL_TITLE, inSetupMode, alerts, stateFilter);
+ : getAlertPanelsByCategory(PANEL_TITLE, !!inSetupMode, alerts, stateFilter);
if (panels.length && !inSetupMode && panels[0].items) {
panels[0].items.push(
...[
diff --git a/x-pack/plugins/monitoring/public/angular/app_modules.ts b/x-pack/plugins/monitoring/public/angular/app_modules.ts
deleted file mode 100644
index 6ded0bce51d4b..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/app_modules.ts
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import angular, { IWindowService } from 'angular';
-import '../views/all';
-// required for `ngSanitize` angular module
-import 'angular-sanitize';
-import 'angular-route';
-import '../index.scss';
-import { upperFirst } from 'lodash';
-import { CoreStart } from 'kibana/public';
-import { i18nDirective, i18nFilter, I18nProvider } from './angular_i18n';
-import { Storage } from '../../../../../src/plugins/kibana_utils/public';
-import { createTopNavDirective, createTopNavHelper } from './top_nav';
-import { MonitoringStartPluginDependencies } from '../types';
-import { GlobalState } from '../url_state';
-import { getSafeForExternalLink } from '../lib/get_safe_for_external_link';
-
-// @ts-ignore
-import { formatMetric, formatNumber } from '../lib/format_number';
-// @ts-ignore
-import { extractIp } from '../lib/extract_ip';
-// @ts-ignore
-import { PrivateProvider } from './providers/private';
-// @ts-ignore
-import { breadcrumbsProvider } from '../services/breadcrumbs';
-// @ts-ignore
-import { monitoringClustersProvider } from '../services/clusters';
-// @ts-ignore
-import { executorProvider } from '../services/executor';
-// @ts-ignore
-import { featuresProvider } from '../services/features';
-// @ts-ignore
-import { licenseProvider } from '../services/license';
-// @ts-ignore
-import { titleProvider } from '../services/title';
-// @ts-ignore
-import { enableAlertsModalProvider } from '../services/enable_alerts_modal';
-// @ts-ignore
-import { monitoringMlListingProvider } from '../directives/elasticsearch/ml_job_listing';
-// @ts-ignore
-import { monitoringMainProvider } from '../directives/main';
-
-export const appModuleName = 'monitoring';
-
-type IPrivate = (provider: (...injectable: unknown[]) => T) => T;
-
-const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react'];
-
-export const localAppModule = ({
- core,
- data: { query },
- navigation,
- externalConfig,
-}: MonitoringStartPluginDependencies) => {
- createLocalI18nModule();
- createLocalPrivateModule();
- createLocalStorage();
- createLocalConfigModule(core);
- createLocalStateModule(query, core.notifications.toasts);
- createLocalTopNavModule(navigation);
- createHrefModule(core);
- createMonitoringAppServices();
- createMonitoringAppDirectives();
- createMonitoringAppConfigConstants(externalConfig);
- createMonitoringAppFilters();
-
- const appModule = angular.module(appModuleName, [
- ...thirdPartyAngularDependencies,
- 'monitoring/I18n',
- 'monitoring/Private',
- 'monitoring/Storage',
- 'monitoring/Config',
- 'monitoring/State',
- 'monitoring/TopNav',
- 'monitoring/href',
- 'monitoring/constants',
- 'monitoring/services',
- 'monitoring/filters',
- 'monitoring/directives',
- ]);
- return appModule;
-};
-
-function createMonitoringAppConfigConstants(
- keys: MonitoringStartPluginDependencies['externalConfig']
-) {
- let constantsModule = angular.module('monitoring/constants', []);
- keys.map(([key, value]) => (constantsModule = constantsModule.constant(key as string, value)));
-}
-
-function createLocalStateModule(
- query: MonitoringStartPluginDependencies['data']['query'],
- toasts: MonitoringStartPluginDependencies['core']['notifications']['toasts']
-) {
- angular
- .module('monitoring/State', ['monitoring/Private'])
- .service(
- 'globalState',
- function (
- Private: IPrivate,
- $rootScope: ng.IRootScopeService,
- $location: ng.ILocationService
- ) {
- function GlobalStateProvider(this: any) {
- const state = new GlobalState(query, toasts, $rootScope, $location, this);
- const initialState: any = state.getState();
- for (const key in initialState) {
- if (!initialState.hasOwnProperty(key)) {
- continue;
- }
- this[key] = initialState[key];
- }
- this.save = () => {
- const newState = { ...this };
- delete newState.save;
- state.setState(newState);
- };
- }
- return Private(GlobalStateProvider);
- }
- );
-}
-
-function createMonitoringAppServices() {
- angular
- .module('monitoring/services', ['monitoring/Private'])
- .service('breadcrumbs', function (Private: IPrivate) {
- return Private(breadcrumbsProvider);
- })
- .service('monitoringClusters', function (Private: IPrivate) {
- return Private(monitoringClustersProvider);
- })
- .service('$executor', function (Private: IPrivate) {
- return Private(executorProvider);
- })
- .service('features', function (Private: IPrivate) {
- return Private(featuresProvider);
- })
- .service('enableAlertsModal', function (Private: IPrivate) {
- return Private(enableAlertsModalProvider);
- })
- .service('license', function (Private: IPrivate) {
- return Private(licenseProvider);
- })
- .service('title', function (Private: IPrivate) {
- return Private(titleProvider);
- });
-}
-
-function createMonitoringAppDirectives() {
- angular
- .module('monitoring/directives', [])
- .directive('monitoringMlListing', monitoringMlListingProvider)
- .directive('monitoringMain', monitoringMainProvider);
-}
-
-function createMonitoringAppFilters() {
- angular
- .module('monitoring/filters', [])
- .filter('capitalize', function () {
- return function (input: string) {
- return upperFirst(input?.toLowerCase());
- };
- })
- .filter('formatNumber', function () {
- return formatNumber;
- })
- .filter('formatMetric', function () {
- return formatMetric;
- })
- .filter('extractIp', function () {
- return extractIp;
- });
-}
-
-function createLocalConfigModule(core: MonitoringStartPluginDependencies['core']) {
- angular.module('monitoring/Config', []).provider('config', function () {
- return {
- $get: () => ({
- get: (key: string) => core.uiSettings?.get(key),
- }),
- };
- });
-}
-
-function createLocalStorage() {
- angular
- .module('monitoring/Storage', [])
- .service('localStorage', function ($window: IWindowService) {
- return new Storage($window.localStorage);
- })
- .service('sessionStorage', function ($window: IWindowService) {
- return new Storage($window.sessionStorage);
- })
- .service('sessionTimeout', function () {
- return {};
- });
-}
-
-function createLocalPrivateModule() {
- angular.module('monitoring/Private', []).provider('Private', PrivateProvider);
-}
-
-function createLocalTopNavModule({ ui }: MonitoringStartPluginDependencies['navigation']) {
- angular
- .module('monitoring/TopNav', ['react'])
- .directive('kbnTopNav', createTopNavDirective)
- .directive('kbnTopNavHelper', createTopNavHelper(ui));
-}
-
-function createLocalI18nModule() {
- angular
- .module('monitoring/I18n', [])
- .provider('i18n', I18nProvider)
- .filter('i18n', i18nFilter)
- .directive('i18nId', i18nDirective);
-}
-
-function createHrefModule(core: CoreStart) {
- const name: string = 'kbnHref';
- angular.module('monitoring/href', []).directive(name, function () {
- return {
- restrict: 'A',
- link: {
- pre: (_$scope, _$el, $attr) => {
- $attr.$observe(name, (val) => {
- if (val) {
- const url = getSafeForExternalLink(val as string);
- $attr.$set('href', core.http.basePath.prepend(url));
- }
- });
-
- _$scope.$on('$locationChangeSuccess', () => {
- const url = getSafeForExternalLink($attr.href as string);
- $attr.$set('href', core.http.basePath.prepend(url));
- });
- },
- },
- };
- });
-}
diff --git a/x-pack/plugins/monitoring/public/angular/helpers/routes.ts b/x-pack/plugins/monitoring/public/angular/helpers/routes.ts
deleted file mode 100644
index 2579e522882a2..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/helpers/routes.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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-type RouteObject = [string, { reloadOnSearch: boolean }];
-interface Redirect {
- redirectTo: string;
-}
-
-class Routes {
- private routes: RouteObject[] = [];
- public redirect?: Redirect = { redirectTo: '/no-data' };
-
- public when = (...args: RouteObject) => {
- const [, routeOptions] = args;
- routeOptions.reloadOnSearch = false;
- this.routes.push(args);
- return this;
- };
-
- public otherwise = (redirect: Redirect) => {
- this.redirect = redirect;
- return this;
- };
-
- public addToProvider = ($routeProvider: any) => {
- this.routes.forEach((args) => {
- $routeProvider.when.apply(this, args);
- });
-
- if (this.redirect) {
- $routeProvider.otherwise(this.redirect);
- }
- };
-}
-export const uiRoutes = new Routes();
diff --git a/x-pack/plugins/monitoring/public/angular/helpers/utils.ts b/x-pack/plugins/monitoring/public/angular/helpers/utils.ts
deleted file mode 100644
index 32184ad71ed8d..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/helpers/utils.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { IScope } from 'angular';
-import * as Rx from 'rxjs';
-
-/**
- * Subscribe to an observable at a $scope, ensuring that the digest cycle
- * is run for subscriber hooks and routing errors to fatalError if not handled.
- */
-export const subscribeWithScope = (
- $scope: IScope,
- observable: Rx.Observable,
- observer?: Rx.PartialObserver
-) => {
- return observable.subscribe({
- next(value) {
- if (observer && observer.next) {
- $scope.$applyAsync(() => observer.next!(value));
- }
- },
- error(error) {
- $scope.$applyAsync(() => {
- if (observer && observer.error) {
- observer.error(error);
- } else {
- throw new Error(
- `Uncaught error in subscribeWithScope(): ${
- error ? error.stack || error.message : error
- }`
- );
- }
- });
- },
- complete() {
- if (observer && observer.complete) {
- $scope.$applyAsync(() => observer.complete!());
- }
- },
- });
-};
diff --git a/x-pack/plugins/monitoring/public/angular/index.ts b/x-pack/plugins/monitoring/public/angular/index.ts
deleted file mode 100644
index 1a655fc1ee256..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/index.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import angular, { IModule } from 'angular';
-import { uiRoutes } from './helpers/routes';
-import { Legacy } from '../legacy_shims';
-import { configureAppAngularModule } from '../angular/top_nav';
-import { localAppModule, appModuleName } from './app_modules';
-import { APP_WRAPPER_CLASS } from '../../../../../src/core/public';
-
-import { MonitoringStartPluginDependencies } from '../types';
-
-export class AngularApp {
- private injector?: angular.auto.IInjectorService;
-
- constructor(deps: MonitoringStartPluginDependencies) {
- const {
- core,
- element,
- data,
- navigation,
- isCloud,
- pluginInitializerContext,
- externalConfig,
- triggersActionsUi,
- usageCollection,
- appMountParameters,
- } = deps;
- const app: IModule = localAppModule(deps);
- app.run(($injector: angular.auto.IInjectorService) => {
- this.injector = $injector;
- Legacy.init(
- {
- core,
- element,
- data,
- navigation,
- isCloud,
- pluginInitializerContext,
- externalConfig,
- triggersActionsUi,
- usageCollection,
- appMountParameters,
- },
- this.injector
- );
- });
-
- app.config(($routeProvider: unknown) => uiRoutes.addToProvider($routeProvider));
-
- const np = { core, env: pluginInitializerContext.env };
- configureAppAngularModule(app, np, true);
- const appElement = document.createElement('div');
- appElement.setAttribute('style', 'height: 100%');
- appElement.innerHTML = '';
-
- if (!element.classList.contains(APP_WRAPPER_CLASS)) {
- element.classList.add(APP_WRAPPER_CLASS);
- }
-
- angular.bootstrap(appElement, [appModuleName]);
- angular.element(element).append(appElement);
- }
-
- public destroy = () => {
- if (this.injector) {
- this.injector.get('$rootScope').$destroy();
- }
- };
-
- public applyScope = () => {
- if (!this.injector) {
- return;
- }
-
- const rootScope = this.injector.get('$rootScope');
- rootScope.$applyAsync();
- };
-}
diff --git a/x-pack/plugins/monitoring/public/angular/providers/private.js b/x-pack/plugins/monitoring/public/angular/providers/private.js
deleted file mode 100644
index 018e2d7d41840..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/providers/private.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * # `Private()`
- * Private module loader, used to merge angular and require js dependency styles
- * by allowing a require.js module to export a single provider function that will
- * create a value used within an angular application. This provider can declare
- * angular dependencies by listing them as arguments, and can be require additional
- * Private modules.
- *
- * ## Define a private module provider:
- * ```js
- * export default function PingProvider($http) {
- * this.ping = function () {
- * return $http.head('/health-check');
- * };
- * };
- * ```
- *
- * ## Require a private module:
- * ```js
- * export default function ServerHealthProvider(Private, Promise) {
- * let ping = Private(require('ui/ping'));
- * return {
- * check: Promise.method(function () {
- * let attempts = 0;
- * return (function attempt() {
- * attempts += 1;
- * return ping.ping()
- * .catch(function (err) {
- * if (attempts < 3) return attempt();
- * })
- * }())
- * .then(function () {
- * return true;
- * })
- * .catch(function () {
- * return false;
- * });
- * })
- * }
- * };
- * ```
- *
- * # `Private.stub(provider, newInstance)`
- * `Private.stub()` replaces the instance of a module with another value. This is all we have needed until now.
- *
- * ```js
- * beforeEach(inject(function ($injector, Private) {
- * Private.stub(
- * // since this module just exports a function, we need to change
- * // what Private returns in order to modify it's behavior
- * require('ui/agg_response/hierarchical/_build_split'),
- * sinon.stub().returns(fakeSplit)
- * );
- * }));
- * ```
- *
- * # `Private.swap(oldProvider, newProvider)`
- * This new method does an 1-for-1 swap of module providers, unlike `stub()` which replaces a modules instance.
- * Pass the module you want to swap out, and the one it should be replaced with, then profit.
- *
- * Note: even though this example shows `swap()` being called in a config
- * function, it can be called from anywhere. It is particularly useful
- * in this scenario though.
- *
- * ```js
- * beforeEach(module('kibana', function (PrivateProvider) {
- * PrivateProvider.swap(
- * function StubbedRedirectProvider($decorate) {
- * // $decorate is a function that will instantiate the original module when called
- * return sinon.spy($decorate());
- * }
- * );
- * }));
- * ```
- *
- * @param {[type]} prov [description]
- */
-import { partial, uniqueId, isObject } from 'lodash';
-
-const nextId = partial(uniqueId, 'privateProvider#');
-
-function name(fn) {
- return fn.name || fn.toString().split('\n').shift();
-}
-
-export function PrivateProvider() {
- const provider = this;
-
- // one cache/swaps per Provider
- const cache = {};
- const swaps = {};
-
- // return the uniq id for this function
- function identify(fn) {
- if (typeof fn !== 'function') {
- throw new TypeError('Expected private module "' + fn + '" to be a function');
- }
-
- if (fn.$$id) return fn.$$id;
- else return (fn.$$id = nextId());
- }
-
- provider.stub = function (fn, instance) {
- cache[identify(fn)] = instance;
- return instance;
- };
-
- provider.swap = function (fn, prov) {
- const id = identify(fn);
- swaps[id] = prov;
- };
-
- provider.$get = [
- '$injector',
- function PrivateFactory($injector) {
- // prevent circular deps by tracking where we came from
- const privPath = [];
- const pathToString = function () {
- return privPath.map(name).join(' -> ');
- };
-
- // call a private provider and return the instance it creates
- function instantiate(prov, locals) {
- if (~privPath.indexOf(prov)) {
- throw new Error(
- 'Circular reference to "' +
- name(prov) +
- '"' +
- ' found while resolving private deps: ' +
- pathToString()
- );
- }
-
- privPath.push(prov);
-
- const context = {};
- let instance = $injector.invoke(prov, context, locals);
- if (!isObject(instance)) instance = context;
-
- privPath.pop();
- return instance;
- }
-
- // retrieve an instance from cache or create and store on
- function get(id, prov, $delegateId, $delegateProv) {
- if (cache[id]) return cache[id];
-
- let instance;
-
- if ($delegateId != null && $delegateProv != null) {
- instance = instantiate(prov, {
- $decorate: partial(get, $delegateId, $delegateProv),
- });
- } else {
- instance = instantiate(prov);
- }
-
- return (cache[id] = instance);
- }
-
- // main api, get the appropriate instance for a provider
- function Private(prov) {
- let id = identify(prov);
- let $delegateId;
- let $delegateProv;
-
- if (swaps[id]) {
- $delegateId = id;
- $delegateProv = prov;
-
- prov = swaps[$delegateId];
- id = identify(prov);
- }
-
- return get(id, prov, $delegateId, $delegateProv);
- }
-
- Private.stub = provider.stub;
- Private.swap = provider.swap;
-
- return Private;
- },
- ];
-
- return provider;
-}
diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx
index c2dfe1c0dae7d..2a2de0a716cea 100644
--- a/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx
+++ b/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx
@@ -34,12 +34,12 @@ export const LogStashPipelinesPage: React.FC = ({ clusters }) =>
const { getPaginationTableProps, getPaginationRouteOptions, updateTotalItemCount } =
useTable('logstash.pipelines');
- const title = i18n.translate('xpack.monitoring.logstash.overview.title', {
- defaultMessage: 'Logstash',
+ const title = i18n.translate('xpack.monitoring.logstash.pipelines.routeTitle', {
+ defaultMessage: 'Logstash Pipelines',
});
- const pageTitle = i18n.translate('xpack.monitoring.logstash.overview.pageTitle', {
- defaultMessage: 'Logstash overview',
+ const pageTitle = i18n.translate('xpack.monitoring.logstash.pipelines.pageTitle', {
+ defaultMessage: 'Logstash pipelines',
});
const getPageData = useCallback(async () => {
diff --git a/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx b/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx
index e798e7d74ad38..e767074aea42b 100644
--- a/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx
+++ b/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx
@@ -17,7 +17,7 @@ import { CODE_PATH_LICENSE, STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../
import { Legacy } from '../../../legacy_shims';
import { Enabler } from './enabler';
import { BreadcrumbContainer } from '../../hooks/use_breadcrumbs';
-import { initSetupModeState } from '../../setup_mode/setup_mode';
+import { initSetupModeState } from '../../../lib/setup_mode';
import { GlobalStateContext } from '../../contexts/global_state_context';
import { useRequestErrorHandler } from '../../hooks/use_request_error_handler';
diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx
index 23eeb2c034a80..c0030cfcfe55c 100644
--- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx
+++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx
@@ -17,7 +17,7 @@ import {
getSetupModeState,
isSetupModeFeatureEnabled,
updateSetupModeData,
-} from '../setup_mode/setup_mode';
+} from '../../lib/setup_mode';
import { SetupModeFeature } from '../../../common/enums';
import { AlertsDropdown } from '../../alerts/alerts_dropdown';
import { ActionMenu } from '../../components/action_menu';
diff --git a/x-pack/plugins/monitoring/public/application/setup_mode/index.ts b/x-pack/plugins/monitoring/public/application/setup_mode/index.ts
index 1bcdcdef09c28..57d734fc6d056 100644
--- a/x-pack/plugins/monitoring/public/application/setup_mode/index.ts
+++ b/x-pack/plugins/monitoring/public/application/setup_mode/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export * from './setup_mode';
+export * from '../../lib/setup_mode';
diff --git a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx
deleted file mode 100644
index 828d5a2d20ae6..0000000000000
--- a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { render } from 'react-dom';
-import { get, includes } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { HttpStart, IHttpFetchError } from 'kibana/public';
-import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
-import { Legacy } from '../../legacy_shims';
-import { SetupModeEnterButton } from '../../components/setup_mode/enter_button';
-import { SetupModeFeature } from '../../../common/enums';
-import { ISetupModeContext } from '../../components/setup_mode/setup_mode_context';
-import { State as GlobalState } from '../contexts/global_state_context';
-
-function isOnPage(hash: string) {
- return includes(window.location.hash, hash);
-}
-
-let globalState: GlobalState;
-let httpService: HttpStart;
-let errorHandler: (error: IHttpFetchError) => void;
-
-interface ISetupModeState {
- enabled: boolean;
- data: any;
- callback?: (() => void) | null;
- hideBottomBar: boolean;
-}
-const setupModeState: ISetupModeState = {
- enabled: false,
- data: null,
- callback: null,
- hideBottomBar: false,
-};
-
-export const getSetupModeState = () => setupModeState;
-
-export const setNewlyDiscoveredClusterUuid = (clusterUuid: string) => {
- globalState.cluster_uuid = clusterUuid;
- globalState.save?.();
-};
-
-export const fetchCollectionData = async (uuid?: string, fetchWithoutClusterUuid = false) => {
- const clusterUuid = globalState.cluster_uuid;
- const ccs = globalState.ccs;
-
- let url = '../api/monitoring/v1/setup/collection';
- if (uuid) {
- url += `/node/${uuid}`;
- } else if (!fetchWithoutClusterUuid && clusterUuid) {
- url += `/cluster/${clusterUuid}`;
- } else {
- url += '/cluster';
- }
-
- try {
- const response = await httpService.post(url, {
- body: JSON.stringify({
- ccs,
- }),
- });
- return response;
- } catch (err) {
- errorHandler(err);
- throw err;
- }
-};
-
-const notifySetupModeDataChange = () => setupModeState.callback && setupModeState.callback();
-
-export const updateSetupModeData = async (uuid?: string, fetchWithoutClusterUuid = false) => {
- const data = await fetchCollectionData(uuid, fetchWithoutClusterUuid);
- setupModeState.data = data;
- const hasPermissions = get(data, '_meta.hasPermissions', false);
- if (!hasPermissions) {
- let text: string = '';
- if (!hasPermissions) {
- text = i18n.translate('xpack.monitoring.setupMode.notAvailablePermissions', {
- defaultMessage: 'You do not have the necessary permissions to do this.',
- });
- }
-
- Legacy.shims.toastNotifications.addDanger({
- title: i18n.translate('xpack.monitoring.setupMode.notAvailableTitle', {
- defaultMessage: 'Setup mode is not available',
- }),
- text,
- });
- return toggleSetupMode(false);
- }
- notifySetupModeDataChange();
-
- const clusterUuid = globalState.cluster_uuid;
- if (!clusterUuid) {
- const liveClusterUuid: string = get(data, '_meta.liveClusterUuid');
- const migratedEsNodes = Object.values(get(data, 'elasticsearch.byUuid', {})).filter(
- (node: any) => node.isPartiallyMigrated || node.isFullyMigrated
- );
- if (liveClusterUuid && migratedEsNodes.length > 0) {
- setNewlyDiscoveredClusterUuid(liveClusterUuid);
- }
- }
-};
-
-export const hideBottomBar = () => {
- setupModeState.hideBottomBar = true;
- notifySetupModeDataChange();
-};
-export const showBottomBar = () => {
- setupModeState.hideBottomBar = false;
- notifySetupModeDataChange();
-};
-
-export const disableElasticsearchInternalCollection = async () => {
- const clusterUuid = globalState.cluster_uuid;
- const url = `../api/monitoring/v1/setup/collection/${clusterUuid}/disable_internal_collection`;
- try {
- const response = await httpService.post(url);
- return response;
- } catch (err) {
- errorHandler(err);
- throw err;
- }
-};
-
-export const toggleSetupMode = (inSetupMode: boolean) => {
- setupModeState.enabled = inSetupMode;
- globalState.inSetupMode = inSetupMode;
- globalState.save?.();
- setSetupModeMenuItem();
- notifySetupModeDataChange();
-
- if (inSetupMode) {
- // Intentionally do not await this so we don't block UI operations
- updateSetupModeData();
- }
-};
-
-export const setSetupModeMenuItem = () => {
- if (isOnPage('no-data')) {
- return;
- }
-
- const enabled = !globalState.inSetupMode;
- const I18nContext = Legacy.shims.I18nContext;
-
- render(
-
-
-
-
- ,
- document.getElementById('setupModeNav')
- );
-};
-
-export const initSetupModeState = async (
- state: GlobalState,
- http: HttpStart,
- handleErrors: (error: IHttpFetchError) => void,
- callback?: () => void
-) => {
- globalState = state;
- httpService = http;
- errorHandler = handleErrors;
- if (callback) {
- setupModeState.callback = callback;
- }
-
- if (globalState.inSetupMode) {
- toggleSetupMode(true);
- }
-};
-
-export const isInSetupMode = (context?: ISetupModeContext, gState: GlobalState = globalState) => {
- if (context?.setupModeSupported === false) {
- return false;
- }
- if (setupModeState.enabled) {
- return true;
- }
-
- return gState.inSetupMode;
-};
-
-export const isSetupModeFeatureEnabled = (feature: SetupModeFeature) => {
- if (!setupModeState.enabled) {
- return false;
- }
-
- if (feature === SetupModeFeature.MetricbeatMigration) {
- if (Legacy.shims.isCloud) {
- return false;
- }
- }
-
- return true;
-};
diff --git a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js
index a9ee2464cd423..df524fa99ae53 100644
--- a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js
+++ b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js
@@ -13,7 +13,7 @@ import {
disableElasticsearchInternalCollection,
toggleSetupMode,
setSetupModeMenuItem,
-} from './setup_mode';
+} from '../../lib/setup_mode';
import { Flyout } from '../../components/metricbeat_migration/flyout';
import {
EuiBottomBar,
diff --git a/x-pack/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js b/x-pack/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js
deleted file mode 100644
index 69579cb831c06..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { capitalize } from 'lodash';
-import numeral from '@elastic/numeral';
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { EuiMonitoringTable } from '../../../components/table';
-import { MachineLearningJobStatusIcon } from '../../../components/elasticsearch/ml_job_listing/status_icon';
-import { LARGE_ABBREVIATED, LARGE_BYTES } from '../../../../common/formatting';
-import { EuiLink, EuiPage, EuiPageContent, EuiPageBody, EuiPanel, EuiSpacer } from '@elastic/eui';
-import { ClusterStatus } from '../../../components/elasticsearch/cluster_status';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
-
-const getColumns = () => [
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.jobIdTitle', {
- defaultMessage: 'Job ID',
- }),
- field: 'job_id',
- sortable: true,
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.stateTitle', {
- defaultMessage: 'State',
- }),
- field: 'state',
- sortable: true,
- render: (state) => (
-
-
-
- {capitalize(state)}
-
- ),
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.processedRecordsTitle', {
- defaultMessage: 'Processed Records',
- }),
- field: 'data_counts.processed_record_count',
- sortable: true,
- render: (value) => {numeral(value).format(LARGE_ABBREVIATED)},
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.modelSizeTitle', {
- defaultMessage: 'Model Size',
- }),
- field: 'model_size_stats.model_bytes',
- sortable: true,
- render: (value) => {numeral(value).format(LARGE_BYTES)},
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.forecastsTitle', {
- defaultMessage: 'Forecasts',
- }),
- field: 'forecasts_stats.total',
- sortable: true,
- render: (value) => {numeral(value).format(LARGE_ABBREVIATED)},
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.nodeTitle', {
- defaultMessage: 'Node',
- }),
- field: 'node.name',
- sortable: true,
- render: (name, node) => {
- if (node) {
- return (
-
- {name}
-
- );
- }
-
- return (
-
- );
- },
- },
-];
-
-//monitoringMlListing
-export function monitoringMlListingProvider() {
- return {
- restrict: 'E',
- scope: {
- jobs: '=',
- paginationSettings: '=',
- sorting: '=',
- onTableChange: '=',
- status: '=',
- },
- link(scope, $el) {
- scope.$on('$destroy', () => $el && $el[0] && unmountComponentAtNode($el[0]));
- const columns = getColumns();
-
- const filterJobsPlaceholder = i18n.translate(
- 'xpack.monitoring.elasticsearch.mlJobListing.filterJobsPlaceholder',
- {
- defaultMessage: 'Filter Jobs…',
- }
- );
-
- scope.$watch('jobs', (_jobs = []) => {
- const jobs = _jobs.map((job) => {
- if (job.ml) {
- return {
- ...job.ml.job,
- node: job.node,
- job_id: job.ml.job.id,
- };
- }
- return job;
- });
- const mlTable = (
-
-
-
-
-
-
-
-
-
-
-
- );
- render(mlTable, $el[0]);
- });
- },
- };
-}
diff --git a/x-pack/plugins/monitoring/public/directives/main/index.html b/x-pack/plugins/monitoring/public/directives/main/index.html
deleted file mode 100644
index fd14120e1db2f..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/main/index.html
+++ /dev/null
@@ -1,323 +0,0 @@
-
-
-
-
-
-
-
-
{{pageTitle || monitoringMain.instance}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/x-pack/plugins/monitoring/public/directives/main/index.js b/x-pack/plugins/monitoring/public/directives/main/index.js
deleted file mode 100644
index 0e464f0a356c4..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/main/index.js
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { EuiSelect, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { get } from 'lodash';
-import template from './index.html';
-import { Legacy } from '../../legacy_shims';
-import { shortenPipelineHash } from '../../../common/formatting';
-import {
- getSetupModeState,
- initSetupModeState,
- isSetupModeFeatureEnabled,
-} from '../../lib/setup_mode';
-import { Subscription } from 'rxjs';
-import { getSafeForExternalLink } from '../../lib/get_safe_for_external_link';
-import { SetupModeFeature } from '../../../common/enums';
-import './index.scss';
-
-const setOptions = (controller) => {
- if (
- !controller.pipelineVersions ||
- !controller.pipelineVersions.length ||
- !controller.pipelineDropdownElement
- ) {
- return;
- }
-
- render(
-
-
- {
- return {
- text: i18n.translate(
- 'xpack.monitoring.logstashNavigation.pipelineVersionDescription',
- {
- defaultMessage:
- 'Version active {relativeLastSeen} and first seen {relativeFirstSeen}',
- values: {
- relativeLastSeen: option.relativeLastSeen,
- relativeFirstSeen: option.relativeFirstSeen,
- },
- }
- ),
- value: option.hash,
- };
- })}
- onChange={controller.onChangePipelineHash}
- />
-
- ,
- controller.pipelineDropdownElement
- );
-};
-
-/*
- * Manage data and provide helper methods for the "main" directive's template
- */
-export class MonitoringMainController {
- // called internally by Angular
- constructor() {
- this.inListing = false;
- this.inAlerts = false;
- this.inOverview = false;
- this.inElasticsearch = false;
- this.inKibana = false;
- this.inLogstash = false;
- this.inBeats = false;
- this.inApm = false;
- }
-
- addTimerangeObservers = () => {
- const timefilter = Legacy.shims.timefilter;
- this.subscriptions = new Subscription();
-
- const refreshIntervalUpdated = () => {
- const { value: refreshInterval, pause: isPaused } = timefilter.getRefreshInterval();
- this.datePicker.onRefreshChange({ refreshInterval, isPaused }, true);
- };
-
- const timeUpdated = () => {
- this.datePicker.onTimeUpdate({ dateRange: timefilter.getTime() }, true);
- };
-
- this.subscriptions.add(
- timefilter.getRefreshIntervalUpdate$().subscribe(refreshIntervalUpdated)
- );
- this.subscriptions.add(timefilter.getTimeUpdate$().subscribe(timeUpdated));
- };
-
- dropdownLoadedHandler() {
- this.pipelineDropdownElement = document.querySelector('#dropdown-elm');
- setOptions(this);
- }
-
- // kick things off from the directive link function
- setup(options) {
- const timefilter = Legacy.shims.timefilter;
- this._licenseService = options.licenseService;
- this._breadcrumbsService = options.breadcrumbsService;
- this._executorService = options.executorService;
-
- Object.assign(this, options.attributes);
-
- this.navName = `${this.name}-nav`;
-
- // set the section we're navigated in
- if (this.product) {
- this.inElasticsearch = this.product === 'elasticsearch';
- this.inKibana = this.product === 'kibana';
- this.inLogstash = this.product === 'logstash';
- this.inBeats = this.product === 'beats';
- this.inApm = this.product === 'apm';
- } else {
- this.inOverview = this.name === 'overview';
- this.inAlerts = this.name === 'alerts';
- this.inListing = this.name === 'listing'; // || this.name === 'no-data';
- }
-
- if (!this.inListing) {
- // no breadcrumbs in cluster listing page
- this.breadcrumbs = this._breadcrumbsService(options.clusterName, this);
- }
-
- if (this.pipelineHash) {
- this.pipelineHashShort = shortenPipelineHash(this.pipelineHash);
- this.onChangePipelineHash = () => {
- window.location.hash = getSafeForExternalLink(
- `#/logstash/pipelines/${this.pipelineId}/${this.pipelineHash}`
- );
- };
- }
-
- this.datePicker = {
- enableTimeFilter: timefilter.isTimeRangeSelectorEnabled(),
- timeRange: timefilter.getTime(),
- refreshInterval: timefilter.getRefreshInterval(),
- onRefreshChange: ({ isPaused, refreshInterval }, skipSet = false) => {
- this.datePicker.refreshInterval = {
- pause: isPaused,
- value: refreshInterval,
- };
- if (!skipSet) {
- timefilter.setRefreshInterval({
- pause: isPaused,
- value: refreshInterval ? refreshInterval : this.datePicker.refreshInterval.value,
- });
- }
- },
- onTimeUpdate: ({ dateRange }, skipSet = false) => {
- this.datePicker.timeRange = {
- ...dateRange,
- };
- if (!skipSet) {
- timefilter.setTime(dateRange);
- }
- this._executorService.cancel();
- this._executorService.run();
- },
- };
- }
-
- // check whether to "highlight" a tab
- isActiveTab(testPath) {
- return this.name === testPath;
- }
-
- // check whether to show ML tab
- isMlSupported() {
- return this._licenseService.mlIsSupported();
- }
-
- isDisabledTab(product) {
- const setupMode = getSetupModeState();
- if (!isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) {
- return false;
- }
-
- if (!setupMode.data) {
- return false;
- }
-
- const data = setupMode.data[product] || {};
- if (data.totalUniqueInstanceCount === 0) {
- return true;
- }
- if (
- data.totalUniqueInternallyCollectedCount === 0 &&
- data.totalUniqueFullyMigratedCount === 0 &&
- data.totalUniquePartiallyMigratedCount === 0
- ) {
- return true;
- }
- return false;
- }
-}
-
-export function monitoringMainProvider(breadcrumbs, license, $injector) {
- const $executor = $injector.get('$executor');
- const $parse = $injector.get('$parse');
-
- return {
- restrict: 'E',
- transclude: true,
- template,
- controller: MonitoringMainController,
- controllerAs: 'monitoringMain',
- bindToController: true,
- link(scope, _element, attributes, controller) {
- scope.$applyAsync(() => {
- controller.addTimerangeObservers();
- const setupObj = getSetupObj();
- controller.setup(setupObj);
- Object.keys(setupObj.attributes).forEach((key) => {
- attributes.$observe(key, () => controller.setup(getSetupObj()));
- });
- if (attributes.onLoaded) {
- const onLoaded = $parse(attributes.onLoaded)(scope);
- onLoaded();
- }
- });
-
- initSetupModeState(scope, $injector, () => {
- controller.setup(getSetupObj());
- });
- if (!scope.cluster) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- scope.cluster = ($route.current.locals.clusters || []).find(
- (cluster) => cluster.cluster_uuid === globalState.cluster_uuid
- );
- }
-
- function getSetupObj() {
- return {
- licenseService: license,
- breadcrumbsService: breadcrumbs,
- executorService: $executor,
- attributes: {
- name: attributes.name,
- product: attributes.product,
- instance: attributes.instance,
- resolver: attributes.resolver,
- page: attributes.page,
- tabIconClass: attributes.tabIconClass,
- tabIconLabel: attributes.tabIconLabel,
- pipelineId: attributes.pipelineId,
- pipelineHash: attributes.pipelineHash,
- pipelineVersions: get(scope, 'pageData.versions'),
- isCcrEnabled: attributes.isCcrEnabled === 'true' || attributes.isCcrEnabled === true,
- },
- clusterName: get(scope, 'cluster.cluster_name'),
- };
- }
-
- scope.$on('$destroy', () => {
- controller.pipelineDropdownElement &&
- unmountComponentAtNode(controller.pipelineDropdownElement);
- controller.subscriptions && controller.subscriptions.unsubscribe();
- });
- scope.$watch('pageData.versions', (versions) => {
- controller.pipelineVersions = versions;
- setOptions(controller);
- });
- },
- };
-}
diff --git a/x-pack/plugins/monitoring/public/directives/main/index.scss b/x-pack/plugins/monitoring/public/directives/main/index.scss
deleted file mode 100644
index db5d2b72ab07b..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/main/index.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.monTopNavSecondItem {
- padding-left: $euiSizeM;
-}
diff --git a/x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js b/x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js
deleted file mode 100644
index 195e11cee6112..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { noop } from 'lodash';
-import expect from '@kbn/expect';
-import { Legacy } from '../../legacy_shims';
-import { MonitoringMainController } from './';
-
-const getMockLicenseService = (options) => ({ mlIsSupported: () => options.mlIsSupported });
-const getMockBreadcrumbsService = () => noop; // breadcrumb service has its own test
-
-describe('Monitoring Main Directive Controller', () => {
- const core = {
- notifications: {},
- application: {},
- i18n: {},
- chrome: {},
- };
- const data = {
- query: {
- timefilter: {
- timefilter: {
- isTimeRangeSelectorEnabled: () => true,
- getTime: () => 1,
- getRefreshInterval: () => 1,
- },
- },
- },
- };
- const isCloud = false;
- const triggersActionsUi = {};
-
- beforeAll(() => {
- Legacy.init({
- core,
- data,
- isCloud,
- triggersActionsUi,
- });
- });
-
- /*
- * Simulates calling the monitoringMain directive the way Cluster Listing
- * does:
- *
- * ...
- */
- it('in Cluster Listing', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'listing',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(true);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('listing');
- expect(controller.product).to.be(undefined);
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /*
- * Simulates calling the monitoringMain directive the way Cluster Alerts
- * Listing does:
- *
- * ...
- */
- it('in Cluster Alerts', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'alerts',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(true);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('alerts');
- expect(controller.product).to.be(undefined);
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /*
- * Simulates calling the monitoringMain directive the way Cluster Overview
- * does:
- *
- * ...
- */
- it('in Cluster Overview', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'overview',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(true);
-
- // attributes
- expect(controller.name).to.be('overview');
- expect(controller.product).to.be(undefined);
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /*
- * Simulates calling the monitoringMain directive the way that Elasticsearch
- * Node / Advanced does:
- *
- * ...
- */
- it('in ES Node - Advanced', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- product: 'elasticsearch',
- name: 'nodes',
- instance: 'es-node-name-01',
- resolver: 'es-node-resolver-01',
- page: 'advanced',
- tabIconClass: 'fa star',
- tabIconLabel: 'Master Node',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('nodes');
- expect(controller.product).to.be('elasticsearch');
- expect(controller.instance).to.be('es-node-name-01');
- expect(controller.resolver).to.be('es-node-resolver-01');
- expect(controller.page).to.be('advanced');
- expect(controller.tabIconClass).to.be('fa star');
- expect(controller.tabIconLabel).to.be('Master Node');
- });
-
- /**
- *
- */
- it('in Kibana Overview', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- product: 'kibana',
- name: 'overview',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('overview');
- expect(controller.product).to.be('kibana');
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /**
- *
- */
- it('in Logstash Listing', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- product: 'logstash',
- name: 'listing',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('listing');
- expect(controller.product).to.be('logstash');
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /*
- * Test `controller.isMlSupported` function
- */
- describe('Checking support for ML', () => {
- it('license supports ML', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService({ mlIsSupported: true }),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'listing',
- },
- });
-
- expect(controller.isMlSupported()).to.be(true);
- });
- it('license does not support ML', () => {
- getMockLicenseService({ mlIsSupported: false });
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService({ mlIsSupported: false }),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'listing',
- },
- });
-
- expect(controller.isMlSupported()).to.be(false);
- });
- });
-});
diff --git a/x-pack/plugins/monitoring/public/lib/setup_mode.test.js b/x-pack/plugins/monitoring/public/lib/setup_mode.test.js
index 6dad6effeecc1..47cae9c4f0851 100644
--- a/x-pack/plugins/monitoring/public/lib/setup_mode.test.js
+++ b/x-pack/plugins/monitoring/public/lib/setup_mode.test.js
@@ -83,7 +83,7 @@ function waitForSetupModeData() {
return new Promise((resolve) => process.nextTick(resolve));
}
-describe('setup_mode', () => {
+xdescribe('setup_mode', () => {
beforeEach(async () => {
setModulesAndMocks();
});
diff --git a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx
index fca7f94731bc5..e582f4aa40812 100644
--- a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx
+++ b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx
@@ -9,37 +9,21 @@ import React from 'react';
import { render } from 'react-dom';
import { get, includes } from 'lodash';
import { i18n } from '@kbn/i18n';
+import { HttpStart, IHttpFetchError } from 'kibana/public';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { Legacy } from '../legacy_shims';
-import { ajaxErrorHandlersProvider } from './ajax_error_handler';
import { SetupModeEnterButton } from '../components/setup_mode/enter_button';
import { SetupModeFeature } from '../../common/enums';
import { ISetupModeContext } from '../components/setup_mode/setup_mode_context';
-import * as setupModeReact from '../application/setup_mode/setup_mode';
-import { isReactMigrationEnabled } from '../external_config';
+import { State as GlobalState } from '../application/contexts/global_state_context';
function isOnPage(hash: string) {
return includes(window.location.hash, hash);
}
-interface IAngularState {
- injector: any;
- scope: any;
-}
-
-const angularState: IAngularState = {
- injector: null,
- scope: null,
-};
-
-const checkAngularState = () => {
- if (!angularState.injector || !angularState.scope) {
- throw new Error(
- 'Unable to interact with setup mode because the angular injector was not previously set.' +
- ' This needs to be set by calling `initSetupModeState`.'
- );
- }
-};
+let globalState: GlobalState;
+let httpService: HttpStart;
+let errorHandler: (error: IHttpFetchError) => void;
interface ISetupModeState {
enabled: boolean;
@@ -57,20 +41,11 @@ const setupModeState: ISetupModeState = {
export const getSetupModeState = () => setupModeState;
export const setNewlyDiscoveredClusterUuid = (clusterUuid: string) => {
- const globalState = angularState.injector.get('globalState');
- const executor = angularState.injector.get('$executor');
- angularState.scope.$apply(() => {
- globalState.cluster_uuid = clusterUuid;
- globalState.save();
- });
- executor.run();
+ globalState.cluster_uuid = clusterUuid;
+ globalState.save?.();
};
export const fetchCollectionData = async (uuid?: string, fetchWithoutClusterUuid = false) => {
- checkAngularState();
-
- const http = angularState.injector.get('$http');
- const globalState = angularState.injector.get('globalState');
const clusterUuid = globalState.cluster_uuid;
const ccs = globalState.ccs;
@@ -84,12 +59,15 @@ export const fetchCollectionData = async (uuid?: string, fetchWithoutClusterUuid
}
try {
- const response = await http.post(url, { ccs });
- return response.data;
+ const response = await httpService.post(url, {
+ body: JSON.stringify({
+ ccs,
+ }),
+ });
+ return response;
} catch (err) {
- const Private = angularState.injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
+ errorHandler(err);
+ throw err;
}
};
@@ -107,19 +85,16 @@ export const updateSetupModeData = async (uuid?: string, fetchWithoutClusterUuid
});
}
- angularState.scope.$evalAsync(() => {
- Legacy.shims.toastNotifications.addDanger({
- title: i18n.translate('xpack.monitoring.setupMode.notAvailableTitle', {
- defaultMessage: 'Setup mode is not available',
- }),
- text,
- });
+ Legacy.shims.toastNotifications.addDanger({
+ title: i18n.translate('xpack.monitoring.setupMode.notAvailableTitle', {
+ defaultMessage: 'Setup mode is not available',
+ }),
+ text,
});
return toggleSetupMode(false);
}
notifySetupModeDataChange();
- const globalState = angularState.injector.get('globalState');
const clusterUuid = globalState.cluster_uuid;
if (!clusterUuid) {
const liveClusterUuid: string = get(data, '_meta.liveClusterUuid');
@@ -142,31 +117,21 @@ export const showBottomBar = () => {
};
export const disableElasticsearchInternalCollection = async () => {
- checkAngularState();
-
- const http = angularState.injector.get('$http');
- const globalState = angularState.injector.get('globalState');
const clusterUuid = globalState.cluster_uuid;
const url = `../api/monitoring/v1/setup/collection/${clusterUuid}/disable_internal_collection`;
try {
- const response = await http.post(url);
- return response.data;
+ const response = await httpService.post(url);
+ return response;
} catch (err) {
- const Private = angularState.injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
+ errorHandler(err);
+ throw err;
}
};
export const toggleSetupMode = (inSetupMode: boolean) => {
- if (isReactMigrationEnabled()) return setupModeReact.toggleSetupMode(inSetupMode);
-
- checkAngularState();
-
- const globalState = angularState.injector.get('globalState');
setupModeState.enabled = inSetupMode;
globalState.inSetupMode = inSetupMode;
- globalState.save();
+ globalState.save?.();
setSetupModeMenuItem();
notifySetupModeDataChange();
@@ -177,13 +142,10 @@ export const toggleSetupMode = (inSetupMode: boolean) => {
};
export const setSetupModeMenuItem = () => {
- checkAngularState();
-
if (isOnPage('no-data')) {
return;
}
- const globalState = angularState.injector.get('globalState');
const enabled = !globalState.inSetupMode;
const I18nContext = Legacy.shims.I18nContext;
@@ -197,23 +159,25 @@ export const setSetupModeMenuItem = () => {
);
};
-export const addSetupModeCallback = (callback: () => void) => (setupModeState.callback = callback);
-
-export const initSetupModeState = async ($scope: any, $injector: any, callback?: () => void) => {
- angularState.scope = $scope;
- angularState.injector = $injector;
+export const initSetupModeState = async (
+ state: GlobalState,
+ http: HttpStart,
+ handleErrors: (error: IHttpFetchError) => void,
+ callback?: () => void
+) => {
+ globalState = state;
+ httpService = http;
+ errorHandler = handleErrors;
if (callback) {
setupModeState.callback = callback;
}
- const globalState = $injector.get('globalState');
if (globalState.inSetupMode) {
toggleSetupMode(true);
}
};
-export const isInSetupMode = (context?: ISetupModeContext) => {
- if (isReactMigrationEnabled()) return setupModeReact.isInSetupMode(context);
+export const isInSetupMode = (context?: ISetupModeContext, gState: GlobalState = globalState) => {
if (context?.setupModeSupported === false) {
return false;
}
@@ -221,20 +185,19 @@ export const isInSetupMode = (context?: ISetupModeContext) => {
return true;
}
- const $injector = angularState.injector || Legacy.shims.getAngularInjector();
- const globalState = $injector.get('globalState');
- return globalState.inSetupMode;
+ return gState.inSetupMode;
};
export const isSetupModeFeatureEnabled = (feature: SetupModeFeature) => {
- if (isReactMigrationEnabled()) return setupModeReact.isSetupModeFeatureEnabled(feature);
if (!setupModeState.enabled) {
return false;
}
+
if (feature === SetupModeFeature.MetricbeatMigration) {
if (Legacy.shims.isCloud) {
return false;
}
}
+
return true;
};
diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts
index 82e49fec5a8d4..f75b76871f58c 100644
--- a/x-pack/plugins/monitoring/public/plugin.ts
+++ b/x-pack/plugins/monitoring/public/plugin.ts
@@ -36,9 +36,6 @@ interface MonitoringSetupPluginDependencies {
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
usageCollection: UsageCollectionSetup;
}
-
-const HASH_CHANGE = 'hashchange';
-
export class MonitoringPlugin
implements
Plugin
@@ -88,7 +85,6 @@ export class MonitoringPlugin
category: DEFAULT_APP_CATEGORIES.management,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
- const { AngularApp } = await import('./angular');
const externalConfig = this.getExternalConfig();
const deps: MonitoringStartPluginDependencies = {
navigation: pluginsStart.navigation,
@@ -118,26 +114,8 @@ export class MonitoringPlugin
const config = Object.fromEntries(externalConfig);
setConfig(config);
- if (config.renderReactApp) {
- const { renderApp } = await import('./application');
- return renderApp(coreStart, pluginsStart, params, config);
- } else {
- const monitoringApp = new AngularApp(deps);
- const removeHistoryListener = params.history.listen((location) => {
- if (location.pathname === '' && location.hash === '') {
- monitoringApp.applyScope();
- }
- });
-
- const removeHashChange = this.setInitialTimefilter(deps);
- return () => {
- if (removeHashChange) {
- removeHashChange();
- }
- removeHistoryListener();
- monitoringApp.destroy();
- };
- }
+ const { renderApp } = await import('./application');
+ return renderApp(coreStart, pluginsStart, params, config);
},
};
@@ -148,28 +126,6 @@ export class MonitoringPlugin
public stop() {}
- private setInitialTimefilter({ data }: MonitoringStartPluginDependencies) {
- const { timefilter } = data.query.timefilter;
- const { pause: pauseByDefault } = timefilter.getRefreshIntervalDefaults();
- if (pauseByDefault) {
- return;
- }
- /**
- * We can't use timefilter.getRefreshIntervalUpdate$ last value,
- * since it's not a BehaviorSubject. This means we need to wait for
- * hash change because of angular's applyAsync
- */
- const onHashChange = () => {
- const { value, pause } = timefilter.getRefreshInterval();
- if (!value && pause) {
- window.removeEventListener(HASH_CHANGE, onHashChange);
- timefilter.setRefreshInterval({ value: 10000, pause: false });
- }
- };
- window.addEventListener(HASH_CHANGE, onHashChange, false);
- return () => window.removeEventListener(HASH_CHANGE, onHashChange);
- }
-
private getExternalConfig() {
const monitoring = this.initializerContext.config.get();
return [
diff --git a/x-pack/plugins/monitoring/public/services/breadcrumbs.js b/x-pack/plugins/monitoring/public/services/breadcrumbs.js
deleted file mode 100644
index 54ff46f4bf0ab..0000000000000
--- a/x-pack/plugins/monitoring/public/services/breadcrumbs.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { Legacy } from '../legacy_shims';
-import { i18n } from '@kbn/i18n';
-
-// Helper for making objects to use in a link element
-const createCrumb = (url, label, testSubj, ignoreGlobalState = false) => {
- const crumb = { url, label, ignoreGlobalState };
- if (testSubj) {
- crumb.testSubj = testSubj;
- }
- return crumb;
-};
-
-// generate Elasticsearch breadcrumbs
-function getElasticsearchBreadcrumbs(mainInstance) {
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/elasticsearch', 'Elasticsearch'));
- if (mainInstance.name === 'indices') {
- breadcrumbs.push(
- createCrumb(
- '#/elasticsearch/indices',
- i18n.translate('xpack.monitoring.breadcrumbs.es.indicesLabel', {
- defaultMessage: 'Indices',
- }),
- 'breadcrumbEsIndices'
- )
- );
- } else if (mainInstance.name === 'nodes') {
- breadcrumbs.push(
- createCrumb(
- '#/elasticsearch/nodes',
- i18n.translate('xpack.monitoring.breadcrumbs.es.nodesLabel', { defaultMessage: 'Nodes' }),
- 'breadcrumbEsNodes'
- )
- );
- } else if (mainInstance.name === 'ml') {
- // ML Instance (for user later)
- breadcrumbs.push(
- createCrumb(
- '#/elasticsearch/ml_jobs',
- i18n.translate('xpack.monitoring.breadcrumbs.es.jobsLabel', {
- defaultMessage: 'Machine learning jobs',
- })
- )
- );
- } else if (mainInstance.name === 'ccr_shard') {
- breadcrumbs.push(
- createCrumb(
- '#/elasticsearch/ccr',
- i18n.translate('xpack.monitoring.breadcrumbs.es.ccrLabel', { defaultMessage: 'CCR' })
- )
- );
- }
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else {
- // don't link to Overview when we're possibly on Overview or its sibling tabs
- breadcrumbs.push(createCrumb(null, 'Elasticsearch'));
- }
- return breadcrumbs;
-}
-
-// generate Kibana breadcrumbs
-function getKibanaBreadcrumbs(mainInstance) {
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/kibana', 'Kibana'));
- breadcrumbs.push(
- createCrumb(
- '#/kibana/instances',
- i18n.translate('xpack.monitoring.breadcrumbs.kibana.instancesLabel', {
- defaultMessage: 'Instances',
- })
- )
- );
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else {
- // don't link to Overview when we're possibly on Overview or its sibling tabs
- breadcrumbs.push(createCrumb(null, 'Kibana'));
- }
- return breadcrumbs;
-}
-
-// generate Logstash breadcrumbs
-function getLogstashBreadcrumbs(mainInstance) {
- const logstashLabel = i18n.translate('xpack.monitoring.breadcrumbs.logstashLabel', {
- defaultMessage: 'Logstash',
- });
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/logstash', logstashLabel));
- if (mainInstance.name === 'nodes') {
- breadcrumbs.push(
- createCrumb(
- '#/logstash/nodes',
- i18n.translate('xpack.monitoring.breadcrumbs.logstash.nodesLabel', {
- defaultMessage: 'Nodes',
- })
- )
- );
- }
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else if (mainInstance.page === 'pipeline') {
- breadcrumbs.push(createCrumb('#/logstash', logstashLabel));
- breadcrumbs.push(
- createCrumb(
- '#/logstash/pipelines',
- i18n.translate('xpack.monitoring.breadcrumbs.logstash.pipelinesLabel', {
- defaultMessage: 'Pipelines',
- })
- )
- );
- } else {
- // don't link to Overview when we're possibly on Overview or its sibling tabs
- breadcrumbs.push(createCrumb(null, logstashLabel));
- }
-
- return breadcrumbs;
-}
-
-// generate Beats breadcrumbs
-function getBeatsBreadcrumbs(mainInstance) {
- const beatsLabel = i18n.translate('xpack.monitoring.breadcrumbs.beatsLabel', {
- defaultMessage: 'Beats',
- });
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/beats', beatsLabel));
- breadcrumbs.push(
- createCrumb(
- '#/beats/beats',
- i18n.translate('xpack.monitoring.breadcrumbs.beats.instancesLabel', {
- defaultMessage: 'Instances',
- })
- )
- );
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else {
- breadcrumbs.push(createCrumb(null, beatsLabel));
- }
-
- return breadcrumbs;
-}
-
-// generate Apm breadcrumbs
-function getApmBreadcrumbs(mainInstance) {
- const apmLabel = i18n.translate('xpack.monitoring.breadcrumbs.apmLabel', {
- defaultMessage: 'APM server',
- });
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/apm', apmLabel));
- breadcrumbs.push(
- createCrumb(
- '#/apm/instances',
- i18n.translate('xpack.monitoring.breadcrumbs.apm.instancesLabel', {
- defaultMessage: 'Instances',
- })
- )
- );
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else {
- // don't link to Overview when we're possibly on Overview or its sibling tabs
- breadcrumbs.push(createCrumb(null, apmLabel));
- }
- return breadcrumbs;
-}
-
-export function breadcrumbsProvider() {
- return function createBreadcrumbs(clusterName, mainInstance) {
- const homeCrumb = i18n.translate('xpack.monitoring.breadcrumbs.clustersLabel', {
- defaultMessage: 'Clusters',
- });
-
- let breadcrumbs = [createCrumb('#/home', homeCrumb, 'breadcrumbClusters', true)];
-
- if (!mainInstance.inOverview && clusterName) {
- breadcrumbs.push(createCrumb('#/overview', clusterName));
- }
-
- if (mainInstance.inElasticsearch) {
- breadcrumbs = breadcrumbs.concat(getElasticsearchBreadcrumbs(mainInstance));
- }
- if (mainInstance.inKibana) {
- breadcrumbs = breadcrumbs.concat(getKibanaBreadcrumbs(mainInstance));
- }
- if (mainInstance.inLogstash) {
- breadcrumbs = breadcrumbs.concat(getLogstashBreadcrumbs(mainInstance));
- }
- if (mainInstance.inBeats) {
- breadcrumbs = breadcrumbs.concat(getBeatsBreadcrumbs(mainInstance));
- }
- if (mainInstance.inApm) {
- breadcrumbs = breadcrumbs.concat(getApmBreadcrumbs(mainInstance));
- }
-
- Legacy.shims.breadcrumbs.set(
- breadcrumbs.map((b) => ({
- text: b.label,
- href: b.url,
- 'data-test-subj': b.testSubj,
- ignoreGlobalState: b.ignoreGlobalState,
- }))
- );
-
- return breadcrumbs;
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/breadcrumbs.test.js b/x-pack/plugins/monitoring/public/services/breadcrumbs.test.js
deleted file mode 100644
index 0af5d59e54555..0000000000000
--- a/x-pack/plugins/monitoring/public/services/breadcrumbs.test.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import expect from '@kbn/expect';
-import { breadcrumbsProvider } from './breadcrumbs';
-import { MonitoringMainController } from '../directives/main';
-import { Legacy } from '../legacy_shims';
-
-describe('Monitoring Breadcrumbs Service', () => {
- const core = {
- notifications: {},
- application: {},
- i18n: {},
- chrome: {},
- };
- const data = {
- query: {
- timefilter: {
- timefilter: {
- isTimeRangeSelectorEnabled: () => true,
- getTime: () => 1,
- getRefreshInterval: () => 1,
- },
- },
- },
- };
- const isCloud = false;
- const triggersActionsUi = {};
-
- beforeAll(() => {
- Legacy.init({
- core,
- data,
- isCloud,
- triggersActionsUi,
- });
- });
-
- it('in Cluster Alerts', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- name: 'alerts',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- ]);
- });
-
- it('in Cluster Overview', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- name: 'overview',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- ]);
- });
-
- it('in ES Node - Advanced', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- product: 'elasticsearch',
- name: 'nodes',
- instance: 'es-node-name-01',
- resolver: 'es-node-resolver-01',
- page: 'advanced',
- tabIconClass: 'fa star',
- tabIconLabel: 'Master Node',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- { url: '#/elasticsearch', label: 'Elasticsearch', ignoreGlobalState: false },
- {
- url: '#/elasticsearch/nodes',
- label: 'Nodes',
- testSubj: 'breadcrumbEsNodes',
- ignoreGlobalState: false,
- },
- { url: null, label: 'es-node-name-01', ignoreGlobalState: false },
- ]);
- });
-
- it('in Kibana Overview', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- product: 'kibana',
- name: 'overview',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- { url: null, label: 'Kibana', ignoreGlobalState: false },
- ]);
- });
-
- /**
- *
- */
- it('in Logstash Listing', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- product: 'logstash',
- name: 'listing',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- { url: null, label: 'Logstash', ignoreGlobalState: false },
- ]);
- });
-
- /**
- *
- */
- it('in Logstash Pipeline Viewer', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- product: 'logstash',
- page: 'pipeline',
- pipelineId: 'main',
- pipelineHash: '42ee890af9...',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- { url: '#/logstash', label: 'Logstash', ignoreGlobalState: false },
- { url: '#/logstash/pipelines', label: 'Pipelines', ignoreGlobalState: false },
- ]);
- });
-});
diff --git a/x-pack/plugins/monitoring/public/services/clusters.js b/x-pack/plugins/monitoring/public/services/clusters.js
deleted file mode 100644
index b19d0ea56765f..0000000000000
--- a/x-pack/plugins/monitoring/public/services/clusters.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../lib/ajax_error_handler';
-import { Legacy } from '../legacy_shims';
-import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../common/constants';
-
-function formatClusters(clusters) {
- return clusters.map(formatCluster);
-}
-
-function formatCluster(cluster) {
- if (cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID) {
- cluster.cluster_name = 'Standalone Cluster';
- }
- return cluster;
-}
-
-export function monitoringClustersProvider($injector) {
- return async (clusterUuid, ccs, codePaths) => {
- const { min, max } = Legacy.shims.timefilter.getBounds();
-
- // append clusterUuid if the parameter is given
- let url = '../api/monitoring/v1/clusters';
- if (clusterUuid) {
- url += `/${clusterUuid}`;
- }
-
- const $http = $injector.get('$http');
-
- async function getClusters() {
- try {
- const response = await $http.post(
- url,
- {
- ccs,
- timeRange: {
- min: min.toISOString(),
- max: max.toISOString(),
- },
- codePaths,
- },
- { headers: { 'kbn-system-request': 'true' } }
- );
- return formatClusters(response.data);
- } catch (err) {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- }
- }
-
- return await getClusters();
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js b/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js
deleted file mode 100644
index 438c5ab83f5e3..0000000000000
--- a/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-import { ajaxErrorHandlersProvider } from '../lib/ajax_error_handler';
-import { showAlertsToast } from '../alerts/lib/alerts_toast';
-
-export function enableAlertsModalProvider($http, $window, $injector) {
- function shouldShowAlertsModal(alerts) {
- const modalHasBeenShown = $window.sessionStorage.getItem('ALERTS_MODAL_HAS_BEEN_SHOWN');
- const decisionMade = $window.localStorage.getItem('ALERTS_MODAL_DECISION_MADE');
-
- if (Object.keys(alerts).length > 0) {
- $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true);
- return false;
- } else if (!modalHasBeenShown && !decisionMade) {
- return true;
- }
-
- return false;
- }
-
- async function enableAlerts() {
- try {
- const { data } = await $http.post('../api/monitoring/v1/alerts/enable', {});
- $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true);
- showAlertsToast(data);
- } catch (err) {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- }
- }
-
- function notAskAgain() {
- $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true);
- }
-
- function hideModalForSession() {
- $window.sessionStorage.setItem('ALERTS_MODAL_HAS_BEEN_SHOWN', true);
- }
-
- return {
- shouldShowAlertsModal,
- enableAlerts,
- notAskAgain,
- hideModalForSession,
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/executor.js b/x-pack/plugins/monitoring/public/services/executor.js
deleted file mode 100644
index 60b2c171eac32..0000000000000
--- a/x-pack/plugins/monitoring/public/services/executor.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { Legacy } from '../legacy_shims';
-import { subscribeWithScope } from '../angular/helpers/utils';
-import { Subscription } from 'rxjs';
-
-export function executorProvider($timeout, $q) {
- const queue = [];
- const subscriptions = new Subscription();
- let executionTimer;
- let ignorePaused = false;
-
- /**
- * Resets the timer to start again
- * @returns {void}
- */
- function reset() {
- cancel();
- start();
- }
-
- function killTimer() {
- if (executionTimer) {
- $timeout.cancel(executionTimer);
- }
- }
-
- /**
- * Cancels the execution timer
- * @returns {void}
- */
- function cancel() {
- killTimer();
- }
-
- /**
- * Registers a service with the executor
- * @param {object} service The service to register
- * @returns {void}
- */
- function register(service) {
- queue.push(service);
- }
-
- /**
- * Stops the executor and empties the service queue
- * @returns {void}
- */
- function destroy() {
- subscriptions.unsubscribe();
- cancel();
- ignorePaused = false;
- queue.splice(0, queue.length);
- }
-
- /**
- * Runs the queue (all at once)
- * @returns {Promise} a promise of all the services
- */
- function run() {
- const noop = () => $q.resolve();
- return $q
- .all(
- queue.map((service) => {
- return service
- .execute()
- .then(service.handleResponse || noop)
- .catch(service.handleError || noop);
- })
- )
- .finally(reset);
- }
-
- function reFetch() {
- cancel();
- run();
- }
-
- function killIfPaused() {
- if (Legacy.shims.timefilter.getRefreshInterval().pause) {
- killTimer();
- }
- }
-
- /**
- * Starts the executor service if the timefilter is not paused
- * @returns {void}
- */
- function start() {
- const timefilter = Legacy.shims.timefilter;
- if (
- (ignorePaused || timefilter.getRefreshInterval().pause === false) &&
- timefilter.getRefreshInterval().value > 0
- ) {
- executionTimer = $timeout(run, timefilter.getRefreshInterval().value);
- }
- }
-
- /**
- * Expose the methods
- */
- return {
- register,
- start($scope) {
- $scope.$applyAsync(() => {
- const timefilter = Legacy.shims.timefilter;
- subscriptions.add(
- subscribeWithScope($scope, timefilter.getFetch$(), {
- next: reFetch,
- })
- );
- subscriptions.add(
- subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), {
- next: killIfPaused,
- })
- );
- start();
- });
- },
- run,
- destroy,
- reset,
- cancel,
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/features.js b/x-pack/plugins/monitoring/public/services/features.js
deleted file mode 100644
index 34564f79c9247..0000000000000
--- a/x-pack/plugins/monitoring/public/services/features.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { has, isUndefined } from 'lodash';
-
-export function featuresProvider($window) {
- function getData() {
- let returnData = {};
- const monitoringData = $window.localStorage.getItem('xpack.monitoring.data');
-
- try {
- returnData = (monitoringData && JSON.parse(monitoringData)) || {};
- } catch (e) {
- console.error('Monitoring UI: error parsing locally stored monitoring data', e);
- }
-
- return returnData;
- }
-
- function update(featureName, value) {
- const monitoringDataObj = getData();
- monitoringDataObj[featureName] = value;
- $window.localStorage.setItem('xpack.monitoring.data', JSON.stringify(monitoringDataObj));
- }
-
- function isEnabled(featureName, defaultSetting) {
- const monitoringDataObj = getData();
- if (has(monitoringDataObj, featureName)) {
- return monitoringDataObj[featureName];
- }
-
- if (isUndefined(defaultSetting)) {
- return false;
- }
-
- return defaultSetting;
- }
-
- return {
- isEnabled,
- update,
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/license.js b/x-pack/plugins/monitoring/public/services/license.js
deleted file mode 100644
index cab5ad01cf58a..0000000000000
--- a/x-pack/plugins/monitoring/public/services/license.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { includes } from 'lodash';
-import { ML_SUPPORTED_LICENSES } from '../../common/constants';
-
-export function licenseProvider() {
- return new (class LicenseService {
- constructor() {
- // do not initialize with usable state
- this.license = {
- type: null,
- expiry_date_in_millis: -Infinity,
- };
- }
-
- // we're required to call this initially
- setLicense(license) {
- this.license = license;
- }
-
- isBasic() {
- return this.license.type === 'basic';
- }
-
- mlIsSupported() {
- return includes(ML_SUPPORTED_LICENSES, this.license.type);
- }
-
- doesExpire() {
- const { expiry_date_in_millis: expiryDateInMillis } = this.license;
- return expiryDateInMillis !== undefined;
- }
-
- isActive() {
- const { expiry_date_in_millis: expiryDateInMillis } = this.license;
- return new Date().getTime() < expiryDateInMillis;
- }
-
- isExpired() {
- if (this.doesExpire()) {
- const { expiry_date_in_millis: expiryDateInMillis } = this.license;
- return new Date().getTime() >= expiryDateInMillis;
- }
- return false;
- }
- })();
-}
diff --git a/x-pack/plugins/monitoring/public/services/title.js b/x-pack/plugins/monitoring/public/services/title.js
deleted file mode 100644
index e12d4936584fa..0000000000000
--- a/x-pack/plugins/monitoring/public/services/title.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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { get } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { Legacy } from '../legacy_shims';
-
-export function titleProvider($rootScope) {
- return function changeTitle(cluster, suffix) {
- let clusterName = get(cluster, 'cluster_name');
- clusterName = clusterName ? `- ${clusterName}` : '';
- suffix = suffix ? `- ${suffix}` : '';
- $rootScope.$applyAsync(() => {
- Legacy.shims.docTitle.change(
- i18n.translate('xpack.monitoring.stackMonitoringDocTitle', {
- defaultMessage: 'Stack Monitoring {clusterName} {suffix}',
- values: { clusterName, suffix },
- })
- );
- });
- };
-}
diff --git a/x-pack/plugins/monitoring/public/views/access_denied/index.html b/x-pack/plugins/monitoring/public/views/access_denied/index.html
deleted file mode 100644
index 24863559212f7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/access_denied/index.html
+++ /dev/null
@@ -1,44 +0,0 @@
-
diff --git a/x-pack/plugins/monitoring/public/views/access_denied/index.js b/x-pack/plugins/monitoring/public/views/access_denied/index.js
deleted file mode 100644
index e52df61dd8966..0000000000000
--- a/x-pack/plugins/monitoring/public/views/access_denied/index.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { uiRoutes } from '../../angular/helpers/routes';
-import template from './index.html';
-
-const tryPrivilege = ($http) => {
- return $http
- .get('../api/monitoring/v1/check_access')
- .then(() => window.history.replaceState(null, null, '#/home'))
- .catch(() => true);
-};
-
-uiRoutes.when('/access-denied', {
- template,
- resolve: {
- /*
- * The user may have been granted privileges in between leaving Monitoring
- * and before coming back to Monitoring. That means, they just be on this
- * page because Kibana remembers the "last app URL". We check for the
- * privilege one time up front (doing it in the resolve makes it happen
- * before the template renders), and then keep retrying every 5 seconds.
- */
- initialCheck($http) {
- return tryPrivilege($http);
- },
- },
- controllerAs: 'accessDenied',
- controller: function ($scope, $injector) {
- const $http = $injector.get('$http');
- const $interval = $injector.get('$interval');
-
- // The template's "Back to Kibana" button click handler
- this.goToKibanaURL = '/app/home';
-
- // keep trying to load data in the background
- const accessPoller = $interval(() => tryPrivilege($http), 5 * 1000); // every 5 seconds
- $scope.$on('$destroy', () => $interval.cancel(accessPoller));
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/all.js b/x-pack/plugins/monitoring/public/views/all.js
deleted file mode 100644
index 3af0c85d95687..0000000000000
--- a/x-pack/plugins/monitoring/public/views/all.js
+++ /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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import './no_data';
-import './access_denied';
-import './license';
-import './cluster/listing';
-import './cluster/overview';
-import './elasticsearch/overview';
-import './elasticsearch/indices';
-import './elasticsearch/index';
-import './elasticsearch/index/advanced';
-import './elasticsearch/nodes';
-import './elasticsearch/node';
-import './elasticsearch/node/advanced';
-import './elasticsearch/ccr';
-import './elasticsearch/ccr/shard';
-import './elasticsearch/ml_jobs';
-import './kibana/overview';
-import './kibana/instances';
-import './kibana/instance';
-import './logstash/overview';
-import './logstash/nodes';
-import './logstash/node';
-import './logstash/node/advanced';
-import './logstash/node/pipelines';
-import './logstash/pipelines';
-import './logstash/pipeline';
-import './beats/overview';
-import './beats/listing';
-import './beats/beat';
-import './apm/overview';
-import './apm/instances';
-import './apm/instance';
-import './loading';
diff --git a/x-pack/plugins/monitoring/public/views/apm/instance/index.html b/x-pack/plugins/monitoring/public/views/apm/instance/index.html
deleted file mode 100644
index 79579990eb649..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/instance/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/apm/instance/index.js b/x-pack/plugins/monitoring/public/views/apm/instance/index.js
deleted file mode 100644
index 0d733036bb266..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/instance/index.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find, get } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { ApmServerInstance } from '../../../components/apm/instance';
-import { CODE_PATH_APM } from '../../../../common/constants';
-
-uiRoutes.when('/apm/instances/:uuid', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_APM] });
- },
- },
-
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const title = $injector.get('title');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
- super({
- title: i18n.translate('xpack.monitoring.apm.instance.routeTitle', {
- defaultMessage: '{apm} - Instance',
- values: {
- apm: 'APM server',
- },
- }),
- telemetryPageViewTitle: 'apm_server_instance',
- api: `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/apm/${$route.current.params.uuid}`,
- defaultData: {},
- reactNodeId: 'apmInstanceReact',
- $scope,
- $injector,
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.setPageTitle(
- i18n.translate('xpack.monitoring.apm.instance.pageTitle', {
- defaultMessage: 'APM server instance: {instanceName}',
- values: {
- instanceName: get(data, 'apmSummary.name'),
- },
- })
- );
- title($scope.cluster, `APM server - ${get(data, 'apmSummary.name')}`);
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/apm/instances/index.html b/x-pack/plugins/monitoring/public/views/apm/instances/index.html
deleted file mode 100644
index fd8029e277d78..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/instances/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/apm/instances/index.js b/x-pack/plugins/monitoring/public/views/apm/instances/index.js
deleted file mode 100644
index f9747ec176e86..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/instances/index.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { ApmServerInstances } from '../../../components/apm/instances';
-import { MonitoringViewBaseEuiTableController } from '../..';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import { APM_SYSTEM_ID, CODE_PATH_APM } from '../../../../common/constants';
-
-uiRoutes.when('/apm/instances', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_APM] });
- },
- },
- controller: class extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: i18n.translate('xpack.monitoring.apm.instances.routeTitle', {
- defaultMessage: '{apm} - Instances',
- values: {
- apm: 'APM server',
- },
- }),
- pageTitle: i18n.translate('xpack.monitoring.apm.instances.pageTitle', {
- defaultMessage: 'APM server instances',
- }),
- storageKey: 'apm.instances',
- api: `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/apm/instances`,
- defaultData: {},
- reactNodeId: 'apmInstancesReact',
- $scope,
- $injector,
- });
-
- this.scope = $scope;
- this.injector = $injector;
- this.onTableChangeRender = this.renderComponent;
-
- $scope.$watch(
- () => this.data,
- () => this.renderComponent()
- );
- }
-
- renderComponent() {
- const { pagination, sorting, onTableChange } = this;
-
- const component = (
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- this.renderReact(component);
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/apm/overview/index.html b/x-pack/plugins/monitoring/public/views/apm/overview/index.html
deleted file mode 100644
index 0cf804e377476..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/overview/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/apm/overview/index.js b/x-pack/plugins/monitoring/public/views/apm/overview/index.js
deleted file mode 100644
index bef17bf4a2fad..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/overview/index.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { ApmOverview } from '../../../components/apm/overview';
-import { CODE_PATH_APM } from '../../../../common/constants';
-
-uiRoutes.when('/apm', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_APM] });
- },
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: i18n.translate('xpack.monitoring.apm.overview.routeTitle', {
- defaultMessage: 'APM server',
- }),
- pageTitle: i18n.translate('xpack.monitoring.apm.overview.pageTitle', {
- defaultMessage: 'APM server overview',
- }),
- api: `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/apm`,
- defaultData: {},
- reactNodeId: 'apmOverviewReact',
- $scope,
- $injector,
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/base_controller.js b/x-pack/plugins/monitoring/public/views/base_controller.js
deleted file mode 100644
index dd9898a6e195c..0000000000000
--- a/x-pack/plugins/monitoring/public/views/base_controller.js
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import moment from 'moment';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { getPageData } from '../lib/get_page_data';
-import { PageLoading } from '../components';
-import { Legacy } from '../legacy_shims';
-import { PromiseWithCancel } from '../../common/cancel_promise';
-import { SetupModeFeature } from '../../common/enums';
-import { updateSetupModeData, isSetupModeFeatureEnabled } from '../lib/setup_mode';
-import { AlertsContext } from '../alerts/context';
-import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
-import { AlertsDropdown } from '../alerts/alerts_dropdown';
-import { HeaderMenuPortal } from '../../../observability/public';
-
-/**
- * Given a timezone, this function will calculate the offset in milliseconds
- * from UTC time.
- *
- * @param {string} timezone
- */
-const getOffsetInMS = (timezone) => {
- if (timezone === 'Browser') {
- return 0;
- }
- const offsetInMinutes = moment.tz(timezone).utcOffset();
- const offsetInMS = offsetInMinutes * 1 * 60 * 1000;
- return offsetInMS;
-};
-
-/**
- * Class to manage common instantiation behaviors in a view controller
- *
- * This is expected to be extended, and behavior enabled using super();
- *
- * Example:
- * uiRoutes.when('/myRoute', {
- * template: importedTemplate,
- * controllerAs: 'myView',
- * controller: class MyView extends MonitoringViewBaseController {
- * constructor($injector, $scope) {
- * super({
- * title: 'Hello World',
- * api: '../api/v1/monitoring/foo/bar',
- * defaultData,
- * reactNodeId,
- * $scope,
- * $injector,
- * options: {
- * enableTimeFilter: false // this will have just the page auto-refresh control show
- * }
- * });
- * }
- * }
- * });
- */
-export class MonitoringViewBaseController {
- /**
- * Create a view controller
- * @param {String} title - Title of the page
- * @param {String} api - Back-end API endpoint to poll for getting the page
- * data using POST and time range data in the body. Whenever possible, use
- * this method for data polling rather than supply the getPageData param.
- * @param {Function} apiUrlFn - Function that returns a string for the back-end
- * API endpoint, in case the string has dynamic query parameters (e.g.
- * show_system_indices) rather than supply the getPageData param.
- * @param {Function} getPageData - (Optional) Function to fetch page data, if
- * simply passing the API string isn't workable.
- * @param {Object} defaultData - Initial model data to populate
- * @param {String} reactNodeId - DOM element ID of the element for mounting
- * the view's main React component
- * @param {Service} $injector - Angular dependency injection service
- * @param {Service} $scope - Angular view data binding service
- * @param {Boolean} options.enableTimeFilter - Whether to show the time filter
- * @param {Boolean} options.enableAutoRefresh - Whether to show the auto
- * refresh control
- */
- constructor({
- title = '',
- pageTitle = '',
- api = '',
- apiUrlFn,
- getPageData: _getPageData = getPageData,
- defaultData,
- reactNodeId = null, // WIP: https://github.com/elastic/x-pack-kibana/issues/5198
- $scope,
- $injector,
- options = {},
- alerts = { shouldFetch: false, options: {} },
- fetchDataImmediately = true,
- telemetryPageViewTitle = '',
- }) {
- const titleService = $injector.get('title');
- const $executor = $injector.get('$executor');
- const $window = $injector.get('$window');
- const config = $injector.get('config');
-
- titleService($scope.cluster, title);
-
- $scope.pageTitle = pageTitle;
- this.setPageTitle = (title) => ($scope.pageTitle = title);
- $scope.pageData = this.data = { ...defaultData };
- this._isDataInitialized = false;
- this.reactNodeId = reactNodeId;
- this.telemetryPageViewTitle = telemetryPageViewTitle || title;
-
- let deferTimer;
- let zoomInLevel = 0;
-
- const popstateHandler = () => zoomInLevel > 0 && --zoomInLevel;
- const removePopstateHandler = () => $window.removeEventListener('popstate', popstateHandler);
- const addPopstateHandler = () => $window.addEventListener('popstate', popstateHandler);
-
- this.zoomInfo = {
- zoomOutHandler: () => $window.history.back(),
- showZoomOutBtn: () => zoomInLevel > 0,
- };
-
- const { enableTimeFilter = true, enableAutoRefresh = true } = options;
-
- async function fetchAlerts() {
- const globalState = $injector.get('globalState');
- const bounds = Legacy.shims.timefilter.getBounds();
- const min = bounds.min?.valueOf();
- const max = bounds.max?.valueOf();
- const options = alerts.options || {};
- try {
- return await Legacy.shims.http.post(
- `/api/monitoring/v1/alert/${globalState.cluster_uuid}/status`,
- {
- body: JSON.stringify({
- alertTypeIds: options.alertTypeIds,
- filters: options.filters,
- timeRange: {
- min,
- max,
- },
- }),
- }
- );
- } catch (err) {
- Legacy.shims.toastNotifications.addDanger({
- title: 'Error fetching alert status',
- text: err.message,
- });
- }
- }
-
- this.updateData = () => {
- if (this.updateDataPromise) {
- // Do not sent another request if one is inflight
- // See https://github.com/elastic/kibana/issues/24082
- this.updateDataPromise.cancel();
- this.updateDataPromise = null;
- }
- const _api = apiUrlFn ? apiUrlFn() : api;
- const promises = [_getPageData($injector, _api, this.getPaginationRouteOptions())];
- if (alerts.shouldFetch) {
- promises.push(fetchAlerts());
- }
- if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) {
- promises.push(updateSetupModeData());
- }
- this.updateDataPromise = new PromiseWithCancel(Promise.allSettled(promises));
- return this.updateDataPromise.promise().then(([pageData, alerts]) => {
- $scope.$apply(() => {
- this._isDataInitialized = true; // render will replace loading screen with the react component
- $scope.pageData = this.data = pageData.value; // update the view's data with the fetch result
- $scope.alerts = this.alerts = alerts && alerts.value ? alerts.value : {};
- });
- });
- };
-
- $scope.$applyAsync(() => {
- const timefilter = Legacy.shims.timefilter;
-
- if (enableTimeFilter === false) {
- timefilter.disableTimeRangeSelector();
- } else {
- timefilter.enableTimeRangeSelector();
- }
-
- if (enableAutoRefresh === false) {
- timefilter.disableAutoRefreshSelector();
- } else {
- timefilter.enableAutoRefreshSelector();
- }
-
- // needed for chart pages
- this.onBrush = ({ xaxis }) => {
- removePopstateHandler();
- const { to, from } = xaxis;
- const timezone = config.get('dateFormat:tz');
- const offset = getOffsetInMS(timezone);
- timefilter.setTime({
- from: moment(from - offset),
- to: moment(to - offset),
- mode: 'absolute',
- });
- $executor.cancel();
- $executor.run();
- ++zoomInLevel;
- clearTimeout(deferTimer);
- /*
- Needed to defer 'popstate' event, so it does not fire immediately after it's added.
- 10ms is to make sure the event is not added with the same code digest
- */
- deferTimer = setTimeout(() => addPopstateHandler(), 10);
- };
-
- // Render loading state
- this.renderReact(null, true);
- fetchDataImmediately && this.updateData();
- });
-
- $executor.register({
- execute: () => this.updateData(),
- });
- $executor.start($scope);
- $scope.$on('$destroy', () => {
- clearTimeout(deferTimer);
- removePopstateHandler();
- const targetElement = document.getElementById(this.reactNodeId);
- if (targetElement) {
- // WIP https://github.com/elastic/x-pack-kibana/issues/5198
- unmountComponentAtNode(targetElement);
- }
- $executor.destroy();
- });
-
- this.setTitle = (title) => titleService($scope.cluster, title);
- }
-
- renderReact(component, trackPageView = false) {
- const renderElement = document.getElementById(this.reactNodeId);
- if (!renderElement) {
- console.warn(`"#${this.reactNodeId}" element has not been added to the DOM yet`);
- return;
- }
- const I18nContext = Legacy.shims.I18nContext;
- const wrappedComponent = (
-
-
-
-
-
-
- {!this._isDataInitialized ? (
-
- ) : (
- component
- )}
-
-
-
- );
- render(wrappedComponent, renderElement);
- }
-
- getPaginationRouteOptions() {
- return {};
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/base_eui_table_controller.js b/x-pack/plugins/monitoring/public/views/base_eui_table_controller.js
deleted file mode 100644
index 0520ce3f10de5..0000000000000
--- a/x-pack/plugins/monitoring/public/views/base_eui_table_controller.js
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { MonitoringViewBaseController } from './';
-import { euiTableStorageGetter, euiTableStorageSetter } from '../components/table';
-import { EUI_SORT_ASCENDING } from '../../common/constants';
-
-const PAGE_SIZE_OPTIONS = [5, 10, 20, 50];
-
-/**
- * Class to manage common instantiation behaviors in a view controller
- * And add persistent state to a table:
- * - page index: in table pagination, which page are we looking at
- * - filter text: what filter was entered in the table's filter bar
- * - sortKey: which column field of table data is used for sorting
- * - sortOrder: is sorting ordered ascending or descending
- *
- * This is expected to be extended, and behavior enabled using super();
- */
-export class MonitoringViewBaseEuiTableController extends MonitoringViewBaseController {
- /**
- * Create a table view controller
- * - used by parent class:
- * @param {String} title - Title of the page
- * @param {Function} getPageData - Function to fetch page data
- * @param {Service} $injector - Angular dependency injection service
- * @param {Service} $scope - Angular view data binding service
- * @param {Boolean} options.enableTimeFilter - Whether to show the time filter
- * @param {Boolean} options.enableAutoRefresh - Whether to show the auto refresh control
- * - specific to this class:
- * @param {String} storageKey - the namespace that will be used to keep the state data in the Monitoring localStorage object
- *
- */
- constructor(args) {
- super(args);
- const { storageKey, $injector } = args;
- const storage = $injector.get('localStorage');
-
- const getLocalStorageData = euiTableStorageGetter(storageKey);
- const setLocalStorageData = euiTableStorageSetter(storageKey);
- const { page, sort } = getLocalStorageData(storage);
-
- this.pagination = {
- pageSize: 20,
- initialPageSize: 20,
- pageIndex: 0,
- initialPageIndex: 0,
- pageSizeOptions: PAGE_SIZE_OPTIONS,
- };
-
- if (page) {
- if (!PAGE_SIZE_OPTIONS.includes(page.size)) {
- page.size = 20;
- }
- this.setPagination(page);
- }
-
- this.setSorting(sort);
-
- this.onTableChange = ({ page, sort }) => {
- this.setPagination(page);
- this.setSorting({ sort });
- setLocalStorageData(storage, {
- page,
- sort: {
- sort,
- },
- });
- if (this.onTableChangeRender) {
- this.onTableChangeRender();
- }
- };
-
- // For pages where we do not fetch immediately, we want to fetch after pagination is applied
- args.fetchDataImmediately === false && this.updateData();
- }
-
- setPagination(page) {
- this.pagination = {
- initialPageSize: page.size,
- pageSize: page.size,
- initialPageIndex: page.index,
- pageIndex: page.index,
- pageSizeOptions: PAGE_SIZE_OPTIONS,
- };
- }
-
- setSorting(sort) {
- this.sorting = sort || { sort: {} };
-
- if (!this.sorting.sort.field) {
- this.sorting.sort.field = 'name';
- }
- if (!this.sorting.sort.direction) {
- this.sorting.sort.direction = EUI_SORT_ASCENDING;
- }
- }
-
- setQueryText(queryText) {
- this.queryText = queryText;
- }
-
- getPaginationRouteOptions() {
- if (!this.pagination || !this.sorting) {
- return {};
- }
-
- return {
- pagination: {
- size: this.pagination.pageSize,
- index: this.pagination.pageIndex,
- },
- ...this.sorting,
- queryText: this.queryText,
- };
- }
-
- getPaginationTableProps(pagination) {
- return {
- sorting: this.sorting,
- pagination: pagination,
- onTableChange: this.onTableChange,
- fetchMoreData: async ({ page, sort, queryText }) => {
- this.setPagination(page);
- this.setSorting(sort);
- this.setQueryText(queryText);
- await this.updateData();
- },
- };
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/base_table_controller.js b/x-pack/plugins/monitoring/public/views/base_table_controller.js
deleted file mode 100644
index a066a91e48c8b..0000000000000
--- a/x-pack/plugins/monitoring/public/views/base_table_controller.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { MonitoringViewBaseController } from './';
-import { tableStorageGetter, tableStorageSetter } from '../components/table';
-
-/**
- * Class to manage common instantiation behaviors in a view controller
- * And add persistent state to a table:
- * - page index: in table pagination, which page are we looking at
- * - filter text: what filter was entered in the table's filter bar
- * - sortKey: which column field of table data is used for sorting
- * - sortOrder: is sorting ordered ascending or descending
- *
- * This is expected to be extended, and behavior enabled using super();
- */
-export class MonitoringViewBaseTableController extends MonitoringViewBaseController {
- /**
- * Create a table view controller
- * - used by parent class:
- * @param {String} title - Title of the page
- * @param {Function} getPageData - Function to fetch page data
- * @param {Service} $injector - Angular dependency injection service
- * @param {Service} $scope - Angular view data binding service
- * @param {Boolean} options.enableTimeFilter - Whether to show the time filter
- * @param {Boolean} options.enableAutoRefresh - Whether to show the auto refresh control
- * - specific to this class:
- * @param {String} storageKey - the namespace that will be used to keep the state data in the Monitoring localStorage object
- *
- */
- constructor(args) {
- super(args);
- const { storageKey, $injector } = args;
- const storage = $injector.get('localStorage');
-
- const getLocalStorageData = tableStorageGetter(storageKey);
- const setLocalStorageData = tableStorageSetter(storageKey);
- const { pageIndex, filterText, sortKey, sortOrder } = getLocalStorageData(storage);
-
- this.pageIndex = pageIndex;
- this.filterText = filterText;
- this.sortKey = sortKey;
- this.sortOrder = sortOrder;
-
- this.onNewState = (newState) => {
- setLocalStorageData(storage, newState);
- };
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js b/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js
deleted file mode 100644
index 7f87fa413d8ca..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/beats/beat/${$route.current.params.beatUuid}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/index.html b/x-pack/plugins/monitoring/public/views/beats/beat/index.html
deleted file mode 100644
index 6ae727e31cbeb..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/beat/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/index.js b/x-pack/plugins/monitoring/public/views/beats/beat/index.js
deleted file mode 100644
index f1a171a19cd89..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/beat/index.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { CODE_PATH_BEATS } from '../../../../common/constants';
-import { Beat } from '../../../components/beats/beat';
-
-uiRoutes.when('/beats/beat/:beatUuid', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_BEATS] });
- },
- pageData: getPageData,
- },
- controllerAs: 'beat',
- controller: class BeatDetail extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- // breadcrumbs + page title
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- const pageData = $route.current.locals.pageData;
- super({
- title: i18n.translate('xpack.monitoring.beats.instance.routeTitle', {
- defaultMessage: 'Beats - {instanceName} - Overview',
- values: {
- instanceName: pageData.summary.name,
- },
- }),
- pageTitle: i18n.translate('xpack.monitoring.beats.instance.pageTitle', {
- defaultMessage: 'Beat instance: {beatName}',
- values: {
- beatName: pageData.summary.name,
- },
- }),
- telemetryPageViewTitle: 'beats_instance',
- getPageData,
- $scope,
- $injector,
- reactNodeId: 'monitoringBeatsInstanceApp',
- });
-
- this.data = pageData;
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js b/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js
deleted file mode 100644
index 99366f05f3ad4..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/beats/beats`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/index.html b/x-pack/plugins/monitoring/public/views/beats/listing/index.html
deleted file mode 100644
index 0ce66a6848dfd..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/listing/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/index.js b/x-pack/plugins/monitoring/public/views/beats/listing/index.js
deleted file mode 100644
index eae74d8a08b9e..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/listing/index.js
+++ /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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import React from 'react';
-import { Listing } from '../../../components/beats/listing/listing';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import { CODE_PATH_BEATS, BEATS_SYSTEM_ID } from '../../../../common/constants';
-
-uiRoutes.when('/beats/beats', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_BEATS] });
- },
- pageData: getPageData,
- },
- controllerAs: 'beats',
- controller: class BeatsListing extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- // breadcrumbs + page title
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: i18n.translate('xpack.monitoring.beats.routeTitle', { defaultMessage: 'Beats' }),
- pageTitle: i18n.translate('xpack.monitoring.beats.listing.pageTitle', {
- defaultMessage: 'Beats listing',
- }),
- telemetryPageViewTitle: 'beats_listing',
- storageKey: 'beats.beats',
- getPageData,
- reactNodeId: 'monitoringBeatsInstancesApp',
- $scope,
- $injector,
- });
-
- this.data = $route.current.locals.pageData;
- this.scope = $scope;
- this.injector = $injector;
- this.onTableChangeRender = this.renderComponent;
-
- $scope.$watch(
- () => this.data,
- () => this.renderComponent()
- );
- }
-
- renderComponent() {
- const { sorting, pagination, onTableChange } = this.scope.beats;
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js b/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js
deleted file mode 100644
index 497ed8cdb0e74..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/beats`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/beats/overview/index.html b/x-pack/plugins/monitoring/public/views/beats/overview/index.html
deleted file mode 100644
index 0b827c96f68fd..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/overview/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/beats/overview/index.js b/x-pack/plugins/monitoring/public/views/beats/overview/index.js
deleted file mode 100644
index 475a63d440c76..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/overview/index.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { CODE_PATH_BEATS } from '../../../../common/constants';
-import { BeatsOverview } from '../../../components/beats/overview';
-
-uiRoutes.when('/beats', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_BEATS] });
- },
- pageData: getPageData,
- },
- controllerAs: 'beats',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- // breadcrumbs + page title
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: i18n.translate('xpack.monitoring.beats.overview.routeTitle', {
- defaultMessage: 'Beats - Overview',
- }),
- pageTitle: i18n.translate('xpack.monitoring.beats.overview.pageTitle', {
- defaultMessage: 'Beats overview',
- }),
- getPageData,
- $scope,
- $injector,
- reactNodeId: 'monitoringBeatsOverviewApp',
- });
-
- this.data = $route.current.locals.pageData;
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/cluster/listing/index.html b/x-pack/plugins/monitoring/public/views/cluster/listing/index.html
deleted file mode 100644
index 713ca8fb1ffc9..0000000000000
--- a/x-pack/plugins/monitoring/public/views/cluster/listing/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
deleted file mode 100644
index 8b365292aeb13..0000000000000
--- a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import template from './index.html';
-import { Listing } from '../../../components/cluster/listing';
-import { CODE_PATH_ALL } from '../../../../common/constants';
-import { EnableAlertsModal } from '../../../alerts/enable_alerts_modal.tsx';
-
-const CODE_PATHS = [CODE_PATH_ALL];
-
-const getPageData = ($injector) => {
- const monitoringClusters = $injector.get('monitoringClusters');
- return monitoringClusters(undefined, undefined, CODE_PATHS);
-};
-
-const getAlerts = (clusters) => {
- return clusters.reduce((alerts, cluster) => ({ ...alerts, ...cluster.alerts.list }), {});
-};
-
-uiRoutes
- .when('/home', {
- template,
- resolve: {
- clusters: (Private) => {
- const routeInit = Private(routeInitProvider);
- return routeInit({
- codePaths: CODE_PATHS,
- fetchAllClusters: true,
- unsetGlobalState: true,
- }).then((clusters) => {
- if (!clusters || !clusters.length) {
- window.location.hash = '#/no-data';
- return Promise.reject();
- }
- if (clusters.length === 1) {
- // Bypass the cluster listing if there is just 1 cluster
- window.history.replaceState(null, null, '#/overview');
- return Promise.reject();
- }
- return clusters;
- });
- },
- },
- controllerAs: 'clusters',
- controller: class ClustersList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- storageKey: 'clusters',
- pageTitle: i18n.translate('xpack.monitoring.cluster.listing.pageTitle', {
- defaultMessage: 'Cluster listing',
- }),
- getPageData,
- $scope,
- $injector,
- reactNodeId: 'monitoringClusterListingApp',
- telemetryPageViewTitle: 'cluster_listing',
- });
-
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const storage = $injector.get('localStorage');
- const showLicenseExpiration = $injector.get('showLicenseExpiration');
-
- this.data = $route.current.locals.clusters;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
- <>
-
-
- >
- );
- }
- );
- }
- },
- })
- .otherwise({ redirectTo: '/loading' });
diff --git a/x-pack/plugins/monitoring/public/views/cluster/overview/index.html b/x-pack/plugins/monitoring/public/views/cluster/overview/index.html
deleted file mode 100644
index 1762ee1c2a282..0000000000000
--- a/x-pack/plugins/monitoring/public/views/cluster/overview/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
deleted file mode 100644
index 20e694ad8548f..0000000000000
--- a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { isEmpty } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { MonitoringViewBaseController } from '../../';
-import { Overview } from '../../../components/cluster/overview';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import { CODE_PATH_ALL } from '../../../../common/constants';
-import { EnableAlertsModal } from '../../../alerts/enable_alerts_modal.tsx';
-
-const CODE_PATHS = [CODE_PATH_ALL];
-
-uiRoutes.when('/overview', {
- template,
- resolve: {
- clusters(Private) {
- // checks license info of all monitored clusters for multi-cluster monitoring usage and capability
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: CODE_PATHS });
- },
- },
- controllerAs: 'monitoringClusterOverview',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const monitoringClusters = $injector.get('monitoringClusters');
- const globalState = $injector.get('globalState');
- const showLicenseExpiration = $injector.get('showLicenseExpiration');
-
- super({
- title: i18n.translate('xpack.monitoring.cluster.overviewTitle', {
- defaultMessage: 'Overview',
- }),
- pageTitle: i18n.translate('xpack.monitoring.cluster.overview.pageTitle', {
- defaultMessage: 'Cluster overview',
- }),
- defaultData: {},
- getPageData: async () => {
- const clusters = await monitoringClusters(
- globalState.cluster_uuid,
- globalState.ccs,
- CODE_PATHS
- );
- return clusters[0];
- },
- reactNodeId: 'monitoringClusterOverviewApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- },
- telemetryPageViewTitle: 'cluster_overview',
- });
-
- this.init = () => this.renderReact(null);
-
- $scope.$watch(
- () => this.data,
- async (data) => {
- if (isEmpty(data)) {
- return;
- }
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js
deleted file mode 100644
index 4f45038986332..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ccr`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html
deleted file mode 100644
index ca0b036ae39e1..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
deleted file mode 100644
index 91cc9c8782b22..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { getPageData } from './get_page_data';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Ccr } from '../../../components/elasticsearch/ccr';
-import { MonitoringViewBaseController } from '../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_CCR_READ_EXCEPTIONS,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../common/constants';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/ccr', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'elasticsearchCcr',
- controller: class ElasticsearchCcrController extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.ccr.routeTitle', {
- defaultMessage: 'Elasticsearch - Ccr',
- }),
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.ccr.pageTitle', {
- defaultMessage: 'Elasticsearch Ccr',
- }),
- reactNodeId: 'elasticsearchCcrReact',
- getPageData,
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_CCR_READ_EXCEPTIONS],
- },
- },
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js
deleted file mode 100644
index ca1aad39e3610..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { Legacy } from '../../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ccr/${$route.current.params.index}/shard/${$route.current.params.shardId}`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html
deleted file mode 100644
index 76469e5d9add5..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
deleted file mode 100644
index 767fb18685633..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { get } from 'lodash';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { getPageData } from './get_page_data';
-import { routeInitProvider } from '../../../../lib/route_init';
-import template from './index.html';
-import { MonitoringViewBaseController } from '../../../base_controller';
-import { CcrShard } from '../../../../components/elasticsearch/ccr_shard';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_CCR_READ_EXCEPTIONS,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../../common/constants';
-import { SetupModeRenderer } from '../../../../components/renderers';
-import { SetupModeContext } from '../../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'elasticsearchCcr',
- controller: class ElasticsearchCcrController extends MonitoringViewBaseController {
- constructor($injector, $scope, pageData) {
- const $route = $injector.get('$route');
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.routeTitle', {
- defaultMessage: 'Elasticsearch - Ccr - Shard',
- }),
- reactNodeId: 'elasticsearchCcrShardReact',
- getPageData,
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_CCR_READ_EXCEPTIONS],
- filters: [
- {
- shardId: $route.current.pathParams.shardId,
- },
- ],
- },
- },
- });
-
- $scope.instance = i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.instanceTitle', {
- defaultMessage: 'Index: {followerIndex} Shard: {shardId}',
- values: {
- followerIndex: get(pageData, 'stat.follower_index'),
- shardId: get(pageData, 'stat.shard_id'),
- },
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.pageTitle', {
- defaultMessage: 'Elasticsearch Ccr Shard - Index: {followerIndex} Shard: {shardId}',
- values: {
- followerIndex: get(
- pageData,
- 'stat.follower.index',
- get(pageData, 'stat.follower_index')
- ),
- shardId: get(
- pageData,
- 'stat.follower.shard.number',
- get(pageData, 'stat.shard_id')
- ),
- },
- })
- );
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.html
deleted file mode 100644
index 159376148d173..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js
deleted file mode 100644
index 9276527951612..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for Advanced Index Detail
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../../legacy_shims';
-import { AdvancedIndex } from '../../../../components/elasticsearch/index/advanced';
-import { MonitoringViewBaseController } from '../../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_LARGE_SHARD_SIZE,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../../common/constants';
-import { SetupModeContext } from '../../../../components/setup_mode/setup_mode_context';
-import { SetupModeRenderer } from '../../../../components/renderers';
-
-function getPageData($injector) {
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/indices/${$route.current.params.index}`;
- const $http = $injector.get('$http');
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: true,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/elasticsearch/indices/:index/advanced', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringElasticsearchAdvancedIndexApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const indexName = $route.current.params.index;
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.indices.advanced.routeTitle', {
- defaultMessage: 'Elasticsearch - Indices - {indexName} - Advanced',
- values: {
- indexName,
- },
- }),
- telemetryPageViewTitle: 'elasticsearch_index_advanced',
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringElasticsearchAdvancedIndexApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LARGE_SHARD_SIZE],
- filters: [
- {
- shardIndex: $route.current.pathParams.index,
- },
- ],
- },
- },
- });
-
- this.indexName = indexName;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.html
deleted file mode 100644
index 84d90f184358d..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js
deleted file mode 100644
index c9efb622ff9d1..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for single index detail
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels';
-import { indicesByNodes } from '../../../components/elasticsearch/shard_allocation/transformers/indices_by_nodes';
-import { Index } from '../../../components/elasticsearch/index/index';
-import { MonitoringViewBaseController } from '../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_LARGE_SHARD_SIZE,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../common/constants';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import { SetupModeRenderer } from '../../../components/renderers';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/indices/${$route.current.params.index}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: false,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/elasticsearch/indices/:index', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringElasticsearchIndexApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const indexName = $route.current.params.index;
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.indices.overview.routeTitle', {
- defaultMessage: 'Elasticsearch - Indices - {indexName} - Overview',
- values: {
- indexName,
- },
- }),
- telemetryPageViewTitle: 'elasticsearch_index',
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.indices.overview.pageTitle', {
- defaultMessage: 'Index: {indexName}',
- values: {
- indexName,
- },
- }),
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringElasticsearchIndexApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LARGE_SHARD_SIZE],
- filters: [
- {
- shardIndex: $route.current.pathParams.index,
- },
- ],
- },
- },
- });
-
- this.indexName = indexName;
- const transformer = indicesByNodes();
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.shards) {
- return;
- }
-
- const shards = data.shards;
- $scope.totalCount = shards.length;
- $scope.showing = transformer(shards, data.nodes);
- $scope.labels = labels.node;
- if (shards.some((shard) => shard.state === 'UNASSIGNED')) {
- $scope.labels = labels.indexWithUnassigned;
- } else {
- $scope.labels = labels.index;
- }
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.html
deleted file mode 100644
index 84013078e0ef1..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js
deleted file mode 100644
index 5acff8be20dcf..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { ElasticsearchIndices } from '../../../components';
-import template from './index.html';
-import {
- CODE_PATH_ELASTICSEARCH,
- ELASTICSEARCH_SYSTEM_ID,
- RULE_LARGE_SHARD_SIZE,
-} from '../../../../common/constants';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/indices', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- },
- controllerAs: 'elasticsearchIndices',
- controller: class ElasticsearchIndicesController extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const features = $injector.get('features');
-
- const { cluster_uuid: clusterUuid } = globalState;
- $scope.cluster = find($route.current.locals.clusters, { cluster_uuid: clusterUuid });
-
- let showSystemIndices = features.isEnabled('showSystemIndices', false);
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.indices.routeTitle', {
- defaultMessage: 'Elasticsearch - Indices',
- }),
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.indices.pageTitle', {
- defaultMessage: 'Elasticsearch indices',
- }),
- storageKey: 'elasticsearch.indices',
- apiUrlFn: () =>
- `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/indices?show_system_indices=${showSystemIndices}`,
- reactNodeId: 'elasticsearchIndicesReact',
- defaultData: {},
- $scope,
- $injector,
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LARGE_SHARD_SIZE],
- },
- },
- });
-
- this.isCcrEnabled = $scope.cluster.isCcrEnabled;
-
- // for binding
- const toggleShowSystemIndices = (isChecked) => {
- // flip the boolean
- showSystemIndices = isChecked;
- // preserve setting in localStorage
- features.update('showSystemIndices', isChecked);
- // update the page (resets pagination and sorting)
- this.updateData();
- };
-
- const renderComponent = () => {
- const { clusterStatus, indices } = this.data;
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- };
-
- this.onTableChangeRender = renderComponent;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
- renderComponent();
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js
deleted file mode 100644
index 39bd2686069de..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ml_jobs`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.html
deleted file mode 100644
index 6fdae46b6b6ed..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js
deleted file mode 100644
index d44b782f3994b..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { CODE_PATH_ELASTICSEARCH, CODE_PATH_ML } from '../../../../common/constants';
-
-uiRoutes.when('/elasticsearch/ml_jobs', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH, CODE_PATH_ML] });
- },
- pageData: getPageData,
- },
- controllerAs: 'mlJobs',
- controller: class MlJobsList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.mlJobs.routeTitle', {
- defaultMessage: 'Elasticsearch - Machine Learning Jobs',
- }),
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.mlJobs.pageTitle', {
- defaultMessage: 'Elasticsearch machine learning jobs',
- }),
- storageKey: 'elasticsearch.mlJobs',
- getPageData,
- $scope,
- $injector,
- });
-
- const $route = $injector.get('$route');
- this.data = $route.current.locals.pageData;
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
- this.isCcrEnabled = Boolean($scope.cluster && $scope.cluster.isCcrEnabled);
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.html
deleted file mode 100644
index c79c4eed46bb7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js
deleted file mode 100644
index dc0456178fbff..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for Advanced Node Detail
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { get } from 'lodash';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../../legacy_shims';
-import { AdvancedNode } from '../../../../components/elasticsearch/node/advanced';
-import { MonitoringViewBaseController } from '../../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_CPU_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MISSING_MONITORING_DATA,
- RULE_DISK_USAGE,
- RULE_MEMORY_USAGE,
-} from '../../../../../common/constants';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/nodes/${$route.current.params.node}`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: true,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/elasticsearch/nodes/:node/advanced', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const nodeName = $route.current.params.node;
-
- super({
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringElasticsearchAdvancedNodeApp',
- telemetryPageViewTitle: 'elasticsearch_node_advanced',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [
- RULE_CPU_USAGE,
- RULE_DISK_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MEMORY_USAGE,
- RULE_MISSING_MONITORING_DATA,
- ],
- filters: [
- {
- nodeUuid: nodeName,
- },
- ],
- },
- },
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.nodeSummary) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.elasticsearch.node.advanced.routeTitle', {
- defaultMessage: 'Elasticsearch - Nodes - {nodeSummaryName} - Advanced',
- values: {
- nodeSummaryName: get(data, 'nodeSummary.name'),
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.elasticsearch.node.overview.pageTitle', {
- defaultMessage: 'Elasticsearch node: {node}',
- values: {
- node: get(data, 'nodeSummary.name'),
- },
- })
- );
-
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js
deleted file mode 100644
index 1d8bc3f3efa32..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/nodes/${$route.current.params.node}`;
- const features = $injector.get('features');
- const showSystemIndices = features.isEnabled('showSystemIndices', false);
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- showSystemIndices,
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: false,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.html
deleted file mode 100644
index 1c3b32728cecd..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js
deleted file mode 100644
index 3ec10aa9d4a4c..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for Node Detail
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { get, partial } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { Node } from '../../../components/elasticsearch/node/node';
-import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels';
-import { nodesByIndices } from '../../../components/elasticsearch/shard_allocation/transformers/nodes_by_indices';
-import { MonitoringViewBaseController } from '../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_CPU_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MISSING_MONITORING_DATA,
- RULE_DISK_USAGE,
- RULE_MEMORY_USAGE,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../common/constants';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/nodes/:node', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringElasticsearchNodeApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const nodeName = $route.current.params.node;
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.node.overview.routeTitle', {
- defaultMessage: 'Elasticsearch - Nodes - {nodeName} - Overview',
- values: {
- nodeName,
- },
- }),
- telemetryPageViewTitle: 'elasticsearch_node',
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringElasticsearchNodeApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [
- RULE_CPU_USAGE,
- RULE_DISK_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MEMORY_USAGE,
- RULE_MISSING_MONITORING_DATA,
- ],
- filters: [
- {
- nodeUuid: nodeName,
- },
- ],
- },
- },
- });
-
- this.nodeName = nodeName;
-
- const features = $injector.get('features');
- const callPageData = partial(getPageData, $injector);
- // show/hide system indices in shard allocation view
- $scope.showSystemIndices = features.isEnabled('showSystemIndices', false);
- $scope.toggleShowSystemIndices = (isChecked) => {
- $scope.showSystemIndices = isChecked;
- // preserve setting in localStorage
- features.update('showSystemIndices', isChecked);
- // update the page
- callPageData().then((data) => (this.data = data));
- };
-
- const transformer = nodesByIndices();
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.shards) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.elasticsearch.node.overview.routeTitle', {
- defaultMessage: 'Elasticsearch - Nodes - {nodeName} - Overview',
- values: {
- nodeName: get(data, 'nodeSummary.name'),
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.elasticsearch.node.overview.pageTitle', {
- defaultMessage: 'Elasticsearch node: {node}',
- values: {
- node: get(data, 'nodeSummary.name'),
- },
- })
- );
-
- const shards = data.shards;
- $scope.totalCount = shards.length;
- $scope.showing = transformer(shards, data.nodes);
- $scope.labels = labels.node;
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.html
deleted file mode 100644
index 95a483a59f20c..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js
deleted file mode 100644
index 5bc546e8590ad..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { Legacy } from '../../../legacy_shims';
-import template from './index.html';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { ElasticsearchNodes } from '../../../components';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { SetupModeRenderer } from '../../../components/renderers';
-import {
- ELASTICSEARCH_SYSTEM_ID,
- CODE_PATH_ELASTICSEARCH,
- RULE_CPU_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MISSING_MONITORING_DATA,
- RULE_DISK_USAGE,
- RULE_MEMORY_USAGE,
-} from '../../../../common/constants';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/nodes', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- },
- controllerAs: 'elasticsearchNodes',
- controller: class ElasticsearchNodesController extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const showCgroupMetricsElasticsearch = $injector.get('showCgroupMetricsElasticsearch');
-
- $scope.cluster =
- find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- }) || {};
-
- const getPageData = ($injector, _api = undefined, routeOptions = {}) => {
- _api; // to fix eslint
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- const getNodes = (clusterUuid = globalState.cluster_uuid) =>
- $http.post(`../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/nodes`, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- ...routeOptions,
- });
-
- const promise = globalState.cluster_uuid
- ? getNodes()
- : new Promise((resolve) => resolve({ data: {} }));
- return promise
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
- };
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.nodes.routeTitle', {
- defaultMessage: 'Elasticsearch - Nodes',
- }),
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.nodes.pageTitle', {
- defaultMessage: 'Elasticsearch nodes',
- }),
- storageKey: 'elasticsearch.nodes',
- reactNodeId: 'elasticsearchNodesReact',
- defaultData: {},
- getPageData,
- $scope,
- $injector,
- fetchDataImmediately: false, // We want to apply pagination before sending the first request,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [
- RULE_CPU_USAGE,
- RULE_DISK_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MEMORY_USAGE,
- RULE_MISSING_MONITORING_DATA,
- ],
- },
- },
- });
-
- this.isCcrEnabled = $scope.cluster.isCcrEnabled;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
-
- const { clusterStatus, nodes, totalNodeCount } = data;
- const pagination = {
- ...this.pagination,
- totalItemCount: totalNodeCount,
- };
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js b/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js
deleted file mode 100644
index f39033fe7014d..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { MonitoringViewBaseController } from '../../';
-import { ElasticsearchOverview } from '../../../components';
-
-export class ElasticsearchOverviewController extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- // breadcrumbs + page title
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: 'Elasticsearch',
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.overview.pageTitle', {
- defaultMessage: 'Elasticsearch overview',
- }),
- api: `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch`,
- defaultData: {
- clusterStatus: { status: '' },
- metrics: null,
- shardActivity: null,
- },
- reactNodeId: 'elasticsearchOverviewReact',
- $scope,
- $injector,
- });
-
- this.isCcrEnabled = $scope.cluster.isCcrEnabled;
- this.showShardActivityHistory = false;
- this.toggleShardActivityHistory = () => {
- this.showShardActivityHistory = !this.showShardActivityHistory;
- $scope.$evalAsync(() => {
- this.renderReact(this.data, $scope.cluster);
- });
- };
-
- this.initScope($scope);
- }
-
- initScope($scope) {
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(data, $scope.cluster);
- }
- );
-
- // HACK to force table to re-render even if data hasn't changed. This
- // happens when the data remains empty after turning on showHistory. The
- // button toggle needs to update the "no data" message based on the value of showHistory
- $scope.$watch(
- () => this.showShardActivityHistory,
- () => {
- const { data } = this;
- const dataWithShardActivityLoading = { ...data, shardActivity: null };
- // force shard activity to rerender by manipulating and then re-setting its data prop
- this.renderReact(dataWithShardActivityLoading, $scope.cluster);
- this.renderReact(data, $scope.cluster);
- }
- );
- }
-
- filterShardActivityData(shardActivity) {
- return shardActivity.filter((row) => {
- return this.showShardActivityHistory || row.stage !== 'DONE';
- });
- }
-
- renderReact(data, cluster) {
- // All data needs to originate in this view, and get passed as a prop to the components, for statelessness
- const { clusterStatus, metrics, shardActivity, logs } = data || {};
- const shardActivityData = shardActivity && this.filterShardActivityData(shardActivity); // no filter on data = null
- const component = (
-
- );
-
- super.renderReact(component);
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.html
deleted file mode 100644
index 127c48add5e8d..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.js
deleted file mode 100644
index cc507934dd767..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { ElasticsearchOverviewController } from './controller';
-import { CODE_PATH_ELASTICSEARCH } from '../../../../common/constants';
-
-uiRoutes.when('/elasticsearch', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- },
- controllerAs: 'elasticsearchOverview',
- controller: ElasticsearchOverviewController,
-});
diff --git a/x-pack/plugins/monitoring/public/views/index.js b/x-pack/plugins/monitoring/public/views/index.js
deleted file mode 100644
index 8cfb8f35e68ba..0000000000000
--- a/x-pack/plugins/monitoring/public/views/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export { MonitoringViewBaseController } from './base_controller';
-export { MonitoringViewBaseTableController } from './base_table_controller';
-export { MonitoringViewBaseEuiTableController } from './base_eui_table_controller';
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instance/index.html b/x-pack/plugins/monitoring/public/views/kibana/instance/index.html
deleted file mode 100644
index 8bb17839683a8..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instance/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instance/index.js b/x-pack/plugins/monitoring/public/views/kibana/instance/index.js
deleted file mode 100644
index a71289b084516..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instance/index.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Kibana Instance
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { get } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiSpacer,
- EuiFlexGrid,
- EuiFlexItem,
- EuiPanel,
-} from '@elastic/eui';
-import { MonitoringTimeseriesContainer } from '../../../components/chart';
-import { DetailStatus } from '../../../components/kibana/detail_status';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_KIBANA, RULE_KIBANA_VERSION_MISMATCH } from '../../../../common/constants';
-import { AlertsCallout } from '../../../alerts/callout';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/kibana/${$route.current.params.uuid}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/kibana/instances/:uuid', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_KIBANA] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringKibanaInstanceApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- title: `Kibana - ${get($scope.pageData, 'kibanaSummary.name')}`,
- telemetryPageViewTitle: 'kibana_instance',
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringKibanaInstanceApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_KIBANA_VERSION_MISMATCH],
- },
- },
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.metrics) {
- return;
- }
- this.setTitle(`Kibana - ${get(data, 'kibanaSummary.name')}`);
- this.setPageTitle(
- i18n.translate('xpack.monitoring.kibana.instance.pageTitle', {
- defaultMessage: 'Kibana instance: {instance}',
- values: {
- instance: get($scope.pageData, 'kibanaSummary.name'),
- },
- })
- );
-
- this.renderReact(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js b/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js
deleted file mode 100644
index 82c49ee0ebb13..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/kibana/instances`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/index.html b/x-pack/plugins/monitoring/public/views/kibana/instances/index.html
deleted file mode 100644
index 8e1639a2323a5..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instances/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js
deleted file mode 100644
index 2601a366e6843..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { KibanaInstances } from '../../../components/kibana/instances';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import {
- KIBANA_SYSTEM_ID,
- CODE_PATH_KIBANA,
- RULE_KIBANA_VERSION_MISMATCH,
-} from '../../../../common/constants';
-
-uiRoutes.when('/kibana/instances', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_KIBANA] });
- },
- pageData: getPageData,
- },
- controllerAs: 'kibanas',
- controller: class KibanaInstancesList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.kibana.instances.routeTitle', {
- defaultMessage: 'Kibana - Instances',
- }),
- pageTitle: i18n.translate('xpack.monitoring.kibana.instances.pageTitle', {
- defaultMessage: 'Kibana instances',
- }),
- storageKey: 'kibana.instances',
- getPageData,
- reactNodeId: 'monitoringKibanaInstancesApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_KIBANA_VERSION_MISMATCH],
- },
- },
- });
-
- const renderReact = () => {
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- };
-
- this.onTableChangeRender = renderReact;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
-
- renderReact();
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/kibana/overview/index.html b/x-pack/plugins/monitoring/public/views/kibana/overview/index.html
deleted file mode 100644
index 5b131e113dfa4..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/overview/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/kibana/overview/index.js b/x-pack/plugins/monitoring/public/views/kibana/overview/index.js
deleted file mode 100644
index ad59265a98531..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/overview/index.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Kibana Overview
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { MonitoringTimeseriesContainer } from '../../../components/chart';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPanel,
- EuiSpacer,
- EuiFlexGroup,
- EuiFlexItem,
-} from '@elastic/eui';
-import { ClusterStatus } from '../../../components/kibana/cluster_status';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_KIBANA } from '../../../../common/constants';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/kibana`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/kibana', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_KIBANA] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringKibanaOverviewApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- title: `Kibana`,
- pageTitle: i18n.translate('xpack.monitoring.kibana.overview.pageTitle', {
- defaultMessage: 'Kibana overview',
- }),
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringKibanaOverviewApp',
- $scope,
- $injector,
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.clusterStatus) {
- return;
- }
-
- this.renderReact(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/license/controller.js b/x-pack/plugins/monitoring/public/views/license/controller.js
deleted file mode 100644
index 297edf6481a55..0000000000000
--- a/x-pack/plugins/monitoring/public/views/license/controller.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { get, find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { Legacy } from '../../legacy_shims';
-import { formatDateTimeLocal } from '../../../common/formatting';
-import { BASE_PATH as MANAGEMENT_BASE_PATH } from '../../../../../plugins/license_management/common/constants';
-import { License } from '../../components';
-
-const REACT_NODE_ID = 'licenseReact';
-
-export class LicenseViewController {
- constructor($injector, $scope) {
- Legacy.shims.timefilter.disableTimeRangeSelector();
- Legacy.shims.timefilter.disableAutoRefreshSelector();
-
- $scope.$on('$destroy', () => {
- unmountComponentAtNode(document.getElementById(REACT_NODE_ID));
- });
-
- this.init($injector, $scope, i18n);
- }
-
- init($injector, $scope) {
- const globalState = $injector.get('globalState');
- const title = $injector.get('title');
- const $route = $injector.get('$route');
-
- const cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
- $scope.cluster = cluster;
- const routeTitle = i18n.translate('xpack.monitoring.license.licenseRouteTitle', {
- defaultMessage: 'License',
- });
- title($scope.cluster, routeTitle);
-
- this.license = cluster.license;
- this.isExpired = Date.now() > get(cluster, 'license.expiry_date_in_millis');
- this.isPrimaryCluster = cluster.isPrimary;
-
- const basePath = Legacy.shims.getBasePath();
- this.uploadLicensePath = basePath + '/app/kibana#' + MANAGEMENT_BASE_PATH + 'upload_license';
-
- this.renderReact($scope);
- }
-
- renderReact($scope) {
- const injector = Legacy.shims.getAngularInjector();
- const timezone = injector.get('config').get('dateFormat:tz');
- $scope.$evalAsync(() => {
- const { isPrimaryCluster, license, isExpired, uploadLicensePath } = this;
- let expiryDate = license.expiry_date_in_millis;
- if (license.expiry_date_in_millis !== undefined) {
- expiryDate = formatDateTimeLocal(license.expiry_date_in_millis, timezone);
- }
-
- // Mount the React component to the template
- render(
- ,
- document.getElementById(REACT_NODE_ID)
- );
- });
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/license/index.html b/x-pack/plugins/monitoring/public/views/license/index.html
deleted file mode 100644
index 7fb9c69941004..0000000000000
--- a/x-pack/plugins/monitoring/public/views/license/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/license/index.js b/x-pack/plugins/monitoring/public/views/license/index.js
deleted file mode 100644
index 0ffb953268690..0000000000000
--- a/x-pack/plugins/monitoring/public/views/license/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { uiRoutes } from '../../angular/helpers/routes';
-import { routeInitProvider } from '../../lib/route_init';
-import template from './index.html';
-import { LicenseViewController } from './controller';
-import { CODE_PATH_LICENSE } from '../../../common/constants';
-
-uiRoutes.when('/license', {
- template,
- resolve: {
- clusters: (Private) => {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LICENSE] });
- },
- },
- controllerAs: 'licenseView',
- controller: LicenseViewController,
-});
diff --git a/x-pack/plugins/monitoring/public/views/loading/index.html b/x-pack/plugins/monitoring/public/views/loading/index.html
deleted file mode 100644
index 9a5971a65bc39..0000000000000
--- a/x-pack/plugins/monitoring/public/views/loading/index.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/loading/index.js b/x-pack/plugins/monitoring/public/views/loading/index.js
deleted file mode 100644
index 6406b9e6364f0..0000000000000
--- a/x-pack/plugins/monitoring/public/views/loading/index.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for single index detail
- */
-import React from 'react';
-import { render } from 'react-dom';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../angular/helpers/routes';
-import { routeInitProvider } from '../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../legacy_shims';
-import { CODE_PATH_ELASTICSEARCH } from '../../../common/constants';
-import { PageLoading } from '../../components';
-import { ajaxErrorHandlersProvider } from '../../lib/ajax_error_handler';
-
-const CODE_PATHS = [CODE_PATH_ELASTICSEARCH];
-uiRoutes.when('/loading', {
- template,
- controllerAs: 'monitoringLoading',
- controller: class {
- constructor($injector, $scope) {
- const Private = $injector.get('Private');
- const titleService = $injector.get('title');
- titleService(
- $scope.cluster,
- i18n.translate('xpack.monitoring.loading.pageTitle', {
- defaultMessage: 'Loading',
- })
- );
-
- this.init = () => {
- const reactNodeId = 'monitoringLoadingReact';
- const renderElement = document.getElementById(reactNodeId);
- if (!renderElement) {
- console.warn(`"#${reactNodeId}" element has not been added to the DOM yet`);
- return;
- }
- const I18nContext = Legacy.shims.I18nContext;
- render(
-
-
- ,
- renderElement
- );
- };
-
- const routeInit = Private(routeInitProvider);
- routeInit({ codePaths: CODE_PATHS, fetchAllClusters: true, unsetGlobalState: true })
- .then((clusters) => {
- if (!clusters || !clusters.length) {
- window.location.hash = '#/no-data';
- $scope.$apply();
- return;
- }
- if (clusters.length === 1) {
- // Bypass the cluster listing if there is just 1 cluster
- window.history.replaceState(null, null, '#/overview');
- $scope.$apply();
- return;
- }
-
- window.history.replaceState(null, null, '#/home');
- $scope.$apply();
- })
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return $scope.$apply(() => ajaxErrorHandlers(err));
- });
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.html b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.html
deleted file mode 100644
index 63f51809fd7e7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js
deleted file mode 100644
index 9acfd81d186fd..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Logstash Node Advanced View
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../../legacy_shims';
-import { MonitoringViewBaseController } from '../../../base_controller';
-import { DetailStatus } from '../../../../components/logstash/detail_status';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPanel,
- EuiSpacer,
- EuiFlexGrid,
- EuiFlexItem,
-} from '@elastic/eui';
-import { MonitoringTimeseriesContainer } from '../../../../components/chart';
-import {
- CODE_PATH_LOGSTASH,
- RULE_LOGSTASH_VERSION_MISMATCH,
-} from '../../../../../common/constants';
-import { AlertsCallout } from '../../../../alerts/callout';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/node/${$route.current.params.uuid}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: true,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/logstash/node/:uuid/advanced', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringLogstashNodeAdvancedApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LOGSTASH_VERSION_MISMATCH],
- },
- },
- telemetryPageViewTitle: 'logstash_node_advanced',
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.nodeSummary) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.logstash.node.advanced.routeTitle', {
- defaultMessage: 'Logstash - {nodeName} - Advanced',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.logstash.node.advanced.pageTitle', {
- defaultMessage: 'Logstash node: {nodeName}',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- const metricsToShow = [
- data.metrics.logstash_node_cpu_utilization,
- data.metrics.logstash_queue_events_count,
- data.metrics.logstash_node_cgroup_cpu,
- data.metrics.logstash_pipeline_queue_size,
- data.metrics.logstash_node_cgroup_stats,
- ];
-
- this.renderReact(
-
-
-
-
-
-
-
-
-
- {metricsToShow.map((metric, index) => (
-
-
-
-
- ))}
-
-
-
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/index.html b/x-pack/plugins/monitoring/public/views/logstash/node/index.html
deleted file mode 100644
index 062c830dd8b7a..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/index.js
deleted file mode 100644
index b23875ba1a3bb..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/index.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Logstash Node
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import { DetailStatus } from '../../../components/logstash/detail_status';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPanel,
- EuiSpacer,
- EuiFlexGrid,
- EuiFlexItem,
-} from '@elastic/eui';
-import { MonitoringTimeseriesContainer } from '../../../components/chart';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_LOGSTASH, RULE_LOGSTASH_VERSION_MISMATCH } from '../../../../common/constants';
-import { AlertsCallout } from '../../../alerts/callout';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/node/${$route.current.params.uuid}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: false,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/logstash/node/:uuid', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringLogstashNodeApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LOGSTASH_VERSION_MISMATCH],
- },
- },
- telemetryPageViewTitle: 'logstash_node',
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.nodeSummary) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.logstash.node.routeTitle', {
- defaultMessage: 'Logstash - {nodeName}',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.logstash.node.pageTitle', {
- defaultMessage: 'Logstash node: {nodeName}',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- const metricsToShow = [
- data.metrics.logstash_events_input_rate,
- data.metrics.logstash_jvm_usage,
- data.metrics.logstash_events_output_rate,
- data.metrics.logstash_node_cpu_metric,
- data.metrics.logstash_events_latency,
- data.metrics.logstash_os_load,
- ];
-
- this.renderReact(
-
-
-
-
-
-
-
-
-
- {metricsToShow.map((metric, index) => (
-
-
-
-
- ))}
-
-
-
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.html b/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.html
deleted file mode 100644
index cae3a169bfd5a..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js
deleted file mode 100644
index 0d5105696102a..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Logstash Node Pipelines Listing
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../../lib/route_init';
-import { isPipelineMonitoringSupportedInVersion } from '../../../../lib/logstash/pipelines';
-import template from './index.html';
-import { Legacy } from '../../../../legacy_shims';
-import { MonitoringViewBaseEuiTableController } from '../../../';
-import { PipelineListing } from '../../../../components/logstash/pipeline_listing/pipeline_listing';
-import { DetailStatus } from '../../../../components/logstash/detail_status';
-import { CODE_PATH_LOGSTASH } from '../../../../../common/constants';
-
-const getPageData = ($injector, _api = undefined, routeOptions = {}) => {
- _api; // fixing eslint
- const $route = $injector.get('$route');
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const Private = $injector.get('Private');
-
- const logstashUuid = $route.current.params.uuid;
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/node/${logstashUuid}/pipelines`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- ...routeOptions,
- })
- .then((response) => response.data)
- .catch((err) => {
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-};
-
-function makeUpgradeMessage(logstashVersion) {
- if (isPipelineMonitoringSupportedInVersion(logstashVersion)) {
- return null;
- }
-
- return i18n.translate('xpack.monitoring.logstash.node.pipelines.notAvailableDescription', {
- defaultMessage:
- 'Pipeline monitoring is only available in Logstash version 6.0.0 or higher. This node is running version {logstashVersion}.',
- values: {
- logstashVersion,
- },
- });
-}
-
-uiRoutes.when('/logstash/node/:uuid/pipelines', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- },
- controller: class extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- const config = $injector.get('config');
-
- super({
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringLogstashNodePipelinesApp',
- $scope,
- $injector,
- fetchDataImmediately: false, // We want to apply pagination before sending the first request
- telemetryPageViewTitle: 'logstash_node_pipelines',
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.nodeSummary) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.logstash.node.pipelines.routeTitle', {
- defaultMessage: 'Logstash - {nodeName} - Pipelines',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.logstash.node.pipelines.pageTitle', {
- defaultMessage: 'Logstash node pipelines: {nodeName}',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- const pagination = {
- ...this.pagination,
- totalItemCount: data.totalPipelineCount,
- };
-
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js b/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js
deleted file mode 100644
index 4c9167a47b0d7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/nodes`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.html b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.html
deleted file mode 100644
index 6da00b1c771b8..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js
deleted file mode 100644
index 56b5d0ec6c82a..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js
+++ /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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { Listing } from '../../../components/logstash/listing';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import {
- CODE_PATH_LOGSTASH,
- LOGSTASH_SYSTEM_ID,
- RULE_LOGSTASH_VERSION_MISMATCH,
-} from '../../../../common/constants';
-
-uiRoutes.when('/logstash/nodes', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'lsNodes',
- controller: class LsNodesList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.logstash.nodes.routeTitle', {
- defaultMessage: 'Logstash - Nodes',
- }),
- pageTitle: i18n.translate('xpack.monitoring.logstash.nodes.pageTitle', {
- defaultMessage: 'Logstash nodes',
- }),
- storageKey: 'logstash.nodes',
- getPageData,
- reactNodeId: 'monitoringLogstashNodesApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LOGSTASH_VERSION_MISMATCH],
- },
- },
- });
-
- const renderComponent = () => {
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- };
-
- this.onTableChangeRender = renderComponent;
-
- $scope.$watch(
- () => this.data,
- () => renderComponent()
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/overview/index.html b/x-pack/plugins/monitoring/public/views/logstash/overview/index.html
deleted file mode 100644
index 088aa35892bbe..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/overview/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/overview/index.js b/x-pack/plugins/monitoring/public/views/logstash/overview/index.js
deleted file mode 100644
index b5e8ecbefc532..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/overview/index.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Logstash Overview
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import { Overview } from '../../../components/logstash/overview';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_LOGSTASH } from '../../../../common/constants';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/logstash', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- title: 'Logstash',
- pageTitle: i18n.translate('xpack.monitoring.logstash.overview.pageTitle', {
- defaultMessage: 'Logstash overview',
- }),
- getPageData,
- reactNodeId: 'monitoringLogstashOverviewApp',
- $scope,
- $injector,
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.html b/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.html
deleted file mode 100644
index afd1d994f1e9c..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.js b/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.js
deleted file mode 100644
index dd7bcc8436358..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.js
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Logstash Node Pipeline View
- */
-import React from 'react';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import moment from 'moment';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import { CALCULATE_DURATION_SINCE, CODE_PATH_LOGSTASH } from '../../../../common/constants';
-import { formatTimestampToDuration } from '../../../../common/format_timestamp_to_duration';
-import template from './index.html';
-import { i18n } from '@kbn/i18n';
-import { List } from '../../../components/logstash/pipeline_viewer/models/list';
-import { PipelineState } from '../../../components/logstash/pipeline_viewer/models/pipeline_state';
-import { PipelineViewer } from '../../../components/logstash/pipeline_viewer';
-import { Pipeline } from '../../../components/logstash/pipeline_viewer/models/pipeline';
-import { vertexFactory } from '../../../components/logstash/pipeline_viewer/models/graph/vertex_factory';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { EuiPageBody, EuiPage, EuiPageContent } from '@elastic/eui';
-
-let previousPipelineHash = undefined;
-let detailVertexId = undefined;
-
-function getPageData($injector) {
- const $route = $injector.get('$route');
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const minIntervalSeconds = $injector.get('minIntervalSeconds');
- const Private = $injector.get('Private');
-
- const { ccs, cluster_uuid: clusterUuid } = globalState;
- const pipelineId = $route.current.params.id;
- const pipelineHash = $route.current.params.hash || '';
-
- // Pipeline version was changed, so clear out detailVertexId since that vertex won't
- // exist in the updated pipeline version
- if (pipelineHash !== previousPipelineHash) {
- previousPipelineHash = pipelineHash;
- detailVertexId = undefined;
- }
-
- const url = pipelineHash
- ? `../api/monitoring/v1/clusters/${clusterUuid}/logstash/pipeline/${pipelineId}/${pipelineHash}`
- : `../api/monitoring/v1/clusters/${clusterUuid}/logstash/pipeline/${pipelineId}`;
- return $http
- .post(url, {
- ccs,
- detailVertexId,
- })
- .then((response) => response.data)
- .then((data) => {
- data.versions = data.versions.map((version) => {
- const relativeFirstSeen = formatTimestampToDuration(
- version.firstSeen,
- CALCULATE_DURATION_SINCE
- );
- const relativeLastSeen = formatTimestampToDuration(
- version.lastSeen,
- CALCULATE_DURATION_SINCE
- );
-
- const fudgeFactorSeconds = 2 * minIntervalSeconds;
- const isLastSeenCloseToNow = Date.now() - version.lastSeen <= fudgeFactorSeconds * 1000;
-
- return {
- ...version,
- relativeFirstSeen: i18n.translate(
- 'xpack.monitoring.logstash.pipeline.relativeFirstSeenAgoLabel',
- {
- defaultMessage: '{relativeFirstSeen} ago',
- values: { relativeFirstSeen },
- }
- ),
- relativeLastSeen: isLastSeenCloseToNow
- ? i18n.translate('xpack.monitoring.logstash.pipeline.relativeLastSeenNowLabel', {
- defaultMessage: 'now',
- })
- : i18n.translate('xpack.monitoring.logstash.pipeline.relativeLastSeenAgoLabel', {
- defaultMessage: 'until {relativeLastSeen} ago',
- values: { relativeLastSeen },
- }),
- };
- });
-
- return data;
- })
- .catch((err) => {
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/logstash/pipelines/:id/:hash?', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const config = $injector.get('config');
- const dateFormat = config.get('dateFormat');
-
- super({
- title: i18n.translate('xpack.monitoring.logstash.pipeline.routeTitle', {
- defaultMessage: 'Logstash - Pipeline',
- }),
- storageKey: 'logstash.pipelines',
- getPageData,
- reactNodeId: 'monitoringLogstashPipelineApp',
- $scope,
- options: {
- enableTimeFilter: false,
- },
- $injector,
- });
-
- const timeseriesTooltipXValueFormatter = (xValue) => moment(xValue).format(dateFormat);
-
- const setDetailVertexId = (vertex) => {
- if (!vertex) {
- detailVertexId = undefined;
- } else {
- detailVertexId = vertex.id;
- }
-
- return this.updateData();
- };
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.pipeline) {
- return;
- }
- this.setPageTitle(
- i18n.translate('xpack.monitoring.logstash.pipeline.pageTitle', {
- defaultMessage: 'Logstash pipeline: {pipeline}',
- values: {
- pipeline: data.pipeline.id,
- },
- })
- );
- this.pipelineState = new PipelineState(data.pipeline);
- this.detailVertex = data.vertex ? vertexFactory(null, data.vertex) : null;
- this.renderReact(
-
-
-
-
-
-
-
- );
- }
- );
-
- $scope.$on('$destroy', () => {
- previousPipelineHash = undefined;
- detailVertexId = undefined;
- });
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.html b/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.html
deleted file mode 100644
index bef8a7a4737f3..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js b/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js
deleted file mode 100644
index f3121687f17db..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import { isPipelineMonitoringSupportedInVersion } from '../../../lib/logstash/pipelines';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import { PipelineListing } from '../../../components/logstash/pipeline_listing/pipeline_listing';
-import { MonitoringViewBaseEuiTableController } from '../..';
-import { CODE_PATH_LOGSTASH } from '../../../../common/constants';
-
-/*
- * Logstash Pipelines Listing page
- */
-
-const getPageData = ($injector, _api = undefined, routeOptions = {}) => {
- _api; // to fix eslint
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const Private = $injector.get('Private');
-
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/pipelines`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- ...routeOptions,
- })
- .then((response) => response.data)
- .catch((err) => {
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-};
-
-function makeUpgradeMessage(logstashVersions) {
- if (
- !Array.isArray(logstashVersions) ||
- logstashVersions.length === 0 ||
- logstashVersions.some(isPipelineMonitoringSupportedInVersion)
- ) {
- return null;
- }
-
- return 'Pipeline monitoring is only available in Logstash version 6.0.0 or higher.';
-}
-
-uiRoutes.when('/logstash/pipelines', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- },
- controller: class LogstashPipelinesList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.logstash.pipelines.routeTitle', {
- defaultMessage: 'Logstash Pipelines',
- }),
- pageTitle: i18n.translate('xpack.monitoring.logstash.pipelines.pageTitle', {
- defaultMessage: 'Logstash pipelines',
- }),
- storageKey: 'logstash.pipelines',
- getPageData,
- reactNodeId: 'monitoringLogstashPipelinesApp',
- $scope,
- $injector,
- fetchDataImmediately: false, // We want to apply pagination before sending the first request
- });
-
- const $route = $injector.get('$route');
- const config = $injector.get('config');
- this.data = $route.current.locals.pageData;
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- const renderReact = (pageData) => {
- if (!pageData) {
- return;
- }
-
- const upgradeMessage = pageData
- ? makeUpgradeMessage(pageData.clusterStatus.versions, i18n)
- : null;
-
- const pagination = {
- ...this.pagination,
- totalItemCount: pageData.totalPipelineCount,
- };
-
- super.renderReact(
- this.onBrush({ xaxis })}
- stats={pageData.clusterStatus}
- data={pageData.pipelines}
- {...this.getPaginationTableProps(pagination)}
- upgradeMessage={upgradeMessage}
- dateFormat={config.get('dateFormat')}
- />
- );
- };
-
- $scope.$watch(
- () => this.data,
- (pageData) => {
- renderReact(pageData);
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/no_data/controller.js b/x-pack/plugins/monitoring/public/views/no_data/controller.js
deleted file mode 100644
index 4a6a73dfb2010..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/controller.js
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import {
- ClusterSettingsChecker,
- NodeSettingsChecker,
- Enabler,
- startChecks,
-} from '../../lib/elasticsearch_settings';
-import { ModelUpdater } from './model_updater';
-import { NoData } from '../../components';
-import { CODE_PATH_LICENSE } from '../../../common/constants';
-import { MonitoringViewBaseController } from '../base_controller';
-import { i18n } from '@kbn/i18n';
-import { Legacy } from '../../legacy_shims';
-
-export class NoDataController extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- window.injectorThree = $injector;
- const monitoringClusters = $injector.get('monitoringClusters');
- const $http = $injector.get('$http');
- const checkers = [new ClusterSettingsChecker($http), new NodeSettingsChecker($http)];
-
- const getData = async () => {
- let catchReason;
- try {
- const monitoringClustersData = await monitoringClusters(undefined, undefined, [
- CODE_PATH_LICENSE,
- ]);
- if (monitoringClustersData && monitoringClustersData.length) {
- window.history.replaceState(null, null, '#/home');
- return monitoringClustersData;
- }
- } catch (err) {
- if (err && err.status === 503) {
- catchReason = {
- property: 'custom',
- message: err.data.message,
- };
- }
- }
-
- this.errors.length = 0;
- if (catchReason) {
- this.reason = catchReason;
- } else if (!this.isCollectionEnabledUpdating && !this.isCollectionIntervalUpdating) {
- /**
- * `no-use-before-define` is fine here, since getData is an async function.
- * Needs to be done this way, since there is no `this` before super is executed
- * */
- await startChecks(checkers, updateModel); // eslint-disable-line no-use-before-define
- }
- };
-
- super({
- title: i18n.translate('xpack.monitoring.noData.routeTitle', {
- defaultMessage: 'Setup Monitoring',
- }),
- getPageData: async () => await getData(),
- reactNodeId: 'noDataReact',
- $scope,
- $injector,
- });
- Object.assign(this, this.getDefaultModel());
-
- //Need to set updateModel after super since there is no `this` otherwise
- const { updateModel } = new ModelUpdater($scope, this);
- const enabler = new Enabler($http, updateModel);
- $scope.$watch(
- () => this,
- () => {
- if (this.isCollectionEnabledUpdated && !this.reason) {
- return;
- }
- this.render(enabler);
- },
- true
- );
- }
-
- getDefaultModel() {
- return {
- errors: [], // errors can happen from trying to check or set ES settings
- checkMessage: null, // message to show while waiting for api response
- isLoading: true, // flag for in-progress state of checking for no data reason
- isCollectionEnabledUpdating: false, // flags to indicate whether to show a spinner while waiting for ajax
- isCollectionEnabledUpdated: false,
- isCollectionIntervalUpdating: false,
- isCollectionIntervalUpdated: false,
- };
- }
-
- render(enabler) {
- const props = this;
- this.renderReact();
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/no_data/index.html b/x-pack/plugins/monitoring/public/views/no_data/index.html
deleted file mode 100644
index c6fc97b639f42..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/index.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/no_data/index.js b/x-pack/plugins/monitoring/public/views/no_data/index.js
deleted file mode 100644
index 4bbc490ce29ed..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/index.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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { uiRoutes } from '../../angular/helpers/routes';
-import template from './index.html';
-import { NoDataController } from './controller';
-
-uiRoutes.when('/no-data', {
- template,
- controller: NoDataController,
-});
diff --git a/x-pack/plugins/monitoring/public/views/no_data/model_updater.js b/x-pack/plugins/monitoring/public/views/no_data/model_updater.js
deleted file mode 100644
index 115dc782162a7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/model_updater.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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Class for handling model updates of an Angular controller
- * Some properties are simple primitives like strings or booleans,
- * but sometimes we need a property in the model to be an Array. For example,
- * there may be multiple errors that happen in a flow.
- *
- * I use 1 method to handling property values that are either primitives or
- * arrays, because it allows the callers to be a little more dumb. All they
- * have to know is the property name, rather than the type as well.
- */
-export class ModelUpdater {
- constructor($scope, model) {
- this.$scope = $scope;
- this.model = model;
- this.updateModel = this.updateModel.bind(this);
- }
-
- updateModel(properties) {
- const { $scope, model } = this;
- const keys = Object.keys(properties);
- $scope.$evalAsync(() => {
- keys.forEach((key) => {
- if (Array.isArray(model[key])) {
- model[key].push(properties[key]);
- } else {
- model[key] = properties[key];
- }
- });
- });
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/no_data/model_updater.test.js b/x-pack/plugins/monitoring/public/views/no_data/model_updater.test.js
deleted file mode 100644
index b286bfb10a9e4..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/model_updater.test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ModelUpdater } from './model_updater';
-
-describe('Model Updater for Angular Controller with React Components', () => {
- let $scope;
- let model;
- let updater;
-
- beforeEach(() => {
- $scope = {};
- $scope.$evalAsync = (cb) => cb();
-
- model = {};
-
- updater = new ModelUpdater($scope, model);
- jest.spyOn(updater, 'updateModel');
- });
-
- test('should successfully construct an object', () => {
- expect(typeof updater).toBe('object');
- expect(updater.updateModel).not.toHaveBeenCalled();
- });
-
- test('updateModel method should add properties to the model', () => {
- expect(typeof updater).toBe('object');
- updater.updateModel({
- foo: 'bar',
- bar: 'baz',
- error: 'monkeywrench',
- });
- expect(model).toEqual({
- foo: 'bar',
- bar: 'baz',
- error: 'monkeywrench',
- });
- });
-
- test('updateModel method should push properties to the model if property is originally an array', () => {
- model.errors = ['first'];
- updater.updateModel({
- errors: 'second',
- primitive: 'hello',
- });
- expect(model).toEqual({
- errors: ['first', 'second'],
- primitive: 'hello',
- });
- });
-});
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 2e17a38e83c68..41cc7825bcf36 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -17940,8 +17940,7 @@
"xpack.monitoring.cluster.overview.logstashPanel.withPersistentQueuesLabel": "永続キューあり",
"xpack.monitoring.cluster.overview.pageTitle": "クラスターの概要",
"xpack.monitoring.cluster.overviewTitle": "概要",
- "xpack.monitoring.clusterAlertsNavigation.clusterAlertsLinkText": "クラスターアラート",
- "xpack.monitoring.clustersNavigation.clustersLinkText": "クラスター",
+ "xpack.monitoring.cluster.listing.tabTitle": "クラスター",
"xpack.monitoring.clusterStats.uuidNotFoundErrorMessage": "選択された時間範囲にクラスターが見つかりませんでした。UUID:{clusterUuid}",
"xpack.monitoring.clusterStats.uuidNotSpecifiedErrorMessage": "{clusterUuid} が指定されていません",
"xpack.monitoring.elasticsearch.ccr.ccrListingTable.alertsColumnTitle": "アラート",
@@ -17953,10 +17952,10 @@
"xpack.monitoring.elasticsearch.ccr.ccrListingTable.syncLagOpsColumnTitle": "同期の遅延(オペレーション数)",
"xpack.monitoring.elasticsearch.ccr.heading": "CCR",
"xpack.monitoring.elasticsearch.ccr.pageTitle": "Elasticsearch - CCR",
- "xpack.monitoring.elasticsearch.ccr.routeTitle": "Elasticsearch - CCR",
+ "xpack.monitoring.elasticsearch.ccr.title": "Elasticsearch - CCR",
"xpack.monitoring.elasticsearch.ccr.shard.instanceTitle": "インデックス{followerIndex} シャード:{shardId}",
"xpack.monitoring.elasticsearch.ccr.shard.pageTitle": "Elasticsearch Ccrシャード - インデックス:{followerIndex} シャード:{shardId}",
- "xpack.monitoring.elasticsearch.ccr.shard.routeTitle": "Elasticsearch - CCR - シャード",
+ "xpack.monitoring.elasticsearch.ccr.shard.title": "Elasticsearch - CCR - シャード",
"xpack.monitoring.elasticsearch.ccr.shardsTable.alertsColumnTitle": "アラート",
"xpack.monitoring.elasticsearch.ccr.shardsTable.errorColumnTitle": "エラー",
"xpack.monitoring.elasticsearch.ccr.shardsTable.lastFetchTimeColumnTitle": "最終取得時刻",
@@ -17990,7 +17989,7 @@
"xpack.monitoring.elasticsearch.indexDetailStatus.totalShardsTitle": "合計シャード数",
"xpack.monitoring.elasticsearch.indexDetailStatus.totalTitle": "合計",
"xpack.monitoring.elasticsearch.indexDetailStatus.unassignedShardsTitle": "未割り当てシャード",
- "xpack.monitoring.elasticsearch.indices.advanced.routeTitle": "Elasticsearch - インデックス - {indexName} - 高度な設定",
+ "xpack.monitoring.elasticsearch.index.advanced.title": "Elasticsearch - インデックス - {indexName} - 高度な設定",
"xpack.monitoring.elasticsearch.indices.alertsColumnTitle": "アラート",
"xpack.monitoring.elasticsearch.indices.dataTitle": "データ",
"xpack.monitoring.elasticsearch.indices.documentCountTitle": "ドキュメントカウント",
@@ -17999,8 +17998,8 @@
"xpack.monitoring.elasticsearch.indices.monitoringTablePlaceholder": "インデックスのフィルタリング…",
"xpack.monitoring.elasticsearch.indices.nameTitle": "名前",
"xpack.monitoring.elasticsearch.indices.noIndicesMatchYourSelectionDescription": "選択項目に一致するインデックスがありません。時間範囲を変更してみてください。",
- "xpack.monitoring.elasticsearch.indices.overview.pageTitle": "インデックス:{indexName}",
- "xpack.monitoring.elasticsearch.indices.overview.routeTitle": "Elasticsearch - インデックス - {indexName} - 概要",
+ "xpack.monitoring.elasticsearch.index.overview.pageTitle": "インデックス:{indexName}",
+ "xpack.monitoring.elasticsearch.index.overview.title": "Elasticsearch - インデックス - {indexName} - 概要",
"xpack.monitoring.elasticsearch.indices.pageTitle": "デフォルトのインデックス",
"xpack.monitoring.elasticsearch.indices.routeTitle": "Elasticsearch - インデックス",
"xpack.monitoring.elasticsearch.indices.searchRateTitle": "検索レート",
@@ -18019,7 +18018,7 @@
"xpack.monitoring.elasticsearch.mlJobListing.statusIconLabel": "ジョブ状態:{status}",
"xpack.monitoring.elasticsearch.mlJobs.pageTitle": "Elasticsearch - 機械学習ジョブ",
"xpack.monitoring.elasticsearch.mlJobs.routeTitle": "Elasticsearch - 機械学習ジョブ",
- "xpack.monitoring.elasticsearch.node.advanced.routeTitle": "Elasticsearch - ノード - {nodeSummaryName} - 高度な設定",
+ "xpack.monitoring.elasticsearch.node.advanced.title": "Elasticsearch - ノード - {nodeName} - 高度な設定",
"xpack.monitoring.elasticsearch.node.cells.tooltip.iconLabel": "このメトリックの詳細",
"xpack.monitoring.elasticsearch.node.cells.tooltip.max": "最高値",
"xpack.monitoring.elasticsearch.node.cells.tooltip.min": "最低値",
@@ -18028,7 +18027,7 @@
"xpack.monitoring.elasticsearch.node.cells.trendingDownText": "ダウン",
"xpack.monitoring.elasticsearch.node.cells.trendingUpText": "アップ",
"xpack.monitoring.elasticsearch.node.overview.pageTitle": "Elasticsearchノード:{node}",
- "xpack.monitoring.elasticsearch.node.overview.routeTitle": "Elasticsearch - ノード - {nodeName} - 概要",
+ "xpack.monitoring.elasticsearch.node.overview.title": "Elasticsearch - ノード - {nodeName} - 概要",
"xpack.monitoring.elasticsearch.node.statusIconLabel": "ステータス:{status}",
"xpack.monitoring.elasticsearch.nodeDetailStatus.alerts": "アラート",
"xpack.monitoring.elasticsearch.nodeDetailStatus.dataLabel": "データ",
@@ -18117,8 +18116,7 @@
"xpack.monitoring.es.nodeType.nodeLabel": "ノード",
"xpack.monitoring.esNavigation.ccrLinkText": "CCR",
"xpack.monitoring.esNavigation.indicesLinkText": "インデックス",
- "xpack.monitoring.esNavigation.instance.advancedLinkText": "高度な設定",
- "xpack.monitoring.esNavigation.instance.overviewLinkText": "概要",
+ "xpack.monitoring.esItemNavigation.advancedLinkText": "高度な設定",
"xpack.monitoring.esNavigation.jobsLinkText": "機械学習ジョブ",
"xpack.monitoring.esNavigation.nodesLinkText": "ノード",
"xpack.monitoring.esNavigation.overviewLinkText": "概要",
@@ -18237,7 +18235,6 @@
"xpack.monitoring.logstash.node.advanced.pageTitle": "Logstashノード:{nodeName}",
"xpack.monitoring.logstash.node.advanced.routeTitle": "Logstash - {nodeName} - 高度な設定",
"xpack.monitoring.logstash.node.pageTitle": "Logstashノード:{nodeName}",
- "xpack.monitoring.logstash.node.pipelines.notAvailableDescription": "パイプラインの監視は Logstash バージョン 6.0.0 以降でのみ利用できます。このノードはバージョン {logstashVersion} を実行しています。",
"xpack.monitoring.logstash.node.pipelines.pageTitle": "Logstashノードパイプライン:{nodeName}",
"xpack.monitoring.logstash.node.pipelines.routeTitle": "Logstash - {nodeName} - パイプライン",
"xpack.monitoring.logstash.node.routeTitle": "Logstash - {nodeName}",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index d990312f5d619..54811936957ab 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -18215,8 +18215,7 @@
"xpack.monitoring.cluster.overview.logstashPanel.withPersistentQueuesLabel": "持久性队列",
"xpack.monitoring.cluster.overview.pageTitle": "集群概览",
"xpack.monitoring.cluster.overviewTitle": "概览",
- "xpack.monitoring.clusterAlertsNavigation.clusterAlertsLinkText": "集群告警",
- "xpack.monitoring.clustersNavigation.clustersLinkText": "集群",
+ "xpack.monitoring.cluster.listing.tabTitle": "集群",
"xpack.monitoring.clusterStats.uuidNotFoundErrorMessage": "在选定时间范围内找不到该集群。UUID:{clusterUuid}",
"xpack.monitoring.clusterStats.uuidNotSpecifiedErrorMessage": "{clusterUuid} 未指定",
"xpack.monitoring.elasticsearch.ccr.ccrListingTable.alertsColumnTitle": "告警",
@@ -18228,10 +18227,10 @@
"xpack.monitoring.elasticsearch.ccr.ccrListingTable.syncLagOpsColumnTitle": "同步延迟(操作)",
"xpack.monitoring.elasticsearch.ccr.heading": "CCR",
"xpack.monitoring.elasticsearch.ccr.pageTitle": "Elasticsearch Ccr",
- "xpack.monitoring.elasticsearch.ccr.routeTitle": "Elasticsearch - CCR",
+ "xpack.monitoring.elasticsearch.ccr.title": "Elasticsearch - CCR",
"xpack.monitoring.elasticsearch.ccr.shard.instanceTitle": "索引:{followerIndex} 分片:{shardId}",
"xpack.monitoring.elasticsearch.ccr.shard.pageTitle": "Elasticsearch Ccr 分片 - 索引:{followerIndex} 分片:{shardId}",
- "xpack.monitoring.elasticsearch.ccr.shard.routeTitle": "Elasticsearch - CCR - 分片",
+ "xpack.monitoring.elasticsearch.ccr.shard.title": "Elasticsearch - CCR - 分片",
"xpack.monitoring.elasticsearch.ccr.shardsTable.alertsColumnTitle": "告警",
"xpack.monitoring.elasticsearch.ccr.shardsTable.errorColumnTitle": "错误",
"xpack.monitoring.elasticsearch.ccr.shardsTable.lastFetchTimeColumnTitle": "上次提取时间",
@@ -18265,7 +18264,7 @@
"xpack.monitoring.elasticsearch.indexDetailStatus.totalShardsTitle": "分片合计",
"xpack.monitoring.elasticsearch.indexDetailStatus.totalTitle": "合计",
"xpack.monitoring.elasticsearch.indexDetailStatus.unassignedShardsTitle": "未分配分片",
- "xpack.monitoring.elasticsearch.indices.advanced.routeTitle": "Elasticsearch - 索引 - {indexName} - 高级",
+ "xpack.monitoring.elasticsearch.index.advanced.title": "Elasticsearch - 索引 - {indexName} - 高级",
"xpack.monitoring.elasticsearch.indices.alertsColumnTitle": "告警",
"xpack.monitoring.elasticsearch.indices.dataTitle": "数据",
"xpack.monitoring.elasticsearch.indices.documentCountTitle": "文档计数",
@@ -18274,8 +18273,8 @@
"xpack.monitoring.elasticsearch.indices.monitoringTablePlaceholder": "筛选索引……",
"xpack.monitoring.elasticsearch.indices.nameTitle": "名称",
"xpack.monitoring.elasticsearch.indices.noIndicesMatchYourSelectionDescription": "没有索引匹配您的选择。请尝试更改时间范围选择。",
- "xpack.monitoring.elasticsearch.indices.overview.pageTitle": "索引:{indexName}",
- "xpack.monitoring.elasticsearch.indices.overview.routeTitle": "Elasticsearch - 索引 - {indexName} - 概览",
+ "xpack.monitoring.elasticsearch.index.overview.pageTitle": "索引:{indexName}",
+ "xpack.monitoring.elasticsearch.index.overview.title": "Elasticsearch - 索引 - {indexName} - 概览",
"xpack.monitoring.elasticsearch.indices.pageTitle": "Elasticsearch 索引",
"xpack.monitoring.elasticsearch.indices.routeTitle": "Elasticsearch - 索引",
"xpack.monitoring.elasticsearch.indices.searchRateTitle": "搜索速率",
@@ -18294,7 +18293,7 @@
"xpack.monitoring.elasticsearch.mlJobListing.statusIconLabel": "作业状态:{status}",
"xpack.monitoring.elasticsearch.mlJobs.pageTitle": "Elasticsearch Machine Learning 作业",
"xpack.monitoring.elasticsearch.mlJobs.routeTitle": "Elasticsearch - Machine Learning 作业",
- "xpack.monitoring.elasticsearch.node.advanced.routeTitle": "Elasticsearch - 节点 - {nodeSummaryName} - 高级",
+ "xpack.monitoring.elasticsearch.node.advanced.title": "Elasticsearch - 节点 - {nodeName} - 高级",
"xpack.monitoring.elasticsearch.node.cells.tooltip.iconLabel": "有关此指标的更多信息",
"xpack.monitoring.elasticsearch.node.cells.tooltip.max": "最大值",
"xpack.monitoring.elasticsearch.node.cells.tooltip.min": "最小值",
@@ -18303,7 +18302,7 @@
"xpack.monitoring.elasticsearch.node.cells.trendingDownText": "向下",
"xpack.monitoring.elasticsearch.node.cells.trendingUpText": "向上",
"xpack.monitoring.elasticsearch.node.overview.pageTitle": "Elasticsearch 节点:{node}",
- "xpack.monitoring.elasticsearch.node.overview.routeTitle": "Elasticsearch - 节点 - {nodeName} - 概览",
+ "xpack.monitoring.elasticsearch.node.overview.title": "Elasticsearch - 节点 - {nodeName} - 概览",
"xpack.monitoring.elasticsearch.node.statusIconLabel": "状态:{status}",
"xpack.monitoring.elasticsearch.nodeDetailStatus.alerts": "告警",
"xpack.monitoring.elasticsearch.nodeDetailStatus.dataLabel": "数据",
@@ -18392,8 +18391,7 @@
"xpack.monitoring.es.nodeType.nodeLabel": "节点",
"xpack.monitoring.esNavigation.ccrLinkText": "CCR",
"xpack.monitoring.esNavigation.indicesLinkText": "索引",
- "xpack.monitoring.esNavigation.instance.advancedLinkText": "高级",
- "xpack.monitoring.esNavigation.instance.overviewLinkText": "概览",
+ "xpack.monitoring.esItemNavigation.advancedLinkText": "高级",
"xpack.monitoring.esNavigation.jobsLinkText": "Machine Learning 作业",
"xpack.monitoring.esNavigation.nodesLinkText": "节点",
"xpack.monitoring.esNavigation.overviewLinkText": "概览",
@@ -18512,7 +18510,6 @@
"xpack.monitoring.logstash.node.advanced.pageTitle": "Logstash 节点:{nodeName}",
"xpack.monitoring.logstash.node.advanced.routeTitle": "Logstash - {nodeName} - 高级",
"xpack.monitoring.logstash.node.pageTitle": "Logstash 节点:{nodeName}",
- "xpack.monitoring.logstash.node.pipelines.notAvailableDescription": "仅 Logstash 版本 6.0.0 或更高版本提供管道监测功能。此节点正在运行版本 {logstashVersion}。",
"xpack.monitoring.logstash.node.pipelines.pageTitle": "Logstash 节点管道:{nodeName}",
"xpack.monitoring.logstash.node.pipelines.routeTitle": "Logstash - {nodeName} - 管道",
"xpack.monitoring.logstash.node.routeTitle": "Logstash - {nodeName}",
From dba055c6543409ff1ca4f1d9f7d46f44963d8da1 Mon Sep 17 00:00:00 2001
From: Trevor Pierce <1Copenut@users.noreply.github.com>
Date: Mon, 18 Oct 2021 11:41:53 -0500
Subject: [PATCH 003/204] Replace Inspector's EuiPopover with EuiComboBox
(#113566)
* Replacing EuiPopover with EuiComboBox
* The combobox will help alleviate issues when the list of options is very long
* Refactoring the Combobox to listen for change events
* Added an onChange handler
* Renamed the method to render the combobox
* Commented out additional blocks of code before final refactor
* Finished refactoring the Request Selector to use EUI Combobox
* Removed three helper methods for the EUIPopover.
* `togglePopover()`
* `closePopover()`
* `renderRequestDropdownItem()`
* Removed the local state object and interface (no longer needed)
* Renamed the const `options` to `selectedOptions` in `handleSelectd()`
method to better reflect where the options array was coming from.
* Updating tests and translations
* Fixed the inspector functional test to use comboBox service
* Removed two unused translations
* Updating Combobox options to pass data-test-sub string
* Updated two tests for Combobox single option
* Updated the test expectations to the default string
* Both tests were looking for a named string instead of a default
message
* Adding error handling to Inspector combobox
* Checking for the item status code
* Adding a " (failed)" message if the status code returns `2`
* Updating test to look for "Chart_data" instead of "Chartdata"
* Updating two tests to validate single combobox options
* Added helper method to check default text against combobox options
* Added helper method to get the selected combobox option
* Checking two inspector instances using helpers
* Adding a defensive check to helper method.
* Correct a type error in test return
* Adding back translated failLabel
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Nathan L Smith
---
.../requests/components/request_selector.tsx | 142 +++++-------------
test/functional/apps/discover/_inspector.ts | 4 +-
test/functional/apps/visualize/_vega_chart.ts | 6 +-
test/functional/services/inspector.ts | 43 ++++--
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
.../apps/maps/embeddable/dashboard.js | 7 +-
7 files changed, 85 insertions(+), 119 deletions(-)
diff --git a/src/plugins/inspector/public/views/requests/components/request_selector.tsx b/src/plugins/inspector/public/views/requests/components/request_selector.tsx
index 2d94c7ff5bb18..04fac0bd93b7e 100644
--- a/src/plugins/inspector/public/views/requests/components/request_selector.tsx
+++ b/src/plugins/inspector/public/views/requests/components/request_selector.tsx
@@ -13,118 +13,73 @@ import { i18n } from '@kbn/i18n';
import {
EuiBadge,
- EuiButtonEmpty,
- EuiContextMenuPanel,
- EuiContextMenuItem,
+ EuiComboBox,
+ EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
- EuiPopover,
- EuiTextColor,
EuiToolTip,
} from '@elastic/eui';
import { RequestStatus } from '../../../../common/adapters';
import { Request } from '../../../../common/adapters/request/types';
-interface RequestSelectorState {
- isPopoverOpen: boolean;
-}
-
interface RequestSelectorProps {
requests: Request[];
selectedRequest: Request;
- onRequestChanged: Function;
+ onRequestChanged: (request: Request) => void;
}
-export class RequestSelector extends Component {
+export class RequestSelector extends Component {
static propTypes = {
requests: PropTypes.array.isRequired,
selectedRequest: PropTypes.object.isRequired,
onRequestChanged: PropTypes.func,
};
- state = {
- isPopoverOpen: false,
- };
+ handleSelected = (selectedOptions: Array>) => {
+ const selectedOption = this.props.requests.find(
+ (request) => request.id === selectedOptions[0].value
+ );
- togglePopover = () => {
- this.setState((prevState: RequestSelectorState) => ({
- isPopoverOpen: !prevState.isPopoverOpen,
- }));
+ if (selectedOption) {
+ this.props.onRequestChanged(selectedOption);
+ }
};
- closePopover = () => {
- this.setState({
- isPopoverOpen: false,
+ renderRequestCombobox() {
+ const options = this.props.requests.map((item) => {
+ const hasFailed = item.status === RequestStatus.ERROR;
+ const testLabel = item.name.replace(/\s+/, '_');
+
+ return {
+ 'data-test-subj': `inspectorRequestChooser${testLabel}`,
+ label: hasFailed
+ ? `${item.name} ${i18n.translate('inspector.requests.failedLabel', {
+ defaultMessage: ' (failed)',
+ })}`
+ : item.name,
+ value: item.id,
+ };
});
- };
-
- renderRequestDropdownItem = (request: Request, index: number) => {
- const hasFailed = request.status === RequestStatus.ERROR;
- const inProgress = request.status === RequestStatus.PENDING;
return (
- {
- this.props.onRequestChanged(request);
- this.closePopover();
- }}
- toolTipContent={request.description}
- toolTipPosition="left"
- data-test-subj={`inspectorRequestChooser${request.name}`}
- >
-
- {request.name}
-
- {hasFailed && (
-
- )}
-
- {inProgress && (
-
- )}
-
-
- );
- };
-
- renderRequestDropdown() {
- const button = (
-
- {this.props.selectedRequest.name}
-
- );
-
- return (
-
-
-
+ isClearable={false}
+ onChange={this.handleSelected}
+ options={options}
+ prepend="Request"
+ selectedOptions={[
+ {
+ label: this.props.selectedRequest.name,
+ value: this.props.selectedRequest.id,
+ },
+ ]}
+ singleSelection={{ asPlainText: true }}
+ />
);
}
@@ -132,23 +87,8 @@ export class RequestSelector extends Component
-
-
-
-
-
-
- {requests.length <= 1 && (
-
- {selectedRequest.name}
-
- )}
- {requests.length > 1 && this.renderRequestDropdown()}
-
+
+ {requests.length && this.renderRequestCombobox()}
{selectedRequest.status !== RequestStatus.PENDING && (
{
- const requests = await inspector.getRequestNames();
+ const singleExampleRequest = await inspector.hasSingleRequest();
+ const selectedExampleRequest = await inspector.getSelectedOption();
- expect(requests).to.be('Unnamed request #0');
+ expect(singleExampleRequest).to.be(true);
+ expect(selectedExampleRequest).to.equal('Unnamed request #0');
});
it('should log the request statistic', async () => {
diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts
index 5364dbebe904c..753d9b7b0b85e 100644
--- a/test/functional/services/inspector.ts
+++ b/test/functional/services/inspector.ts
@@ -16,6 +16,7 @@ export class InspectorService extends FtrService {
private readonly flyout = this.ctx.getService('flyout');
private readonly testSubjects = this.ctx.getService('testSubjects');
private readonly find = this.ctx.getService('find');
+ private readonly comboBox = this.ctx.getService('comboBox');
private async getIsEnabled(): Promise {
const ariaDisabled = await this.testSubjects.getAttribute('openInspectorButton', 'disabled');
@@ -206,20 +207,29 @@ export class InspectorService extends FtrService {
}
/**
- * Returns request name as the comma-separated string
+ * Returns the selected option value from combobox
*/
- public async getRequestNames(): Promise {
+ public async getSelectedOption(): Promise {
await this.openInspectorRequestsView();
- const requestChooserExists = await this.testSubjects.exists('inspectorRequestChooser');
- if (requestChooserExists) {
- await this.testSubjects.click('inspectorRequestChooser');
- const menu = await this.testSubjects.find('inspectorRequestChooserMenuPanel');
- const requestNames = await menu.getVisibleText();
- return requestNames.trim().split('\n').join(',');
+ const selectedOption = await this.comboBox.getComboBoxSelectedOptions(
+ 'inspectorRequestChooser'
+ );
+
+ if (selectedOption.length !== 1) {
+ return 'Combobox has multiple options';
}
- const singleRequest = await this.testSubjects.find('inspectorRequestName');
- return await singleRequest.getVisibleText();
+ return selectedOption[0];
+ }
+
+ /**
+ * Returns request name as the comma-separated string from combobox
+ */
+ public async getRequestNames(): Promise {
+ await this.openInspectorRequestsView();
+
+ const comboBoxOptions = await this.comboBox.getOptionsList('inspectorRequestChooser');
+ return comboBoxOptions.trim().split('\n').join(',');
}
public getOpenRequestStatisticButton() {
@@ -233,4 +243,17 @@ export class InspectorService extends FtrService {
public getOpenRequestDetailResponseButton() {
return this.testSubjects.find('inspectorRequestDetailResponse');
}
+
+ /**
+ * Returns true if the value equals the combobox options list
+ * @param value default combobox single option text
+ */
+ public async hasSingleRequest(
+ value: string = "You've selected all available options"
+ ): Promise {
+ await this.openInspectorRequestsView();
+ const comboBoxOptions = await this.comboBox.getOptionsList('inspectorRequestChooser');
+
+ return value === comboBoxOptions;
+ }
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 41cc7825bcf36..79d092cebe366 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -4162,7 +4162,6 @@
"inspector.requests.noRequestsLoggedTitle": "リクエストが記録されていません",
"inspector.requests.requestFailedTooltipTitle": "リクエストに失敗しました",
"inspector.requests.requestInProgressAriaLabel": "リクエストが進行中",
- "inspector.requests.requestLabel": "リクエスト:",
"inspector.requests.requestsDescriptionTooltip": "データを収集したリクエストを表示します",
"inspector.requests.requestsTitle": "リクエスト",
"inspector.requests.requestSucceededTooltipTitle": "リクエスト成功",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 54811936957ab..c80cd968f38a8 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -4201,7 +4201,6 @@
"inspector.requests.noRequestsLoggedTitle": "未记录任何请求",
"inspector.requests.requestFailedTooltipTitle": "请求失败",
"inspector.requests.requestInProgressAriaLabel": "进行中的请求",
- "inspector.requests.requestLabel": "请求:",
"inspector.requests.requestsDescriptionTooltip": "查看已收集数据的请求",
"inspector.requests.requestsTitle": "请求",
"inspector.requests.requestSucceededTooltipTitle": "请求成功",
diff --git a/x-pack/test/functional/apps/maps/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/embeddable/dashboard.js
index 6c962c98c6a98..a892a0d547339 100644
--- a/x-pack/test/functional/apps/maps/embeddable/dashboard.js
+++ b/x-pack/test/functional/apps/maps/embeddable/dashboard.js
@@ -77,9 +77,12 @@ export default function ({ getPageObjects, getService }) {
await inspector.close();
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
- const gridExampleRequestNames = await inspector.getRequestNames();
+ const singleExampleRequest = await inspector.hasSingleRequest();
+ const selectedExampleRequest = await inspector.getSelectedOption();
await inspector.close();
- expect(gridExampleRequestNames).to.equal('logstash-*');
+
+ expect(singleExampleRequest).to.be(true);
+ expect(selectedExampleRequest).to.equal('logstash-*');
});
it('should apply container state (time, query, filters) to embeddable when loaded', async () => {
From 2f27ccffbfb8b66510c8b17853eaa8fbd69f21a1 Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Mon, 18 Oct 2021 13:16:13 -0400
Subject: [PATCH 004/204] [Fleet] Allow package to specify cluster privileges
(#114945)
---
.../plugins/fleet/common/types/models/epm.ts | 5 +
.../common/types/models/package_policy.ts | 5 +
x-pack/plugins/fleet/server/mocks/index.ts | 2 +-
.../routes/package_policy/handlers.test.ts | 2 +-
.../fleet/server/saved_objects/index.ts | 10 ++
...kage_policies_to_agent_permissions.test.ts | 98 +++++++++++++++++++
.../package_policies_to_agent_permissions.ts | 9 ++
.../server/services/package_policy.test.ts | 96 +++++++++++++++---
.../fleet/server/services/package_policy.ts | 42 +++++---
9 files changed, 242 insertions(+), 27 deletions(-)
diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts
index eaac2a8113231..6f107ae44bfa7 100644
--- a/x-pack/plugins/fleet/common/types/models/epm.ts
+++ b/x-pack/plugins/fleet/common/types/models/epm.ts
@@ -126,6 +126,11 @@ interface RegistryAdditionalProperties {
readme?: string;
internal?: boolean; // Registry addition[0] and EPM uses it[1] [0]: https://github.com/elastic/package-registry/blob/dd7b021893aa8d66a5a5fde963d8ff2792a9b8fa/util/package.go#L63 [1]
data_streams?: RegistryDataStream[]; // Registry addition [0] [0]: https://github.com/elastic/package-registry/blob/dd7b021893aa8d66a5a5fde963d8ff2792a9b8fa/util/package.go#L65
+ elasticsearch?: {
+ privileges?: {
+ cluster?: string[];
+ };
+ };
}
interface RegistryOverridePropertyValue {
icons?: RegistryImage[];
diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts
index aca537ae31b52..df484646ef66b 100644
--- a/x-pack/plugins/fleet/common/types/models/package_policy.ts
+++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts
@@ -65,6 +65,11 @@ export interface NewPackagePolicy {
package?: PackagePolicyPackage;
inputs: NewPackagePolicyInput[];
vars?: PackagePolicyConfigRecord;
+ elasticsearch?: {
+ privileges?: {
+ cluster?: string[];
+ };
+ };
}
export interface UpdatePackagePolicy extends NewPackagePolicy {
diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts
index 0e7b335da6775..c7f6b6fefc414 100644
--- a/x-pack/plugins/fleet/server/mocks/index.ts
+++ b/x-pack/plugins/fleet/server/mocks/index.ts
@@ -65,7 +65,7 @@ export const xpackMocks = {
export const createPackagePolicyServiceMock = (): jest.Mocked => {
return {
- compilePackagePolicyInputs: jest.fn(),
+ _compilePackagePolicyInputs: jest.fn(),
buildPackagePolicyFromPackage: jest.fn(),
bulkCreate: jest.fn(),
create: jest.fn(),
diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
index 729417fa96060..5441af0af686a 100644
--- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
+++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
@@ -35,7 +35,7 @@ jest.mock(
} => {
return {
packagePolicyService: {
- compilePackagePolicyInputs: jest.fn((packageInfo, vars, dataInputs) =>
+ _compilePackagePolicyInputs: jest.fn((registryPkgInfo, packageInfo, vars, dataInputs) =>
Promise.resolve(dataInputs)
),
buildPackagePolicyFromPackage: jest.fn(),
diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts
index ac5ca401da000..f0b51b19dda33 100644
--- a/x-pack/plugins/fleet/server/saved_objects/index.ts
+++ b/x-pack/plugins/fleet/server/saved_objects/index.ts
@@ -236,6 +236,16 @@ const getSavedObjectTypes = (
version: { type: 'keyword' },
},
},
+ elasticsearch: {
+ enabled: false,
+ properties: {
+ privileges: {
+ properties: {
+ cluster: { type: 'keyword' },
+ },
+ },
+ },
+ },
vars: { type: 'flattened' },
inputs: {
type: 'nested',
diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts
index 2ce68b46387c9..72566a18bd66e 100644
--- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts
+++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts
@@ -279,6 +279,104 @@ describe('storedPackagePoliciesToAgentPermissions()', () => {
});
});
+ it('Returns the cluster privileges if there is one in the package policy', async () => {
+ getPackageInfoMock.mockResolvedValueOnce({
+ name: 'test-package',
+ version: '0.0.0',
+ latestVersion: '0.0.0',
+ release: 'experimental',
+ format_version: '1.0.0',
+ title: 'Test Package',
+ description: '',
+ icons: [],
+ owner: { github: '' },
+ status: 'not_installed',
+ assets: {
+ kibana: {
+ dashboard: [],
+ visualization: [],
+ search: [],
+ index_pattern: [],
+ map: [],
+ lens: [],
+ security_rule: [],
+ ml_module: [],
+ tag: [],
+ },
+ elasticsearch: {
+ component_template: [],
+ ingest_pipeline: [],
+ ilm_policy: [],
+ transform: [],
+ index_template: [],
+ data_stream_ilm_policy: [],
+ ml_model: [],
+ },
+ },
+ data_streams: [
+ {
+ type: 'logs',
+ dataset: 'some-logs',
+ title: '',
+ release: '',
+ package: 'test-package',
+ path: '',
+ ingest_pipeline: '',
+ streams: [{ input: 'test-logs', title: 'Test Logs', template_path: '' }],
+ },
+ ],
+ });
+
+ const packagePolicies: PackagePolicy[] = [
+ {
+ id: '12345',
+ name: 'test-policy',
+ namespace: 'test',
+ enabled: true,
+ package: { name: 'test-package', version: '0.0.0', title: 'Test Package' },
+ elasticsearch: {
+ privileges: {
+ cluster: ['monitor'],
+ },
+ },
+ inputs: [
+ {
+ type: 'test-logs',
+ enabled: true,
+ streams: [
+ {
+ id: 'test-logs',
+ enabled: true,
+ data_stream: { type: 'logs', dataset: 'some-logs' },
+ compiled_stream: { data_stream: { dataset: 'compiled' } },
+ },
+ ],
+ },
+ ],
+ created_at: '',
+ updated_at: '',
+ created_by: '',
+ updated_by: '',
+ revision: 1,
+ policy_id: '',
+ output_id: '',
+ },
+ ];
+
+ const permissions = await storedPackagePoliciesToAgentPermissions(soClient, packagePolicies);
+ expect(permissions).toMatchObject({
+ 'test-policy': {
+ indices: [
+ {
+ names: ['logs-compiled-test'],
+ privileges: ['auto_configure', 'create_doc'],
+ },
+ ],
+ cluster: ['monitor'],
+ },
+ });
+ });
+
it('Returns the dataset for osquery_manager package', async () => {
getPackageInfoMock.mockResolvedValueOnce({
format_version: '1.0.0',
diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts
index 22dcb8ac7b4cb..383747fe126c0 100644
--- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts
+++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts
@@ -121,12 +121,21 @@ export async function storedPackagePoliciesToAgentPermissions(
});
}
+ let clusterRoleDescriptor = {};
+ const cluster = packagePolicy?.elasticsearch?.privileges?.cluster ?? [];
+ if (cluster.length > 0) {
+ clusterRoleDescriptor = {
+ cluster,
+ };
+ }
+
return [
packagePolicy.name,
{
indices: dataStreamsForPermissions.map((ds) =>
getDataStreamPrivileges(ds, packagePolicy.namespace)
),
+ ...clusterRoleDescriptor,
},
];
}
diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts
index 0b6b3579f7b87..9dc05ee2cb4ba 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.test.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts
@@ -33,6 +33,7 @@ import type {
InputsOverride,
NewPackagePolicy,
NewPackagePolicyInput,
+ RegistryPackage,
} from '../../common';
import { IngestManagerError } from '../errors';
@@ -43,6 +44,7 @@ import {
_applyIndexPrivileges,
} from './package_policy';
import { appContextService } from './app_context';
+import { fetchInfo } from './epm/registry';
async function mockedGetAssetsData(_a: any, _b: any, dataset: string) {
if (dataset === 'dataset1') {
@@ -88,6 +90,10 @@ hosts:
];
}
+function mockedRegistryInfo(): RegistryPackage {
+ return {} as RegistryPackage;
+}
+
jest.mock('./epm/packages/assets', () => {
return {
getAssetsData: mockedGetAssetsData,
@@ -100,11 +106,7 @@ jest.mock('./epm/packages', () => {
};
});
-jest.mock('./epm/registry', () => {
- return {
- fetchInfo: () => ({}),
- };
-});
+jest.mock('./epm/registry');
jest.mock('./agent_policy', () => {
return {
@@ -126,12 +128,18 @@ jest.mock('./agent_policy', () => {
};
});
+const mockedFetchInfo = fetchInfo as jest.Mock>;
+
type CombinedExternalCallback = PutPackagePolicyUpdateCallback | PostPackagePolicyCreateCallback;
describe('Package policy service', () => {
- describe('compilePackagePolicyInputs', () => {
+ beforeEach(() => {
+ mockedFetchInfo.mockResolvedValue({} as RegistryPackage);
+ });
+ describe('_compilePackagePolicyInputs', () => {
it('should work with config variables from the stream', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -194,7 +202,8 @@ describe('Package policy service', () => {
});
it('should work with a two level dataset name', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -246,7 +255,8 @@ describe('Package policy service', () => {
});
it('should work with config variables at the input level', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -309,7 +319,8 @@ describe('Package policy service', () => {
});
it('should work with config variables at the package level', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -377,7 +388,8 @@ describe('Package policy service', () => {
});
it('should work with an input with a template and no streams', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [],
policy_templates: [
@@ -419,7 +431,8 @@ describe('Package policy service', () => {
});
it('should work with an input with a template and streams', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -524,7 +537,8 @@ describe('Package policy service', () => {
});
it('should work with a package without input', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
policy_templates: [
{
@@ -540,7 +554,8 @@ describe('Package policy service', () => {
});
it('should work with a package with a empty inputs array', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
policy_templates: [
{
@@ -834,6 +849,59 @@ describe('Package policy service', () => {
expect(modifiedStream.vars!.paths.value).toEqual(expect.arrayContaining(['north', 'south']));
expect(modifiedStream.vars!.period.value).toEqual('12mo');
});
+
+ it('should update elasticsearch.priviles.cluster when updating', async () => {
+ const savedObjectsClient = savedObjectsClientMock.create();
+ const mockPackagePolicy = createPackagePolicyMock();
+
+ const attributes = {
+ ...mockPackagePolicy,
+ inputs: [],
+ };
+
+ mockedFetchInfo.mockResolvedValue({
+ elasticsearch: {
+ privileges: {
+ cluster: ['monitor'],
+ },
+ },
+ } as RegistryPackage);
+
+ savedObjectsClient.get.mockResolvedValue({
+ id: 'test',
+ type: 'abcd',
+ references: [],
+ version: 'test',
+ attributes,
+ });
+
+ savedObjectsClient.update.mockImplementation(
+ async (
+ type: string,
+ id: string,
+ attrs: any
+ ): Promise> => {
+ savedObjectsClient.get.mockResolvedValue({
+ id: 'test',
+ type: 'abcd',
+ references: [],
+ version: 'test',
+ attributes: attrs,
+ });
+ return attrs;
+ }
+ );
+ const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
+
+ const result = await packagePolicyService.update(
+ savedObjectsClient,
+ elasticsearchClient,
+ 'the-package-policy-id',
+ { ...mockPackagePolicy, inputs: [] }
+ );
+
+ expect(result.elasticsearch).toMatchObject({ privileges: { cluster: ['monitor'] } });
+ });
});
describe('runDeleteExternalCallbacks', () => {
diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts
index b7772892f542a..b0c0b9499c68e 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.ts
@@ -114,7 +114,7 @@ class PackagePolicyService {
'There is already a package with the same name on this agent policy'
);
}
-
+ let elasticsearch: PackagePolicy['elasticsearch'];
// Add ids to stream
const packagePolicyId = options?.id || uuid.v4();
let inputs: PackagePolicyInput[] = packagePolicy.inputs.map((input) =>
@@ -155,7 +155,15 @@ class PackagePolicyService {
}
}
- inputs = await this.compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs);
+ const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version);
+ inputs = await this._compilePackagePolicyInputs(
+ registryPkgInfo,
+ pkgInfo,
+ packagePolicy.vars || {},
+ inputs
+ );
+
+ elasticsearch = registryPkgInfo.elasticsearch;
}
const isoDate = new Date().toISOString();
@@ -164,6 +172,7 @@ class PackagePolicyService {
{
...packagePolicy,
inputs,
+ elasticsearch,
revision: 1,
created_at: isoDate,
created_by: options?.user?.username ?? 'system',
@@ -375,15 +384,21 @@ class PackagePolicyService {
);
inputs = enforceFrozenInputs(oldPackagePolicy.inputs, inputs);
-
+ let elasticsearch: PackagePolicy['elasticsearch'];
if (packagePolicy.package?.name) {
const pkgInfo = await getPackageInfo({
savedObjectsClient: soClient,
pkgName: packagePolicy.package.name,
pkgVersion: packagePolicy.package.version,
});
-
- inputs = await this.compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs);
+ const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version);
+ inputs = await this._compilePackagePolicyInputs(
+ registryPkgInfo,
+ pkgInfo,
+ packagePolicy.vars || {},
+ inputs
+ );
+ elasticsearch = registryPkgInfo.elasticsearch;
}
await soClient.update(
@@ -392,6 +407,7 @@ class PackagePolicyService {
{
...restOfPackagePolicy,
inputs,
+ elasticsearch,
revision: oldPackagePolicy.revision + 1,
updated_at: new Date().toISOString(),
updated_by: options?.user?.username ?? 'system',
@@ -563,12 +579,14 @@ class PackagePolicyService {
packageInfo,
packageToPackagePolicyInputs(packageInfo) as InputsOverride[]
);
-
- updatePackagePolicy.inputs = await this.compilePackagePolicyInputs(
+ const registryPkgInfo = await Registry.fetchInfo(packageInfo.name, packageInfo.version);
+ updatePackagePolicy.inputs = await this._compilePackagePolicyInputs(
+ registryPkgInfo,
packageInfo,
updatePackagePolicy.vars || {},
updatePackagePolicy.inputs as PackagePolicyInput[]
);
+ updatePackagePolicy.elasticsearch = registryPkgInfo.elasticsearch;
await this.update(soClient, esClient, id, updatePackagePolicy, options);
result.push({
@@ -618,12 +636,14 @@ class PackagePolicyService {
packageToPackagePolicyInputs(packageInfo) as InputsOverride[],
true
);
-
- updatedPackagePolicy.inputs = await this.compilePackagePolicyInputs(
+ const registryPkgInfo = await Registry.fetchInfo(packageInfo.name, packageInfo.version);
+ updatedPackagePolicy.inputs = await this._compilePackagePolicyInputs(
+ registryPkgInfo,
packageInfo,
updatedPackagePolicy.vars || {},
updatedPackagePolicy.inputs as PackagePolicyInput[]
);
+ updatedPackagePolicy.elasticsearch = registryPkgInfo.elasticsearch;
const hasErrors = 'errors' in updatedPackagePolicy;
@@ -663,12 +683,12 @@ class PackagePolicyService {
}
}
- public async compilePackagePolicyInputs(
+ public async _compilePackagePolicyInputs(
+ registryPkgInfo: RegistryPackage,
pkgInfo: PackageInfo,
vars: PackagePolicy['vars'],
inputs: PackagePolicyInput[]
): Promise {
- const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version);
const inputsPromises = inputs.map(async (input) => {
const compiledInput = await _compilePackagePolicyInput(registryPkgInfo, pkgInfo, vars, input);
const compiledStreams = await _compilePackageStreams(registryPkgInfo, pkgInfo, vars, input);
From fcf86628a078abf4766ef9eea97f2fab70349430 Mon Sep 17 00:00:00 2001
From: Frank Hassanabad
Date: Mon, 18 Oct 2021 11:22:49 -0600
Subject: [PATCH 005/204] Adds missing DOM.Iterable (#115218)
## Summary
It was brought to our attention from security solutions that developers found we are missing the iterators and entries and others from TypeScript that are available when you include within `lib` `DOM.iterable`.
For example without it you cannot use `entries` like this:
Until you add it within he base config. Developers are indicating they noticed that workarounds such as lodash or casting is possible but I think this is the wrong direction to go and it's just an oversight that we missed adding the `DOM.iterable` unless someone tells us otherwise. If it is intentional to omit I would like to add docs to the `tsconfig.base.json` about why we omit it for programmers to understand the intention and why we should discourage use or recommend a library such as lodash instead.
---
tsconfig.base.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 9de81f68110c1..18c0ad38f4601 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -31,7 +31,8 @@
"lib": [
"esnext",
// includes support for browser APIs
- "dom"
+ "dom",
+ "DOM.Iterable"
],
// Node 8 should support everything output by esnext, we override this
// in webpack with loader-level compiler options
From 584d09e60cc2582a9366c7a4d398fbaaf39f564c Mon Sep 17 00:00:00 2001
From: Tim Sullivan
Date: Mon, 18 Oct 2021 10:24:16 -0700
Subject: [PATCH 006/204] [Reporting] Revisit handling timeouts for different
phases of screenshot capture (#113807)
* [Reporting] Revisit handling timeouts for different phases of screenshot capture
* remove translations for changed text
* add wip unit test
* simplify class
* todo more testing
* fix ts
* update snapshots
* simplify open_url
* fixup me
* move setupPage to a method of the ObservableHandler class
* do not pass entire config object to helper functions
* distinguish internal timeouts vs external timeout
* add tests for waitUntil
* checkIsPageOpen test
* restore passing of renderErrors
* updates per feedback
* Update x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
Co-authored-by: Michael Dokolin
* Update x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
Co-authored-by: Michael Dokolin
* Update x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
Co-authored-by: Michael Dokolin
* Update x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
Co-authored-by: Michael Dokolin
* Update x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
Co-authored-by: Michael Dokolin
* fix parsing
* apply simplifications consistently
* dont main waitUntil a higher order component
* resolve the timeouts options outside of the service
* comment correction
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Michael Dokolin
---
.../lib/screenshots/check_browser_open.ts | 22 --
.../screenshots/get_number_of_items.test.ts | 9 +-
.../lib/screenshots/get_number_of_items.ts | 12 +-
.../reporting/server/lib/screenshots/index.ts | 19 ++
.../server/lib/screenshots/observable.test.ts | 10 +-
.../server/lib/screenshots/observable.ts | 179 ++++-----------
.../screenshots/observable_handler.test.ts | 160 +++++++++++++
.../lib/screenshots/observable_handler.ts | 214 ++++++++++++++++++
.../server/lib/screenshots/open_url.ts | 19 +-
.../server/lib/screenshots/wait_for_render.ts | 8 +-
.../screenshots/wait_for_visualizations.ts | 13 +-
.../translations/translations/ja-JP.json | 3 -
.../translations/translations/zh-CN.json | 3 -
13 files changed, 465 insertions(+), 206 deletions(-)
delete mode 100644 x-pack/plugins/reporting/server/lib/screenshots/check_browser_open.ts
create mode 100644 x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts
create mode 100644 x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/check_browser_open.ts b/x-pack/plugins/reporting/server/lib/screenshots/check_browser_open.ts
deleted file mode 100644
index 95bfa7af870fe..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/check_browser_open.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { HeadlessChromiumDriver } from '../../browsers';
-import { getChromiumDisconnectedError } from '../../browsers/chromium';
-
-/*
- * Call this function within error-handling `catch` blocks while setup and wait
- * for the Kibana URL to be ready for screenshot. This detects if a block of
- * code threw an exception because the page is closed or crashed.
- *
- * Also call once after `setup$` fires in the screenshot pipeline
- */
-export const checkPageIsOpen = (browser: HeadlessChromiumDriver) => {
- if (!browser.isPageOpen()) {
- throw getChromiumDisconnectedError();
- }
-};
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts
index 0ca622d67283c..f160fcb8b27ad 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts
@@ -6,6 +6,7 @@
*/
import { set } from 'lodash';
+import { durationToNumber } from '../../../common/schema_utils';
import { HeadlessChromiumDriver } from '../../browsers';
import {
createMockBrowserDriverFactory,
@@ -25,6 +26,7 @@ describe('getNumberOfItems', () => {
let layout: LayoutInstance;
let logger: jest.Mocked;
let browser: HeadlessChromiumDriver;
+ let timeout: number;
beforeEach(async () => {
const schema = createMockConfigSchema(set({}, 'capture.timeouts.waitForElements', 0));
@@ -34,6 +36,7 @@ describe('getNumberOfItems', () => {
captureConfig = config.get('capture');
layout = createMockLayoutInstance(captureConfig);
logger = createMockLevelLogger();
+ timeout = durationToNumber(captureConfig.timeouts.waitForElements);
await createMockBrowserDriverFactory(core, logger, {
evaluate: jest.fn(
@@ -62,7 +65,7 @@ describe('getNumberOfItems', () => {
`;
- await expect(getNumberOfItems(captureConfig, browser, layout, logger)).resolves.toBe(10);
+ await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(10);
});
it('should determine the number of items by selector ', async () => {
@@ -72,7 +75,7 @@ describe('getNumberOfItems', () => {
`;
- await expect(getNumberOfItems(captureConfig, browser, layout, logger)).resolves.toBe(3);
+ await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(3);
});
it('should fall back to the selector when the attribute is empty', async () => {
@@ -82,6 +85,6 @@ describe('getNumberOfItems', () => {
`;
- await expect(getNumberOfItems(captureConfig, browser, layout, logger)).resolves.toBe(2);
+ await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(2);
});
});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts
index 9bbd8e07898be..9e5dfa180fd0f 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts
@@ -6,15 +6,13 @@
*/
import { i18n } from '@kbn/i18n';
-import { durationToNumber } from '../../../common/schema_utils';
import { LevelLogger, startTrace } from '../';
import { HeadlessChromiumDriver } from '../../browsers';
-import { CaptureConfig } from '../../types';
import { LayoutInstance } from '../layouts';
import { CONTEXT_GETNUMBEROFITEMS, CONTEXT_READMETADATA } from './constants';
export const getNumberOfItems = async (
- captureConfig: CaptureConfig,
+ timeout: number,
browser: HeadlessChromiumDriver,
layout: LayoutInstance,
logger: LevelLogger
@@ -33,7 +31,6 @@ export const getNumberOfItems = async (
// the dashboard is using the `itemsCountAttribute` attribute to let us
// know how many items to expect since gridster incrementally adds panels
// we have to use this hint to wait for all of them
- const timeout = durationToNumber(captureConfig.timeouts.waitForElements);
await browser.waitForSelector(
`${renderCompleteSelector},[${itemsCountAttribute}]`,
{ timeout },
@@ -65,11 +62,8 @@ export const getNumberOfItems = async (
logger.error(err);
throw new Error(
i18n.translate('xpack.reporting.screencapture.readVisualizationsError', {
- defaultMessage: `An error occurred when trying to read the page for visualization panel info. You may need to increase '{configKey}'. {error}`,
- values: {
- error: err,
- configKey: 'xpack.reporting.capture.timeouts.waitForElements',
- },
+ defaultMessage: `An error occurred when trying to read the page for visualization panel info: {error}`,
+ values: { error: err },
})
);
}
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/index.ts b/x-pack/plugins/reporting/server/lib/screenshots/index.ts
index 1ca8b5e00fee4..2b8a0d6207a9b 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/index.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/index.ts
@@ -12,6 +12,19 @@ import { LayoutInstance } from '../layouts';
export { getScreenshots$ } from './observable';
+export interface PhaseInstance {
+ timeoutValue: number;
+ configValue: string;
+ label: string;
+}
+
+export interface PhaseTimeouts {
+ openUrl: PhaseInstance;
+ waitForElements: PhaseInstance;
+ renderComplete: PhaseInstance;
+ loadDelay: number;
+}
+
export interface ScreenshotObservableOpts {
logger: LevelLogger;
urlsOrUrlLocatorTuples: UrlOrUrlLocatorTuple[];
@@ -49,6 +62,12 @@ export interface Screenshot {
description: string | null;
}
+export interface PageSetupResults {
+ elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null;
+ timeRange: string | null;
+ error?: Error;
+}
+
export interface ScreenshotResults {
timeRange: string | null;
screenshots: Screenshot[];
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts
index c33bdad44f9e7..3dc06996f0f04 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts
@@ -98,7 +98,6 @@ describe('Screenshot Observable Pipeline', () => {
},
],
"error": undefined,
- "renderErrors": undefined,
"screenshots": Array [
Object {
"data": Object {
@@ -173,7 +172,6 @@ describe('Screenshot Observable Pipeline', () => {
},
],
"error": undefined,
- "renderErrors": undefined,
"screenshots": Array [
Object {
"data": Object {
@@ -225,7 +223,6 @@ describe('Screenshot Observable Pipeline', () => {
},
],
"error": undefined,
- "renderErrors": undefined,
"screenshots": Array [
Object {
"data": Object {
@@ -314,8 +311,7 @@ describe('Screenshot Observable Pipeline', () => {
},
},
],
- "error": [Error: An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. Error: Mock error!],
- "renderErrors": undefined,
+ "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!],
"screenshots": Array [
Object {
"data": Object {
@@ -357,8 +353,7 @@ describe('Screenshot Observable Pipeline', () => {
},
},
],
- "error": [Error: An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. Error: Mock error!],
- "renderErrors": undefined,
+ "error": [Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!],
"screenshots": Array [
Object {
"data": Object {
@@ -465,7 +460,6 @@ describe('Screenshot Observable Pipeline', () => {
},
],
"error": undefined,
- "renderErrors": undefined,
"screenshots": Array [
Object {
"data": Object {
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts
index b8fecdc91a3f2..173dbaaf99557 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts
@@ -8,138 +8,70 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
import { catchError, concatMap, first, mergeMap, take, takeUntil, toArray } from 'rxjs/operators';
+import { durationToNumber } from '../../../common/schema_utils';
import { HeadlessChromiumDriverFactory } from '../../browsers';
import { CaptureConfig } from '../../types';
-import { ElementsPositionAndAttribute, ScreenshotObservableOpts, ScreenshotResults } from './';
-import { checkPageIsOpen } from './check_browser_open';
-import { DEFAULT_PAGELOAD_SELECTOR } from './constants';
-import { getElementPositionAndAttributes } from './get_element_position_data';
-import { getNumberOfItems } from './get_number_of_items';
-import { getScreenshots } from './get_screenshots';
-import { getTimeRange } from './get_time_range';
-import { getRenderErrors } from './get_render_errors';
-import { injectCustomCss } from './inject_css';
-import { openUrl } from './open_url';
-import { waitForRenderComplete } from './wait_for_render';
-import { waitForVisualizations } from './wait_for_visualizations';
+import {
+ ElementPosition,
+ ElementsPositionAndAttribute,
+ PageSetupResults,
+ ScreenshotObservableOpts,
+ ScreenshotResults,
+} from './';
+import { ScreenshotObservableHandler } from './observable_handler';
-const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200;
-const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800;
+export { ElementPosition, ElementsPositionAndAttribute, ScreenshotResults };
-interface ScreenSetupData {
- elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null;
- timeRange: string | null;
- renderErrors?: string[];
- error?: Error;
-}
+const getTimeouts = (captureConfig: CaptureConfig) => ({
+ openUrl: {
+ timeoutValue: durationToNumber(captureConfig.timeouts.openUrl),
+ configValue: `xpack.reporting.capture.timeouts.openUrl`,
+ label: 'open URL',
+ },
+ waitForElements: {
+ timeoutValue: durationToNumber(captureConfig.timeouts.waitForElements),
+ configValue: `xpack.reporting.capture.timeouts.waitForElements`,
+ label: 'wait for elements',
+ },
+ renderComplete: {
+ timeoutValue: durationToNumber(captureConfig.timeouts.renderComplete),
+ configValue: `xpack.reporting.capture.timeouts.renderComplete`,
+ label: 'render complete',
+ },
+ loadDelay: durationToNumber(captureConfig.loadDelay),
+});
export function getScreenshots$(
captureConfig: CaptureConfig,
browserDriverFactory: HeadlessChromiumDriverFactory,
- {
- logger,
- urlsOrUrlLocatorTuples,
- conditionalHeaders,
- layout,
- browserTimezone,
- }: ScreenshotObservableOpts
+ opts: ScreenshotObservableOpts
): Rx.Observable {
const apmTrans = apm.startTransaction(`reporting screenshot pipeline`, 'reporting');
-
const apmCreatePage = apmTrans?.startSpan('create_page', 'wait');
- const create$ = browserDriverFactory.createPage({ browserTimezone }, logger);
+ const { browserTimezone, logger } = opts;
- return create$.pipe(
+ return browserDriverFactory.createPage({ browserTimezone }, logger).pipe(
mergeMap(({ driver, exit$ }) => {
apmCreatePage?.end();
exit$.subscribe({ error: () => apmTrans?.end() });
- return Rx.from(urlsOrUrlLocatorTuples).pipe(
- concatMap((urlOrUrlLocatorTuple, index) => {
- const setup$: Rx.Observable = Rx.of(1).pipe(
- mergeMap(() => {
- // If we're moving to another page in the app, we'll want to wait for the app to tell us
- // it's loaded the next page.
- const page = index + 1;
- const pageLoadSelector =
- page > 1 ? `[data-shared-page="${page}"]` : DEFAULT_PAGELOAD_SELECTOR;
+ const screen = new ScreenshotObservableHandler(driver, opts, getTimeouts(captureConfig));
- return openUrl(
- captureConfig,
- driver,
- urlOrUrlLocatorTuple,
- pageLoadSelector,
- conditionalHeaders,
- logger
- );
- }),
- mergeMap(() => getNumberOfItems(captureConfig, driver, layout, logger)),
- mergeMap(async (itemsCount) => {
- // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout
- const viewport = layout.getViewport(itemsCount) || getDefaultViewPort();
- await Promise.all([
- driver.setViewport(viewport, logger),
- waitForVisualizations(captureConfig, driver, itemsCount, layout, logger),
- ]);
- }),
- mergeMap(async () => {
- // Waiting till _after_ elements have rendered before injecting our CSS
- // allows for them to be displayed properly in many cases
- await injectCustomCss(driver, layout, logger);
-
- const apmPositionElements = apmTrans?.startSpan('position_elements', 'correction');
- if (layout.positionElements) {
- // position panel elements for print layout
- await layout.positionElements(driver, logger);
- }
- if (apmPositionElements) apmPositionElements.end();
-
- await waitForRenderComplete(captureConfig, driver, layout, logger);
- }),
- mergeMap(async () => {
- return await Promise.all([
- getTimeRange(driver, layout, logger),
- getElementPositionAndAttributes(driver, layout, logger),
- getRenderErrors(driver, layout, logger),
- ]).then(([timeRange, elementsPositionAndAttributes, renderErrors]) => ({
- elementsPositionAndAttributes,
- timeRange,
- renderErrors,
- }));
- }),
+ return Rx.from(opts.urlsOrUrlLocatorTuples).pipe(
+ concatMap((urlOrUrlLocatorTuple, index) => {
+ return Rx.of(1).pipe(
+ screen.setupPage(index, urlOrUrlLocatorTuple, apmTrans),
catchError((err) => {
- checkPageIsOpen(driver); // if browser has closed, throw a relevant error about it
+ screen.checkPageIsOpen(); // this fails the job if the browser has closed
logger.error(err);
- return Rx.of({
- elementsPositionAndAttributes: null,
- timeRange: null,
- error: err,
- });
- })
- );
-
- return setup$.pipe(
+ return Rx.of({ ...defaultSetupResult, error: err }); // allow failover screenshot capture
+ }),
takeUntil(exit$),
- mergeMap(async (data: ScreenSetupData): Promise => {
- checkPageIsOpen(driver); // re-check that the browser has not closed
-
- const elements = data.elementsPositionAndAttributes
- ? data.elementsPositionAndAttributes
- : getDefaultElementPosition(layout.getViewport(1));
- const screenshots = await getScreenshots(driver, elements, logger);
- const { timeRange, error: setupError, renderErrors } = data;
- return {
- timeRange,
- screenshots,
- error: setupError,
- renderErrors,
- elementsPositionAndAttributes: elements,
- };
- })
+ screen.getScreenshots()
);
}),
- take(urlsOrUrlLocatorTuples.length),
+ take(opts.urlsOrUrlLocatorTuples.length),
toArray()
);
}),
@@ -147,30 +79,7 @@ export function getScreenshots$(
);
}
-/*
- * If Kibana is showing a non-HTML error message, the viewport might not be
- * provided by the browser.
- */
-const getDefaultViewPort = () => ({
- height: DEFAULT_SCREENSHOT_CLIP_HEIGHT,
- width: DEFAULT_SCREENSHOT_CLIP_WIDTH,
- zoom: 1,
-});
-/*
- * If an error happens setting up the page, we don't know if there actually
- * are any visualizations showing. These defaults should help capture the page
- * enough for the user to see the error themselves
- */
-const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => {
- const height = dimensions?.height || DEFAULT_SCREENSHOT_CLIP_HEIGHT;
- const width = dimensions?.width || DEFAULT_SCREENSHOT_CLIP_WIDTH;
-
- const defaultObject = {
- position: {
- boundingClientRect: { top: 0, left: 0, height, width },
- scroll: { x: 0, y: 0 },
- },
- attributes: {},
- };
- return [defaultObject];
+const defaultSetupResult: PageSetupResults = {
+ elementsPositionAndAttributes: null,
+ timeRange: null,
};
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts
new file mode 100644
index 0000000000000..25a8bed370d86
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts
@@ -0,0 +1,160 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import * as Rx from 'rxjs';
+import { first, map } from 'rxjs/operators';
+import { HeadlessChromiumDriver } from '../../browsers';
+import { ReportingConfigType } from '../../config';
+import { ConditionalHeaders } from '../../export_types/common';
+import {
+ createMockBrowserDriverFactory,
+ createMockConfigSchema,
+ createMockLayoutInstance,
+ createMockLevelLogger,
+ createMockReportingCore,
+} from '../../test_helpers';
+import { LayoutInstance } from '../layouts';
+import { PhaseTimeouts, ScreenshotObservableOpts } from './';
+import { ScreenshotObservableHandler } from './observable_handler';
+
+const logger = createMockLevelLogger();
+
+describe('ScreenshotObservableHandler', () => {
+ let captureConfig: ReportingConfigType['capture'];
+ let layout: LayoutInstance;
+ let conditionalHeaders: ConditionalHeaders;
+ let opts: ScreenshotObservableOpts;
+ let timeouts: PhaseTimeouts;
+ let driver: HeadlessChromiumDriver;
+
+ beforeAll(async () => {
+ captureConfig = {
+ timeouts: {
+ openUrl: 30000,
+ waitForElements: 30000,
+ renderComplete: 30000,
+ },
+ loadDelay: 5000,
+ } as unknown as typeof captureConfig;
+
+ layout = createMockLayoutInstance(captureConfig);
+
+ conditionalHeaders = {
+ headers: { testHeader: 'testHeadValue' },
+ conditions: {} as unknown as ConditionalHeaders['conditions'],
+ };
+
+ opts = {
+ conditionalHeaders,
+ layout,
+ logger,
+ urlsOrUrlLocatorTuples: [],
+ };
+
+ timeouts = {
+ openUrl: {
+ timeoutValue: 60000,
+ configValue: `xpack.reporting.capture.timeouts.openUrl`,
+ label: 'open URL',
+ },
+ waitForElements: {
+ timeoutValue: 30000,
+ configValue: `xpack.reporting.capture.timeouts.waitForElements`,
+ label: 'wait for elements',
+ },
+ renderComplete: {
+ timeoutValue: 60000,
+ configValue: `xpack.reporting.capture.timeouts.renderComplete`,
+ label: 'render complete',
+ },
+ loadDelay: 5000,
+ };
+ });
+
+ beforeEach(async () => {
+ const reporting = await createMockReportingCore(createMockConfigSchema());
+ const driverFactory = await createMockBrowserDriverFactory(reporting, logger);
+ ({ driver } = await driverFactory.createPage({}, logger).pipe(first()).toPromise());
+ driver.isPageOpen = jest.fn().mockImplementation(() => true);
+ });
+
+ describe('waitUntil', () => {
+ it('catches TimeoutError and references the timeout config in a custom message', async () => {
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.interval(1000).pipe(
+ screenshots.waitUntil({
+ timeoutValue: 200,
+ configValue: 'test.config.value',
+ label: 'Test Config',
+ })
+ );
+
+ const testPipeline = () => test$.toPromise();
+ await expect(testPipeline).rejects.toMatchInlineSnapshot(
+ `[Error: The "Test Config" phase took longer than 0.2 seconds. You may need to increase "test.config.value": TimeoutError: Timeout has occurred]`
+ );
+ });
+
+ it('catches other Errors and explains where they were thrown', async () => {
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.throwError(new Error(`Test Error to Throw`)).pipe(
+ screenshots.waitUntil({
+ timeoutValue: 200,
+ configValue: 'test.config.value',
+ label: 'Test Config',
+ })
+ );
+
+ const testPipeline = () => test$.toPromise();
+ await expect(testPipeline).rejects.toMatchInlineSnapshot(
+ `[Error: The "Test Config" phase encountered an error: Error: Test Error to Throw]`
+ );
+ });
+
+ it('is a pass-through if there is no Error', async () => {
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.of('nice to see you').pipe(
+ screenshots.waitUntil({
+ timeoutValue: 20,
+ configValue: 'xxxxxxxxxxxxxxxxx',
+ label: 'xxxxxxxxxxx',
+ })
+ );
+
+ await expect(test$.toPromise()).resolves.toBe(`nice to see you`);
+ });
+ });
+
+ describe('checkPageIsOpen', () => {
+ it('throws a decorated Error when page is not open', async () => {
+ driver.isPageOpen = jest.fn().mockImplementation(() => false);
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.of(234455).pipe(
+ map((input) => {
+ screenshots.checkPageIsOpen();
+ return input;
+ })
+ );
+
+ await expect(test$.toPromise()).rejects.toMatchInlineSnapshot(
+ `[Error: Browser was closed unexpectedly! Check the server logs for more info.]`
+ );
+ });
+
+ it('is a pass-through when the page is open', async () => {
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.of(234455).pipe(
+ map((input) => {
+ screenshots.checkPageIsOpen();
+ return input;
+ })
+ );
+
+ await expect(test$.toPromise()).resolves.toBe(234455);
+ });
+ });
+});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
new file mode 100644
index 0000000000000..87c247273ef04
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
@@ -0,0 +1,214 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import apm from 'elastic-apm-node';
+import * as Rx from 'rxjs';
+import { catchError, mergeMap, timeout } from 'rxjs/operators';
+import { numberToDuration } from '../../../common/schema_utils';
+import { UrlOrUrlLocatorTuple } from '../../../common/types';
+import { HeadlessChromiumDriver } from '../../browsers';
+import { getChromiumDisconnectedError } from '../../browsers/chromium';
+import {
+ PageSetupResults,
+ PhaseInstance,
+ PhaseTimeouts,
+ ScreenshotObservableOpts,
+ ScreenshotResults,
+} from './';
+import { getElementPositionAndAttributes } from './get_element_position_data';
+import { getNumberOfItems } from './get_number_of_items';
+import { getRenderErrors } from './get_render_errors';
+import { getScreenshots } from './get_screenshots';
+import { getTimeRange } from './get_time_range';
+import { injectCustomCss } from './inject_css';
+import { openUrl } from './open_url';
+import { waitForRenderComplete } from './wait_for_render';
+import { waitForVisualizations } from './wait_for_visualizations';
+
+export class ScreenshotObservableHandler {
+ private conditionalHeaders: ScreenshotObservableOpts['conditionalHeaders'];
+ private layout: ScreenshotObservableOpts['layout'];
+ private logger: ScreenshotObservableOpts['logger'];
+ private waitErrorRegistered = false;
+
+ constructor(
+ private readonly driver: HeadlessChromiumDriver,
+ opts: ScreenshotObservableOpts,
+ private timeouts: PhaseTimeouts
+ ) {
+ this.conditionalHeaders = opts.conditionalHeaders;
+ this.layout = opts.layout;
+ this.logger = opts.logger;
+ }
+
+ /*
+ * Decorates a TimeoutError with context of the phase that has timed out.
+ */
+ public waitUntil(phase: PhaseInstance) {
+ const { timeoutValue, label, configValue } = phase;
+ return (source: Rx.Observable) => {
+ return source.pipe(
+ timeout(timeoutValue),
+ catchError((error: string | Error) => {
+ if (this.waitErrorRegistered) {
+ throw error; // do not create a stack of errors within the error
+ }
+
+ this.logger.error(error);
+ let throwError = new Error(`The "${label}" phase encountered an error: ${error}`);
+
+ if (error instanceof Rx.TimeoutError) {
+ throwError = new Error(
+ `The "${label}" phase took longer than` +
+ ` ${numberToDuration(timeoutValue).asSeconds()} seconds.` +
+ ` You may need to increase "${configValue}": ${error}`
+ );
+ }
+
+ this.waitErrorRegistered = true;
+ this.logger.error(throwError);
+ throw throwError;
+ })
+ );
+ };
+ }
+
+ private openUrl(index: number, urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple) {
+ return mergeMap(() =>
+ openUrl(
+ this.timeouts.openUrl.timeoutValue,
+ this.driver,
+ index,
+ urlOrUrlLocatorTuple,
+ this.conditionalHeaders,
+ this.logger
+ )
+ );
+ }
+
+ private waitForElements() {
+ const driver = this.driver;
+ const waitTimeout = this.timeouts.waitForElements.timeoutValue;
+ return (withPageOpen: Rx.Observable) =>
+ withPageOpen.pipe(
+ mergeMap(() => getNumberOfItems(waitTimeout, driver, this.layout, this.logger)),
+ mergeMap(async (itemsCount) => {
+ // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout
+ const viewport = this.layout.getViewport(itemsCount) || getDefaultViewPort();
+ await Promise.all([
+ driver.setViewport(viewport, this.logger),
+ waitForVisualizations(waitTimeout, driver, itemsCount, this.layout, this.logger),
+ ]);
+ })
+ );
+ }
+
+ private completeRender(apmTrans: apm.Transaction | null) {
+ const driver = this.driver;
+ const layout = this.layout;
+ const logger = this.logger;
+
+ return (withElements: Rx.Observable) =>
+ withElements.pipe(
+ mergeMap(async () => {
+ // Waiting till _after_ elements have rendered before injecting our CSS
+ // allows for them to be displayed properly in many cases
+ await injectCustomCss(driver, layout, logger);
+
+ const apmPositionElements = apmTrans?.startSpan('position_elements', 'correction');
+ // position panel elements for print layout
+ await layout.positionElements?.(driver, logger);
+ apmPositionElements?.end();
+
+ await waitForRenderComplete(this.timeouts.loadDelay, driver, layout, logger);
+ }),
+ mergeMap(() =>
+ Promise.all([
+ getTimeRange(driver, layout, logger),
+ getElementPositionAndAttributes(driver, layout, logger),
+ getRenderErrors(driver, layout, logger),
+ ]).then(([timeRange, elementsPositionAndAttributes, renderErrors]) => ({
+ elementsPositionAndAttributes,
+ timeRange,
+ renderErrors,
+ }))
+ )
+ );
+ }
+
+ public setupPage(
+ index: number,
+ urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple,
+ apmTrans: apm.Transaction | null
+ ) {
+ return (initial: Rx.Observable) =>
+ initial.pipe(
+ this.openUrl(index, urlOrUrlLocatorTuple),
+ this.waitUntil(this.timeouts.openUrl),
+ this.waitForElements(),
+ this.waitUntil(this.timeouts.waitForElements),
+ this.completeRender(apmTrans),
+ this.waitUntil(this.timeouts.renderComplete)
+ );
+ }
+
+ public getScreenshots() {
+ return (withRenderComplete: Rx.Observable) =>
+ withRenderComplete.pipe(
+ mergeMap(async (data: PageSetupResults): Promise => {
+ this.checkPageIsOpen(); // fail the report job if the browser has closed
+
+ const elements =
+ data.elementsPositionAndAttributes ??
+ getDefaultElementPosition(this.layout.getViewport(1));
+ const screenshots = await getScreenshots(this.driver, elements, this.logger);
+ const { timeRange, error: setupError } = data;
+
+ return {
+ timeRange,
+ screenshots,
+ error: setupError,
+ elementsPositionAndAttributes: elements,
+ };
+ })
+ );
+ }
+
+ public checkPageIsOpen() {
+ if (!this.driver.isPageOpen()) {
+ throw getChromiumDisconnectedError();
+ }
+ }
+}
+
+const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200;
+const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800;
+
+const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => {
+ const height = dimensions?.height || DEFAULT_SCREENSHOT_CLIP_HEIGHT;
+ const width = dimensions?.width || DEFAULT_SCREENSHOT_CLIP_WIDTH;
+
+ return [
+ {
+ position: {
+ boundingClientRect: { top: 0, left: 0, height, width },
+ scroll: { x: 0, y: 0 },
+ },
+ attributes: {},
+ },
+ ];
+};
+
+/*
+ * If Kibana is showing a non-HTML error message, the viewport might not be
+ * provided by the browser.
+ */
+const getDefaultViewPort = () => ({
+ height: DEFAULT_SCREENSHOT_CLIP_HEIGHT,
+ width: DEFAULT_SCREENSHOT_CLIP_WIDTH,
+ zoom: 1,
+});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts
index 588cd792bdf06..63a5e80289e3e 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts
@@ -6,21 +6,25 @@
*/
import { i18n } from '@kbn/i18n';
-import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../common/types';
import { LevelLogger, startTrace } from '../';
-import { durationToNumber } from '../../../common/schema_utils';
+import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../common/types';
import { HeadlessChromiumDriver } from '../../browsers';
import { ConditionalHeaders } from '../../export_types/common';
-import { CaptureConfig } from '../../types';
+import { DEFAULT_PAGELOAD_SELECTOR } from './constants';
export const openUrl = async (
- captureConfig: CaptureConfig,
+ timeout: number,
browser: HeadlessChromiumDriver,
+ index: number,
urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple,
- waitForSelector: string,
conditionalHeaders: ConditionalHeaders,
logger: LevelLogger
): Promise => {
+ // If we're moving to another page in the app, we'll want to wait for the app to tell us
+ // it's loaded the next page.
+ const page = index + 1;
+ const waitForSelector = page > 1 ? `[data-shared-page="${page}"]` : DEFAULT_PAGELOAD_SELECTOR;
+
const endTrace = startTrace('open_url', 'wait');
let url: string;
let locator: undefined | LocatorParams;
@@ -32,14 +36,13 @@ export const openUrl = async (
}
try {
- const timeout = durationToNumber(captureConfig.timeouts.openUrl);
await browser.open(url, { conditionalHeaders, waitForSelector, timeout, locator }, logger);
} catch (err) {
logger.error(err);
throw new Error(
i18n.translate('xpack.reporting.screencapture.couldntLoadKibana', {
- defaultMessage: `An error occurred when trying to open the Kibana URL. You may need to increase '{configKey}'. {error}`,
- values: { configKey: 'xpack.reporting.capture.timeouts.openUrl', error: err },
+ defaultMessage: `An error occurred when trying to open the Kibana URL: {error}`,
+ values: { error: err },
})
);
}
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts
index f8293bfce3346..1ac4b58b61507 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts
@@ -6,15 +6,13 @@
*/
import { i18n } from '@kbn/i18n';
-import { durationToNumber } from '../../../common/schema_utils';
-import { HeadlessChromiumDriver } from '../../browsers';
-import { CaptureConfig } from '../../types';
import { LevelLogger, startTrace } from '../';
+import { HeadlessChromiumDriver } from '../../browsers';
import { LayoutInstance } from '../layouts';
import { CONTEXT_WAITFORRENDER } from './constants';
export const waitForRenderComplete = async (
- captureConfig: CaptureConfig,
+ loadDelay: number,
browser: HeadlessChromiumDriver,
layout: LayoutInstance,
logger: LevelLogger
@@ -69,7 +67,7 @@ export const waitForRenderComplete = async (
return Promise.all(renderedTasks).then(hackyWaitForVisualizations);
},
- args: [layout.selectors.renderComplete, durationToNumber(captureConfig.loadDelay)],
+ args: [layout.selectors.renderComplete, loadDelay],
},
{ context: CONTEXT_WAITFORRENDER },
logger
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts
index 0ab274da7e1cf..d4bf1db2a0c5a 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts
@@ -6,10 +6,8 @@
*/
import { i18n } from '@kbn/i18n';
-import { durationToNumber } from '../../../common/schema_utils';
import { LevelLogger, startTrace } from '../';
import { HeadlessChromiumDriver } from '../../browsers';
-import { CaptureConfig } from '../../types';
import { LayoutInstance } from '../layouts';
import { CONTEXT_WAITFORELEMENTSTOBEINDOM } from './constants';
@@ -25,7 +23,7 @@ const getCompletedItemsCount = ({ renderCompleteSelector }: SelectorArgs) => {
* 3. Wait for the render complete event to be fired once for each item
*/
export const waitForVisualizations = async (
- captureConfig: CaptureConfig,
+ timeout: number,
browser: HeadlessChromiumDriver,
toEqual: number,
layout: LayoutInstance,
@@ -42,7 +40,6 @@ export const waitForVisualizations = async (
);
try {
- const timeout = durationToNumber(captureConfig.timeouts.renderComplete);
await browser.waitFor(
{ fn: getCompletedItemsCount, args: [{ renderCompleteSelector }], toEqual, timeout },
{ context: CONTEXT_WAITFORELEMENTSTOBEINDOM },
@@ -54,12 +51,8 @@ export const waitForVisualizations = async (
logger.error(err);
throw new Error(
i18n.translate('xpack.reporting.screencapture.couldntFinishRendering', {
- defaultMessage: `An error occurred when trying to wait for {count} visualizations to finish rendering. You may need to increase '{configKey}'. {error}`,
- values: {
- count: toEqual,
- configKey: 'xpack.reporting.capture.timeouts.renderComplete',
- error: err,
- },
+ defaultMessage: `An error occurred when trying to wait for {count} visualizations to finish rendering. {error}`,
+ values: { count: toEqual, error: err },
})
);
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 79d092cebe366..c941cbd9ddf80 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -19451,13 +19451,10 @@
"xpack.reporting.registerFeature.reportingDescription": "Discover、可視化、ダッシュボードから生成されたレポートを管理します。",
"xpack.reporting.registerFeature.reportingTitle": "レポート",
"xpack.reporting.screencapture.browserWasClosed": "ブラウザーは予期せず終了しました。詳細については、サーバーログを確認してください。",
- "xpack.reporting.screencapture.couldntFinishRendering": "{count} 件のビジュアライゼーションのレンダリングが完了するのを待つ間にエラーが発生しました。「{configKey}」を増やす必要があるかもしれません。 {error}",
- "xpack.reporting.screencapture.couldntLoadKibana": "Kibana URL を開こうとするときにエラーが発生しました。「{configKey}」を増やす必要があるかもしれません。 {error}",
"xpack.reporting.screencapture.injectCss": "Kibana CSS をレポート用に更新しようとしたときにエラーが発生しました。{error}",
"xpack.reporting.screencapture.injectingCss": "カスタム css の投入中",
"xpack.reporting.screencapture.logWaitingForElements": "要素または項目のカウント属性を待ち、または見つからないため中断",
"xpack.reporting.screencapture.noElements": "ビジュアライゼーションパネルのページを読み取る間にエラーが発生しました:パネルが見つかりませんでした。",
- "xpack.reporting.screencapture.readVisualizationsError": "ビジュアライゼーションパネル情報のページを読み取ろうとしたときにエラーが発生しました。「{configKey}」を増やす必要があるかもしれません。 {error}",
"xpack.reporting.screencapture.renderIsComplete": "レンダリングが完了しました",
"xpack.reporting.screencapture.screenshotsTaken": "撮影したスクリーンショット:{numScreenhots}",
"xpack.reporting.screencapture.takingScreenshots": "スクリーンショットの撮影中",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index c80cd968f38a8..e9e9f02c8fe99 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -19733,13 +19733,10 @@
"xpack.reporting.registerFeature.reportingDescription": "管理您从 Discover、Visualize 和 Dashboard 生成的报告。",
"xpack.reporting.registerFeature.reportingTitle": "Reporting",
"xpack.reporting.screencapture.browserWasClosed": "浏览器已意外关闭!有关更多信息,请查看服务器日志。",
- "xpack.reporting.screencapture.couldntFinishRendering": "尝试等候 {count} 个可视化完成渲染时发生错误。您可能需要增加“{configKey}”。{error}",
- "xpack.reporting.screencapture.couldntLoadKibana": "尝试打开 Kibana URL 时发生了错误。您可能需要增加“{configKey}”。{error}",
"xpack.reporting.screencapture.injectCss": "尝试为 Reporting 更新 Kibana CSS 时发生错误。{error}",
"xpack.reporting.screencapture.injectingCss": "正在注入定制 css",
"xpack.reporting.screencapture.logWaitingForElements": "等候元素或项目计数属性;或未发现要中断",
"xpack.reporting.screencapture.noElements": "读取页面以获取可视化面板时发生了错误:未找到任何面板。",
- "xpack.reporting.screencapture.readVisualizationsError": "尝试页面以获取可视化面板信息时发生了错误。您可能需要增加“{configKey}”。{error}",
"xpack.reporting.screencapture.renderIsComplete": "渲染已完成",
"xpack.reporting.screencapture.screenshotsTaken": "已捕获的屏幕截图:{numScreenhots}",
"xpack.reporting.screencapture.takingScreenshots": "正在捕获屏幕截图",
From 8c3b4337eba67d83a67e6ae46f252c66c35646c0 Mon Sep 17 00:00:00 2001
From: Uladzislau Lasitsa
Date: Mon, 18 Oct 2021 21:43:49 +0300
Subject: [PATCH 007/204] [DataTable] Add rowHeightsOptions to table (#114637)
* Upgraded the version of EUI to 38.2.0 from 38.0.1
* Updated the i18n mappings required for EUI v.38.2.0
* Update i18n snapshots and resolve linting error
* Removed html_id_generator mocks.
Current mock was failing due to missing useGeneratedHtmlId export. This is safe to remove because EUI contains a .testenv that contains an mock for html_id_generator. More info at https://github.com/elastic/eui/blob/master/src/services/accessibility/html_id_generator.testenv.ts
* Resolve linting error in i18n mapping file
* Removed html_id_generator mocks.
Current mock was failing due to missing useGeneratedHtmlId export. This is safe to remove because EUI contains a .testenv that contains a mock for html_id_generator. More info at https://github.com/elastic/eui/blob/master/src/services/accessibility/html_id_generator.testenv.ts
* Update plugin snapshots
* Resolve merge conflict in license_checker config.ts file
* Upgrade EUI to version 39.0.0 from the original target (38.2.0) to handle an issue found with a functional test during the original upgrade
* Updated the i18n mapping for EUI v.39.0.0
* Update various snapshots to account for the an i18n translation token addition in EUI v. 39.0.0
* Updated test cases marked as obsolete by CI
* Update src/dev/license_checker/config.ts
Removing TODO comments from src/dev/license_checker/config.ts as they are no longer needed.
Co-authored-by: Constance
* Add option auto fit row to content
* Fix tests
* Fix tests
* Add temp fix for correct rendering grid with auto-height when changing data or setting
* Fix lint
* Fix lint and tests
* Adds new dependency for temp fix
Co-authored-by: Brianna Hall
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Bree Hall <40739624+breehall@users.noreply.github.com>
Co-authored-by: Constance
---
src/plugins/vis_types/table/common/types.ts | 1 +
.../__snapshots__/table_vis_fn.test.ts.snap | 1 +
.../table_vis_basic.test.tsx.snap | 3 ++
.../components/table_vis_basic.test.tsx | 3 +-
.../public/components/table_vis_basic.tsx | 36 +++++++++++++++++--
.../public/components/table_vis_cell.tsx | 4 +--
.../public/components/table_vis_options.tsx | 10 ++++++
.../table/public/table_vis_fn.test.ts | 1 +
.../vis_types/table/public/table_vis_fn.ts | 5 +++
.../vis_types/table/public/table_vis_type.ts | 1 +
src/plugins/vis_types/table/public/to_ast.ts | 1 +
11 files changed, 60 insertions(+), 6 deletions(-)
diff --git a/src/plugins/vis_types/table/common/types.ts b/src/plugins/vis_types/table/common/types.ts
index 015af80adf0dc..9f607a964977b 100644
--- a/src/plugins/vis_types/table/common/types.ts
+++ b/src/plugins/vis_types/table/common/types.ts
@@ -24,5 +24,6 @@ export interface TableVisParams {
showTotal: boolean;
totalFunc: AggTypes;
percentageCol: string;
+ autoFitRowToContent?: boolean;
row?: boolean;
}
diff --git a/src/plugins/vis_types/table/public/__snapshots__/table_vis_fn.test.ts.snap b/src/plugins/vis_types/table/public/__snapshots__/table_vis_fn.test.ts.snap
index be7e2822f128d..1a2badbd26634 100644
--- a/src/plugins/vis_types/table/public/__snapshots__/table_vis_fn.test.ts.snap
+++ b/src/plugins/vis_types/table/public/__snapshots__/table_vis_fn.test.ts.snap
@@ -26,6 +26,7 @@ Object {
"type": "render",
"value": Object {
"visConfig": Object {
+ "autoFitRowToContent": false,
"buckets": Array [],
"metrics": Array [
Object {
diff --git a/src/plugins/vis_types/table/public/components/__snapshots__/table_vis_basic.test.tsx.snap b/src/plugins/vis_types/table/public/components/__snapshots__/table_vis_basic.test.tsx.snap
index 85cf9422630d6..38e3dcbb7097c 100644
--- a/src/plugins/vis_types/table/public/components/__snapshots__/table_vis_basic.test.tsx.snap
+++ b/src/plugins/vis_types/table/public/components/__snapshots__/table_vis_basic.test.tsx.snap
@@ -17,6 +17,7 @@ exports[`TableVisBasic should init data grid 1`] = `
"header": "underline",
}
}
+ key="0"
minSizeForControls={1}
onColumnResize={[Function]}
renderCellValue={[Function]}
@@ -55,6 +56,7 @@ exports[`TableVisBasic should init data grid with title provided - for split mod
"header": "underline",
}
}
+ key="0"
minSizeForControls={1}
onColumnResize={[Function]}
renderCellValue={[Function]}
@@ -86,6 +88,7 @@ exports[`TableVisBasic should render the toolbar 1`] = `
"header": "underline",
}
}
+ key="0"
minSizeForControls={1}
onColumnResize={[Function]}
renderCellValue={[Function]}
diff --git a/src/plugins/vis_types/table/public/components/table_vis_basic.test.tsx b/src/plugins/vis_types/table/public/components/table_vis_basic.test.tsx
index 0fb74a41b5df0..0296f2ec1327a 100644
--- a/src/plugins/vis_types/table/public/components/table_vis_basic.test.tsx
+++ b/src/plugins/vis_types/table/public/components/table_vis_basic.test.tsx
@@ -67,6 +67,7 @@ describe('TableVisBasic', () => {
});
it('should sort rows by column and pass the sorted rows for consumers', () => {
+ (createTableVisCell as jest.Mock).mockClear();
const uiStateProps = {
...props.uiStateProps,
sort: {
@@ -96,7 +97,7 @@ describe('TableVisBasic', () => {
visConfig={{ ...props.visConfig, showToolbar: true }}
/>
);
- expect(createTableVisCell).toHaveBeenCalledWith(sortedRows, table.formattedColumns);
+ expect(createTableVisCell).toHaveBeenCalledWith(sortedRows, table.formattedColumns, undefined);
expect(createGridColumns).toHaveBeenCalledWith(
table.columns,
sortedRows,
diff --git a/src/plugins/vis_types/table/public/components/table_vis_basic.tsx b/src/plugins/vis_types/table/public/components/table_vis_basic.tsx
index e627b9e7f92f2..cfe1ce5d40a1e 100644
--- a/src/plugins/vis_types/table/public/components/table_vis_basic.tsx
+++ b/src/plugins/vis_types/table/public/components/table_vis_basic.tsx
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import React, { memo, useCallback, useMemo } from 'react';
+import React, { memo, useCallback, useMemo, useEffect, useState, useRef } from 'react';
import { EuiDataGrid, EuiDataGridProps, EuiDataGridSorting, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { orderBy } from 'lodash';
@@ -47,8 +47,16 @@ export const TableVisBasic = memo(
// renderCellValue is a component which renders a cell based on column and row indexes
const renderCellValue = useMemo(
- () => createTableVisCell(sortedRows, formattedColumns),
- [formattedColumns, sortedRows]
+ () => createTableVisCell(sortedRows, formattedColumns, visConfig.autoFitRowToContent),
+ [formattedColumns, sortedRows, visConfig.autoFitRowToContent]
+ );
+
+ const rowHeightsOptions = useMemo(
+ () =>
+ visConfig.autoFitRowToContent
+ ? ({ defaultHeight: 'auto' } as unknown as EuiDataGridProps['rowHeightsOptions'])
+ : undefined,
+ [visConfig.autoFitRowToContent]
);
// Columns config
@@ -103,6 +111,26 @@ export const TableVisBasic = memo(
[columns, setColumnsWidth]
);
+ const firstRender = useRef(true);
+ const [dataGridUpdateCounter, setDataGridUpdateCounter] = useState(0);
+
+ // key was added as temporary solution to force re-render if we change autoFitRowToContent or we get new data
+ // cause we have problem with correct updating height cache in EUI datagrid when we use auto-height
+ // will be removed as soon as fix problem on EUI side
+ useEffect(() => {
+ // skip first render
+ if (firstRender.current) {
+ firstRender.current = false;
+ return;
+ }
+ // skip if auto height was turned off
+ if (!visConfig.autoFitRowToContent) {
+ return;
+ }
+ // update counter to remount grid from scratch
+ setDataGridUpdateCounter((counter) => counter + 1);
+ }, [visConfig.autoFitRowToContent, table, sort, pagination, columnsWidth]);
+
return (
<>
{title && (
@@ -111,12 +139,14 @@ export const TableVisBasic = memo(
)}
id),
diff --git a/src/plugins/vis_types/table/public/components/table_vis_cell.tsx b/src/plugins/vis_types/table/public/components/table_vis_cell.tsx
index 9749cdcb5740c..7d7af447db489 100644
--- a/src/plugins/vis_types/table/public/components/table_vis_cell.tsx
+++ b/src/plugins/vis_types/table/public/components/table_vis_cell.tsx
@@ -13,7 +13,7 @@ import { DatatableRow } from 'src/plugins/expressions';
import { FormattedColumns } from '../types';
export const createTableVisCell =
- (rows: DatatableRow[], formattedColumns: FormattedColumns) =>
+ (rows: DatatableRow[], formattedColumns: FormattedColumns, autoFitRowToContent?: boolean) =>
({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => {
const rowValue = rows[rowIndex][columnId];
const column = formattedColumns[columnId];
@@ -28,7 +28,7 @@ export const createTableVisCell =
*/
dangerouslySetInnerHTML={{ __html: content }} // eslint-disable-line react/no-danger
data-test-subj="tbvChartCellContent"
- className="tbvChartCellContent"
+ className={autoFitRowToContent ? '' : 'tbvChartCellContent'}
/>
);
diff --git a/src/plugins/vis_types/table/public/components/table_vis_options.tsx b/src/plugins/vis_types/table/public/components/table_vis_options.tsx
index 8a6b8586fce7d..698aca6034a6b 100644
--- a/src/plugins/vis_types/table/public/components/table_vis_options.tsx
+++ b/src/plugins/vis_types/table/public/components/table_vis_options.tsx
@@ -93,6 +93,16 @@ function TableOptions({
data-test-subj="showMetricsAtAllLevels"
/>
+
+
{
splitColumn: undefined,
splitRow: undefined,
showMetricsAtAllLevels: false,
+ autoFitRowToContent: false,
sort: {
columnIndex: null,
direction: null,
diff --git a/src/plugins/vis_types/table/public/table_vis_fn.ts b/src/plugins/vis_types/table/public/table_vis_fn.ts
index ebddb0b4b7fef..861923ef5086e 100644
--- a/src/plugins/vis_types/table/public/table_vis_fn.ts
+++ b/src/plugins/vis_types/table/public/table_vis_fn.ts
@@ -118,6 +118,11 @@ export const createTableVisFn = (): TableExpressionFunctionDefinition => ({
defaultMessage: 'Specifies calculating function for the total row. Possible options are: ',
}),
},
+ autoFitRowToContent: {
+ types: ['boolean'],
+ help: '',
+ default: false,
+ },
},
fn(input, args, handlers) {
const convertedData = tableVisResponseHandler(input, args);
diff --git a/src/plugins/vis_types/table/public/table_vis_type.ts b/src/plugins/vis_types/table/public/table_vis_type.ts
index 4664e87cea79b..a641224e23f52 100644
--- a/src/plugins/vis_types/table/public/table_vis_type.ts
+++ b/src/plugins/vis_types/table/public/table_vis_type.ts
@@ -35,6 +35,7 @@ export const tableVisTypeDefinition: VisTypeDefinition = {
showToolbar: false,
totalFunc: 'sum',
percentageCol: '',
+ autoFitRowToContent: false,
},
},
editorConfig: {
diff --git a/src/plugins/vis_types/table/public/to_ast.ts b/src/plugins/vis_types/table/public/to_ast.ts
index 8e1c92c8dde4f..0268708f22dfe 100644
--- a/src/plugins/vis_types/table/public/to_ast.ts
+++ b/src/plugins/vis_types/table/public/to_ast.ts
@@ -64,6 +64,7 @@ export const toExpressionAst: VisToExpressionAst = (vis, params)
showMetricsAtAllLevels: vis.params.showMetricsAtAllLevels,
showToolbar: vis.params.showToolbar,
showTotal: vis.params.showTotal,
+ autoFitRowToContent: vis.params.autoFitRowToContent,
totalFunc: vis.params.totalFunc,
title: vis.title,
metrics: metrics.map(prepareDimension),
From 4dcb09df59d4f9c4400b8cfc5b279179932da7d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cau=C3=AA=20Marcondes?=
<55978943+cauemarcondes@users.noreply.github.com>
Date: Mon, 18 Oct 2021 14:52:01 -0400
Subject: [PATCH 008/204] [APM] Adding error rate tests (#115190)
---
.../tests/error_rate/service_apis.ts | 213 ++++++++++++++++++
.../test/apm_api_integration/tests/index.ts | 4 +
2 files changed, 217 insertions(+)
create mode 100644 x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts
diff --git a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts
new file mode 100644
index 0000000000000..75ea10ed4d9d4
--- /dev/null
+++ b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts
@@ -0,0 +1,213 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { service, timerange } from '@elastic/apm-generator';
+import expect from '@kbn/expect';
+import { mean, meanBy, sumBy } from 'lodash';
+import { LatencyAggregationType } from '../../../../plugins/apm/common/latency_aggregation_types';
+import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number';
+import { PromiseReturnType } from '../../../../plugins/observability/typings/common';
+import { FtrProviderContext } from '../../common/ftr_provider_context';
+import { registry } from '../../common/registry';
+
+export default function ApiTest({ getService }: FtrProviderContext) {
+ const apmApiClient = getService('apmApiClient');
+ const traceData = getService('traceData');
+
+ const serviceName = 'synth-go';
+ const start = new Date('2021-01-01T00:00:00.000Z').getTime();
+ const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
+
+ async function getErrorRateValues({
+ processorEvent,
+ }: {
+ processorEvent: 'transaction' | 'metric';
+ }) {
+ const commonQuery = {
+ start: new Date(start).toISOString(),
+ end: new Date(end).toISOString(),
+ environment: 'ENVIRONMENT_ALL',
+ };
+ const [
+ serviceInventoryAPIResponse,
+ transactionsErrorRateChartAPIResponse,
+ transactionsGroupDetailsAPIResponse,
+ serviceInstancesAPIResponse,
+ ] = await Promise.all([
+ apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/services',
+ params: {
+ query: {
+ ...commonQuery,
+ kuery: `service.name : "${serviceName}" and processor.event : "${processorEvent}"`,
+ },
+ },
+ }),
+ apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate',
+ params: {
+ path: { serviceName },
+ query: {
+ ...commonQuery,
+ kuery: `processor.event : "${processorEvent}"`,
+ transactionType: 'request',
+ },
+ },
+ }),
+ apmApiClient.readUser({
+ endpoint: `GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics`,
+ params: {
+ path: { serviceName },
+ query: {
+ ...commonQuery,
+ kuery: `processor.event : "${processorEvent}"`,
+ transactionType: 'request',
+ latencyAggregationType: 'avg' as LatencyAggregationType,
+ },
+ },
+ }),
+ apmApiClient.readUser({
+ endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`,
+ params: {
+ path: { serviceName },
+ query: {
+ ...commonQuery,
+ kuery: `processor.event : "${processorEvent}"`,
+ transactionType: 'request',
+ latencyAggregationType: 'avg' as LatencyAggregationType,
+ },
+ },
+ }),
+ ]);
+
+ const serviceInventoryErrorRate =
+ serviceInventoryAPIResponse.body.items[0].transactionErrorRate;
+
+ const errorRateChartApiMean = meanBy(
+ transactionsErrorRateChartAPIResponse.body.currentPeriod.transactionErrorRate.filter(
+ (item) => isFiniteNumber(item.y) && item.y > 0
+ ),
+ 'y'
+ );
+
+ const transactionsGroupErrorRateSum = sumBy(
+ transactionsGroupDetailsAPIResponse.body.transactionGroups,
+ 'errorRate'
+ );
+
+ const serviceInstancesErrorRateSum = sumBy(
+ serviceInstancesAPIResponse.body.currentPeriod,
+ 'errorRate'
+ );
+
+ return {
+ serviceInventoryErrorRate,
+ errorRateChartApiMean,
+ transactionsGroupErrorRateSum,
+ serviceInstancesErrorRateSum,
+ };
+ }
+
+ let errorRateMetricValues: PromiseReturnType;
+ let errorTransactionValues: PromiseReturnType;
+
+ registry.when('Services APIs', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => {
+ describe('when data is loaded ', () => {
+ const GO_PROD_LIST_RATE = 75;
+ const GO_PROD_LIST_ERROR_RATE = 25;
+ const GO_PROD_ID_RATE = 50;
+ const GO_PROD_ID_ERROR_RATE = 50;
+ before(async () => {
+ const serviceGoProdInstance = service(serviceName, 'production', 'go').instance(
+ 'instance-a'
+ );
+
+ const transactionNameProductList = 'GET /api/product/list';
+ const transactionNameProductId = 'GET /api/product/:id';
+
+ await traceData.index([
+ ...timerange(start, end)
+ .interval('1m')
+ .rate(GO_PROD_LIST_RATE)
+ .flatMap((timestamp) =>
+ serviceGoProdInstance
+ .transaction(transactionNameProductList)
+ .timestamp(timestamp)
+ .duration(1000)
+ .success()
+ .serialize()
+ ),
+ ...timerange(start, end)
+ .interval('1m')
+ .rate(GO_PROD_LIST_ERROR_RATE)
+ .flatMap((timestamp) =>
+ serviceGoProdInstance
+ .transaction(transactionNameProductList)
+ .duration(1000)
+ .timestamp(timestamp)
+ .failure()
+ .serialize()
+ ),
+ ...timerange(start, end)
+ .interval('1m')
+ .rate(GO_PROD_ID_RATE)
+ .flatMap((timestamp) =>
+ serviceGoProdInstance
+ .transaction(transactionNameProductId)
+ .timestamp(timestamp)
+ .duration(1000)
+ .success()
+ .serialize()
+ ),
+ ...timerange(start, end)
+ .interval('1m')
+ .rate(GO_PROD_ID_ERROR_RATE)
+ .flatMap((timestamp) =>
+ serviceGoProdInstance
+ .transaction(transactionNameProductId)
+ .duration(1000)
+ .timestamp(timestamp)
+ .failure()
+ .serialize()
+ ),
+ ]);
+ });
+
+ after(() => traceData.clean());
+
+ describe('compare error rate value between service inventory, error rate chart, service inventory and transactions apis', () => {
+ before(async () => {
+ [errorTransactionValues, errorRateMetricValues] = await Promise.all([
+ getErrorRateValues({ processorEvent: 'transaction' }),
+ getErrorRateValues({ processorEvent: 'metric' }),
+ ]);
+ });
+
+ it('returns same avg error rate value for Transaction-based and Metric-based data', () => {
+ [
+ errorTransactionValues.serviceInventoryErrorRate,
+ errorTransactionValues.errorRateChartApiMean,
+ errorTransactionValues.serviceInstancesErrorRateSum,
+ errorRateMetricValues.serviceInventoryErrorRate,
+ errorRateMetricValues.errorRateChartApiMean,
+ errorRateMetricValues.serviceInstancesErrorRateSum,
+ ].forEach((value) =>
+ expect(value).to.be.equal(mean([GO_PROD_LIST_ERROR_RATE, GO_PROD_ID_ERROR_RATE]) / 100)
+ );
+ });
+
+ it('returns same sum error rate value for Transaction-based and Metric-based data', () => {
+ [
+ errorTransactionValues.transactionsGroupErrorRateSum,
+ errorRateMetricValues.transactionsGroupErrorRateSum,
+ ].forEach((value) =>
+ expect(value).to.be.equal((GO_PROD_LIST_ERROR_RATE + GO_PROD_ID_ERROR_RATE) / 100)
+ );
+ });
+ });
+ });
+ });
+}
diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts
index c15a7d39a6cf6..09f4e2596ea46 100644
--- a/x-pack/test/apm_api_integration/tests/index.ts
+++ b/x-pack/test/apm_api_integration/tests/index.ts
@@ -229,6 +229,10 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte
loadTestFile(require.resolve('./historical_data/has_data'));
});
+ describe('error_rate/service_apis', function () {
+ loadTestFile(require.resolve('./error_rate/service_apis'));
+ });
+
describe('latency/service_apis', function () {
loadTestFile(require.resolve('./latency/service_apis'));
});
From 83e9c7afdf18a1bd338da3497dfa8370e810e6ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yulia=20=C4=8Cech?=
<6585477+yuliacech@users.noreply.github.com>
Date: Mon, 18 Oct 2021 20:55:57 +0200
Subject: [PATCH 009/204] [Snapshot and Restore] Server side snapshots
pagination (#110266)
* [Snapshot % Restore] Added server side pagination and sorting to get snapshots route, refactored snapshots table to use EuiBasicTable with controlled pagination instead of EuiInMemoryTable
* [Snapshot & Restore] Added server side sorting by shards and failed shards counts
* [Snapshot & Restore] Fixed i18n errors
* [Snapshot & Restore] Added server side sorting by repository
* [Snapshot & Restore] Implemented server side search request for snapshot, repository and policy name
* [Snapshot & Restore] Fixed eslint errors
* [Snapshot & Restore] Removed uncommented code
* [Snapshot & Restore] Fixed pagination/search bug
* [Snapshot & Restore] Fixed pagination/search bug
* [Snapshot & Restore] Fixed text truncate bug
* [Snapshot & Restore] Fixed non existent repository search error
* Update x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx
Co-authored-by: CJ Cenizal
* Update x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx
Co-authored-by: James Rodewig <40268737+jrodewig@users.noreply.github.com>
* [Snapshot & Restore] Fixed missing i18n and no snapshots callout
* [Snapshot & Restore] Moved "getSnapshotSearchWildcard" to a separate file and added unit tests
* [Snapshot & Restore] Added api integration tests for "get snapshots" endpoint (pagination, sorting, search)
* [Snapshot & Restore] Renamed SnapshotSearchOptions/SnapshotTableOptions to -Params and added the link to the specs issue
* [Snapshot & Restore] Fixed search wildcard to also match string in the middle of the value, not only starting with the string. Also updated the tests following the code review suggestions to make them more readable.
* [Snapshot & Restore] Added incremental search back to snapshots list and a debounce of 500ms
* [Snapshot & Restore] Updated snapshot search debounce value and extracted it into a constant
* [Snapshot & Restore] Renamed debounceValue to cachedListParams and added a comment why debounce is used
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: CJ Cenizal
Co-authored-by: James Rodewig <40268737+jrodewig@users.noreply.github.com>
---
.../helpers/http_requests.ts | 4 +-
.../__jest__/client_integration/home.test.ts | 14 +-
.../snapshot_restore/common/constants.ts | 6 -
.../public/application/lib/index.ts | 9 +
.../application/lib/snapshot_list_params.ts | 88 +++
.../{snapshot_table => components}/index.ts | 4 +
.../components/repository_empty_prompt.tsx | 62 ++
.../components/repository_error.tsx | 51 ++
.../components/snapshot_empty_prompt.tsx | 123 +++
.../components/snapshot_search_bar.tsx | 178 +++++
.../snapshot_table.tsx | 224 ++----
.../home/snapshot_list/snapshot_list.tsx | 315 ++------
.../services/http/snapshot_requests.ts | 7 +-
.../snapshot_restore/public/shared_imports.ts | 2 +
.../lib/get_snapshot_search_wildcard.test.ts | 60 ++
.../lib/get_snapshot_search_wildcard.ts | 30 +
.../server/routes/api/snapshots.test.ts | 4 +
.../server/routes/api/snapshots.ts | 102 ++-
.../server/routes/api/validate_schemas.ts | 25 +
.../snapshot_restore/server/routes/helpers.ts | 2 +-
.../translations/translations/ja-JP.json | 3 -
.../translations/translations/zh-CN.json | 3 -
.../apis/management/snapshot_restore/index.ts | 3 +-
.../snapshot_restore/lib/elasticsearch.ts | 39 +-
.../management/snapshot_restore/lib/index.ts | 2 +-
.../{snapshot_restore.ts => policies.ts} | 11 +-
.../management/snapshot_restore/snapshots.ts | 729 ++++++++++++++++++
x-pack/test/api_integration/config.ts | 1 +
28 files changed, 1640 insertions(+), 461 deletions(-)
create mode 100644 x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts
rename x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/{snapshot_table => components}/index.ts (55%)
create mode 100644 x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx
create mode 100644 x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx
create mode 100644 x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx
create mode 100644 x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx
rename x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/{snapshot_table => components}/snapshot_table.tsx (71%)
create mode 100644 x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.test.ts
create mode 100644 x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.ts
rename x-pack/test/api_integration/apis/management/snapshot_restore/{snapshot_restore.ts => policies.ts} (95%)
create mode 100644 x-pack/test/api_integration/apis/management/snapshot_restore/snapshots.ts
diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts
index 45b8b23cae477..605265f7311ba 100644
--- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts
+++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts
@@ -6,7 +6,7 @@
*/
import sinon, { SinonFakeServer } from 'sinon';
-import { API_BASE_PATH } from '../../../common/constants';
+import { API_BASE_PATH } from '../../../common';
type HttpResponse = Record | any[];
@@ -54,7 +54,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
};
const setLoadSnapshotsResponse = (response: HttpResponse = {}) => {
- const defaultResponse = { errors: {}, snapshots: [], repositories: [] };
+ const defaultResponse = { errors: {}, snapshots: [], repositories: [], total: 0 };
server.respondWith('GET', `${API_BASE_PATH}snapshots`, mockResponse(defaultResponse, response));
};
diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts
index 52303e1134f9d..071868e23f7fe 100644
--- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts
+++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts
@@ -8,7 +8,7 @@
import { act } from 'react-dom/test-utils';
import * as fixtures from '../../test/fixtures';
import { SNAPSHOT_STATE } from '../../public/application/constants';
-import { API_BASE_PATH } from '../../common/constants';
+import { API_BASE_PATH } from '../../common';
import {
setupEnvironment,
pageHelpers,
@@ -431,6 +431,7 @@ describe('', () => {
httpRequestsMockHelpers.setLoadSnapshotsResponse({
snapshots: [],
repositories: ['my-repo'],
+ total: 0,
});
testBed = await setup();
@@ -469,6 +470,7 @@ describe('', () => {
httpRequestsMockHelpers.setLoadSnapshotsResponse({
snapshots,
repositories: [REPOSITORY_NAME],
+ total: 2,
});
testBed = await setup();
@@ -501,18 +503,10 @@ describe('', () => {
});
});
- test('should show a warning if the number of snapshots exceeded the limit', () => {
- // We have mocked the SNAPSHOT_LIST_MAX_SIZE to 2, so the warning should display
- const { find, exists } = testBed;
- expect(exists('maxSnapshotsWarning')).toBe(true);
- expect(find('maxSnapshotsWarning').text()).toContain(
- 'Cannot show the full list of snapshots'
- );
- });
-
test('should show a warning if one repository contains errors', async () => {
httpRequestsMockHelpers.setLoadSnapshotsResponse({
snapshots,
+ total: 2,
repositories: [REPOSITORY_NAME],
errors: {
repository_with_errors: {
diff --git a/x-pack/plugins/snapshot_restore/common/constants.ts b/x-pack/plugins/snapshot_restore/common/constants.ts
index a7c83ecf702e0..b18e118dc5ff6 100644
--- a/x-pack/plugins/snapshot_restore/common/constants.ts
+++ b/x-pack/plugins/snapshot_restore/common/constants.ts
@@ -65,9 +65,3 @@ export const TIME_UNITS: { [key: string]: 'd' | 'h' | 'm' | 's' } = {
MINUTE: 'm',
SECOND: 's',
};
-
-/**
- * [Temporary workaround] In order to prevent client-side performance issues for users with a large number of snapshots,
- * we set a hard-coded limit on the number of snapshots we return from the ES snapshots API
- */
-export const SNAPSHOT_LIST_MAX_SIZE = 1000;
diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/index.ts b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts
index 1ec4d5b2907f2..19a42bef4cea4 100644
--- a/x-pack/plugins/snapshot_restore/public/application/lib/index.ts
+++ b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts
@@ -6,3 +6,12 @@
*/
export { useDecodedParams } from './use_decoded_params';
+
+export {
+ SortField,
+ SortDirection,
+ SnapshotListParams,
+ getListParams,
+ getQueryFromListParams,
+ DEFAULT_SNAPSHOT_LIST_PARAMS,
+} from './snapshot_list_params';
diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts b/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts
new file mode 100644
index 0000000000000..b75a3e01fb617
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Direction, Query } from '@elastic/eui';
+
+export type SortField =
+ | 'snapshot'
+ | 'repository'
+ | 'indices'
+ | 'startTimeInMillis'
+ | 'durationInMillis'
+ | 'shards.total'
+ | 'shards.failed';
+
+export type SortDirection = Direction;
+
+interface SnapshotTableParams {
+ sortField: SortField;
+ sortDirection: SortDirection;
+ pageIndex: number;
+ pageSize: number;
+}
+interface SnapshotSearchParams {
+ searchField?: string;
+ searchValue?: string;
+ searchMatch?: string;
+ searchOperator?: string;
+}
+export type SnapshotListParams = SnapshotTableParams & SnapshotSearchParams;
+
+// By default, we'll display the most recent snapshots at the top of the table (no query).
+export const DEFAULT_SNAPSHOT_LIST_PARAMS: SnapshotListParams = {
+ sortField: 'startTimeInMillis',
+ sortDirection: 'desc',
+ pageIndex: 0,
+ pageSize: 20,
+};
+
+const resetSearchOptions = (listParams: SnapshotListParams): SnapshotListParams => ({
+ ...listParams,
+ searchField: undefined,
+ searchValue: undefined,
+ searchMatch: undefined,
+ searchOperator: undefined,
+});
+
+// to init the query for repository and policyName search passed via url
+export const getQueryFromListParams = (listParams: SnapshotListParams): Query => {
+ const { searchField, searchValue } = listParams;
+ if (!searchField || !searchValue) {
+ return Query.MATCH_ALL;
+ }
+ return Query.parse(`${searchField}=${searchValue}`);
+};
+
+export const getListParams = (listParams: SnapshotListParams, query: Query): SnapshotListParams => {
+ if (!query) {
+ return resetSearchOptions(listParams);
+ }
+ const clause = query.ast.clauses[0];
+ if (!clause) {
+ return resetSearchOptions(listParams);
+ }
+ // term queries (free word search) converts to snapshot name search
+ if (clause.type === 'term') {
+ return {
+ ...listParams,
+ searchField: 'snapshot',
+ searchValue: String(clause.value),
+ searchMatch: clause.match,
+ searchOperator: 'eq',
+ };
+ }
+ if (clause.type === 'field') {
+ return {
+ ...listParams,
+ searchField: clause.field,
+ searchValue: String(clause.value),
+ searchMatch: clause.match,
+ searchOperator: clause.operator,
+ };
+ }
+ return resetSearchOptions(listParams);
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/index.ts b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/index.ts
similarity index 55%
rename from x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/index.ts
rename to x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/index.ts
index 7ea85f4150900..8c69ca0297e3e 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/index.ts
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/index.ts
@@ -6,3 +6,7 @@
*/
export { SnapshotTable } from './snapshot_table';
+export { RepositoryError } from './repository_error';
+export { RepositoryEmptyPrompt } from './repository_empty_prompt';
+export { SnapshotEmptyPrompt } from './snapshot_empty_prompt';
+export { SnapshotSearchBar } from './snapshot_search_bar';
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx
new file mode 100644
index 0000000000000..4c5e050ea489c
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { EuiButton, EuiEmptyPrompt, EuiPageContent } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { reactRouterNavigate } from '../../../../../shared_imports';
+import { linkToAddRepository } from '../../../../services/navigation';
+
+export const RepositoryEmptyPrompt: React.FunctionComponent = () => {
+ const history = useHistory();
+ return (
+
+
+
+
+ }
+ body={
+ <>
+
+
+
+
+
+
+
+
+ >
+ }
+ data-test-subj="emptyPrompt"
+ />
+
+ );
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx
new file mode 100644
index 0000000000000..d3902770333cc
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiEmptyPrompt, EuiLink, EuiPageContent } from '@elastic/eui';
+import { reactRouterNavigate } from '../../../../../shared_imports';
+import { linkToRepositories } from '../../../../services/navigation';
+
+export const RepositoryError: React.FunctionComponent = () => {
+ const history = useHistory();
+ return (
+
+
+
+
+ }
+ body={
+
+
+
+
+ ),
+ }}
+ />
+
+ }
+ />
+
+ );
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx
new file mode 100644
index 0000000000000..2cfc1d5ebefc5
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx
@@ -0,0 +1,123 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { Fragment } from 'react';
+import { useHistory } from 'react-router-dom';
+import { EuiButton, EuiEmptyPrompt, EuiIcon, EuiLink, EuiPageContent } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../../common';
+import { reactRouterNavigate, WithPrivileges } from '../../../../../shared_imports';
+import { linkToAddPolicy, linkToPolicies } from '../../../../services/navigation';
+import { useCore } from '../../../../app_context';
+
+export const SnapshotEmptyPrompt: React.FunctionComponent<{ policiesCount: number }> = ({
+ policiesCount,
+}) => {
+ const { docLinks } = useCore();
+ const history = useHistory();
+ return (
+
+
+
+
+ }
+ body={
+ `cluster.${name}`)}>
+ {({ hasPrivileges }) =>
+ hasPrivileges ? (
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+ {policiesCount === 0 ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ ) : (
+
+
+
+
+
+
+ {' '}
+
+
+
+
+ )
+ }
+
+ }
+ data-test-subj="emptyPrompt"
+ />
+
+ );
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx
new file mode 100644
index 0000000000000..b3e2c24e396f0
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx
@@ -0,0 +1,178 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState } from 'react';
+import useDebounce from 'react-use/lib/useDebounce';
+
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import { SearchFilterConfig } from '@elastic/eui/src/components/search_bar/search_filters';
+import { SchemaType } from '@elastic/eui/src/components/search_bar/search_box';
+import { EuiSearchBarOnChangeArgs } from '@elastic/eui/src/components/search_bar/search_bar';
+import { EuiButton, EuiCallOut, EuiSearchBar, EuiSpacer, Query } from '@elastic/eui';
+import { SnapshotDeleteProvider } from '../../../../components';
+import { SnapshotDetails } from '../../../../../../common/types';
+import { getQueryFromListParams, SnapshotListParams, getListParams } from '../../../../lib';
+
+const SEARCH_DEBOUNCE_VALUE_MS = 200;
+
+const onlyOneClauseMessage = i18n.translate(
+ 'xpack.snapshotRestore.snapshotList.searchBar.onlyOneClauseMessage',
+ {
+ defaultMessage: 'You can only use one clause in the search bar',
+ }
+);
+// for now limit the search bar to snapshot, repository and policyName queries
+const searchSchema: SchemaType = {
+ strict: true,
+ fields: {
+ snapshot: {
+ type: 'string',
+ },
+ repository: {
+ type: 'string',
+ },
+ policyName: {
+ type: 'string',
+ },
+ },
+};
+
+interface Props {
+ listParams: SnapshotListParams;
+ setListParams: (listParams: SnapshotListParams) => void;
+ reload: () => void;
+ selectedItems: SnapshotDetails[];
+ onSnapshotDeleted: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void;
+ repositories: string[];
+}
+
+export const SnapshotSearchBar: React.FunctionComponent = ({
+ listParams,
+ setListParams,
+ reload,
+ selectedItems,
+ onSnapshotDeleted,
+ repositories,
+}) => {
+ const [cachedListParams, setCachedListParams] = useState(listParams);
+ // send the request after the user has stopped typing
+ useDebounce(
+ () => {
+ setListParams(cachedListParams);
+ },
+ SEARCH_DEBOUNCE_VALUE_MS,
+ [cachedListParams]
+ );
+
+ const deleteButton = selectedItems.length ? (
+
+ {(
+ deleteSnapshotPrompt: (
+ ids: Array<{ snapshot: string; repository: string }>,
+ onSuccess?: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void
+ ) => void
+ ) => {
+ return (
+
+ deleteSnapshotPrompt(
+ selectedItems.map(({ snapshot, repository }) => ({ snapshot, repository })),
+ onSnapshotDeleted
+ )
+ }
+ color="danger"
+ data-test-subj="srSnapshotListBulkDeleteActionButton"
+ >
+
+
+ );
+ }}
+
+ ) : (
+ []
+ );
+ const searchFilters: SearchFilterConfig[] = [
+ {
+ type: 'field_value_selection' as const,
+ field: 'repository',
+ name: i18n.translate('xpack.snapshotRestore.snapshotList.table.repositoryFilterLabel', {
+ defaultMessage: 'Repository',
+ }),
+ operator: 'exact',
+ multiSelect: false,
+ options: repositories.map((repository) => ({
+ value: repository,
+ view: repository,
+ })),
+ },
+ ];
+ const reloadButton = (
+
+
+
+ );
+
+ const [query, setQuery] = useState(getQueryFromListParams(listParams));
+ const [error, setError] = useState(null);
+
+ const onSearchBarChange = (args: EuiSearchBarOnChangeArgs) => {
+ const { query: changedQuery, error: queryError } = args;
+ if (queryError) {
+ setError(queryError);
+ } else if (changedQuery) {
+ setError(null);
+ setQuery(changedQuery);
+ if (changedQuery.ast.clauses.length > 1) {
+ setError({ name: onlyOneClauseMessage, message: onlyOneClauseMessage });
+ } else {
+ setCachedListParams(getListParams(listParams, changedQuery));
+ }
+ }
+ };
+
+ return (
+ <>
+
+
+ {error ? (
+ <>
+
+ }
+ />
+
+ >
+ ) : null}
+ >
+ );
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_table.tsx
similarity index 71%
rename from x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx
rename to x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_table.tsx
index 47f8d9b833e40..5db702fcbd963 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_table.tsx
@@ -7,34 +7,28 @@
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types';
+
import {
- EuiButton,
- EuiInMemoryTable,
EuiLink,
- Query,
EuiLoadingSpinner,
EuiToolTip,
EuiButtonIcon,
+ Criteria,
+ EuiBasicTable,
} from '@elastic/eui';
-
import { SnapshotDetails } from '../../../../../../common/types';
-import { UseRequestResponse } from '../../../../../shared_imports';
+import { UseRequestResponse, reactRouterNavigate } from '../../../../../shared_imports';
import { SNAPSHOT_STATE, UIM_SNAPSHOT_SHOW_DETAILS_CLICK } from '../../../../constants';
import { useServices } from '../../../../app_context';
-import { linkToRepository, linkToRestoreSnapshot } from '../../../../services/navigation';
+import {
+ linkToRepository,
+ linkToRestoreSnapshot,
+ linkToSnapshot as openSnapshotDetailsUrl,
+} from '../../../../services/navigation';
+import { SnapshotListParams, SortDirection, SortField } from '../../../../lib';
import { DataPlaceholder, FormattedDateTime, SnapshotDeleteProvider } from '../../../../components';
-
-import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public';
-
-interface Props {
- snapshots: SnapshotDetails[];
- repositories: string[];
- reload: UseRequestResponse['resendRequest'];
- openSnapshotDetailsUrl: (repositoryName: string, snapshotId: string) => string;
- repositoryFilter?: string;
- policyFilter?: string;
- onSnapshotDeleted: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void;
-}
+import { SnapshotSearchBar } from './snapshot_search_bar';
const getLastSuccessfulManagedSnapshot = (
snapshots: SnapshotDetails[]
@@ -51,15 +45,28 @@ const getLastSuccessfulManagedSnapshot = (
return successfulSnapshots[0];
};
-export const SnapshotTable: React.FunctionComponent = ({
- snapshots,
- repositories,
- reload,
- openSnapshotDetailsUrl,
- onSnapshotDeleted,
- repositoryFilter,
- policyFilter,
-}) => {
+interface Props {
+ snapshots: SnapshotDetails[];
+ repositories: string[];
+ reload: UseRequestResponse['resendRequest'];
+ onSnapshotDeleted: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void;
+ listParams: SnapshotListParams;
+ setListParams: (listParams: SnapshotListParams) => void;
+ totalItemCount: number;
+ isLoading: boolean;
+}
+
+export const SnapshotTable: React.FunctionComponent = (props: Props) => {
+ const {
+ snapshots,
+ repositories,
+ reload,
+ onSnapshotDeleted,
+ listParams,
+ setListParams,
+ totalItemCount,
+ isLoading,
+ } = props;
const { i18n, uiMetricService, history } = useServices();
const [selectedItems, setSelectedItems] = useState([]);
@@ -71,7 +78,7 @@ export const SnapshotTable: React.FunctionComponent = ({
name: i18n.translate('xpack.snapshotRestore.snapshotList.table.snapshotColumnTitle', {
defaultMessage: 'Snapshot',
}),
- truncateText: true,
+ truncateText: false,
sortable: true,
render: (snapshotId: string, snapshot: SnapshotDetails) => (
= ({
name: i18n.translate('xpack.snapshotRestore.snapshotList.table.repositoryColumnTitle', {
defaultMessage: 'Repository',
}),
- truncateText: true,
+ truncateText: false,
sortable: true,
render: (repositoryName: string) => (
= ({
name: i18n.translate('xpack.snapshotRestore.snapshotList.table.startTimeColumnTitle', {
defaultMessage: 'Date created',
}),
- truncateText: true,
+ truncateText: false,
sortable: true,
render: (startTimeInMillis: number) => (
@@ -263,30 +270,20 @@ export const SnapshotTable: React.FunctionComponent = ({
},
];
- // By default, we'll display the most recent snapshots at the top of the table.
- const sorting = {
+ const sorting: EuiTableSortingType = {
sort: {
- field: 'startTimeInMillis',
- direction: 'desc' as const,
+ field: listParams.sortField as keyof SnapshotDetails,
+ direction: listParams.sortDirection,
},
};
const pagination = {
- initialPageSize: 20,
+ pageIndex: listParams.pageIndex,
+ pageSize: listParams.pageSize,
+ totalItemCount,
pageSizeOptions: [10, 20, 50],
};
- const searchSchema = {
- fields: {
- repository: {
- type: 'string',
- },
- policyName: {
- type: 'string',
- },
- },
- };
-
const selection = {
onSelectionChange: (newSelectedItems: SnapshotDetails[]) => setSelectedItems(newSelectedItems),
selectable: ({ snapshot }: SnapshotDetails) =>
@@ -306,103 +303,44 @@ export const SnapshotTable: React.FunctionComponent = ({
},
};
- const search = {
- toolsLeft: selectedItems.length ? (
-
- {(
- deleteSnapshotPrompt: (
- ids: Array<{ snapshot: string; repository: string }>,
- onSuccess?: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void
- ) => void
- ) => {
- return (
-
- deleteSnapshotPrompt(
- selectedItems.map(({ snapshot, repository }) => ({ snapshot, repository })),
- onSnapshotDeleted
- )
- }
- color="danger"
- data-test-subj="srSnapshotListBulkDeleteActionButton"
- >
-
-
- );
- }}
-
- ) : undefined,
- toolsRight: (
-
-
-
- ),
- box: {
- incremental: true,
- schema: searchSchema,
- },
- filters: [
- {
- type: 'field_value_selection' as const,
- field: 'repository',
- name: i18n.translate('xpack.snapshotRestore.snapshotList.table.repositoryFilterLabel', {
- defaultMessage: 'Repository',
- }),
- multiSelect: false,
- options: repositories.map((repository) => ({
- value: repository,
- view: repository,
- })),
- },
- ],
- defaultQuery: policyFilter
- ? Query.parse(`policyName="${policyFilter}"`, {
- schema: {
- ...searchSchema,
- strict: true,
- },
- })
- : repositoryFilter
- ? Query.parse(`repository="${repositoryFilter}"`, {
- schema: {
- ...searchSchema,
- strict: true,
- },
- })
- : '',
- };
-
return (
- ({
- 'data-test-subj': 'row',
- })}
- cellProps={() => ({
- 'data-test-subj': 'cell',
- })}
- data-test-subj="snapshotTable"
- />
+ <>
+
+ ) => {
+ const { page: { index, size } = {}, sort: { field, direction } = {} } = criteria;
+
+ setListParams({
+ ...listParams,
+ sortField: (field as SortField) ?? listParams.sortField,
+ sortDirection: (direction as SortDirection) ?? listParams.sortDirection,
+ pageIndex: index ?? listParams.pageIndex,
+ pageSize: size ?? listParams.pageSize,
+ });
+ }}
+ loading={isLoading}
+ isSelectable={true}
+ selection={selection}
+ pagination={pagination}
+ rowProps={() => ({
+ 'data-test-subj': 'row',
+ })}
+ cellProps={() => ({
+ 'data-test-subj': 'cell',
+ })}
+ data-test-subj="snapshotTable"
+ />
+ >
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx
index 92c03d1be936d..da7ec42f746a3 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx
@@ -5,37 +5,26 @@
* 2.0.
*/
-import React, { Fragment, useState, useEffect } from 'react';
+import React, { useState, useEffect } from 'react';
import { parse } from 'query-string';
import { FormattedMessage } from '@kbn/i18n/react';
import { RouteComponentProps } from 'react-router-dom';
-import {
- EuiPageContent,
- EuiButton,
- EuiCallOut,
- EuiLink,
- EuiEmptyPrompt,
- EuiSpacer,
- EuiIcon,
-} from '@elastic/eui';
+import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
-import { APP_SLM_CLUSTER_PRIVILEGES, SNAPSHOT_LIST_MAX_SIZE } from '../../../../../common';
-import { WithPrivileges, PageLoading, PageError, Error } from '../../../../shared_imports';
+import { PageLoading, PageError, Error, reactRouterNavigate } from '../../../../shared_imports';
import { BASE_PATH, UIM_SNAPSHOT_LIST_LOAD } from '../../../constants';
import { useLoadSnapshots } from '../../../services/http';
-import {
- linkToRepositories,
- linkToAddRepository,
- linkToPolicies,
- linkToAddPolicy,
- linkToSnapshot,
-} from '../../../services/navigation';
-import { useCore, useServices } from '../../../app_context';
-import { useDecodedParams } from '../../../lib';
-import { SnapshotDetails } from './snapshot_details';
-import { SnapshotTable } from './snapshot_table';
+import { linkToRepositories } from '../../../services/navigation';
+import { useServices } from '../../../app_context';
+import { useDecodedParams, SnapshotListParams, DEFAULT_SNAPSHOT_LIST_PARAMS } from '../../../lib';
-import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
+import { SnapshotDetails } from './snapshot_details';
+import {
+ SnapshotTable,
+ RepositoryEmptyPrompt,
+ SnapshotEmptyPrompt,
+ RepositoryError,
+} from './components';
interface MatchParams {
repositoryName?: string;
@@ -47,22 +36,22 @@ export const SnapshotList: React.FunctionComponent {
const { repositoryName, snapshotId } = useDecodedParams();
+ const [listParams, setListParams] = useState(DEFAULT_SNAPSHOT_LIST_PARAMS);
const {
error,
+ isInitialRequest,
isLoading,
- data: { snapshots = [], repositories = [], policies = [], errors = {} },
+ data: {
+ snapshots = [],
+ repositories = [],
+ policies = [],
+ errors = {},
+ total: totalSnapshotsCount,
+ },
resendRequest: reload,
- } = useLoadSnapshots();
+ } = useLoadSnapshots(listParams);
- const { uiMetricService, i18n } = useServices();
- const { docLinks } = useCore();
-
- const openSnapshotDetailsUrl = (
- repositoryNameToOpen: string,
- snapshotIdToOpen: string
- ): string => {
- return linkToSnapshot(repositoryNameToOpen, snapshotIdToOpen);
- };
+ const { uiMetricService } = useServices();
const closeSnapshotDetails = () => {
history.push(`${BASE_PATH}/snapshots`);
@@ -86,22 +75,32 @@ export const SnapshotList: React.FunctionComponent(undefined);
- const [filteredPolicy, setFilteredPolicy] = useState(undefined);
useEffect(() => {
if (search) {
const parsedParams = parse(search.replace(/^\?/, ''), { sort: false });
const { repository, policy } = parsedParams;
- if (policy && policy !== filteredPolicy) {
- setFilteredPolicy(String(policy));
+ if (policy) {
+ setListParams((prev: SnapshotListParams) => ({
+ ...prev,
+ searchField: 'policyName',
+ searchValue: String(policy),
+ searchMatch: 'must',
+ searchOperator: 'exact',
+ }));
history.replace(`${BASE_PATH}/snapshots`);
- } else if (repository && repository !== filteredRepository) {
- setFilteredRepository(String(repository));
+ } else if (repository) {
+ setListParams((prev: SnapshotListParams) => ({
+ ...prev,
+ searchField: 'repository',
+ searchValue: String(repository),
+ searchMatch: 'must',
+ searchOperator: 'exact',
+ }));
history.replace(`${BASE_PATH}/snapshots`);
}
}
- }, [filteredPolicy, filteredRepository, history, search]);
+ }, [listParams, history, search]);
// Track component loaded
useEffect(() => {
@@ -110,7 +109,8 @@ export const SnapshotList: React.FunctionComponent
@@ -134,190 +134,11 @@ export const SnapshotList: React.FunctionComponent
);
} else if (Object.keys(errors).length && repositories.length === 0) {
- content = (
-
-
-
-
- }
- body={
-
-
-
-
- ),
- }}
- />
-
- }
- />
-
- );
+ content = ;
} else if (repositories.length === 0) {
- content = (
-
-
-
-
- }
- body={
- <>
-
-
-
-
-
-
-
-
- >
- }
- data-test-subj="emptyPrompt"
- />
-
- );
- } else if (snapshots.length === 0) {
- content = (
-
-
-
-
- }
- body={
- `cluster.${name}`)}
- >
- {({ hasPrivileges }) =>
- hasPrivileges ? (
-
-
-
-
-
- ),
- }}
- />
-
-
- {policies.length === 0 ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
- ) : (
-
-
-
-
-
-
- {' '}
-
-
-
-
- )
- }
-
- }
- data-test-subj="emptyPrompt"
- />
-
- );
+ content = ;
+ } else if (totalSnapshotsCount === 0 && !listParams.searchField && !isLoading) {
+ content = ;
} else {
const repositoryErrorsWarning = Object.keys(errors).length ? (
<>
@@ -351,53 +172,19 @@ export const SnapshotList: React.FunctionComponent
) : null;
- const maxSnapshotsWarning = snapshots.length === SNAPSHOT_LIST_MAX_SIZE && (
- <>
-
-
-
-
- ),
- }}
- />
-
-
- >
- );
-
content = (
{repositoryErrorsWarning}
- {maxSnapshotsWarning}
-
);
diff --git a/x-pack/plugins/snapshot_restore/public/application/services/http/snapshot_requests.ts b/x-pack/plugins/snapshot_restore/public/application/services/http/snapshot_requests.ts
index 3d64dc96958de..c02d0f053f783 100644
--- a/x-pack/plugins/snapshot_restore/public/application/services/http/snapshot_requests.ts
+++ b/x-pack/plugins/snapshot_restore/public/application/services/http/snapshot_requests.ts
@@ -5,8 +5,10 @@
* 2.0.
*/
-import { API_BASE_PATH } from '../../../../common/constants';
+import { HttpFetchQuery } from 'kibana/public';
+import { API_BASE_PATH } from '../../../../common';
import { UIM_SNAPSHOT_DELETE, UIM_SNAPSHOT_DELETE_MANY } from '../../constants';
+import { SnapshotListParams } from '../../lib';
import { UiMetricService } from '../ui_metric';
import { sendRequest, useRequest } from './use_request';
@@ -18,11 +20,12 @@ export const setUiMetricServiceSnapshot = (_uiMetricService: UiMetricService) =>
};
// End hack
-export const useLoadSnapshots = () =>
+export const useLoadSnapshots = (query: SnapshotListParams) =>
useRequest({
path: `${API_BASE_PATH}snapshots`,
method: 'get',
initialData: [],
+ query: query as unknown as HttpFetchQuery,
});
export const useLoadSnapshot = (repositoryName: string, snapshotId: string) =>
diff --git a/x-pack/plugins/snapshot_restore/public/shared_imports.ts b/x-pack/plugins/snapshot_restore/public/shared_imports.ts
index d1b9f37703c0c..a3cda90d26f2a 100644
--- a/x-pack/plugins/snapshot_restore/public/shared_imports.ts
+++ b/x-pack/plugins/snapshot_restore/public/shared_imports.ts
@@ -26,3 +26,5 @@ export {
} from '../../../../src/plugins/es_ui_shared/public';
export { APP_WRAPPER_CLASS } from '../../../../src/core/public';
+
+export { reactRouterNavigate } from '../../../../src/plugins/kibana_react/public';
diff --git a/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.test.ts b/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.test.ts
new file mode 100644
index 0000000000000..d3e5c604d22ad
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.test.ts
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { getSnapshotSearchWildcard } from './get_snapshot_search_wildcard';
+
+describe('getSnapshotSearchWildcard', () => {
+ it('exact match search converts to a wildcard without *', () => {
+ const searchParams = {
+ field: 'snapshot',
+ value: 'testSearch',
+ operator: 'exact',
+ match: 'must',
+ };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('testSearch');
+ });
+
+ it('partial match search converts to a wildcard with *', () => {
+ const searchParams = { field: 'snapshot', value: 'testSearch', operator: 'eq', match: 'must' };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('*testSearch*');
+ });
+
+ it('excluding search converts to "all, except" wildcard (exact match)', () => {
+ const searchParams = {
+ field: 'snapshot',
+ value: 'testSearch',
+ operator: 'exact',
+ match: 'must_not',
+ };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('*,-testSearch');
+ });
+
+ it('excluding search converts to "all, except" wildcard (partial match)', () => {
+ const searchParams = {
+ field: 'snapshot',
+ value: 'testSearch',
+ operator: 'eq',
+ match: 'must_not',
+ };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('*,-*testSearch*');
+ });
+
+ it('excluding search for policy name converts to "all,_none, except" wildcard', () => {
+ const searchParams = {
+ field: 'policyName',
+ value: 'testSearch',
+ operator: 'exact',
+ match: 'must_not',
+ };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('*,_none,-testSearch');
+ });
+});
diff --git a/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.ts b/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.ts
new file mode 100644
index 0000000000000..df8926d785712
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+interface SearchParams {
+ field: string;
+ value: string;
+ match?: string;
+ operator?: string;
+}
+
+export const getSnapshotSearchWildcard = ({
+ field,
+ value,
+ match,
+ operator,
+}: SearchParams): string => {
+ // if the operator is NOT for exact match, convert to *value* wildcard that matches any substring
+ value = operator === 'exact' ? value : `*${value}*`;
+
+ // ES API new "-"("except") wildcard removes matching items from a list of already selected items
+ // To find all items not containing the search value, use "*,-{searchValue}"
+ // When searching for policy name, also add "_none" to find snapshots without a policy as well
+ const excludingWildcard = field === 'policyName' ? `*,_none,-${value}` : `*,-${value}`;
+
+ return match === 'must_not' ? excludingWildcard : value;
+};
diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts
index f71c5ec9ffc08..4ecd34a43adb9 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts
@@ -51,6 +51,10 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => {
const mockRequest: RequestMock = {
method: 'get',
path: addBasePath('snapshots'),
+ query: {
+ sortField: 'startTimeInMillis',
+ sortDirection: 'desc',
+ },
};
const mockSnapshotGetManagedRepositoryEsResponse = {
diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts
index 6838ae2700f3a..4de0c3011fed5 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts
@@ -7,11 +7,36 @@
import { schema, TypeOf } from '@kbn/config-schema';
import type { SnapshotDetailsEs } from '../../../common/types';
-import { SNAPSHOT_LIST_MAX_SIZE } from '../../../common/constants';
import { deserializeSnapshotDetails } from '../../../common/lib';
import type { RouteDependencies } from '../../types';
import { getManagedRepositoryName } from '../../lib';
import { addBasePath } from '../helpers';
+import { snapshotListSchema } from './validate_schemas';
+import { getSnapshotSearchWildcard } from '../../lib/get_snapshot_search_wildcard';
+
+const sortFieldToESParams = {
+ snapshot: 'name',
+ repository: 'repository',
+ indices: 'index_count',
+ startTimeInMillis: 'start_time',
+ durationInMillis: 'duration',
+ 'shards.total': 'shard_count',
+ 'shards.failed': 'failed_shard_count',
+};
+
+const isSearchingForNonExistentRepository = (
+ repositories: string[],
+ value: string,
+ match?: string,
+ operator?: string
+): boolean => {
+ // only check if searching for an exact match (repository=test)
+ if (match === 'must' && operator === 'exact') {
+ return !(repositories || []).includes(value);
+ }
+ // otherwise we will use a wildcard, so allow the request
+ return false;
+};
export function registerSnapshotsRoutes({
router,
@@ -20,9 +45,18 @@ export function registerSnapshotsRoutes({
}: RouteDependencies) {
// GET all snapshots
router.get(
- { path: addBasePath('snapshots'), validate: false },
+ { path: addBasePath('snapshots'), validate: { query: snapshotListSchema } },
license.guardApiRoute(async (ctx, req, res) => {
const { client: clusterClient } = ctx.core.elasticsearch;
+ const sortField =
+ sortFieldToESParams[(req.query as TypeOf).sortField];
+ const sortDirection = (req.query as TypeOf).sortDirection;
+ const pageIndex = (req.query as TypeOf).pageIndex;
+ const pageSize = (req.query as TypeOf).pageSize;
+ const searchField = (req.query as TypeOf).searchField;
+ const searchValue = (req.query as TypeOf).searchValue;
+ const searchMatch = (req.query as TypeOf).searchMatch;
+ const searchOperator = (req.query as TypeOf).searchOperator;
const managedRepository = await getManagedRepositoryName(clusterClient.asCurrentUser);
@@ -55,18 +89,60 @@ export function registerSnapshotsRoutes({
return handleEsError({ error: e, response: res });
}
+ // if the search is for a repository name with exact match (repository=test)
+ // and that repository doesn't exist, ES request throws an error
+ // that is why we return an empty snapshots array instead of sending an ES request
+ if (
+ searchField === 'repository' &&
+ isSearchingForNonExistentRepository(repositories, searchValue!, searchMatch, searchOperator)
+ ) {
+ return res.ok({
+ body: {
+ snapshots: [],
+ policies,
+ repositories,
+ errors: [],
+ total: 0,
+ },
+ });
+ }
try {
// If any of these repositories 504 they will cost the request significant time.
const { body: fetchedSnapshots } = await clusterClient.asCurrentUser.snapshot.get({
- repository: '_all',
- snapshot: '_all',
+ repository:
+ searchField === 'repository'
+ ? getSnapshotSearchWildcard({
+ field: searchField,
+ value: searchValue!,
+ match: searchMatch,
+ operator: searchOperator,
+ })
+ : '_all',
ignore_unavailable: true, // Allow request to succeed even if some snapshots are unavailable.
- // @ts-expect-error @elastic/elasticsearch "desc" is a new param
- order: 'desc',
- // TODO We are temporarily hard-coding the maximum number of snapshots returned
- // in order to prevent an unusable UI for users with large number of snapshots
- // In the near future, this will be resolved with server-side pagination
- size: SNAPSHOT_LIST_MAX_SIZE,
+ snapshot:
+ searchField === 'snapshot'
+ ? getSnapshotSearchWildcard({
+ field: searchField,
+ value: searchValue!,
+ match: searchMatch,
+ operator: searchOperator,
+ })
+ : '_all',
+ // @ts-expect-error @elastic/elasticsearch new API params
+ // https://github.com/elastic/elasticsearch-specification/issues/845
+ slm_policy_filter:
+ searchField === 'policyName'
+ ? getSnapshotSearchWildcard({
+ field: searchField,
+ value: searchValue!,
+ match: searchMatch,
+ operator: searchOperator,
+ })
+ : '*,_none',
+ order: sortDirection,
+ sort: sortField,
+ size: pageSize,
+ offset: pageIndex * pageSize,
});
// Decorate each snapshot with the repository with which it's associated.
@@ -79,8 +155,10 @@ export function registerSnapshotsRoutes({
snapshots: snapshots || [],
policies,
repositories,
- // @ts-expect-error @elastic/elasticsearch "failures" is a new field in the response
+ // @ts-expect-error @elastic/elasticsearch https://github.com/elastic/elasticsearch-specification/issues/845
errors: fetchedSnapshots?.failures,
+ // @ts-expect-error @elastic/elasticsearch "total" is a new field in the response
+ total: fetchedSnapshots?.total,
},
});
} catch (e) {
@@ -170,7 +248,7 @@ export function registerSnapshotsRoutes({
const snapshots = req.body;
try {
- // We intentially perform deletion requests sequentially (blocking) instead of in parallel (non-blocking)
+ // We intentionally perform deletion requests sequentially (blocking) instead of in parallel (non-blocking)
// because there can only be one snapshot deletion task performed at a time (ES restriction).
for (let i = 0; i < snapshots.length; i++) {
const { snapshot, repository } = snapshots[i];
diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/validate_schemas.ts b/x-pack/plugins/snapshot_restore/server/routes/api/validate_schemas.ts
index af31466c2cefe..e93ee2b3d78ca 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/api/validate_schemas.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/api/validate_schemas.ts
@@ -26,6 +26,31 @@ const snapshotRetentionSchema = schema.object({
minCount: schema.maybe(schema.oneOf([schema.number(), schema.literal('')])),
});
+export const snapshotListSchema = schema.object({
+ sortField: schema.oneOf([
+ schema.literal('snapshot'),
+ schema.literal('repository'),
+ schema.literal('indices'),
+ schema.literal('durationInMillis'),
+ schema.literal('startTimeInMillis'),
+ schema.literal('shards.total'),
+ schema.literal('shards.failed'),
+ ]),
+ sortDirection: schema.oneOf([schema.literal('desc'), schema.literal('asc')]),
+ pageIndex: schema.number(),
+ pageSize: schema.number(),
+ searchField: schema.maybe(
+ schema.oneOf([
+ schema.literal('snapshot'),
+ schema.literal('repository'),
+ schema.literal('policyName'),
+ ])
+ ),
+ searchValue: schema.maybe(schema.string()),
+ searchMatch: schema.maybe(schema.oneOf([schema.literal('must'), schema.literal('must_not')])),
+ searchOperator: schema.maybe(schema.oneOf([schema.literal('eq'), schema.literal('exact')])),
+});
+
export const policySchema = schema.object({
name: schema.string(),
snapshotName: schema.string(),
diff --git a/x-pack/plugins/snapshot_restore/server/routes/helpers.ts b/x-pack/plugins/snapshot_restore/server/routes/helpers.ts
index 1f49d2f3cabfb..e73db4d992ff2 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/helpers.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/helpers.ts
@@ -5,6 +5,6 @@
* 2.0.
*/
-import { API_BASE_PATH } from '../../common/constants';
+import { API_BASE_PATH } from '../../common';
export const addBasePath = (uri: string): string => API_BASE_PATH + uri;
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c941cbd9ddf80..3df5e4ee6c48a 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -23900,9 +23900,6 @@
"xpack.snapshotRestore.snapshotList.table.snapshotColumnTitle": "スナップショット",
"xpack.snapshotRestore.snapshotList.table.startTimeColumnTitle": "日付が作成されました",
"xpack.snapshotRestore.snapshots.breadcrumbTitle": "スナップショット",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDescription": "表示可能なスナップショットの最大数に達しました。スナップショットをすべて表示するには、{docLink}を使用してください。",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDocLinkText": "Elasticsearch API",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedTitle": "スナップショットの一覧を表示できません。",
"xpack.snapshotRestore.snapshotState.completeLabel": "スナップショット完了",
"xpack.snapshotRestore.snapshotState.failedLabel": "スナップショット失敗",
"xpack.snapshotRestore.snapshotState.incompatibleLabel": "互換性のないバージョン",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index e9e9f02c8fe99..d9af3edb8101d 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -24302,9 +24302,6 @@
"xpack.snapshotRestore.snapshotList.table.snapshotColumnTitle": "快照",
"xpack.snapshotRestore.snapshotList.table.startTimeColumnTitle": "创建日期",
"xpack.snapshotRestore.snapshots.breadcrumbTitle": "快照",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDescription": "已达到最大可查看快照数目。要查看您的所有快照,请使用{docLink}。",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDocLinkText": "Elasticsearch API",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedTitle": "无法显示快照的完整列表",
"xpack.snapshotRestore.snapshotState.completeLabel": "快照完成",
"xpack.snapshotRestore.snapshotState.failedLabel": "快照失败",
"xpack.snapshotRestore.snapshotState.incompatibleLabel": "不兼容版本",
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts
index 4d39ff1494f89..db5dbc9735e66 100644
--- a/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts
@@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Snapshot and Restore', () => {
- loadTestFile(require.resolve('./snapshot_restore'));
+ loadTestFile(require.resolve('./policies'));
+ loadTestFile(require.resolve('./snapshots'));
});
}
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/elasticsearch.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/elasticsearch.ts
index 9b4d39a3b10b3..a59c90fe29132 100644
--- a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/elasticsearch.ts
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/elasticsearch.ts
@@ -7,9 +7,10 @@
import { FtrProviderContext } from '../../../../ftr_provider_context';
-interface SlmPolicy {
+export interface SlmPolicy {
+ policyName: string;
+ // snapshot name
name: string;
- snapshotName: string;
schedule: string;
repository: string;
isManagedPolicy: boolean;
@@ -29,23 +30,22 @@ interface SlmPolicy {
}
/**
- * Helpers to create and delete SLM policies on the Elasticsearch instance
+ * Helpers to create and delete SLM policies, repositories and snapshots on the Elasticsearch instance
* during our tests.
- * @param {ElasticsearchClient} es The Elasticsearch client instance
*/
export const registerEsHelpers = (getService: FtrProviderContext['getService']) => {
let policiesCreated: string[] = [];
const es = getService('es');
- const createRepository = (repoName: string) => {
+ const createRepository = (repoName: string, repoPath?: string) => {
return es.snapshot
.createRepository({
repository: repoName,
body: {
type: 'fs',
settings: {
- location: '/tmp/',
+ location: repoPath ?? '/tmp/repo',
},
},
verify: false,
@@ -55,12 +55,12 @@ export const registerEsHelpers = (getService: FtrProviderContext['getService'])
const createPolicy = (policy: SlmPolicy, cachePolicy?: boolean) => {
if (cachePolicy) {
- policiesCreated.push(policy.name);
+ policiesCreated.push(policy.policyName);
}
return es.slm
.putLifecycle({
- policy_id: policy.name,
+ policy_id: policy.policyName,
// TODO: bring {@link SlmPolicy} in line with {@link PutSnapshotLifecycleRequest['body']}
// @ts-expect-error
body: policy,
@@ -90,11 +90,34 @@ export const registerEsHelpers = (getService: FtrProviderContext['getService'])
console.log(`[Cleanup error] Error deleting ES resources: ${err.message}`);
});
+ const executePolicy = (policyName: string) => {
+ return es.slm.executeLifecycle({ policy_id: policyName }).then(({ body }) => body);
+ };
+
+ const createSnapshot = (snapshotName: string, repositoryName: string) => {
+ return es.snapshot
+ .create({ snapshot: snapshotName, repository: repositoryName })
+ .then(({ body }) => body);
+ };
+
+ const deleteSnapshots = (repositoryName: string) => {
+ es.snapshot
+ .delete({ repository: repositoryName, snapshot: '*' })
+ .then(() => {})
+ .catch((err) => {
+ // eslint-disable-next-line no-console
+ console.log(`[Cleanup error] Error deleting snapshots: ${err.message}`);
+ });
+ };
+
return {
createRepository,
createPolicy,
deletePolicy,
cleanupPolicies,
getPolicy,
+ executePolicy,
+ createSnapshot,
+ deleteSnapshots,
};
};
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts
index 27a4d9c59cff0..a9721c5856598 100644
--- a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export { registerEsHelpers } from './elasticsearch';
+export { registerEsHelpers, SlmPolicy } from './elasticsearch';
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/policies.ts
similarity index 95%
rename from x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts
rename to x-pack/test/api_integration/apis/management/snapshot_restore/policies.ts
index a6ac2d057c84e..e0734680887d2 100644
--- a/x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/policies.ts
@@ -19,7 +19,7 @@ export default function ({ getService }: FtrProviderContext) {
const { createRepository, createPolicy, deletePolicy, cleanupPolicies, getPolicy } =
registerEsHelpers(getService);
- describe('Snapshot Lifecycle Management', function () {
+ describe('SLM policies', function () {
this.tags(['skipCloud']); // file system repositories are not supported in cloud
before(async () => {
@@ -134,9 +134,8 @@ export default function ({ getService }: FtrProviderContext) {
describe('Update', () => {
const POLICY_NAME = 'test_update_policy';
+ const SNAPSHOT_NAME = 'my_snapshot';
const POLICY = {
- name: POLICY_NAME,
- snapshotName: 'my_snapshot',
schedule: '0 30 1 * * ?',
repository: REPO_NAME,
config: {
@@ -159,7 +158,7 @@ export default function ({ getService }: FtrProviderContext) {
before(async () => {
// Create SLM policy that can be used to test PUT request
try {
- await createPolicy(POLICY, true);
+ await createPolicy({ ...POLICY, policyName: POLICY_NAME, name: SNAPSHOT_NAME }, true);
} catch (err) {
// eslint-disable-next-line no-console
console.log('[Setup error] Error creating policy');
@@ -175,6 +174,8 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'xxx')
.send({
...POLICY,
+ name: POLICY_NAME,
+ snapshotName: SNAPSHOT_NAME,
schedule: '0 0 0 ? * 7',
})
.expect(200);
@@ -212,7 +213,7 @@ export default function ({ getService }: FtrProviderContext) {
const { body } = await supertest
.put(uri)
.set('kbn-xsrf', 'xxx')
- .send(requiredFields)
+ .send({ ...requiredFields, name: POLICY_NAME, snapshotName: SNAPSHOT_NAME })
.expect(200);
expect(body).to.eql({
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/snapshots.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/snapshots.ts
new file mode 100644
index 0000000000000..1677013dd5e7e
--- /dev/null
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/snapshots.ts
@@ -0,0 +1,729 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+import { registerEsHelpers, SlmPolicy } from './lib';
+import { SnapshotDetails } from '../../../../../plugins/snapshot_restore/common/types';
+
+const REPO_NAME_1 = 'test_repo_1';
+const REPO_NAME_2 = 'test_another_repo_2';
+const REPO_PATH_1 = '/tmp/repo_1';
+const REPO_PATH_2 = '/tmp/repo_2';
+// SLM policies to test policyName filter
+const POLICY_NAME_1 = 'test_policy_1';
+const POLICY_NAME_2 = 'test_another_policy_2';
+const POLICY_SNAPSHOT_NAME_1 = 'backup_snapshot';
+const POLICY_SNAPSHOT_NAME_2 = 'a_snapshot';
+// snapshots created without SLM policies
+const BATCH_SIZE_1 = 3;
+const BATCH_SIZE_2 = 5;
+const BATCH_SNAPSHOT_NAME_1 = 'another_snapshot';
+const BATCH_SNAPSHOT_NAME_2 = 'xyz_another_snapshot';
+// total count consists of both batches' sizes + 2 snapshots created by 2 SLM policies (one each)
+const SNAPSHOT_COUNT = BATCH_SIZE_1 + BATCH_SIZE_2 + 2;
+// API defaults used in the UI
+const PAGE_INDEX = 0;
+const PAGE_SIZE = 20;
+const SORT_FIELD = 'startTimeInMillis';
+const SORT_DIRECTION = 'desc';
+
+interface ApiParams {
+ pageIndex?: number;
+ pageSize?: number;
+
+ sortField?: string;
+ sortDirection?: string;
+
+ searchField?: string;
+ searchValue?: string;
+ searchMatch?: string;
+ searchOperator?: string;
+}
+const getApiPath = ({
+ pageIndex,
+ pageSize,
+ sortField,
+ sortDirection,
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+}: ApiParams): string => {
+ let path = `/api/snapshot_restore/snapshots?sortField=${sortField ?? SORT_FIELD}&sortDirection=${
+ sortDirection ?? SORT_DIRECTION
+ }&pageIndex=${pageIndex ?? PAGE_INDEX}&pageSize=${pageSize ?? PAGE_SIZE}`;
+ // all 4 parameters should be used at the same time to configure the correct search request
+ if (searchField && searchValue && searchMatch && searchOperator) {
+ path = `${path}&searchField=${searchField}&searchValue=${searchValue}&searchMatch=${searchMatch}&searchOperator=${searchOperator}`;
+ }
+ return path;
+};
+const getPolicyBody = (policy: Partial): SlmPolicy => {
+ return {
+ policyName: 'default_policy',
+ name: 'default_snapshot',
+ schedule: '0 30 1 * * ?',
+ repository: 'default_repo',
+ isManagedPolicy: false,
+ config: {
+ indices: ['default_index'],
+ ignoreUnavailable: true,
+ },
+ ...policy,
+ };
+};
+
+export default function ({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+
+ const {
+ createSnapshot,
+ createRepository,
+ createPolicy,
+ executePolicy,
+ cleanupPolicies,
+ deleteSnapshots,
+ } = registerEsHelpers(getService);
+
+ describe('Snapshots', function () {
+ this.tags(['skipCloud']); // file system repositories are not supported in cloud
+
+ // names of snapshots created by SLM policies have random suffixes, save full names for tests
+ let snapshotName1: string;
+ let snapshotName2: string;
+
+ before(async () => {
+ /*
+ * This setup creates following repos, SLM policies and snapshots:
+ * Repo 1 "test_repo_1" with 5 snapshots
+ * "backup_snapshot..." (created by SLM policy "test_policy_1")
+ * "a_snapshot..." (created by SLM policy "test_another_policy_2")
+ * "another_snapshot_0" to "another_snapshot_2" (no SLM policy)
+ *
+ * Repo 2 "test_another_repo_2" with 5 snapshots
+ * "xyz_another_snapshot_0" to "xyz_another_snapshot_4" (no SLM policy)
+ */
+ try {
+ await createRepository(REPO_NAME_1, REPO_PATH_1);
+ await createRepository(REPO_NAME_2, REPO_PATH_2);
+ await createPolicy(
+ getPolicyBody({
+ policyName: POLICY_NAME_1,
+ repository: REPO_NAME_1,
+ name: POLICY_SNAPSHOT_NAME_1,
+ }),
+ true
+ );
+ await createPolicy(
+ getPolicyBody({
+ policyName: POLICY_NAME_2,
+ repository: REPO_NAME_1,
+ name: POLICY_SNAPSHOT_NAME_2,
+ }),
+ true
+ );
+ ({ snapshot_name: snapshotName1 } = await executePolicy(POLICY_NAME_1));
+ // a short timeout to let the 1st snapshot start, otherwise the sorting by start time might be flaky
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ ({ snapshot_name: snapshotName2 } = await executePolicy(POLICY_NAME_2));
+ for (let i = 0; i < BATCH_SIZE_1; i++) {
+ await createSnapshot(`${BATCH_SNAPSHOT_NAME_1}_${i}`, REPO_NAME_1);
+ }
+ for (let i = 0; i < BATCH_SIZE_2; i++) {
+ await createSnapshot(`${BATCH_SNAPSHOT_NAME_2}_${i}`, REPO_NAME_2);
+ }
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.log('[Setup error] Error creating snapshots');
+ throw err;
+ }
+ });
+
+ after(async () => {
+ await cleanupPolicies();
+ await deleteSnapshots(REPO_NAME_1);
+ await deleteSnapshots(REPO_NAME_2);
+ });
+
+ describe('pagination', () => {
+ it('returns pageSize number of snapshots', async () => {
+ const pageSize = 7;
+ const {
+ body: { total, snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ pageSize,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ expect(total).to.eql(SNAPSHOT_COUNT);
+ expect(snapshots.length).to.eql(pageSize);
+ });
+
+ it('returns next page of snapshots', async () => {
+ const pageSize = 3;
+ let pageIndex = 0;
+ const {
+ body: { snapshots: firstPageSnapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ pageIndex,
+ pageSize,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ const firstPageSnapshotName = firstPageSnapshots[0].snapshot;
+ expect(firstPageSnapshots.length).to.eql(pageSize);
+
+ pageIndex = 1;
+ const {
+ body: { snapshots: secondPageSnapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ pageIndex,
+ pageSize,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ const secondPageSnapshotName = secondPageSnapshots[0].snapshot;
+ expect(secondPageSnapshots.length).to.eql(pageSize);
+ expect(secondPageSnapshotName).to.not.eql(firstPageSnapshotName);
+ });
+ });
+
+ describe('sorting', () => {
+ it('sorts by snapshot name (asc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'snapshot',
+ sortDirection: 'asc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ /*
+ * snapshots name in asc order:
+ * "a_snapshot...", "another_snapshot...", "backup_snapshot...", "xyz_another_snapshot..."
+ */
+ const snapshotName = snapshots[0].snapshot;
+ // snapshotName2 is "a_snapshot..."
+ expect(snapshotName).to.eql(snapshotName2);
+ });
+
+ it('sorts by snapshot name (desc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'snapshot',
+ sortDirection: 'desc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ /*
+ * snapshots name in desc order:
+ * "xyz_another_snapshot...", "backup_snapshot...", "another_snapshot...", "a_snapshot..."
+ */
+ const snapshotName = snapshots[0].snapshot;
+ expect(snapshotName).to.eql('xyz_another_snapshot_4');
+ });
+
+ it('sorts by repository name (asc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'repository',
+ sortDirection: 'asc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ // repositories in asc order: "test_another_repo_2", "test_repo_1"
+ const repositoryName = snapshots[0].repository;
+ expect(repositoryName).to.eql(REPO_NAME_2); // "test_another_repo_2"
+ });
+
+ it('sorts by repository name (desc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'repository',
+ sortDirection: 'desc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ // repositories in desc order: "test_repo_1", "test_another_repo_2"
+ const repositoryName = snapshots[0].repository;
+ expect(repositoryName).to.eql(REPO_NAME_1); // "test_repo_1"
+ });
+
+ it('sorts by startTimeInMillis (asc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'startTimeInMillis',
+ sortDirection: 'asc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ const snapshotName = snapshots[0].snapshot;
+ // the 1st snapshot that was created during this test setup
+ expect(snapshotName).to.eql(snapshotName1);
+ });
+
+ it('sorts by startTimeInMillis (desc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'startTimeInMillis',
+ sortDirection: 'desc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ const snapshotName = snapshots[0].snapshot;
+ // the last snapshot that was created during this test setup
+ expect(snapshotName).to.eql('xyz_another_snapshot_4');
+ });
+
+ // these properties are only tested as being accepted by the API
+ const sortFields = ['indices', 'durationInMillis', 'shards.total', 'shards.failed'];
+ sortFields.forEach((sortField: string) => {
+ it(`allows sorting by ${sortField} (asc)`, async () => {
+ await supertest
+ .get(
+ getApiPath({
+ sortField,
+ sortDirection: 'asc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send()
+ .expect(200);
+ });
+
+ it(`allows sorting by ${sortField} (desc)`, async () => {
+ await supertest
+ .get(
+ getApiPath({
+ sortField,
+ sortDirection: 'desc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send()
+ .expect(200);
+ });
+ });
+ });
+
+ describe('search', () => {
+ describe('snapshot name', () => {
+ it('exact match', async () => {
+ // list snapshots with the name "another_snapshot_2"
+ const searchField = 'snapshot';
+ const searchValue = 'another_snapshot_2';
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(1);
+ expect(snapshots[0].snapshot).to.eql('another_snapshot_2');
+ });
+
+ it('partial match', async () => {
+ // list snapshots with the name containing with "another"
+ const searchField = 'snapshot';
+ const searchValue = 'another';
+ const searchMatch = 'must';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // both batches created snapshots containing "another" in the name
+ expect(snapshots.length).to.eql(BATCH_SIZE_1 + BATCH_SIZE_2);
+ const snapshotNamesContainSearch = snapshots.every((snapshot: SnapshotDetails) =>
+ snapshot.snapshot.includes('another')
+ );
+ expect(snapshotNamesContainSearch).to.eql(true);
+ });
+
+ it('excluding search with exact match', async () => {
+ // list snapshots with the name not "another_snapshot_2"
+ const searchField = 'snapshot';
+ const searchValue = 'another_snapshot_2';
+ const searchMatch = 'must_not';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(SNAPSHOT_COUNT - 1);
+ const snapshotIsExcluded = snapshots.every(
+ (snapshot: SnapshotDetails) => snapshot.snapshot !== 'another_snapshot_2'
+ );
+ expect(snapshotIsExcluded).to.eql(true);
+ });
+
+ it('excluding search with partial match', async () => {
+ // list snapshots with the name not starting with "another"
+ const searchField = 'snapshot';
+ const searchValue = 'another';
+ const searchMatch = 'must_not';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // both batches created snapshots with names containing "another"
+ expect(snapshots.length).to.eql(SNAPSHOT_COUNT - BATCH_SIZE_1 - BATCH_SIZE_2);
+ const snapshotsAreExcluded = snapshots.every(
+ (snapshot: SnapshotDetails) => !snapshot.snapshot.includes('another')
+ );
+ expect(snapshotsAreExcluded).to.eql(true);
+ });
+ });
+
+ describe('repository name', () => {
+ it('search for non-existent repository returns an empty snapshot array', async () => {
+ // search for non-existent repository
+ const searchField = 'repository';
+ const searchValue = 'non-existent';
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(0);
+ });
+
+ it('exact match', async () => {
+ // list snapshots from repository "test_repo_1"
+ const searchField = 'repository';
+ const searchValue = REPO_NAME_1;
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // repo 1 contains snapshots from batch 1 and 2 snapshots created by 2 SLM policies
+ expect(snapshots.length).to.eql(BATCH_SIZE_1 + 2);
+ const repositoryNameMatches = snapshots.every(
+ (snapshot: SnapshotDetails) => snapshot.repository === REPO_NAME_1
+ );
+ expect(repositoryNameMatches).to.eql(true);
+ });
+
+ it('partial match', async () => {
+ // list snapshots from repository with the name containing "another"
+ // i.e. snapshots from repo 2
+ const searchField = 'repository';
+ const searchValue = 'another';
+ const searchMatch = 'must';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // repo 2 only contains snapshots created by batch 2
+ expect(snapshots.length).to.eql(BATCH_SIZE_2);
+ const repositoryNameMatches = snapshots.every((snapshot: SnapshotDetails) =>
+ snapshot.repository.includes('another')
+ );
+ expect(repositoryNameMatches).to.eql(true);
+ });
+
+ it('excluding search with exact match', async () => {
+ // list snapshots from repositories with the name not "test_repo_1"
+ const searchField = 'repository';
+ const searchValue = REPO_NAME_1;
+ const searchMatch = 'must_not';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // snapshots not in repo 1 are only snapshots created in batch 2
+ expect(snapshots.length).to.eql(BATCH_SIZE_2);
+ const repositoryNameMatches = snapshots.every(
+ (snapshot: SnapshotDetails) => snapshot.repository !== REPO_NAME_1
+ );
+ expect(repositoryNameMatches).to.eql(true);
+ });
+
+ it('excluding search with partial match', async () => {
+ // list snapshots from repository with the name not containing "test"
+ const searchField = 'repository';
+ const searchValue = 'test';
+ const searchMatch = 'must_not';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(0);
+ });
+ });
+
+ describe('policy name', () => {
+ it('search for non-existent policy returns an empty snapshot array', async () => {
+ // search for non-existent policy
+ const searchField = 'policyName';
+ const searchValue = 'non-existent';
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(0);
+ });
+
+ it('exact match', async () => {
+ // list snapshots created by the policy "test_policy_1"
+ const searchField = 'policyName';
+ const searchValue = POLICY_NAME_1;
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(1);
+ expect(snapshots[0].policyName).to.eql(POLICY_NAME_1);
+ });
+
+ it('partial match', async () => {
+ // list snapshots created by the policy with the name containing "another"
+ const searchField = 'policyName';
+ const searchValue = 'another';
+ const searchMatch = 'must';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // 1 snapshot was created by the policy "test_another_policy_2"
+ expect(snapshots.length).to.eql(1);
+ const policyNameMatches = snapshots.every((snapshot: SnapshotDetails) =>
+ snapshot.policyName!.includes('another')
+ );
+ expect(policyNameMatches).to.eql(true);
+ });
+
+ it('excluding search with exact match', async () => {
+ // list snapshots created by the policy with the name not "test_policy_1"
+ const searchField = 'policyName';
+ const searchValue = POLICY_NAME_1;
+ const searchMatch = 'must_not';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // only 1 snapshot was created by policy 1
+ // search results should also contain snapshots without SLM policy
+ expect(snapshots.length).to.eql(SNAPSHOT_COUNT - 1);
+ const snapshotsExcluded = snapshots.every(
+ (snapshot: SnapshotDetails) => (snapshot.policyName ?? '') !== POLICY_NAME_1
+ );
+ expect(snapshotsExcluded).to.eql(true);
+ });
+
+ it('excluding search with partial match', async () => {
+ // list snapshots created by the policy with the name not containing "another"
+ const searchField = 'policyName';
+ const searchValue = 'another';
+ const searchMatch = 'must_not';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // only 1 snapshot was created by SLM policy containing "another" in the name
+ // search results should also contain snapshots without SLM policy
+ expect(snapshots.length).to.eql(SNAPSHOT_COUNT - 1);
+ const snapshotsExcluded = snapshots.every(
+ (snapshot: SnapshotDetails) => !(snapshot.policyName ?? '').includes('another')
+ );
+ expect(snapshotsExcluded).to.eql(true);
+ });
+ });
+ });
+ });
+}
diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts
index 3690f661c621c..678f7a0d3d929 100644
--- a/x-pack/test/api_integration/config.ts
+++ b/x-pack/test/api_integration/config.ts
@@ -41,6 +41,7 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi
serverArgs: [
...xPackFunctionalTestsConfig.get('esTestCluster.serverArgs'),
'node.attr.name=apiIntegrationTestNode',
+ 'path.repo=/tmp/repo,/tmp/repo_1,/tmp/repo_2',
],
},
};
From 83739b08ca0779f9697958e105cb3fbf3ff497d0 Mon Sep 17 00:00:00 2001
From: Kyle Pollich
Date: Mon, 18 Oct 2021 15:20:24 -0400
Subject: [PATCH 010/204] [Fleet] Address Package Policy upgrade UX review
(#115414)
* Fix package update button + icon
* Adjust order of modal of states based on UX review
* Clarify integration/agent policies in integration UI policy table
---
.../integrations/sections/epm/screens/detail/index.tsx | 2 +-
.../epm/screens/detail/policies/package_policies.tsx | 2 +-
.../sections/epm/screens/detail/settings/update_button.tsx | 7 +++----
3 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
index 881fc566c932d..6e3eba19c52e3 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
@@ -452,7 +452,7 @@ export function Detail() {
name: (
),
isSelected: panel === 'policies',
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
index d6dc7e8440dae..69487454dcb94 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
@@ -211,7 +211,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
{
field: 'packagePolicy.name',
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.name', {
- defaultMessage: 'Integration',
+ defaultMessage: 'Integration Policy',
}),
render(_, { packagePolicy }) {
return ;
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx
index 2acd5634b1e5f..b5a8394fa2cb2 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx
@@ -154,6 +154,7 @@ export const UpdateButton: React.FunctionComponent = ({
return;
}
+ setIsUpdateModalVisible(false);
setIsUpgradingPackagePolicies(true);
await installPackage({ name, version, title });
@@ -166,7 +167,6 @@ export const UpdateButton: React.FunctionComponent = ({
);
setIsUpgradingPackagePolicies(false);
- setIsUpdateModalVisible(false);
notifications.toasts.addSuccess({
title: toMountPoint(
@@ -285,15 +285,14 @@ export const UpdateButton: React.FunctionComponent = ({
setIsUpdateModalVisible(true) : handleClickUpdate
}
>
From 3f5ad2ab9463067c1f0a9661ab315af74c5f4c19 Mon Sep 17 00:00:00 2001
From: Ignacio Rivas
Date: Mon, 18 Oct 2021 21:07:02 +0100
Subject: [PATCH 011/204] Fix test (#115362)
---
.../helpers/http_requests.ts | 9 ++++
.../home/indices_tab.helpers.ts | 19 ++++---
.../home/indices_tab.test.ts | 50 ++++++++-----------
3 files changed, 42 insertions(+), 36 deletions(-)
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
index 58edb1aae80e2..b9f1191da8af7 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
@@ -28,6 +28,14 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};
+ const setReloadIndicesResponse = (response: HttpResponse = []) => {
+ server.respondWith('POST', `${API_BASE_PATH}/indices/reload`, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(response),
+ ]);
+ };
+
const setLoadDataStreamsResponse = (response: HttpResponse = []) => {
server.respondWith('GET', `${API_BASE_PATH}/data_streams`, [
200,
@@ -118,6 +126,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
return {
setLoadTemplatesResponse,
setLoadIndicesResponse,
+ setReloadIndicesResponse,
setLoadDataStreamsResponse,
setLoadDataStreamResponse,
setDeleteDataStreamResponse,
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
index 900f7ddbf084b..2576b5f92b7b2 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
@@ -47,9 +47,12 @@ export const setup = async (overridingDependencies: any = {}): Promise {
- const { find } = testBed;
- const contextMenu = find('indexContextMenu');
- contextMenu.find(`button[data-test-subj="${optionDataTestSubject}"]`).simulate('click');
+ const { find, component } = testBed;
+
+ await act(async () => {
+ find(`indexContextMenu.${optionDataTestSubject}`).simulate('click');
+ });
+ component.update();
};
const clickIncludeHiddenIndicesToggle = () => {
@@ -57,9 +60,13 @@ export const setup = async (overridingDependencies: any = {}): Promise {
- const { find } = testBed;
- find('indexActionsContextMenuButton').simulate('click');
+ const clickManageContextMenuButton = async () => {
+ const { find, component } = testBed;
+
+ await act(async () => {
+ find('indexActionsContextMenuButton').simulate('click');
+ });
+ component.update();
};
const getIncludeHiddenIndicesToggleStatus = () => {
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
index e23c1a59eb135..f95ea373d58b4 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
@@ -10,7 +10,7 @@ import { act } from 'react-dom/test-utils';
import { API_BASE_PATH } from '../../../common/constants';
import { setupEnvironment, nextTick } from '../helpers';
import { IndicesTestBed, setup } from './indices_tab.helpers';
-import { createDataStreamPayload } from './data_streams_tab.helpers';
+import { createDataStreamPayload, createNonDataStreamIndex } from './data_streams_tab.helpers';
/**
* The below import is required to avoid a console error warn from the "brace" package
@@ -23,9 +23,14 @@ import { createMemoryHistory } from 'history';
stubWebWorker();
// unhandled promise rejection https://github.com/elastic/kibana/issues/112699
-describe.skip('', () => {
- const { server, httpRequestsMockHelpers } = setupEnvironment();
+describe('', () => {
let testBed: IndicesTestBed;
+ let server: ReturnType['server'];
+ let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers'];
+
+ beforeEach(() => {
+ ({ server, httpRequestsMockHelpers } = setupEnvironment());
+ });
afterAll(() => {
server.restore();
@@ -108,19 +113,9 @@ describe.skip('', () => {
describe('index detail panel with % character in index name', () => {
const indexName = 'test%';
+
beforeEach(async () => {
- const index = {
- health: 'green',
- status: 'open',
- primary: 1,
- replica: 1,
- documents: 10000,
- documents_deleted: 100,
- size: '156kb',
- primary_size: '156kb',
- name: indexName,
- };
- httpRequestsMockHelpers.setLoadIndicesResponse([index]);
+ httpRequestsMockHelpers.setLoadIndicesResponse([createNonDataStreamIndex(indexName)]);
testBed = await setup();
const { component, find } = testBed;
@@ -165,20 +160,11 @@ describe.skip('', () => {
describe('index actions', () => {
const indexName = 'testIndex';
+
beforeEach(async () => {
- const index = {
- health: 'green',
- status: 'open',
- primary: 1,
- replica: 1,
- documents: 10000,
- documents_deleted: 100,
- size: '156kb',
- primary_size: '156kb',
- name: indexName,
- };
-
- httpRequestsMockHelpers.setLoadIndicesResponse([index]);
+ httpRequestsMockHelpers.setLoadIndicesResponse([createNonDataStreamIndex(indexName)]);
+ httpRequestsMockHelpers.setReloadIndicesResponse({ indexNames: [indexName] });
+
testBed = await setup();
const { find, component } = testBed;
component.update();
@@ -188,11 +174,15 @@ describe.skip('', () => {
test('should be able to flush index', async () => {
const { actions } = testBed;
+
await actions.clickManageContextMenuButton();
await actions.clickContextMenuOption('flushIndexMenuButton');
- const latestRequest = server.requests[server.requests.length - 1];
- expect(latestRequest.url).toBe(`${API_BASE_PATH}/indices/flush`);
+ const requestsCount = server.requests.length;
+ expect(server.requests[requestsCount - 2].url).toBe(`${API_BASE_PATH}/indices/flush`);
+ // After the indices are flushed, we imediately reload them. So we need to expect to see
+ // a reload server call also.
+ expect(server.requests[requestsCount - 1].url).toBe(`${API_BASE_PATH}/indices/reload`);
});
});
});
From 70c57bca08aa8d8331a26d461a79be26b19b66aa Mon Sep 17 00:00:00 2001
From: DeDe Morton
Date: Mon, 18 Oct 2021 13:58:07 -0700
Subject: [PATCH 012/204] Update doc links to Fleet/Agent docs (#115289)
---
docs/getting-started/quick-start-guide.asciidoc | 2 +-
docs/osquery/osquery.asciidoc | 2 +-
docs/setup/connect-to-elasticsearch.asciidoc | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/getting-started/quick-start-guide.asciidoc b/docs/getting-started/quick-start-guide.asciidoc
index d614aece5b425..2bddd9bf61452 100644
--- a/docs/getting-started/quick-start-guide.asciidoc
+++ b/docs/getting-started/quick-start-guide.asciidoc
@@ -138,7 +138,7 @@ image::images/dashboard_sampleDataAddFilter_7.15.0.png[The [eCommerce] Revenue D
[[quick-start-whats-next]]
== What's next?
-*Add your own data.* Ready to add your own data? Go to {fleet-guide}/fleet-quick-start.html[Quick start: Get logs and metrics into the Elastic Stack] to learn how to ingest your data, or go to <> and learn about all the other ways you can add data.
+*Add your own data.* Ready to add your own data? Go to {observability-guide}/ingest-logs-metrics-uptime.html[Ingest logs, metrics, and uptime data with {agent}], or go to <> and learn about all the other ways you can add data.
*Explore your own data in Discover.* Ready to learn more about exploring your data in *Discover*? Go to <>.
diff --git a/docs/osquery/osquery.asciidoc b/docs/osquery/osquery.asciidoc
index 1e4e6604a7c70..a4f3c80463143 100644
--- a/docs/osquery/osquery.asciidoc
+++ b/docs/osquery/osquery.asciidoc
@@ -365,7 +365,7 @@ The following is an example of an **error response** for an undefined action que
== System requirements
* {fleet-guide}/fleet-overview.html[Fleet] is enabled on your cluster, and
-one or more {fleet-guide}/elastic-agent-installation-configuration.html[Elastic Agents] is enrolled.
+one or more {fleet-guide}/elastic-agent-installation.html[Elastic Agents] is enrolled.
* The https://docs.elastic.co/en/integrations/osquery_manager[*Osquery Manager*] integration
has been added and configured
for an agent policy through Fleet.
diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc
index c7f745648cbe9..ad38ac1710fd5 100644
--- a/docs/setup/connect-to-elasticsearch.asciidoc
+++ b/docs/setup/connect-to-elasticsearch.asciidoc
@@ -47,7 +47,7 @@ so you can quickly get insights into your data, and {fleet} mode offers several
image::images/addData_fleet_7.15.0.png[Add data using Fleet]
To get started, refer to
-{fleet-guide}/fleet-quick-start.html[Quick start: Get logs and metrics into the Elastic Stack].
+{observability-guide}/ingest-logs-metrics-uptime.html[Ingest logs, metrics, and uptime data with {agent}].
[discrete]
[[upload-data-kibana]]
From 7e1acab9dd3c3504fe0ef58be29547919d7b3a26 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Mon, 18 Oct 2021 23:09:04 +0200
Subject: [PATCH 013/204] [Exploratory View] Added step level
filtering/breakdowns (#115182)
Co-authored-by: Dominique Clarke
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../configurations/constants/constants.ts | 3 +
.../constants/field_names/synthetics.ts | 2 +
.../configurations/constants/labels.ts | 14 ++++
.../configurations/lens_attributes.ts | 16 ++++-
.../synthetics/data_distribution_config.ts | 6 +-
.../synthetics/field_formats.ts | 14 ++++
.../synthetics/kpi_over_time_config.ts | 54 +++++++++++++-
.../test_data/sample_attribute_cwv.ts | 3 +-
.../series_editor/breakdown/breakdowns.tsx | 49 +++++++++++--
.../columns/incomplete_badge.tsx | 9 ++-
.../columns/report_definition_col.tsx | 70 ++++++++++++++++---
.../columns/report_definition_field.tsx | 47 +++++++++----
.../series_editor/expanded_series_row.tsx | 6 +-
.../shared/exploratory_view/types.ts | 10 ++-
.../field_value_combobox.tsx | 2 +
.../shared/field_value_suggestions/index.tsx | 5 +-
.../shared/field_value_suggestions/types.ts | 1 +
.../common/header/action_menu_content.tsx | 5 +-
18 files changed, 268 insertions(+), 48 deletions(-)
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
index e4473b183d729..c12e67bc9b1ae 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
@@ -49,7 +49,9 @@ import {
MONITORS_DURATION_LABEL,
PAGE_LOAD_TIME_LABEL,
LABELS_FIELD,
+ STEP_NAME_LABEL,
} from './labels';
+import { SYNTHETICS_STEP_NAME } from './field_names/synthetics';
export const DEFAULT_TIME = { from: 'now-1h', to: 'now' };
@@ -77,6 +79,7 @@ export const FieldLabels: Record = {
'monitor.id': MONITOR_ID_LABEL,
'monitor.status': MONITOR_STATUS_LABEL,
'monitor.duration.us': MONITORS_DURATION_LABEL,
+ [SYNTHETICS_STEP_NAME]: STEP_NAME_LABEL,
'agent.hostname': AGENT_HOST_LABEL,
'host.hostname': HOST_NAME_LABEL,
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts
index eff73d242de75..0f28648552728 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts
@@ -11,3 +11,5 @@ export const SYNTHETICS_LCP = 'browser.experience.lcp.us';
export const SYNTHETICS_FCP = 'browser.experience.fcp.us';
export const SYNTHETICS_DOCUMENT_ONLOAD = 'browser.experience.load.us';
export const SYNTHETICS_DCL = 'browser.experience.dcl.us';
+export const SYNTHETICS_STEP_NAME = 'synthetics.step.name.keyword';
+export const SYNTHETICS_STEP_DURATION = 'synthetics.step.duration.us';
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
index cdaa89fc71389..599f846af2ff9 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
@@ -231,6 +231,20 @@ export const MONITORS_DURATION_LABEL = i18n.translate(
}
);
+export const STEP_DURATION_LABEL = i18n.translate(
+ 'xpack.observability.expView.fieldLabels.stepDurationLabel',
+ {
+ defaultMessage: 'Step duration',
+ }
+);
+
+export const STEP_NAME_LABEL = i18n.translate(
+ 'xpack.observability.expView.fieldLabels.stepNameLabel',
+ {
+ defaultMessage: 'Step name',
+ }
+);
+
export const WEB_APPLICATION_LABEL = i18n.translate(
'xpack.observability.expView.fieldLabels.webApplication',
{
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
index 38c9ecc06491d..a31bef7c9c214 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
@@ -194,10 +194,12 @@ export class LensAttributes {
label,
sourceField,
columnType,
+ columnFilter,
operationType,
}: {
sourceField: string;
columnType?: string;
+ columnFilter?: ColumnFilter;
operationType?: string;
label?: string;
seriesConfig: SeriesConfig;
@@ -214,6 +216,7 @@ export class LensAttributes {
operationType,
label,
seriesConfig,
+ columnFilter,
});
}
if (operationType?.includes('th')) {
@@ -228,11 +231,13 @@ export class LensAttributes {
label,
seriesConfig,
operationType,
+ columnFilter,
}: {
sourceField: string;
operationType: 'average' | 'median' | 'sum' | 'unique_count';
label?: string;
seriesConfig: SeriesConfig;
+ columnFilter?: ColumnFilter;
}):
| AvgIndexPatternColumn
| MedianIndexPatternColumn
@@ -247,6 +252,7 @@ export class LensAttributes {
operationType: capitalize(operationType),
},
}),
+ filter: columnFilter,
operationType,
};
}
@@ -391,6 +397,7 @@ export class LensAttributes {
return this.getNumberColumn({
sourceField: fieldName,
columnType,
+ columnFilter: columnFilters?.[0],
operationType,
label: columnLabel || label,
seriesConfig: layerConfig.seriesConfig,
@@ -447,10 +454,10 @@ export class LensAttributes {
return this.getColumnBasedOnType({
sourceField,
- operationType: breakdown === PERCENTILE ? PERCENTILE_RANKS[0] : operationType,
label,
layerConfig,
colIndex: 0,
+ operationType: breakdown === PERCENTILE ? PERCENTILE_RANKS[0] : operationType,
});
}
@@ -629,7 +636,12 @@ export class LensAttributes {
[`y-axis-column-${layerId}`]: {
...mainYAxis,
label,
- filter: { query: columnFilter, language: 'kuery' },
+ filter: {
+ query: mainYAxis.filter
+ ? `${columnFilter} and ${mainYAxis.filter.query}`
+ : columnFilter,
+ language: 'kuery',
+ },
...(timeShift ? { timeShift } : {}),
},
...(breakdown && sourceField !== USE_BREAK_DOWN_COLUMN && breakdown !== PERCENTILE
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts
index da90f45d15201..fb44da8e4327f 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts
@@ -29,6 +29,7 @@ import {
SYNTHETICS_FCP,
SYNTHETICS_LCP,
} from '../constants/field_names/synthetics';
+import { buildExistsFilter } from '../utils';
export function getSyntheticsDistributionConfig({
series,
@@ -58,7 +59,10 @@ export function getSyntheticsDistributionConfig({
'url.port',
],
baseFilters: [],
- definitionFields: ['monitor.name', 'url.full'],
+ definitionFields: [
+ { field: 'monitor.name', nested: 'synthetics.step.name.keyword', singleSelection: true },
+ { field: 'url.full', filters: buildExistsFilter('summary.up', indexPattern) },
+ ],
metricOptions: [
{
label: MONITORS_DURATION_LABEL,
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts
index 5f8a6a28ca81d..f3e3fe0817845 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts
@@ -11,6 +11,7 @@ import {
SYNTHETICS_DOCUMENT_ONLOAD,
SYNTHETICS_FCP,
SYNTHETICS_LCP,
+ SYNTHETICS_STEP_DURATION,
} from '../constants/field_names/synthetics';
export const syntheticsFieldFormats: FieldFormat[] = [
@@ -27,6 +28,19 @@ export const syntheticsFieldFormats: FieldFormat[] = [
},
},
},
+ {
+ field: SYNTHETICS_STEP_DURATION,
+ format: {
+ id: 'duration',
+ params: {
+ inputFormat: 'microseconds',
+ outputFormat: 'humanizePrecise',
+ outputPrecision: 1,
+ showSuffix: true,
+ useShortSuffix: true,
+ },
+ },
+ },
{
field: SYNTHETICS_LCP,
format: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts
index 6df9cdcd0503a..8951ffcda63d8 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConfigProps, SeriesConfig } from '../../types';
+import { ColumnFilter, ConfigProps, SeriesConfig } from '../../types';
import {
FieldLabels,
OPERATION_COLUMN,
@@ -21,6 +21,7 @@ import {
FCP_LABEL,
LCP_LABEL,
MONITORS_DURATION_LABEL,
+ STEP_DURATION_LABEL,
UP_LABEL,
} from '../constants/labels';
import {
@@ -30,10 +31,26 @@ import {
SYNTHETICS_DOCUMENT_ONLOAD,
SYNTHETICS_FCP,
SYNTHETICS_LCP,
+ SYNTHETICS_STEP_DURATION,
+ SYNTHETICS_STEP_NAME,
} from '../constants/field_names/synthetics';
+import { buildExistsFilter } from '../utils';
const SUMMARY_UP = 'summary.up';
const SUMMARY_DOWN = 'summary.down';
+export const isStepLevelMetric = (metric?: string) => {
+ if (!metric) {
+ return false;
+ }
+ return [
+ SYNTHETICS_LCP,
+ SYNTHETICS_FCP,
+ SYNTHETICS_CLS,
+ SYNTHETICS_DCL,
+ SYNTHETICS_STEP_DURATION,
+ SYNTHETICS_DOCUMENT_ONLOAD,
+ ].includes(metric);
+};
export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesConfig {
return {
reportType: ReportTypes.KPI,
@@ -50,10 +67,19 @@ export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesCon
],
hasOperationType: false,
filterFields: ['observer.geo.name', 'monitor.type', 'tags'],
- breakdownFields: ['observer.geo.name', 'monitor.type', 'monitor.name', PERCENTILE],
+ breakdownFields: [
+ 'observer.geo.name',
+ 'monitor.type',
+ 'monitor.name',
+ SYNTHETICS_STEP_NAME,
+ PERCENTILE,
+ ],
baseFilters: [],
palette: { type: 'palette', name: 'status' },
- definitionFields: ['monitor.name', 'url.full'],
+ definitionFields: [
+ { field: 'monitor.name', nested: SYNTHETICS_STEP_NAME, singleSelection: true },
+ { field: 'url.full', filters: buildExistsFilter('summary.up', indexPattern) },
+ ],
metricOptions: [
{
label: MONITORS_DURATION_LABEL,
@@ -73,37 +99,59 @@ export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesCon
label: DOWN_LABEL,
columnType: OPERATION_COLUMN,
},
+ {
+ label: STEP_DURATION_LABEL,
+ field: SYNTHETICS_STEP_DURATION,
+ id: SYNTHETICS_STEP_DURATION,
+ columnType: OPERATION_COLUMN,
+ columnFilters: [STEP_END_FILTER],
+ },
{
label: LCP_LABEL,
field: SYNTHETICS_LCP,
id: SYNTHETICS_LCP,
columnType: OPERATION_COLUMN,
+ columnFilters: [STEP_METRIC_FILTER],
},
{
label: FCP_LABEL,
field: SYNTHETICS_FCP,
id: SYNTHETICS_FCP,
columnType: OPERATION_COLUMN,
+ columnFilters: [STEP_METRIC_FILTER],
},
{
label: DCL_LABEL,
field: SYNTHETICS_DCL,
id: SYNTHETICS_DCL,
columnType: OPERATION_COLUMN,
+ columnFilters: [STEP_METRIC_FILTER],
},
{
label: DOCUMENT_ONLOAD_LABEL,
field: SYNTHETICS_DOCUMENT_ONLOAD,
id: SYNTHETICS_DOCUMENT_ONLOAD,
columnType: OPERATION_COLUMN,
+ columnFilters: [STEP_METRIC_FILTER],
},
{
label: CLS_LABEL,
field: SYNTHETICS_CLS,
id: SYNTHETICS_CLS,
columnType: OPERATION_COLUMN,
+ columnFilters: [STEP_METRIC_FILTER],
},
],
labels: { ...FieldLabels, [SUMMARY_UP]: UP_LABEL, [SUMMARY_DOWN]: DOWN_LABEL },
};
}
+
+const STEP_METRIC_FILTER: ColumnFilter = {
+ language: 'kuery',
+ query: `synthetics.type: step/metrics`,
+};
+
+const STEP_END_FILTER: ColumnFilter = {
+ language: 'kuery',
+ query: `synthetics.type: step/end`,
+};
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts
index adc6d4bb14462..4563509eeb19a 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts
@@ -77,7 +77,8 @@ export const sampleAttributeCoreWebVital = {
dataType: 'number',
filter: {
language: 'kuery',
- query: 'transaction.type: page-load and processor.event: transaction',
+ query:
+ 'transaction.type: page-load and processor.event: transaction and transaction.marks.agent.largestContentfulPaint < 2500',
},
isBucketed: false,
label: 'Good',
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/breakdown/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/breakdown/breakdowns.tsx
index a235cbd8852ad..f30a80f87ebb7 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/breakdown/breakdowns.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/breakdown/breakdowns.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React from 'react';
+import React, { useEffect } from 'react';
import styled from 'styled-components';
import { EuiSuperSelect, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -17,6 +17,8 @@ import {
PERCENTILE,
} from '../../configurations/constants';
import { SeriesConfig, SeriesUrl } from '../../types';
+import { SYNTHETICS_STEP_NAME } from '../../configurations/constants/field_names/synthetics';
+import { isStepLevelMetric } from '../../configurations/synthetics/kpi_over_time_config';
interface Props {
seriesId: number;
@@ -51,6 +53,18 @@ export function Breakdowns({ seriesConfig, seriesId, series }: Props) {
}
};
+ useEffect(() => {
+ if (
+ !isStepLevelMetric(series.selectedMetricField) &&
+ selectedBreakdown === SYNTHETICS_STEP_NAME
+ ) {
+ setSeries(seriesId, {
+ ...series,
+ breakdown: undefined,
+ });
+ }
+ });
+
if (!seriesConfig) {
return null;
}
@@ -71,11 +85,26 @@ export function Breakdowns({ seriesConfig, seriesId, series }: Props) {
}
const options = items
- .map(({ id, label }) => ({
- inputDisplay: label,
- value: id,
- dropdownDisplay: label,
- }))
+ .map(({ id, label }) => {
+ if (id === SYNTHETICS_STEP_NAME && !isStepLevelMetric(series.selectedMetricField)) {
+ return {
+ inputDisplay: label,
+ value: id,
+ dropdownDisplay: (
+
+ <>{label}>
+
+ ),
+ disabled: true,
+ };
+ } else {
+ return {
+ inputDisplay: label,
+ value: id,
+ dropdownDisplay: label,
+ };
+ }
+ })
.filter(({ value }) => !(value === PERCENTILE && isRecordsMetric));
let valueOfSelected =
@@ -121,6 +150,14 @@ export const BREAKDOWN_WARNING = i18n.translate('xpack.observability.exp.breakDo
defaultMessage: 'Breakdowns can be applied to only one series at a time.',
});
+export const BREAKDOWN_UNAVAILABLE = i18n.translate(
+ 'xpack.observability.exp.breakDownFilter.unavailable',
+ {
+ defaultMessage:
+ 'Step name breakdown is not available for monitor duration metric. Use step duration metric to breakdown by step name.',
+ }
+);
+
const Wrapper = styled.span`
.euiToolTipAnchor {
width: 100%;
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/incomplete_badge.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/incomplete_badge.tsx
index 4e1c385921908..8e64f4bcea680 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/incomplete_badge.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/incomplete_badge.tsx
@@ -31,7 +31,14 @@ export function IncompleteBadge({ seriesConfig, series }: Props) {
const incompleteDefinition = isEmpty(reportDefinitions)
? i18n.translate('xpack.observability.overview.exploratoryView.missingReportDefinition', {
defaultMessage: 'Missing {reportDefinition}',
- values: { reportDefinition: labels?.[definitionFields[0]] },
+ values: {
+ reportDefinition:
+ labels?.[
+ typeof definitionFields[0] === 'string'
+ ? definitionFields[0]
+ : definitionFields[0].field
+ ],
+ },
})
: '';
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.tsx
index fbd7c34303d94..a665ec1999133 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_col.tsx
@@ -6,10 +6,13 @@
*/
import React from 'react';
+import { isEmpty } from 'lodash';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { useSeriesStorage } from '../../hooks/use_series_storage';
import { SeriesConfig, SeriesUrl } from '../../types';
import { ReportDefinitionField } from './report_definition_field';
+import { isStepLevelMetric } from '../../configurations/synthetics/kpi_over_time_config';
+import { SYNTHETICS_STEP_NAME } from '../../configurations/constants/field_names/synthetics';
export function ReportDefinitionCol({
seriesId,
@@ -41,19 +44,64 @@ export function ReportDefinitionCol({
}
};
+ const hasFieldDataSelected = (field: string) => {
+ return !isEmpty(series.reportDefinitions?.[field]);
+ };
+
return (
- {definitionFields.map((field) => (
-
-
-
- ))}
+ {definitionFields.map((field) => {
+ const fieldStr = typeof field === 'string' ? field : field.field;
+ const singleSelection = typeof field !== 'string' && field.singleSelection;
+ const nestedField = typeof field !== 'string' && field.nested;
+ const filters = typeof field !== 'string' ? field.filters : undefined;
+
+ const isNonStepMetric = !isStepLevelMetric(series.selectedMetricField);
+
+ const hideNestedStep = nestedField === SYNTHETICS_STEP_NAME && isNonStepMetric;
+
+ if (hideNestedStep && nestedField && selectedReportDefinitions[nestedField]?.length > 0) {
+ setSeries(seriesId, {
+ ...series,
+ reportDefinitions: { ...selectedReportDefinitions, [nestedField]: [] },
+ });
+ }
+
+ let nestedFieldElement;
+
+ if (nestedField && hasFieldDataSelected(fieldStr) && !hideNestedStep) {
+ nestedFieldElement = (
+
+
+
+ );
+ }
+
+ return (
+ <>
+
+
+
+ {nestedFieldElement}
+ >
+ );
+ })}
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx
index 01f36e85c03ae..f3e0eb767d336 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx
@@ -7,7 +7,7 @@
import React, { useMemo } from 'react';
import { isEmpty } from 'lodash';
-import { ExistsFilter } from '@kbn/es-query';
+import { ExistsFilter, PhraseFilter } from '@kbn/es-query';
import FieldValueSuggestions from '../../../field_value_suggestions';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
import { ESFilter } from '../../../../../../../../../src/core/types/elasticsearch';
@@ -19,32 +19,49 @@ import { ALL_VALUES_SELECTED } from '../../../field_value_suggestions/field_valu
interface Props {
seriesId: number;
series: SeriesUrl;
- field: string;
+ singleSelection?: boolean;
+ keepHistory?: boolean;
+ field: string | { field: string; nested: string };
seriesConfig: SeriesConfig;
onChange: (field: string, value?: string[]) => void;
+ filters?: Array;
}
-export function ReportDefinitionField({ series, field, seriesConfig, onChange }: Props) {
+export function ReportDefinitionField({
+ singleSelection,
+ keepHistory,
+ series,
+ field: fieldProp,
+ seriesConfig,
+ onChange,
+ filters,
+}: Props) {
const { indexPattern } = useAppIndexPatternContext(series.dataType);
+ const field = typeof fieldProp === 'string' ? fieldProp : fieldProp.field;
+
const { reportDefinitions: selectedReportDefinitions = {} } = series;
const { labels, baseFilters, definitionFields } = seriesConfig;
const queryFilters = useMemo(() => {
const filtersN: ESFilter[] = [];
- (baseFilters ?? []).forEach((qFilter: PersistableFilter | ExistsFilter) => {
- if (qFilter.query) {
- filtersN.push(qFilter.query);
- }
- const existFilter = qFilter as ExistsFilter;
- if (existFilter.query.exists) {
- filtersN.push({ exists: existFilter.query.exists });
- }
- });
+ (baseFilters ?? [])
+ .concat(filters ?? [])
+ .forEach((qFilter: PersistableFilter | ExistsFilter) => {
+ if (qFilter.query) {
+ filtersN.push(qFilter.query);
+ }
+ const existFilter = qFilter as ExistsFilter;
+ if (existFilter.query.exists) {
+ filtersN.push({ exists: existFilter.query.exists });
+ }
+ });
if (!isEmpty(selectedReportDefinitions)) {
- definitionFields.forEach((fieldT) => {
+ definitionFields.forEach((fieldObj) => {
+ const fieldT = typeof fieldObj === 'string' ? fieldObj : fieldObj.field;
+
if (indexPattern && selectedReportDefinitions?.[fieldT] && fieldT !== field) {
const values = selectedReportDefinitions?.[fieldT];
if (!values.includes(ALL_VALUES_SELECTED)) {
@@ -65,7 +82,7 @@ export function ReportDefinitionField({ series, field, seriesConfig, onChange }:
return (
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/expanded_series_row.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/expanded_series_row.tsx
index 180be1ac0414f..12e0ceca20649 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/expanded_series_row.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/expanded_series_row.tsx
@@ -48,13 +48,13 @@ export function ExpandedSeriesRow(seriesProps: Props) {
return (
-
-
+
+
-
+
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
index 001664cf12783..acd49fc25588e 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
@@ -56,7 +56,15 @@ export interface SeriesConfig {
filterFields: Array;
seriesTypes: SeriesType[];
baseFilters?: Array;
- definitionFields: string[];
+ definitionFields: Array<
+ | string
+ | {
+ field: string;
+ nested?: string;
+ singleSelection?: boolean;
+ filters?: Array;
+ }
+ >;
metricOptions?: MetricOption[];
labels: Record;
hasOperationType: boolean;
diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx
index 0735df53888aa..e04d5463d5494 100644
--- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx
+++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx
@@ -42,6 +42,7 @@ export function FieldValueCombobox({
usePrependLabel = true,
compressed = true,
required = true,
+ singleSelection = false,
allowAllValuesSelection,
onChange: onSelectionChange,
}: FieldValueSelectionProps) {
@@ -68,6 +69,7 @@ export function FieldValueCombobox({
const comboBox = (
);
diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts
index 6f6d520a83154..95b24aa69b1e7 100644
--- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts
+++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts
@@ -29,6 +29,7 @@ interface CommonProps {
allowAllValuesSelection?: boolean;
cardinalityField?: string;
required?: boolean;
+ keepHistory?: boolean;
}
export type FieldValueSuggestionsProps = CommonProps & {
diff --git a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
index 21ef3428696e9..bcfbf18c93cf5 100644
--- a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
+++ b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
@@ -56,9 +56,8 @@ export function ActionMenuContent(): React.ReactElement {
time: { from: dateRangeStart, to: dateRangeEnd },
breakdown: monitorId ? 'observer.geo.name' : 'monitor.type',
reportDefinitions: {
- 'monitor.name': selectedMonitor?.monitor?.name
- ? [selectedMonitor?.monitor?.name]
- : ['ALL_VALUES'],
+ 'monitor.name': selectedMonitor?.monitor?.name ? [selectedMonitor?.monitor?.name] : [],
+ 'url.full': ['ALL_VALUES'],
},
name: monitorId ? `${monitorId}-response-duration` : 'All monitors response duration',
},
From 85d7115d4a3051846655e46575b3558530491fd0 Mon Sep 17 00:00:00 2001
From: Rich Kuzsma <62522248+richkuz@users.noreply.github.com>
Date: Mon, 18 Oct 2021 17:19:00 -0400
Subject: [PATCH 014/204] Document edge cases for enterpriseSearch.host
(#115446)
Fixes https://github.com/elastic/enterprise-search-team/issues/517
---
docs/setup/settings.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index 16fa8eb734204..4802a4da8182c 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -275,7 +275,7 @@ that the {kib} server uses to perform maintenance on the {kib} index at startup.
is an alternative to `elasticsearch.username` and `elasticsearch.password`.
| `enterpriseSearch.host`
- | The URL of your Enterprise Search instance
+ | The http(s) URL of your Enterprise Search instance. For example, in a local self-managed setup, set this to `http://localhost:3002`. Authentication between Kibana and the Enterprise Search host URL, such as via OAuth, is not supported. You can also {enterprise-search-ref}/configure-ssl-tls.html#configure-ssl-tls-in-kibana[configure Kibana to trust your Enterprise Search TLS certificate authority].
| `interpreter.enableInVisualize`
| Enables use of interpreter in Visualize. *Default: `true`*
From eb5ffff7d09f53880d24f5d6d43289afe1eee2d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?=
Date: Mon, 18 Oct 2021 23:25:01 +0200
Subject: [PATCH 015/204] Clean angular from moved code, global state and
legacy shims (#115420)
---
.../public/alerts/alert_form.test.tsx | 18 +-
.../public/angular/angular_i18n/directive.ts | 103 ------
.../public/angular/angular_i18n/filter.ts | 19 -
.../public/angular/angular_i18n/index.ts | 15 -
.../public/angular/angular_i18n/provider.ts | 25 --
.../helpers/format_angular_http_error.ts | 43 ---
.../public/angular/top_nav/angular_config.tsx | 349 ------------------
.../public/angular/top_nav/index.ts | 10 -
.../public/angular/top_nav/kbn_top_nav.d.ts | 16 -
.../public/angular/top_nav/kbn_top_nav.js | 119 ------
.../contexts/global_state_context.tsx | 25 +-
.../plugins/monitoring/public/legacy_shims.ts | 25 +-
x-pack/plugins/monitoring/public/url_state.ts | 34 --
13 files changed, 18 insertions(+), 783 deletions(-)
delete mode 100644 x-pack/plugins/monitoring/public/angular/angular_i18n/directive.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/angular_i18n/filter.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/angular_i18n/index.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/angular_i18n/provider.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/helpers/format_angular_http_error.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/top_nav/angular_config.tsx
delete mode 100644 x-pack/plugins/monitoring/public/angular/top_nav/index.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/top_nav/kbn_top_nav.d.ts
delete mode 100644 x-pack/plugins/monitoring/public/angular/top_nav/kbn_top_nav.js
diff --git a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx
index 8752a4118fb6a..1a44beab260cb 100644
--- a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx
@@ -50,17 +50,13 @@ const initLegacyShims = () => {
ruleTypeRegistry: ruleTypeRegistryMock.create(),
};
const data = { query: { timefilter: { timefilter: {} } } } as any;
- const ngInjector = {} as angular.auto.IInjectorService;
- Legacy.init(
- {
- core: coreMock.createStart(),
- data,
- isCloud: false,
- triggersActionsUi,
- usageCollection: {},
- } as any,
- ngInjector
- );
+ Legacy.init({
+ core: coreMock.createStart(),
+ data,
+ isCloud: false,
+ triggersActionsUi,
+ usageCollection: {},
+ } as any);
};
const ALERTS_FEATURE_ID = 'alerts';
diff --git a/x-pack/plugins/monitoring/public/angular/angular_i18n/directive.ts b/x-pack/plugins/monitoring/public/angular/angular_i18n/directive.ts
deleted file mode 100644
index 1aaff99a6a5c1..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/angular_i18n/directive.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { IDirective, IRootElementService, IScope } from 'angular';
-
-import { I18nServiceType } from './provider';
-
-interface I18nScope extends IScope {
- values?: Record;
- defaultMessage: string;
- id: string;
-}
-
-const HTML_KEY_PREFIX = 'html_';
-const PLACEHOLDER_SEPARATOR = '@I18N@';
-
-export const i18nDirective: [string, string, typeof i18nDirectiveFn] = [
- 'i18n',
- '$sanitize',
- i18nDirectiveFn,
-];
-
-function i18nDirectiveFn(
- i18n: I18nServiceType,
- $sanitize: (html: string) => string
-): IDirective {
- return {
- restrict: 'A',
- scope: {
- id: '@i18nId',
- defaultMessage: '@i18nDefaultMessage',
- values: ' {
- setContent($element, $scope, $sanitize, i18n);
- });
- } else {
- setContent($element, $scope, $sanitize, i18n);
- }
- },
- };
-}
-
-function setContent(
- $element: IRootElementService,
- $scope: I18nScope,
- $sanitize: (html: string) => string,
- i18n: I18nServiceType
-) {
- const originalValues = $scope.values;
- const valuesWithPlaceholders = {} as Record;
- let hasValuesWithPlaceholders = false;
-
- // If we have values with the keys that start with HTML_KEY_PREFIX we should replace
- // them with special placeholders that later on will be inserted as HTML
- // into the DOM, the rest of the content will be treated as text. We don't
- // sanitize values at this stage as some of the values can be excluded from
- // the translated string (e.g. not used by ICU conditional statements).
- if (originalValues) {
- for (const [key, value] of Object.entries(originalValues)) {
- if (key.startsWith(HTML_KEY_PREFIX)) {
- valuesWithPlaceholders[
- key.slice(HTML_KEY_PREFIX.length)
- ] = `${PLACEHOLDER_SEPARATOR}${key}${PLACEHOLDER_SEPARATOR}`;
-
- hasValuesWithPlaceholders = true;
- } else {
- valuesWithPlaceholders[key] = value;
- }
- }
- }
-
- const label = i18n($scope.id, {
- values: valuesWithPlaceholders,
- defaultMessage: $scope.defaultMessage,
- });
-
- // If there are no placeholders to replace treat everything as text, otherwise
- // insert label piece by piece replacing every placeholder with corresponding
- // sanitized HTML content.
- if (!hasValuesWithPlaceholders) {
- $element.text(label);
- } else {
- $element.empty();
- for (const contentOrPlaceholder of label.split(PLACEHOLDER_SEPARATOR)) {
- if (!contentOrPlaceholder) {
- continue;
- }
-
- $element.append(
- originalValues!.hasOwnProperty(contentOrPlaceholder)
- ? $sanitize(originalValues![contentOrPlaceholder])
- : document.createTextNode(contentOrPlaceholder)
- );
- }
- }
-}
diff --git a/x-pack/plugins/monitoring/public/angular/angular_i18n/filter.ts b/x-pack/plugins/monitoring/public/angular/angular_i18n/filter.ts
deleted file mode 100644
index e4e553fa47b6f..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/angular_i18n/filter.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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { I18nServiceType } from './provider';
-
-export const i18nFilter: [string, typeof i18nFilterFn] = ['i18n', i18nFilterFn];
-
-function i18nFilterFn(i18n: I18nServiceType) {
- return (id: string, { defaultMessage = '', values = {} } = {}) => {
- return i18n(id, {
- values,
- defaultMessage,
- });
- };
-}
diff --git a/x-pack/plugins/monitoring/public/angular/angular_i18n/index.ts b/x-pack/plugins/monitoring/public/angular/angular_i18n/index.ts
deleted file mode 100644
index 8915c96e59be0..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/angular_i18n/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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export { I18nProvider } from './provider';
-
-export { i18nFilter } from './filter';
-export { i18nDirective } from './directive';
-
-// re-export types: https://github.com/babel/babel-loader/issues/603
-import { I18nServiceType as _I18nServiceType } from './provider';
-export type I18nServiceType = _I18nServiceType;
diff --git a/x-pack/plugins/monitoring/public/angular/angular_i18n/provider.ts b/x-pack/plugins/monitoring/public/angular/angular_i18n/provider.ts
deleted file mode 100644
index b1da1bad6e399..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/angular_i18n/provider.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-
-export type I18nServiceType = ReturnType;
-
-export class I18nProvider implements angular.IServiceProvider {
- public addTranslation = i18n.addTranslation;
- public getTranslation = i18n.getTranslation;
- public setLocale = i18n.setLocale;
- public getLocale = i18n.getLocale;
- public setDefaultLocale = i18n.setDefaultLocale;
- public getDefaultLocale = i18n.getDefaultLocale;
- public setFormats = i18n.setFormats;
- public getFormats = i18n.getFormats;
- public getRegisteredLocales = i18n.getRegisteredLocales;
- public init = i18n.init;
- public load = i18n.load;
- public $get = () => i18n.translate;
-}
diff --git a/x-pack/plugins/monitoring/public/angular/helpers/format_angular_http_error.ts b/x-pack/plugins/monitoring/public/angular/helpers/format_angular_http_error.ts
deleted file mode 100644
index abdcf157a3c86..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/helpers/format_angular_http_error.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import type { IHttpResponse } from 'angular';
-
-type AngularHttpError = IHttpResponse<{ message: string }>;
-
-export function isAngularHttpError(error: any): error is AngularHttpError {
- return (
- error &&
- typeof error.status === 'number' &&
- typeof error.statusText === 'string' &&
- error.data &&
- typeof error.data.message === 'string'
- );
-}
-
-export function formatAngularHttpError(error: AngularHttpError) {
- // is an Angular $http "error object"
- if (error.status === -1) {
- // status = -1 indicates that the request was failed to reach the server
- return i18n.translate('xpack.monitoring.notify.fatalError.unavailableServerErrorMessage', {
- defaultMessage:
- 'An HTTP request has failed to connect. ' +
- 'Please check if the Kibana server is running and that your browser has a working connection, ' +
- 'or contact your system administrator.',
- });
- }
-
- return i18n.translate('xpack.monitoring.notify.fatalError.errorStatusMessage', {
- defaultMessage: 'Error {errStatus} {errStatusText}: {errMessage}',
- values: {
- errStatus: error.status,
- errStatusText: error.statusText,
- errMessage: error.data.message,
- },
- });
-}
diff --git a/x-pack/plugins/monitoring/public/angular/top_nav/angular_config.tsx b/x-pack/plugins/monitoring/public/angular/top_nav/angular_config.tsx
deleted file mode 100644
index 9c2e931d24a94..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/top_nav/angular_config.tsx
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
- ICompileProvider,
- IHttpProvider,
- IHttpService,
- ILocationProvider,
- IModule,
- IRootScopeService,
- IRequestConfig,
-} from 'angular';
-import $ from 'jquery';
-import { set } from '@elastic/safer-lodash-set';
-import { get } from 'lodash';
-import * as Rx from 'rxjs';
-import { ChromeBreadcrumb, EnvironmentMode, PackageInfo } from 'kibana/public';
-import { History } from 'history';
-
-import { CoreStart } from 'kibana/public';
-import { formatAngularHttpError, isAngularHttpError } from '../helpers/format_angular_http_error';
-
-export interface RouteConfiguration {
- controller?: string | ((...args: any[]) => void);
- redirectTo?: string;
- resolveRedirectTo?: (...args: any[]) => void;
- reloadOnSearch?: boolean;
- reloadOnUrl?: boolean;
- outerAngularWrapperRoute?: boolean;
- resolve?: object;
- template?: string;
- k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[];
- requireUICapability?: string;
-}
-
-function isSystemApiRequest(request: IRequestConfig) {
- const { headers } = request;
- return headers && !!headers['kbn-system-request'];
-}
-
-/**
- * Detects whether a given angular route is a dummy route that doesn't
- * require any action. There are two ways this can happen:
- * If `outerAngularWrapperRoute` is set on the route config object,
- * it means the local application service set up this route on the outer angular
- * and the internal routes will handle the hooks.
- *
- * If angular did not detect a route and it is the local angular, we are currently
- * navigating away from a URL controlled by a local angular router and the
- * application will get unmounted. In this case the outer router will handle
- * the hooks.
- * @param $route Injected $route dependency
- * @param isLocalAngular Flag whether this is the local angular router
- */
-function isDummyRoute($route: any, isLocalAngular: boolean) {
- return (
- ($route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute) ||
- (!$route.current && isLocalAngular)
- );
-}
-
-export const configureAppAngularModule = (
- angularModule: IModule,
- newPlatform: {
- core: CoreStart;
- readonly env: {
- mode: Readonly;
- packageInfo: Readonly;
- };
- },
- isLocalAngular: boolean,
- getHistory?: () => History
-) => {
- const core = 'core' in newPlatform ? newPlatform.core : newPlatform;
- const packageInfo = newPlatform.env.packageInfo;
-
- angularModule
- .value('kbnVersion', packageInfo.version)
- .value('buildNum', packageInfo.buildNum)
- .value('buildSha', packageInfo.buildSha)
- .value('esUrl', getEsUrl(core))
- .value('uiCapabilities', core.application.capabilities)
- .config(setupCompileProvider(newPlatform.env.mode.dev))
- .config(setupLocationProvider())
- .config($setupXsrfRequestInterceptor(packageInfo.version))
- .run(capture$httpLoadingCount(core))
- .run(digestOnHashChange(getHistory))
- .run($setupBreadcrumbsAutoClear(core, isLocalAngular))
- .run($setupBadgeAutoClear(core, isLocalAngular))
- .run($setupHelpExtensionAutoClear(core, isLocalAngular))
- .run($setupUICapabilityRedirect(core));
-};
-
-const getEsUrl = (newPlatform: CoreStart) => {
- const a = document.createElement('a');
- a.href = newPlatform.http.basePath.prepend('/elasticsearch');
- const protocolPort = /https/.test(a.protocol) ? 443 : 80;
- const port = a.port || protocolPort;
- return {
- host: a.hostname,
- port,
- protocol: a.protocol,
- pathname: a.pathname,
- };
-};
-
-const digestOnHashChange = (getHistory?: () => History) => ($rootScope: IRootScopeService) => {
- if (!getHistory) return;
- const unlisten = getHistory().listen(() => {
- // dispatch synthetic hash change event to update hash history objects and angular routing
- // this is necessary because hash updates triggered by using popState won't trigger this event naturally.
- // this has to happen in the next tick to not change the existing timing of angular digest cycles.
- setTimeout(() => {
- window.dispatchEvent(new HashChangeEvent('hashchange'));
- }, 0);
- });
- $rootScope.$on('$destroy', unlisten);
-};
-
-const setupCompileProvider = (devMode: boolean) => ($compileProvider: ICompileProvider) => {
- if (!devMode) {
- $compileProvider.debugInfoEnabled(false);
- }
-};
-
-const setupLocationProvider = () => ($locationProvider: ILocationProvider) => {
- $locationProvider.html5Mode({
- enabled: false,
- requireBase: false,
- rewriteLinks: false,
- });
-
- $locationProvider.hashPrefix('');
-};
-
-export const $setupXsrfRequestInterceptor = (version: string) => {
- // Configure jQuery prefilter
- $.ajaxPrefilter(({ kbnXsrfToken = true }: any, originalOptions, jqXHR) => {
- if (kbnXsrfToken) {
- jqXHR.setRequestHeader('kbn-version', version);
- }
- });
-
- return ($httpProvider: IHttpProvider) => {
- // Configure $httpProvider interceptor
- $httpProvider.interceptors.push(() => {
- return {
- request(opts) {
- const { kbnXsrfToken = true } = opts as any;
- if (kbnXsrfToken) {
- set(opts, ['headers', 'kbn-version'], version);
- }
- return opts;
- },
- };
- });
- };
-};
-
-/**
- * Injected into angular module by ui/chrome angular integration
- * and adds a root-level watcher that will capture the count of
- * active $http requests on each digest loop and expose the count to
- * the core.loadingCount api
- */
-const capture$httpLoadingCount =
- (newPlatform: CoreStart) => ($rootScope: IRootScopeService, $http: IHttpService) => {
- newPlatform.http.addLoadingCountSource(
- new Rx.Observable((observer) => {
- const unwatch = $rootScope.$watch(() => {
- const reqs = $http.pendingRequests || [];
- observer.next(reqs.filter((req) => !isSystemApiRequest(req)).length);
- });
-
- return unwatch;
- })
- );
- };
-
-/**
- * integrates with angular to automatically redirect to home if required
- * capability is not met
- */
-const $setupUICapabilityRedirect =
- (newPlatform: CoreStart) => ($rootScope: IRootScopeService, $injector: any) => {
- const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana');
- // this feature only works within kibana app for now after everything is
- // switched to the application service, this can be changed to handle all
- // apps.
- if (!isKibanaAppRoute) {
- return;
- }
- $rootScope.$on(
- '$routeChangeStart',
- (event, { $$route: route }: { $$route?: RouteConfiguration } = {}) => {
- if (!route || !route.requireUICapability) {
- return;
- }
-
- if (!get(newPlatform.application.capabilities, route.requireUICapability)) {
- $injector.get('$location').url('/home');
- event.preventDefault();
- }
- }
- );
- };
-
-/**
- * internal angular run function that will be called when angular bootstraps and
- * lets us integrate with the angular router so that we can automatically clear
- * the breadcrumbs if we switch to a Kibana app that does not use breadcrumbs correctly
- */
-const $setupBreadcrumbsAutoClear =
- (newPlatform: CoreStart, isLocalAngular: boolean) =>
- ($rootScope: IRootScopeService, $injector: any) => {
- // A flag used to determine if we should automatically
- // clear the breadcrumbs between angular route changes.
- let breadcrumbSetSinceRouteChange = false;
- const $route = $injector.has('$route') ? $injector.get('$route') : {};
-
- // reset breadcrumbSetSinceRouteChange any time the breadcrumbs change, even
- // if it was done directly through the new platform
- newPlatform.chrome.getBreadcrumbs$().subscribe({
- next() {
- breadcrumbSetSinceRouteChange = true;
- },
- });
-
- $rootScope.$on('$routeChangeStart', () => {
- breadcrumbSetSinceRouteChange = false;
- });
-
- $rootScope.$on('$routeChangeSuccess', () => {
- if (isDummyRoute($route, isLocalAngular)) {
- return;
- }
- const current = $route.current || {};
-
- if (breadcrumbSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
- return;
- }
-
- const k7BreadcrumbsProvider = current.k7Breadcrumbs;
- if (!k7BreadcrumbsProvider) {
- newPlatform.chrome.setBreadcrumbs([]);
- return;
- }
-
- try {
- newPlatform.chrome.setBreadcrumbs($injector.invoke(k7BreadcrumbsProvider));
- } catch (error) {
- if (isAngularHttpError(error)) {
- error = formatAngularHttpError(error);
- }
- newPlatform.fatalErrors.add(error, 'location');
- }
- });
- };
-
-/**
- * internal angular run function that will be called when angular bootstraps and
- * lets us integrate with the angular router so that we can automatically clear
- * the badge if we switch to a Kibana app that does not use the badge correctly
- */
-const $setupBadgeAutoClear =
- (newPlatform: CoreStart, isLocalAngular: boolean) =>
- ($rootScope: IRootScopeService, $injector: any) => {
- // A flag used to determine if we should automatically
- // clear the badge between angular route changes.
- let badgeSetSinceRouteChange = false;
- const $route = $injector.has('$route') ? $injector.get('$route') : {};
-
- $rootScope.$on('$routeChangeStart', () => {
- badgeSetSinceRouteChange = false;
- });
-
- $rootScope.$on('$routeChangeSuccess', () => {
- if (isDummyRoute($route, isLocalAngular)) {
- return;
- }
- const current = $route.current || {};
-
- if (badgeSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
- return;
- }
-
- const badgeProvider = current.badge;
- if (!badgeProvider) {
- newPlatform.chrome.setBadge(undefined);
- return;
- }
-
- try {
- newPlatform.chrome.setBadge($injector.invoke(badgeProvider));
- } catch (error) {
- if (isAngularHttpError(error)) {
- error = formatAngularHttpError(error);
- }
- newPlatform.fatalErrors.add(error, 'location');
- }
- });
- };
-
-/**
- * internal angular run function that will be called when angular bootstraps and
- * lets us integrate with the angular router so that we can automatically clear
- * the helpExtension if we switch to a Kibana app that does not set its own
- * helpExtension
- */
-const $setupHelpExtensionAutoClear =
- (newPlatform: CoreStart, isLocalAngular: boolean) =>
- ($rootScope: IRootScopeService, $injector: any) => {
- /**
- * reset helpExtensionSetSinceRouteChange any time the helpExtension changes, even
- * if it was done directly through the new platform
- */
- let helpExtensionSetSinceRouteChange = false;
- newPlatform.chrome.getHelpExtension$().subscribe({
- next() {
- helpExtensionSetSinceRouteChange = true;
- },
- });
-
- const $route = $injector.has('$route') ? $injector.get('$route') : {};
-
- $rootScope.$on('$routeChangeStart', () => {
- if (isDummyRoute($route, isLocalAngular)) {
- return;
- }
- helpExtensionSetSinceRouteChange = false;
- });
-
- $rootScope.$on('$routeChangeSuccess', () => {
- if (isDummyRoute($route, isLocalAngular)) {
- return;
- }
- const current = $route.current || {};
-
- if (helpExtensionSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {
- return;
- }
-
- newPlatform.chrome.setHelpExtension(current.helpExtension);
- });
- };
diff --git a/x-pack/plugins/monitoring/public/angular/top_nav/index.ts b/x-pack/plugins/monitoring/public/angular/top_nav/index.ts
deleted file mode 100644
index b3501e4cbad1f..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/top_nav/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export * from './angular_config';
-// @ts-ignore
-export { createTopNavDirective, createTopNavHelper, loadKbnTopNavDirectives } from './kbn_top_nav';
diff --git a/x-pack/plugins/monitoring/public/angular/top_nav/kbn_top_nav.d.ts b/x-pack/plugins/monitoring/public/angular/top_nav/kbn_top_nav.d.ts
deleted file mode 100644
index 0cff77241bb9c..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/top_nav/kbn_top_nav.d.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { Injectable, IDirectiveFactory, IScope, IAttributes, IController } from 'angular';
-
-export const createTopNavDirective: Injectable<
- IDirectiveFactory
->;
-export const createTopNavHelper: (
- options: unknown
-) => Injectable>;
-export function loadKbnTopNavDirectives(navUi: unknown): void;
diff --git a/x-pack/plugins/monitoring/public/angular/top_nav/kbn_top_nav.js b/x-pack/plugins/monitoring/public/angular/top_nav/kbn_top_nav.js
deleted file mode 100644
index 6edcca6aa714a..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/top_nav/kbn_top_nav.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import angular from 'angular';
-import 'ngreact';
-
-export function createTopNavDirective() {
- return {
- restrict: 'E',
- template: '',
- compile: (elem) => {
- const child = document.createElement('kbn-top-nav-helper');
-
- // Copy attributes to the child directive
- for (const attr of elem[0].attributes) {
- child.setAttribute(attr.name, attr.value);
- }
-
- // Add a special attribute that will change every time that one
- // of the config array's disableButton function return value changes.
- child.setAttribute('disabled-buttons', 'disabledButtons');
-
- // Append helper directive
- elem.append(child);
-
- const linkFn = ($scope, _, $attr) => {
- // Watch config changes
- $scope.$watch(
- () => {
- const config = $scope.$eval($attr.config) || [];
- return config.map((item) => {
- // Copy key into id, as it's a reserved react propery.
- // This is done for Angular directive backward compatibility.
- // In React only id is recognized.
- if (item.key && !item.id) {
- item.id = item.key;
- }
-
- // Watch the disableButton functions
- if (typeof item.disableButton === 'function') {
- return item.disableButton();
- }
- return item.disableButton;
- });
- },
- (newVal) => {
- $scope.disabledButtons = newVal;
- },
- true
- );
- };
-
- return linkFn;
- },
- };
-}
-
-export const createTopNavHelper =
- ({ TopNavMenu }) =>
- (reactDirective) => {
- return reactDirective(TopNavMenu, [
- ['config', { watchDepth: 'value' }],
- ['setMenuMountPoint', { watchDepth: 'reference' }],
- ['disabledButtons', { watchDepth: 'reference' }],
-
- ['query', { watchDepth: 'reference' }],
- ['savedQuery', { watchDepth: 'reference' }],
- ['intl', { watchDepth: 'reference' }],
-
- ['onQuerySubmit', { watchDepth: 'reference' }],
- ['onFiltersUpdated', { watchDepth: 'reference' }],
- ['onRefreshChange', { watchDepth: 'reference' }],
- ['onClearSavedQuery', { watchDepth: 'reference' }],
- ['onSaved', { watchDepth: 'reference' }],
- ['onSavedQueryUpdated', { watchDepth: 'reference' }],
- ['onSavedQueryIdChange', { watchDepth: 'reference' }],
-
- ['indexPatterns', { watchDepth: 'collection' }],
- ['filters', { watchDepth: 'collection' }],
-
- // All modifiers default to true.
- // Set to false to hide subcomponents.
- 'showSearchBar',
- 'showQueryBar',
- 'showQueryInput',
- 'showSaveQuery',
- 'showDatePicker',
- 'showFilterBar',
-
- 'appName',
- 'screenTitle',
- 'dateRangeFrom',
- 'dateRangeTo',
- 'savedQueryId',
- 'isRefreshPaused',
- 'refreshInterval',
- 'disableAutoFocus',
- 'showAutoRefreshOnly',
-
- // temporary flag to use the stateful components
- 'useDefaultBehaviors',
- ]);
- };
-
-let isLoaded = false;
-
-export function loadKbnTopNavDirectives(navUi) {
- if (!isLoaded) {
- isLoaded = true;
- angular
- .module('kibana')
- .directive('kbnTopNav', createTopNavDirective)
- .directive('kbnTopNavHelper', createTopNavHelper(navUi));
- }
-}
diff --git a/x-pack/plugins/monitoring/public/application/contexts/global_state_context.tsx b/x-pack/plugins/monitoring/public/application/contexts/global_state_context.tsx
index e6638b4c4fede..cc8619dbc7ad2 100644
--- a/x-pack/plugins/monitoring/public/application/contexts/global_state_context.tsx
+++ b/x-pack/plugins/monitoring/public/application/contexts/global_state_context.tsx
@@ -32,31 +32,8 @@ export const GlobalStateProvider: React.FC = ({
toasts,
children,
}) => {
- // TODO: remove fakeAngularRootScope and fakeAngularLocation when angular is removed
- const fakeAngularRootScope: Partial = {
- $on:
- (name: string, listener: (event: ng.IAngularEvent, ...args: any[]) => any): (() => void) =>
- () => {},
- $applyAsync: () => {},
- };
-
- const fakeAngularLocation: Partial = {
- search: () => {
- return {} as any;
- },
- replace: () => {
- return {} as any;
- },
- };
-
const localState: State = {};
- const state = new GlobalState(
- query,
- toasts,
- fakeAngularRootScope,
- fakeAngularLocation,
- localState as { [key: string]: unknown }
- );
+ const state = new GlobalState(query, toasts, localState as { [key: string]: unknown });
const initialState: any = state.getState();
for (const key in initialState) {
diff --git a/x-pack/plugins/monitoring/public/legacy_shims.ts b/x-pack/plugins/monitoring/public/legacy_shims.ts
index 72d50aac1dbb8..48484421839bd 100644
--- a/x-pack/plugins/monitoring/public/legacy_shims.ts
+++ b/x-pack/plugins/monitoring/public/legacy_shims.ts
@@ -44,7 +44,7 @@ const angularNoop = () => {
export interface IShims {
toastNotifications: CoreStart['notifications']['toasts'];
capabilities: CoreStart['application']['capabilities'];
- getAngularInjector: typeof angularNoop | (() => angular.auto.IInjectorService);
+ getAngularInjector: typeof angularNoop;
getBasePath: () => string;
getInjected: (name: string, defaultValue?: unknown) => unknown;
breadcrumbs: {
@@ -73,23 +73,18 @@ export interface IShims {
export class Legacy {
private static _shims: IShims;
- public static init(
- {
- core,
- data,
- isCloud,
- triggersActionsUi,
- usageCollection,
- appMountParameters,
- }: MonitoringStartPluginDependencies,
- ngInjector?: angular.auto.IInjectorService
- ) {
+ public static init({
+ core,
+ data,
+ isCloud,
+ triggersActionsUi,
+ usageCollection,
+ appMountParameters,
+ }: MonitoringStartPluginDependencies) {
this._shims = {
toastNotifications: core.notifications.toasts,
capabilities: core.application.capabilities,
- getAngularInjector: ngInjector
- ? (): angular.auto.IInjectorService => ngInjector
- : angularNoop,
+ getAngularInjector: angularNoop,
getBasePath: (): string => core.http.basePath.get(),
getInjected: (name: string, defaultValue?: unknown): string | unknown =>
core.injectedMetadata.getInjectedVar(name, defaultValue),
diff --git a/x-pack/plugins/monitoring/public/url_state.ts b/x-pack/plugins/monitoring/public/url_state.ts
index 25086411c65a3..8f89df732b800 100644
--- a/x-pack/plugins/monitoring/public/url_state.ts
+++ b/x-pack/plugins/monitoring/public/url_state.ts
@@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
import { Subscription } from 'rxjs';
import { History, createHashHistory } from 'history';
import { MonitoringStartPluginDependencies } from './types';
@@ -27,10 +26,6 @@ import {
withNotifyOnErrors,
} from '../../../../src/plugins/kibana_utils/public';
-interface Route {
- params: { _g: unknown };
-}
-
interface RawObject {
[key: string]: unknown;
}
@@ -57,7 +52,6 @@ export interface MonitoringAppStateTransitions {
const GLOBAL_STATE_KEY = '_g';
const objectEquals = (objA: any, objB: any) => JSON.stringify(objA) === JSON.stringify(objB);
-// TODO: clean all angular references after angular is removed
export class GlobalState {
private readonly stateSyncRef: ISyncStateRef;
private readonly stateContainer: StateContainer<
@@ -70,13 +64,10 @@ export class GlobalState {
private readonly timefilterRef: MonitoringStartPluginDependencies['data']['query']['timefilter']['timefilter'];
private lastAssignedState: MonitoringAppState = {};
- private lastKnownGlobalState?: string;
constructor(
queryService: MonitoringStartPluginDependencies['data']['query'],
toasts: MonitoringStartPluginDependencies['core']['notifications']['toasts'],
- rootScope: Partial,
- ngLocation: Partial,
externalState: RawObject
) {
this.timefilterRef = queryService.timefilter.timefilter;
@@ -102,9 +93,6 @@ export class GlobalState {
this.stateContainerChangeSub = this.stateContainer.state$.subscribe(() => {
this.lastAssignedState = this.getState();
- if (!this.stateContainer.get() && this.lastKnownGlobalState) {
- ngLocation.search?.(`${GLOBAL_STATE_KEY}=${this.lastKnownGlobalState}`).replace();
- }
// TODO: check if this is not needed after https://github.com/elastic/kibana/pull/109132 is merged
if (Legacy.isInitializated()) {
@@ -112,15 +100,11 @@ export class GlobalState {
}
this.syncExternalState(externalState);
- rootScope.$applyAsync?.();
});
this.syncQueryStateWithUrlManager = syncQueryStateWithUrl(queryService, this.stateStorage);
this.stateSyncRef.start();
- this.startHashSync(rootScope, ngLocation);
this.lastAssignedState = this.getState();
-
- rootScope.$on?.('$destroy', () => this.destroy());
}
private syncExternalState(externalState: { [key: string]: unknown }) {
@@ -137,24 +121,6 @@ export class GlobalState {
}
}
- private startHashSync(
- rootScope: Partial,
- ngLocation: Partial
- ) {
- rootScope.$on?.(
- '$routeChangeStart',
- (_: { preventDefault: () => void }, newState: Route, oldState: Route) => {
- const currentGlobalState = oldState?.params?._g;
- const nextGlobalState = newState?.params?._g;
- if (!nextGlobalState && currentGlobalState && typeof currentGlobalState === 'string') {
- newState.params._g = currentGlobalState;
- ngLocation.search?.(`${GLOBAL_STATE_KEY}=${currentGlobalState}`).replace();
- }
- this.lastKnownGlobalState = (nextGlobalState || currentGlobalState) as string;
- }
- );
- }
-
public setState(state?: { [key: string]: unknown }) {
const currentAppState = this.getState();
const newAppState = { ...currentAppState, ...state };
From 70bd56a04fd30e0ac8fb0c55ddfec7e3c11d9349 Mon Sep 17 00:00:00 2001
From: Vadim Yakhin
Date: Mon, 18 Oct 2021 14:50:59 -0700
Subject: [PATCH 016/204] [Fleet, App search] Add App Search ingestion methods
to the unified integrations view (#115433)
* Add a new category for Web crawler
* Add App Search integrations
* Fix isBeta flag for Web Crawler
It's already GA
---
.../custom_integrations/common/index.ts | 1 +
.../enterprise_search/server/integrations.ts | 63 +++++++++++++++++++
2 files changed, 64 insertions(+)
diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts
index 944ac6ba3e6ee..98148bb22c816 100755
--- a/src/plugins/custom_integrations/common/index.ts
+++ b/src/plugins/custom_integrations/common/index.ts
@@ -49,6 +49,7 @@ export const INTEGRATION_CATEGORY_DISPLAY = {
project_management: 'Project Management',
software_development: 'Software Development',
upload_file: 'Upload a file',
+ website_search: 'Website Search',
};
/**
diff --git a/x-pack/plugins/enterprise_search/server/integrations.ts b/x-pack/plugins/enterprise_search/server/integrations.ts
index 48909261243e8..eee5cdc3aaec3 100644
--- a/x-pack/plugins/enterprise_search/server/integrations.ts
+++ b/x-pack/plugins/enterprise_search/server/integrations.ts
@@ -301,4 +301,67 @@ export const registerEnterpriseSearchIntegrations = (
...integration,
});
});
+
+ customIntegrations.registerCustomIntegration({
+ id: 'app_search_web_crawler',
+ title: i18n.translate('xpack.enterpriseSearch.appSearch.integrations.webCrawlerName', {
+ defaultMessage: 'Web Crawler',
+ }),
+ description: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.integrations.webCrawlerDescription',
+ {
+ defaultMessage: "Add search to your website with App Search's web crawler.",
+ }
+ ),
+ categories: ['website_search'],
+ uiInternalPath: '/app/enterprise_search/app_search/engines/new?method=crawler',
+ icons: [
+ {
+ type: 'eui',
+ src: 'globe',
+ },
+ ],
+ shipper: 'enterprise_search',
+ isBeta: false,
+ });
+
+ customIntegrations.registerCustomIntegration({
+ id: 'app_search_json',
+ title: i18n.translate('xpack.enterpriseSearch.appSearch.integrations.jsonName', {
+ defaultMessage: 'JSON',
+ }),
+ description: i18n.translate('xpack.enterpriseSearch.appSearch.integrations.jsonDescription', {
+ defaultMessage: 'Search over your JSON data with App Search.',
+ }),
+ categories: ['upload_file'],
+ uiInternalPath: '/app/enterprise_search/app_search/engines/new?method=json',
+ icons: [
+ {
+ type: 'eui',
+ src: 'exportAction',
+ },
+ ],
+ shipper: 'enterprise_search',
+ isBeta: false,
+ });
+
+ customIntegrations.registerCustomIntegration({
+ id: 'app_search_api',
+ title: i18n.translate('xpack.enterpriseSearch.appSearch.integrations.apiName', {
+ defaultMessage: 'API',
+ }),
+ description: i18n.translate('xpack.enterpriseSearch.appSearch.integrations.apiDescription', {
+ defaultMessage: "Add search to your application with App Search's robust APIs.",
+ }),
+ categories: ['custom'],
+ uiInternalPath: '/app/enterprise_search/app_search/engines/new?method=api',
+ icons: [
+ {
+ type: 'eui',
+ src: 'editorCodeBlock',
+ },
+ ],
+ shipper: 'enterprise_search',
+ isBeta: false,
+ });
};
From 62f057dee12a9f2f2693c249ddbf3f5e93ee6ca8 Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Tue, 19 Oct 2021 00:12:07 +0200
Subject: [PATCH 017/204] [ML] APM Correlations: Get trace samples tab overall
distribution via APM endpoint. (#114615)
This creates an APM API endpoint that fetches data for the latency distribution chart in the trace samples tab on the transactions page. Previously, this data was fetched via the custom Kibana search strategies used for APM Correlations which causes issues in load balancing setups.
---
.../failed_transactions_correlations/types.ts | 13 +-
.../latency_correlations/types.ts | 12 +-
.../apm/common/search_strategies/types.ts | 12 +-
.../latency_correlations.test.tsx | 5 +-
.../distribution/index.test.tsx | 59 +++------
.../distribution/index.tsx | 79 +++++++++---
.../apm/public/hooks/use_search_strategy.ts | 10 +-
.../get_overall_latency_distribution.ts | 121 ++++++++++++++++++
.../latency/get_percentile_threshold_value.ts | 53 ++++++++
.../plugins/apm/server/lib/latency/types.ts | 23 ++++
...ransactions_correlations_search_service.ts | 31 ++---
.../failed_transactions_correlations/index.ts | 6 +-
.../latency_correlations/index.ts | 6 +-
.../latency_correlations_search_service.ts | 31 ++---
.../field_stats/get_field_stats.test.ts | 4 +-
.../queries/get_query_with_params.test.ts | 12 +-
.../queries/get_query_with_params.ts | 17 +--
.../queries/get_request_base.test.ts | 4 +
.../queries/query_correlation.test.ts | 4 +-
.../queries/query_field_candidates.test.ts | 4 +-
.../queries/query_field_value_pairs.test.ts | 4 +-
.../queries/query_fractions.test.ts | 4 +-
.../queries/query_histogram.test.ts | 4 +-
.../query_histogram_range_steps.test.ts | 4 +-
.../queries/query_histogram_range_steps.ts | 6 +-
.../query_histograms_generator.test.ts | 4 +-
.../queries/query_percentiles.test.ts | 4 +-
.../queries/query_ranges.test.ts | 4 +-
.../search_strategy_provider.test.ts | 4 +-
.../search_strategy_provider.ts | 104 +++++++++++----
.../get_global_apm_server_route_repository.ts | 2 +
.../apm/server/routes/latency_distribution.ts | 63 +++++++++
.../tests/correlations/failed_transactions.ts | 4 +-
.../tests/correlations/latency.ts | 23 ++--
.../test/apm_api_integration/tests/index.ts | 4 +
.../latency_overall_distribution.ts | 65 ++++++++++
36 files changed, 590 insertions(+), 219 deletions(-)
create mode 100644 x-pack/plugins/apm/server/lib/latency/get_overall_latency_distribution.ts
create mode 100644 x-pack/plugins/apm/server/lib/latency/get_percentile_threshold_value.ts
create mode 100644 x-pack/plugins/apm/server/lib/latency/types.ts
create mode 100644 x-pack/plugins/apm/server/routes/latency_distribution.ts
create mode 100644 x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts
diff --git a/x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/types.ts b/x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/types.ts
index 266d7246c35d4..28ce2ff24b961 100644
--- a/x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/types.ts
+++ b/x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/types.ts
@@ -5,12 +5,7 @@
* 2.0.
*/
-import {
- FieldValuePair,
- HistogramItem,
- RawResponseBase,
- SearchStrategyClientParams,
-} from '../types';
+import { FieldValuePair, HistogramItem } from '../types';
import { FAILED_TRANSACTIONS_IMPACT_THRESHOLD } from './constants';
import { FieldStats } from '../field_stats_types';
@@ -33,11 +28,7 @@ export interface FailedTransactionsCorrelationsParams {
percentileThreshold: number;
}
-export type FailedTransactionsCorrelationsRequestParams =
- FailedTransactionsCorrelationsParams & SearchStrategyClientParams;
-
-export interface FailedTransactionsCorrelationsRawResponse
- extends RawResponseBase {
+export interface FailedTransactionsCorrelationsRawResponse {
log: string[];
failedTransactionsCorrelations?: FailedTransactionsCorrelation[];
percentileThresholdValue?: number;
diff --git a/x-pack/plugins/apm/common/search_strategies/latency_correlations/types.ts b/x-pack/plugins/apm/common/search_strategies/latency_correlations/types.ts
index 2eb2b37159459..ea74175a3dacb 100644
--- a/x-pack/plugins/apm/common/search_strategies/latency_correlations/types.ts
+++ b/x-pack/plugins/apm/common/search_strategies/latency_correlations/types.ts
@@ -5,12 +5,7 @@
* 2.0.
*/
-import {
- FieldValuePair,
- HistogramItem,
- RawResponseBase,
- SearchStrategyClientParams,
-} from '../types';
+import { FieldValuePair, HistogramItem } from '../types';
import { FieldStats } from '../field_stats_types';
export interface LatencyCorrelation extends FieldValuePair {
@@ -33,10 +28,7 @@ export interface LatencyCorrelationsParams {
analyzeCorrelations: boolean;
}
-export type LatencyCorrelationsRequestParams = LatencyCorrelationsParams &
- SearchStrategyClientParams;
-
-export interface LatencyCorrelationsRawResponse extends RawResponseBase {
+export interface LatencyCorrelationsRawResponse {
log: string[];
overallHistogram?: HistogramItem[];
percentileThresholdValue?: number;
diff --git a/x-pack/plugins/apm/common/search_strategies/types.ts b/x-pack/plugins/apm/common/search_strategies/types.ts
index d7c6eab1f07c1..ff925f70fc9b0 100644
--- a/x-pack/plugins/apm/common/search_strategies/types.ts
+++ b/x-pack/plugins/apm/common/search_strategies/types.ts
@@ -31,16 +31,26 @@ export interface RawResponseBase {
took: number;
}
-export interface SearchStrategyClientParams {
+export interface SearchStrategyClientParamsBase {
environment: string;
kuery: string;
serviceName?: string;
transactionName?: string;
transactionType?: string;
+}
+
+export interface RawSearchStrategyClientParams
+ extends SearchStrategyClientParamsBase {
start?: string;
end?: string;
}
+export interface SearchStrategyClientParams
+ extends SearchStrategyClientParamsBase {
+ start: number;
+ end: number;
+}
+
export interface SearchStrategyServerParams {
index: string;
includeFrozen?: boolean;
diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx
index 9956452c565b3..918f94e64ef09 100644
--- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx
@@ -19,6 +19,7 @@ import type { IKibanaSearchResponse } from 'src/plugins/data/public';
import { EuiThemeProvider } from 'src/plugins/kibana_react/common';
import { createKibanaReactContext } from 'src/plugins/kibana_react/public';
import type { LatencyCorrelationsRawResponse } from '../../../../common/search_strategies/latency_correlations/types';
+import type { RawResponseBase } from '../../../../common/search_strategies/types';
import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider';
import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context';
import {
@@ -34,7 +35,9 @@ function Wrapper({
dataSearchResponse,
}: {
children?: ReactNode;
- dataSearchResponse: IKibanaSearchResponse;
+ dataSearchResponse: IKibanaSearchResponse<
+ LatencyCorrelationsRawResponse & RawResponseBase
+ >;
}) {
const mockDataSearch = jest.fn(() => of(dataSearchResponse));
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx
index bd0ff4c87c3be..0e9639de4aa74 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx
@@ -8,43 +8,24 @@
import { render, screen, waitFor } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React, { ReactNode } from 'react';
-import { of } from 'rxjs';
import { CoreStart } from 'kibana/public';
import { merge } from 'lodash';
-import { dataPluginMock } from 'src/plugins/data/public/mocks';
-import type { IKibanaSearchResponse } from 'src/plugins/data/public';
import { EuiThemeProvider } from 'src/plugins/kibana_react/common';
import { createKibanaReactContext } from 'src/plugins/kibana_react/public';
-import type { LatencyCorrelationsRawResponse } from '../../../../../common/search_strategies/latency_correlations/types';
import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider';
import { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context';
import {
mockApmPluginContextValue,
MockApmPluginContextWrapper,
} from '../../../../context/apm_plugin/mock_apm_plugin_context';
+import * as useFetcherModule from '../../../../hooks/use_fetcher';
import { fromQuery } from '../../../shared/Links/url_helpers';
import { getFormattedSelection, TransactionDistribution } from './index';
-function Wrapper({
- children,
- dataSearchResponse,
-}: {
- children?: ReactNode;
- dataSearchResponse: IKibanaSearchResponse;
-}) {
- const mockDataSearch = jest.fn(() => of(dataSearchResponse));
-
- const dataPluginMockStart = dataPluginMock.createStartContract();
+function Wrapper({ children }: { children?: ReactNode }) {
const KibanaReactContext = createKibanaReactContext({
- data: {
- ...dataPluginMockStart,
- search: {
- ...dataPluginMockStart.search,
- search: mockDataSearch,
- },
- },
usageCollection: { reportUiCounter: () => {} },
} as Partial);
@@ -105,18 +86,14 @@ describe('transaction_details/distribution', () => {
describe('TransactionDistribution', () => {
it('shows loading indicator when the service is running and returned no results yet', async () => {
+ jest.spyOn(useFetcherModule, 'useFetcher').mockImplementation(() => ({
+ data: {},
+ refetch: () => {},
+ status: useFetcherModule.FETCH_STATUS.LOADING,
+ }));
+
render(
-
+
{
});
it("doesn't show loading indicator when the service isn't running", async () => {
+ jest.spyOn(useFetcherModule, 'useFetcher').mockImplementation(() => ({
+ data: { percentileThresholdValue: 1234, overallHistogram: [] },
+ refetch: () => {},
+ status: useFetcherModule.FETCH_STATUS.SUCCESS,
+ }));
+
render(
-
+
{
+ if (serviceName && environment && start && end) {
+ return callApmApi({
+ endpoint: 'GET /internal/apm/latency/overall_distribution',
+ params: {
+ query: {
+ serviceName,
+ transactionName,
+ transactionType,
+ kuery,
+ environment,
+ start,
+ end,
+ percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
+ },
+ },
+ });
+ }
+ },
+ [
+ serviceName,
+ transactionName,
+ transactionType,
+ kuery,
+ environment,
+ start,
+ end,
+ ]
);
+ const overallHistogram =
+ data.overallHistogram === undefined && status !== FETCH_STATUS.LOADING
+ ? []
+ : data.overallHistogram;
+ const hasData =
+ Array.isArray(overallHistogram) && overallHistogram.length > 0;
+
useEffect(() => {
- if (isErrorMessage(progress.error)) {
+ if (isErrorMessage(error)) {
notifications.toasts.addDanger({
title: i18n.translate(
'xpack.apm.transactionDetails.distribution.errorTitle',
@@ -119,10 +156,10 @@ export function TransactionDistribution({
defaultMessage: 'An error occurred fetching the distribution',
}
),
- text: progress.error.toString(),
+ text: error.toString(),
});
}
- }, [progress.error, notifications.toasts]);
+ }, [error, notifications.toasts]);
const trackApmEvent = useUiTracker({ app: 'apm' });
@@ -213,7 +250,7 @@ export function TransactionDistribution({
data={transactionDistributionChartData}
markerCurrentTransaction={markerCurrentTransaction}
markerPercentile={DEFAULT_PERCENTILE_THRESHOLD}
- markerValue={response.percentileThresholdValue ?? 0}
+ markerValue={data.percentileThresholdValue ?? 0}
onChartSelection={onTrackedChartSelection as BrushEndListener}
hasData={hasData}
selection={selection}
diff --git a/x-pack/plugins/apm/public/hooks/use_search_strategy.ts b/x-pack/plugins/apm/public/hooks/use_search_strategy.ts
index ca8d28b106f84..275eddb68ae00 100644
--- a/x-pack/plugins/apm/public/hooks/use_search_strategy.ts
+++ b/x-pack/plugins/apm/public/hooks/use_search_strategy.ts
@@ -16,7 +16,7 @@ import {
} from '../../../../../src/plugins/data/public';
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
-import type { SearchStrategyClientParams } from '../../common/search_strategies/types';
+import type { RawSearchStrategyClientParams } from '../../common/search_strategies/types';
import type { RawResponseBase } from '../../common/search_strategies/types';
import type {
LatencyCorrelationsParams,
@@ -77,13 +77,15 @@ interface SearchStrategyReturnBase {
export function useSearchStrategy(
searchStrategyName: typeof APM_SEARCH_STRATEGIES.APM_LATENCY_CORRELATIONS,
searchStrategyParams: LatencyCorrelationsParams
-): SearchStrategyReturnBase;
+): SearchStrategyReturnBase;
// Function overload for Failed Transactions Correlations
export function useSearchStrategy(
searchStrategyName: typeof APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS,
searchStrategyParams: FailedTransactionsCorrelationsParams
-): SearchStrategyReturnBase;
+): SearchStrategyReturnBase<
+ FailedTransactionsCorrelationsRawResponse & RawResponseBase
+>;
export function useSearchStrategy<
TRawResponse extends RawResponseBase,
@@ -145,7 +147,7 @@ export function useSearchStrategy<
// Submit the search request using the `data.search` service.
searchSubscription$.current = data.search
.search<
- IKibanaSearchRequest,
+ IKibanaSearchRequest,
IKibanaSearchResponse
>(request, {
strategy: searchStrategyName,
diff --git a/x-pack/plugins/apm/server/lib/latency/get_overall_latency_distribution.ts b/x-pack/plugins/apm/server/lib/latency/get_overall_latency_distribution.ts
new file mode 100644
index 0000000000000..39470869488c3
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/latency/get_overall_latency_distribution.ts
@@ -0,0 +1,121 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { estypes } from '@elastic/elasticsearch';
+
+import { ProcessorEvent } from '../../../common/processor_event';
+
+import { withApmSpan } from '../../utils/with_apm_span';
+
+import {
+ getHistogramIntervalRequest,
+ getHistogramRangeSteps,
+} from '../search_strategies/queries/query_histogram_range_steps';
+import { getTransactionDurationRangesRequest } from '../search_strategies/queries/query_ranges';
+
+import { getPercentileThresholdValue } from './get_percentile_threshold_value';
+import type {
+ OverallLatencyDistributionOptions,
+ OverallLatencyDistributionResponse,
+} from './types';
+
+export async function getOverallLatencyDistribution(
+ options: OverallLatencyDistributionOptions
+) {
+ return withApmSpan('get_overall_latency_distribution', async () => {
+ const overallLatencyDistribution: OverallLatencyDistributionResponse = {
+ log: [],
+ };
+
+ const { setup, ...rawParams } = options;
+ const { apmEventClient } = setup;
+ const params = {
+ // pass on an empty index because we're using only the body attribute
+ // of the request body getters we're reusing from search strategies.
+ index: '',
+ ...rawParams,
+ };
+
+ // #1: get 95th percentile to be displayed as a marker in the log log chart
+ overallLatencyDistribution.percentileThresholdValue =
+ await getPercentileThresholdValue(options);
+
+ // finish early if we weren't able to identify the percentileThresholdValue.
+ if (!overallLatencyDistribution.percentileThresholdValue) {
+ return overallLatencyDistribution;
+ }
+
+ // #2: get histogram range steps
+ const steps = 100;
+
+ const { body: histogramIntervalRequestBody } =
+ getHistogramIntervalRequest(params);
+
+ const histogramIntervalResponse = (await apmEventClient.search(
+ 'get_histogram_interval',
+ {
+ // TODO: add support for metrics
+ apm: { events: [ProcessorEvent.transaction] },
+ body: histogramIntervalRequestBody,
+ }
+ )) as {
+ aggregations?: {
+ transaction_duration_min: estypes.AggregationsValueAggregate;
+ transaction_duration_max: estypes.AggregationsValueAggregate;
+ };
+ hits: { total: estypes.SearchTotalHits };
+ };
+
+ if (
+ !histogramIntervalResponse.aggregations ||
+ histogramIntervalResponse.hits.total.value === 0
+ ) {
+ return overallLatencyDistribution;
+ }
+
+ const min =
+ histogramIntervalResponse.aggregations.transaction_duration_min.value;
+ const max =
+ histogramIntervalResponse.aggregations.transaction_duration_max.value * 2;
+
+ const histogramRangeSteps = getHistogramRangeSteps(min, max, steps);
+
+ // #3: get histogram chart data
+ const { body: transactionDurationRangesRequestBody } =
+ getTransactionDurationRangesRequest(params, histogramRangeSteps);
+
+ const transactionDurationRangesResponse = (await apmEventClient.search(
+ 'get_transaction_duration_ranges',
+ {
+ // TODO: add support for metrics
+ apm: { events: [ProcessorEvent.transaction] },
+ body: transactionDurationRangesRequestBody,
+ }
+ )) as {
+ aggregations?: {
+ logspace_ranges: estypes.AggregationsMultiBucketAggregate<{
+ from: number;
+ doc_count: number;
+ }>;
+ };
+ };
+
+ if (!transactionDurationRangesResponse.aggregations) {
+ return overallLatencyDistribution;
+ }
+
+ overallLatencyDistribution.overallHistogram =
+ transactionDurationRangesResponse.aggregations.logspace_ranges.buckets
+ .map((d) => ({
+ key: d.from,
+ doc_count: d.doc_count,
+ }))
+ .filter((d) => d.key !== undefined);
+
+ return overallLatencyDistribution;
+ });
+}
diff --git a/x-pack/plugins/apm/server/lib/latency/get_percentile_threshold_value.ts b/x-pack/plugins/apm/server/lib/latency/get_percentile_threshold_value.ts
new file mode 100644
index 0000000000000..0d417a370e0b6
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/latency/get_percentile_threshold_value.ts
@@ -0,0 +1,53 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { estypes } from '@elastic/elasticsearch';
+
+import { ProcessorEvent } from '../../../common/processor_event';
+
+import { getTransactionDurationPercentilesRequest } from '../search_strategies/queries/query_percentiles';
+
+import type { OverallLatencyDistributionOptions } from './types';
+
+export async function getPercentileThresholdValue(
+ options: OverallLatencyDistributionOptions
+) {
+ const { setup, percentileThreshold, ...rawParams } = options;
+ const { apmEventClient } = setup;
+ const params = {
+ // pass on an empty index because we're using only the body attribute
+ // of the request body getters we're reusing from search strategies.
+ index: '',
+ ...rawParams,
+ };
+
+ const { body: transactionDurationPercentilesRequestBody } =
+ getTransactionDurationPercentilesRequest(params, [percentileThreshold]);
+
+ const transactionDurationPercentilesResponse = (await apmEventClient.search(
+ 'get_transaction_duration_percentiles',
+ {
+ // TODO: add support for metrics
+ apm: { events: [ProcessorEvent.transaction] },
+ body: transactionDurationPercentilesRequestBody,
+ }
+ )) as {
+ aggregations?: {
+ transaction_duration_percentiles: estypes.AggregationsTDigestPercentilesAggregate;
+ };
+ };
+
+ if (!transactionDurationPercentilesResponse.aggregations) {
+ return;
+ }
+
+ const percentilesResponseThresholds =
+ transactionDurationPercentilesResponse.aggregations
+ .transaction_duration_percentiles?.values ?? {};
+
+ return percentilesResponseThresholds[`${percentileThreshold}.0`];
+}
diff --git a/x-pack/plugins/apm/server/lib/latency/types.ts b/x-pack/plugins/apm/server/lib/latency/types.ts
new file mode 100644
index 0000000000000..8dad1a39bd159
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/latency/types.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Setup } from '../helpers/setup_request';
+import { CorrelationsOptions } from '../search_strategies/queries/get_filters';
+
+export interface OverallLatencyDistributionOptions extends CorrelationsOptions {
+ percentileThreshold: number;
+ setup: Setup;
+}
+
+export interface OverallLatencyDistributionResponse {
+ log: string[];
+ percentileThresholdValue?: number;
+ overallHistogram?: Array<{
+ key: number;
+ doc_count: number;
+ }>;
+}
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts
index af5e535abdc3f..efc28ce98e5e0 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts
@@ -9,17 +9,15 @@ import { chunk } from 'lodash';
import type { ElasticsearchClient } from 'src/core/server';
-import type { ISearchStrategy } from '../../../../../../../src/plugins/data/server';
-import {
- IKibanaSearchRequest,
- IKibanaSearchResponse,
-} from '../../../../../../../src/plugins/data/common';
-
import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames';
import { EventOutcome } from '../../../../common/event_outcome';
-import type { SearchStrategyServerParams } from '../../../../common/search_strategies/types';
import type {
- FailedTransactionsCorrelationsRequestParams,
+ SearchStrategyClientParams,
+ SearchStrategyServerParams,
+ RawResponseBase,
+} from '../../../../common/search_strategies/types';
+import type {
+ FailedTransactionsCorrelationsParams,
FailedTransactionsCorrelationsRawResponse,
} from '../../../../common/search_strategies/failed_transactions_correlations/types';
import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices';
@@ -38,22 +36,18 @@ import { failedTransactionsCorrelationsSearchServiceStateProvider } from './fail
import { ERROR_CORRELATION_THRESHOLD } from '../constants';
import { fetchFieldsStats } from '../queries/field_stats/get_fields_stats';
-export type FailedTransactionsCorrelationsSearchServiceProvider =
+type FailedTransactionsCorrelationsSearchServiceProvider =
SearchServiceProvider<
- FailedTransactionsCorrelationsRequestParams,
- FailedTransactionsCorrelationsRawResponse
+ FailedTransactionsCorrelationsParams & SearchStrategyClientParams,
+ FailedTransactionsCorrelationsRawResponse & RawResponseBase
>;
-export type FailedTransactionsCorrelationsSearchStrategy = ISearchStrategy<
- IKibanaSearchRequest,
- IKibanaSearchResponse
->;
-
export const failedTransactionsCorrelationsSearchServiceProvider: FailedTransactionsCorrelationsSearchServiceProvider =
(
esClient: ElasticsearchClient,
getApmIndices: () => Promise,
- searchServiceParams: FailedTransactionsCorrelationsRequestParams,
+ searchServiceParams: FailedTransactionsCorrelationsParams &
+ SearchStrategyClientParams,
includeFrozen: boolean
) => {
const { addLogMessage, getLogMessages } = searchServiceLogProvider();
@@ -63,7 +57,8 @@ export const failedTransactionsCorrelationsSearchServiceProvider: FailedTransact
async function fetchErrorCorrelations() {
try {
const indices = await getApmIndices();
- const params: FailedTransactionsCorrelationsRequestParams &
+ const params: FailedTransactionsCorrelationsParams &
+ SearchStrategyClientParams &
SearchStrategyServerParams = {
...searchServiceParams,
index: indices.transaction,
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/index.ts b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/index.ts
index ec91165cb481b..4763cd994d309 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/index.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/index.ts
@@ -5,8 +5,4 @@
* 2.0.
*/
-export {
- failedTransactionsCorrelationsSearchServiceProvider,
- FailedTransactionsCorrelationsSearchServiceProvider,
- FailedTransactionsCorrelationsSearchStrategy,
-} from './failed_transactions_correlations_search_service';
+export { failedTransactionsCorrelationsSearchServiceProvider } from './failed_transactions_correlations_search_service';
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/index.ts b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/index.ts
index 073bb122896ff..040aa5a7e424e 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/index.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/index.ts
@@ -5,8 +5,4 @@
* 2.0.
*/
-export {
- latencyCorrelationsSearchServiceProvider,
- LatencyCorrelationsSearchServiceProvider,
- LatencyCorrelationsSearchStrategy,
-} from './latency_correlations_search_service';
+export { latencyCorrelationsSearchServiceProvider } from './latency_correlations_search_service';
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts
index 4862f7dd1de1a..f170818d018d4 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts
@@ -8,15 +8,13 @@
import { range } from 'lodash';
import type { ElasticsearchClient } from 'src/core/server';
-import type { ISearchStrategy } from '../../../../../../../src/plugins/data/server';
-import {
- IKibanaSearchRequest,
- IKibanaSearchResponse,
-} from '../../../../../../../src/plugins/data/common';
-
-import type { SearchStrategyServerParams } from '../../../../common/search_strategies/types';
import type {
- LatencyCorrelationsRequestParams,
+ RawResponseBase,
+ SearchStrategyClientParams,
+ SearchStrategyServerParams,
+} from '../../../../common/search_strategies/types';
+import type {
+ LatencyCorrelationsParams,
LatencyCorrelationsRawResponse,
} from '../../../../common/search_strategies/latency_correlations/types';
@@ -38,21 +36,16 @@ import type { SearchServiceProvider } from '../search_strategy_provider';
import { latencyCorrelationsSearchServiceStateProvider } from './latency_correlations_search_service_state';
import { fetchFieldsStats } from '../queries/field_stats/get_fields_stats';
-export type LatencyCorrelationsSearchServiceProvider = SearchServiceProvider<
- LatencyCorrelationsRequestParams,
- LatencyCorrelationsRawResponse
->;
-
-export type LatencyCorrelationsSearchStrategy = ISearchStrategy<
- IKibanaSearchRequest,
- IKibanaSearchResponse
+type LatencyCorrelationsSearchServiceProvider = SearchServiceProvider<
+ LatencyCorrelationsParams & SearchStrategyClientParams,
+ LatencyCorrelationsRawResponse & RawResponseBase
>;
export const latencyCorrelationsSearchServiceProvider: LatencyCorrelationsSearchServiceProvider =
(
esClient: ElasticsearchClient,
getApmIndices: () => Promise,
- searchServiceParams: LatencyCorrelationsRequestParams,
+ searchServiceParams: LatencyCorrelationsParams & SearchStrategyClientParams,
includeFrozen: boolean
) => {
const { addLogMessage, getLogMessages } = searchServiceLogProvider();
@@ -61,7 +54,9 @@ export const latencyCorrelationsSearchServiceProvider: LatencyCorrelationsSearch
async function fetchCorrelations() {
let params:
- | (LatencyCorrelationsRequestParams & SearchStrategyServerParams)
+ | (LatencyCorrelationsParams &
+ SearchStrategyClientParams &
+ SearchStrategyServerParams)
| undefined;
try {
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/field_stats/get_field_stats.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/field_stats/get_field_stats.test.ts
index deb89ace47c5d..d3cee1c4ca596 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/field_stats/get_field_stats.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/field_stats/get_field_stats.test.ts
@@ -15,8 +15,8 @@ import { fetchFieldsStats } from './get_fields_stats';
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.test.ts
index c77b4df78f865..9d0441e513198 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.test.ts
@@ -14,8 +14,8 @@ describe('correlations', () => {
const query = getQueryWithParams({
params: {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
@@ -45,8 +45,8 @@ describe('correlations', () => {
index: 'apm-*',
serviceName: 'actualServiceName',
transactionName: 'actualTransactionName',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
environment: 'dev',
kuery: '',
includeFrozen: false,
@@ -93,8 +93,8 @@ describe('correlations', () => {
const query = getQueryWithParams({
params: {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.ts
index f00c89503f103..31a98b0a6bb18 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.ts
@@ -6,15 +6,10 @@
*/
import type { estypes } from '@elastic/elasticsearch';
-import { getOrElse } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import * as t from 'io-ts';
-import { failure } from 'io-ts/lib/PathReporter';
import type {
FieldValuePair,
SearchStrategyParams,
} from '../../../../common/search_strategies/types';
-import { rangeRt } from '../../../routes/default_api_types';
import { getCorrelationsFilters } from './get_filters';
export const getTermsQuery = ({ fieldName, fieldValue }: FieldValuePair) => {
@@ -36,22 +31,14 @@ export const getQueryWithParams = ({ params, termFilters }: QueryParams) => {
transactionName,
} = params;
- // converts string based start/end to epochmillis
- const decodedRange = pipe(
- rangeRt.decode({ start, end }),
- getOrElse((errors) => {
- throw new Error(failure(errors).join('\n'));
- })
- );
-
const correlationFilters = getCorrelationsFilters({
environment,
kuery,
serviceName,
transactionType,
transactionName,
- start: decodedRange.start,
- end: decodedRange.end,
+ start,
+ end,
});
return {
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.test.ts
index fd5f52207d4c5..eb771e1e1aaf4 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.test.ts
@@ -16,6 +16,8 @@ describe('correlations', () => {
includeFrozen: true,
environment: ENVIRONMENT_ALL.value,
kuery: '',
+ start: 1577836800000,
+ end: 1609459200000,
});
expect(requestBase).toEqual({
index: 'apm-*',
@@ -29,6 +31,8 @@ describe('correlations', () => {
index: 'apm-*',
environment: ENVIRONMENT_ALL.value,
kuery: '',
+ start: 1577836800000,
+ end: 1609459200000,
});
expect(requestBase).toEqual({
index: 'apm-*',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.test.ts
index fc2dacce61a73..40fcc17444492 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.test.ts
@@ -18,8 +18,8 @@ import {
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.test.ts
index 6e0521ac1a008..bae42666e6db0 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.test.ts
@@ -20,8 +20,8 @@ import {
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.test.ts
index 9ffbf6b2ce18d..ab7a0b4e02072 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.test.ts
@@ -20,8 +20,8 @@ import {
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.test.ts
index daf6b368c78b1..9c704ef7b489a 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.test.ts
@@ -17,8 +17,8 @@ import {
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.test.ts
index 7ecb1d2d8a333..7cc6106f671a7 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.test.ts
@@ -17,8 +17,8 @@ import {
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts
index ffc86c7ef6c32..41a2fa9a5039e 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts
@@ -17,8 +17,8 @@ import {
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts
index 790919d193028..439bb9e4b9cd6 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts
@@ -17,7 +17,11 @@ import type { SearchStrategyParams } from '../../../../common/search_strategies/
import { getQueryWithParams } from './get_query_with_params';
import { getRequestBase } from './get_request_base';
-const getHistogramRangeSteps = (min: number, max: number, steps: number) => {
+export const getHistogramRangeSteps = (
+ min: number,
+ max: number,
+ steps: number
+) => {
// A d3 based scale function as a helper to get equally distributed bins on a log scale.
// We round the final values because the ES range agg we use won't accept numbers with decimals for `transaction.duration.us`.
const logFn = scaleLog().domain([min, max]).range([1, steps]);
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.test.ts
index 375e32b1472c6..00e8c26497eb2 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.test.ts
@@ -17,8 +17,8 @@ import { fetchTransactionDurationHistograms } from './query_histograms_generator
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.test.ts
index ce86ffd9654e6..57e3e6cadb9bc 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.test.ts
@@ -17,8 +17,8 @@ import {
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.test.ts
index e210eb7d41e78..7d67e80ae3398 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.test.ts
@@ -17,8 +17,8 @@ import {
const params = {
index: 'apm-*',
- start: '2020',
- end: '2021',
+ start: 1577836800000,
+ end: 1609459200000,
includeFrozen: false,
environment: ENVIRONMENT_ALL.value,
kuery: '',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts
index 8a9d04df32036..034bd2a60ad19 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts
@@ -13,7 +13,7 @@ import { IKibanaSearchRequest } from '../../../../../../src/plugins/data/common'
import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
import type { LatencyCorrelationsParams } from '../../../common/search_strategies/latency_correlations/types';
-import type { SearchStrategyClientParams } from '../../../common/search_strategies/types';
+import type { RawSearchStrategyClientParams } from '../../../common/search_strategies/types';
import type { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices';
@@ -112,7 +112,7 @@ describe('APM Correlations search strategy', () => {
let mockDeps: SearchStrategyDependencies;
let params: Required<
IKibanaSearchRequest<
- LatencyCorrelationsParams & SearchStrategyClientParams
+ LatencyCorrelationsParams & RawSearchStrategyClientParams
>
>['params'];
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.ts b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.ts
index cec10294460b0..8035e9e4d97ca 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.ts
@@ -7,6 +7,10 @@
import uuid from 'uuid';
import { of } from 'rxjs';
+import { getOrElse } from 'fp-ts/lib/Either';
+import { pipe } from 'fp-ts/lib/pipeable';
+import * as t from 'io-ts';
+import { failure } from 'io-ts/lib/PathReporter';
import type { ElasticsearchClient } from 'src/core/server';
@@ -16,18 +20,21 @@ import {
IKibanaSearchResponse,
} from '../../../../../../src/plugins/data/common';
-import type { SearchStrategyClientParams } from '../../../common/search_strategies/types';
-import type { RawResponseBase } from '../../../common/search_strategies/types';
-import type { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices';
-
import type {
- LatencyCorrelationsSearchServiceProvider,
- LatencyCorrelationsSearchStrategy,
-} from './latency_correlations';
+ RawResponseBase,
+ RawSearchStrategyClientParams,
+ SearchStrategyClientParams,
+} from '../../../common/search_strategies/types';
+import type {
+ LatencyCorrelationsParams,
+ LatencyCorrelationsRawResponse,
+} from '../../../common/search_strategies/latency_correlations/types';
import type {
- FailedTransactionsCorrelationsSearchServiceProvider,
- FailedTransactionsCorrelationsSearchStrategy,
-} from './failed_transactions_correlations';
+ FailedTransactionsCorrelationsParams,
+ FailedTransactionsCorrelationsRawResponse,
+} from '../../../common/search_strategies/failed_transactions_correlations/types';
+import { rangeRt } from '../../routes/default_api_types';
+import type { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices';
interface SearchServiceState {
cancel: () => void;
@@ -56,35 +63,50 @@ export type SearchServiceProvider<
// Failed Transactions Correlations function overload
export function searchStrategyProvider(
- searchServiceProvider: FailedTransactionsCorrelationsSearchServiceProvider,
+ searchServiceProvider: SearchServiceProvider<
+ FailedTransactionsCorrelationsParams & SearchStrategyClientParams,
+ FailedTransactionsCorrelationsRawResponse & RawResponseBase
+ >,
getApmIndices: () => Promise,
includeFrozen: boolean
-): FailedTransactionsCorrelationsSearchStrategy;
+): ISearchStrategy<
+ IKibanaSearchRequest<
+ FailedTransactionsCorrelationsParams & RawSearchStrategyClientParams
+ >,
+ IKibanaSearchResponse<
+ FailedTransactionsCorrelationsRawResponse & RawResponseBase
+ >
+>;
// Latency Correlations function overload
export function searchStrategyProvider(
- searchServiceProvider: LatencyCorrelationsSearchServiceProvider,
+ searchServiceProvider: SearchServiceProvider<
+ LatencyCorrelationsParams & SearchStrategyClientParams,
+ LatencyCorrelationsRawResponse & RawResponseBase
+ >,
getApmIndices: () => Promise,
includeFrozen: boolean
-): LatencyCorrelationsSearchStrategy;
+): ISearchStrategy<
+ IKibanaSearchRequest<
+ LatencyCorrelationsParams & RawSearchStrategyClientParams
+ >,
+ IKibanaSearchResponse
+>;
-export function searchStrategyProvider<
- TSearchStrategyClientParams extends SearchStrategyClientParams,
- TRawResponse extends RawResponseBase
->(
+export function searchStrategyProvider(
searchServiceProvider: SearchServiceProvider<
- TSearchStrategyClientParams,
- TRawResponse
+ TRequestParams & SearchStrategyClientParams,
+ TResponseParams & RawResponseBase
>,
getApmIndices: () => Promise,
includeFrozen: boolean
): ISearchStrategy<
- IKibanaSearchRequest,
- IKibanaSearchResponse
+ IKibanaSearchRequest,
+ IKibanaSearchResponse
> {
const searchServiceMap = new Map<
string,
- GetSearchServiceState
+ GetSearchServiceState
>();
return {
@@ -93,9 +115,21 @@ export function searchStrategyProvider<
throw new Error('Invalid request parameters.');
}
+ const { start: startString, end: endString } = request.params;
+
+ // converts string based start/end to epochmillis
+ const decodedRange = pipe(
+ rangeRt.decode({ start: startString, end: endString }),
+ getOrElse((errors) => {
+ throw new Error(failure(errors).join('\n'));
+ })
+ );
+
// The function to fetch the current state of the search service.
// This will be either an existing service for a follow up fetch or a new one for new requests.
- let getSearchServiceState: GetSearchServiceState;
+ let getSearchServiceState: GetSearchServiceState<
+ TResponseParams & RawResponseBase
+ >;
// If the request includes an ID, we require that the search service already exists
// otherwise we throw an error. The client should never poll a service that's been cancelled or finished.
@@ -111,10 +145,30 @@ export function searchStrategyProvider<
getSearchServiceState = existingGetSearchServiceState;
} else {
+ const {
+ start,
+ end,
+ environment,
+ kuery,
+ serviceName,
+ transactionName,
+ transactionType,
+ ...requestParams
+ } = request.params;
+
getSearchServiceState = searchServiceProvider(
deps.esClient.asCurrentUser,
getApmIndices,
- request.params as TSearchStrategyClientParams,
+ {
+ environment,
+ kuery,
+ serviceName,
+ transactionName,
+ transactionType,
+ start: decodedRange.start,
+ end: decodedRange.end,
+ ...(requestParams as unknown as TRequestParams),
+ },
includeFrozen
);
}
diff --git a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts
index 472e46fecfa10..3fa6152d953f3 100644
--- a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts
+++ b/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts
@@ -17,6 +17,7 @@ import { environmentsRouteRepository } from './environments';
import { errorsRouteRepository } from './errors';
import { apmFleetRouteRepository } from './fleet';
import { indexPatternRouteRepository } from './index_pattern';
+import { latencyDistributionRouteRepository } from './latency_distribution';
import { metricsRouteRepository } from './metrics';
import { observabilityOverviewRouteRepository } from './observability_overview';
import { rumRouteRepository } from './rum_client';
@@ -41,6 +42,7 @@ const getTypedGlobalApmServerRouteRepository = () => {
.merge(indexPatternRouteRepository)
.merge(environmentsRouteRepository)
.merge(errorsRouteRepository)
+ .merge(latencyDistributionRouteRepository)
.merge(metricsRouteRepository)
.merge(observabilityOverviewRouteRepository)
.merge(rumRouteRepository)
diff --git a/x-pack/plugins/apm/server/routes/latency_distribution.ts b/x-pack/plugins/apm/server/routes/latency_distribution.ts
new file mode 100644
index 0000000000000..ea921a7f4838d
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/latency_distribution.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import * as t from 'io-ts';
+import { toNumberRt } from '@kbn/io-ts-utils';
+import { getOverallLatencyDistribution } from '../lib/latency/get_overall_latency_distribution';
+import { setupRequest } from '../lib/helpers/setup_request';
+import { createApmServerRoute } from './create_apm_server_route';
+import { createApmServerRouteRepository } from './create_apm_server_route_repository';
+import { environmentRt, kueryRt, rangeRt } from './default_api_types';
+
+const latencyOverallDistributionRoute = createApmServerRoute({
+ endpoint: 'GET /internal/apm/latency/overall_distribution',
+ params: t.type({
+ query: t.intersection([
+ t.partial({
+ serviceName: t.string,
+ transactionName: t.string,
+ transactionType: t.string,
+ }),
+ environmentRt,
+ kueryRt,
+ rangeRt,
+ t.type({
+ percentileThreshold: toNumberRt,
+ }),
+ ]),
+ }),
+ options: { tags: ['access:apm'] },
+ handler: async (resources) => {
+ const setup = await setupRequest(resources);
+
+ const {
+ environment,
+ kuery,
+ serviceName,
+ transactionType,
+ transactionName,
+ start,
+ end,
+ percentileThreshold,
+ } = resources.params.query;
+
+ return getOverallLatencyDistribution({
+ environment,
+ kuery,
+ serviceName,
+ transactionType,
+ transactionName,
+ start,
+ end,
+ percentileThreshold,
+ setup,
+ });
+ },
+});
+
+export const latencyDistributionRouteRepository =
+ createApmServerRouteRepository().add(latencyOverallDistributionRoute);
diff --git a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts
index 6e2025a7fa2ca..3388d5b4aa379 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts
+++ b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts
@@ -10,7 +10,7 @@ import expect from '@kbn/expect';
import { IKibanaSearchRequest } from '../../../../../src/plugins/data/common';
import type { FailedTransactionsCorrelationsParams } from '../../../../plugins/apm/common/search_strategies/failed_transactions_correlations/types';
-import type { SearchStrategyClientParams } from '../../../../plugins/apm/common/search_strategies/types';
+import type { RawSearchStrategyClientParams } from '../../../../plugins/apm/common/search_strategies/types';
import { APM_SEARCH_STRATEGIES } from '../../../../plugins/apm/common/search_strategies/constants';
import { FtrProviderContext } from '../../common/ftr_provider_context';
@@ -23,7 +23,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const getRequestBody = () => {
const request: IKibanaSearchRequest<
- FailedTransactionsCorrelationsParams & SearchStrategyClientParams
+ FailedTransactionsCorrelationsParams & RawSearchStrategyClientParams
> = {
params: {
environment: 'ENVIRONMENT_ALL',
diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency.ts b/x-pack/test/apm_api_integration/tests/correlations/latency.ts
index 99aee770c625d..75a4edd447c70 100644
--- a/x-pack/test/apm_api_integration/tests/correlations/latency.ts
+++ b/x-pack/test/apm_api_integration/tests/correlations/latency.ts
@@ -10,7 +10,7 @@ import expect from '@kbn/expect';
import { IKibanaSearchRequest } from '../../../../../src/plugins/data/common';
import type { LatencyCorrelationsParams } from '../../../../plugins/apm/common/search_strategies/latency_correlations/types';
-import type { SearchStrategyClientParams } from '../../../../plugins/apm/common/search_strategies/types';
+import type { RawSearchStrategyClientParams } from '../../../../plugins/apm/common/search_strategies/types';
import { APM_SEARCH_STRATEGIES } from '../../../../plugins/apm/common/search_strategies/constants';
import { FtrProviderContext } from '../../common/ftr_provider_context';
@@ -22,16 +22,17 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const supertest = getService('legacySupertestAsApmReadUser');
const getRequestBody = () => {
- const request: IKibanaSearchRequest = {
- params: {
- environment: 'ENVIRONMENT_ALL',
- start: '2020',
- end: '2021',
- kuery: '',
- percentileThreshold: 95,
- analyzeCorrelations: true,
- },
- };
+ const request: IKibanaSearchRequest =
+ {
+ params: {
+ environment: 'ENVIRONMENT_ALL',
+ start: '2020',
+ end: '2021',
+ kuery: '',
+ percentileThreshold: 95,
+ analyzeCorrelations: true,
+ },
+ };
return {
batch: [
diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts
index 09f4e2596ea46..f68a49658f2ee 100644
--- a/x-pack/test/apm_api_integration/tests/index.ts
+++ b/x-pack/test/apm_api_integration/tests/index.ts
@@ -175,6 +175,10 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte
loadTestFile(require.resolve('./transactions/error_rate'));
});
+ describe('transactions/latency_overall_distribution', function () {
+ loadTestFile(require.resolve('./transactions/latency_overall_distribution'));
+ });
+
describe('transactions/latency', function () {
loadTestFile(require.resolve('./transactions/latency'));
});
diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts b/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts
new file mode 100644
index 0000000000000..c915ac8911e37
--- /dev/null
+++ b/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../common/ftr_provider_context';
+import { registry } from '../../common/registry';
+
+export default function ApiTest({ getService }: FtrProviderContext) {
+ const apmApiClient = getService('apmApiClient');
+
+ const endpoint = 'GET /internal/apm/latency/overall_distribution';
+
+ // This matches the parameters used for the other tab's search strategy approach in `../correlations/*`.
+ const getOptions = () => ({
+ params: {
+ query: {
+ environment: 'ENVIRONMENT_ALL',
+ start: '2020',
+ end: '2021',
+ kuery: '',
+ percentileThreshold: '95',
+ },
+ },
+ });
+
+ registry.when(
+ 'latency overall distribution without data',
+ { config: 'trial', archives: [] },
+ () => {
+ it('handles the empty state', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
+
+ expect(response.status).to.be(200);
+ expect(response.body?.percentileThresholdValue).to.be(undefined);
+ expect(response.body?.overallHistogram?.length).to.be(undefined);
+ });
+ }
+ );
+
+ registry.when(
+ 'latency overall distribution with data and default args',
+ // This uses the same archive used for the other tab's search strategy approach in `../correlations/*`.
+ { config: 'trial', archives: ['8.0.0'] },
+ () => {
+ it('returns percentileThresholdValue and overall histogram', async () => {
+ const response = await apmApiClient.readUser({
+ endpoint,
+ ...getOptions(),
+ });
+
+ expect(response.status).to.eql(200);
+ // This matches the values returned for the other tab's search strategy approach in `../correlations/*`.
+ expect(response.body?.percentileThresholdValue).to.be(1309695.875);
+ expect(response.body?.overallHistogram?.length).to.be(101);
+ });
+ }
+ );
+}
From 512d59467bcdbbf52d1b9038f1d6a1d1a2e0b964 Mon Sep 17 00:00:00 2001
From: Dominique Clarke
Date: Mon, 18 Oct 2021 18:16:20 -0400
Subject: [PATCH 018/204] [Uptime] redirect Uptime tutorials to the Elastic
Synthetics Integration (#115229)
* redirect uptime tutorials
* adjust tests and aria labels
---
.../shared/add_data_buttons/synthetics_add_data.tsx | 4 ++--
.../observability/public/pages/overview/empty_section.ts | 2 +-
x-pack/plugins/uptime/public/apps/use_no_data_config.ts | 4 ++--
.../components/common/header/action_menu_content.test.tsx | 6 ++++--
.../public/components/common/header/action_menu_content.tsx | 6 ++++--
x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx | 2 +-
6 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/x-pack/plugins/observability/public/components/shared/add_data_buttons/synthetics_add_data.tsx b/x-pack/plugins/observability/public/components/shared/add_data_buttons/synthetics_add_data.tsx
index af91624769e6b..d852d6fdb9a31 100644
--- a/x-pack/plugins/observability/public/components/shared/add_data_buttons/synthetics_add_data.tsx
+++ b/x-pack/plugins/observability/public/components/shared/add_data_buttons/synthetics_add_data.tsx
@@ -16,9 +16,9 @@ export function SyntheticsAddData() {
return (
diff --git a/x-pack/plugins/observability/public/pages/overview/empty_section.ts b/x-pack/plugins/observability/public/pages/overview/empty_section.ts
index 2747b2ecdebc9..f249a820a60a4 100644
--- a/x-pack/plugins/observability/public/pages/overview/empty_section.ts
+++ b/x-pack/plugins/observability/public/pages/overview/empty_section.ts
@@ -69,7 +69,7 @@ export const getEmptySections = ({ core }: { core: CoreStart }): ISection[] => {
linkTitle: i18n.translate('xpack.observability.emptySection.apps.uptime.link', {
defaultMessage: 'Install Heartbeat',
}),
- href: core.http.basePath.prepend('/app/home#/tutorial/uptimeMonitors'),
+ href: core.http.basePath.prepend('/app/integrations/detail/synthetics/overview'),
},
{
id: 'ux',
diff --git a/x-pack/plugins/uptime/public/apps/use_no_data_config.ts b/x-pack/plugins/uptime/public/apps/use_no_data_config.ts
index dc00a25e3a111..6e73a6d5e8268 100644
--- a/x-pack/plugins/uptime/public/apps/use_no_data_config.ts
+++ b/x-pack/plugins/uptime/public/apps/use_no_data_config.ts
@@ -31,13 +31,13 @@ export function useNoDataConfig(): KibanaPageTemplateProps['noDataConfig'] {
actions: {
beats: {
title: i18n.translate('xpack.uptime.noDataConfig.beatsCard.title', {
- defaultMessage: 'Add monitors with Heartbeat',
+ defaultMessage: 'Add monitors with the Elastic Synthetics integration',
}),
description: i18n.translate('xpack.uptime.noDataConfig.beatsCard.description', {
defaultMessage:
'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users experience.',
}),
- href: basePath + `/app/home#/tutorial/uptimeMonitors`,
+ href: basePath + `/app/integrations/detail/synthetics/overview`,
},
},
docsLink: docLinks!.links.observability.guide,
diff --git a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.test.tsx b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.test.tsx
index 0265588c3fdeb..76b9378ca4ff6 100644
--- a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.test.tsx
+++ b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.test.tsx
@@ -45,11 +45,13 @@ describe('ActionMenuContent', () => {
it('renders Add Data link', () => {
const { getByLabelText, getByText } = render();
- const addDataAnchor = getByLabelText('Navigate to a tutorial about adding Uptime data');
+ const addDataAnchor = getByLabelText(
+ 'Navigate to the Elastic Synthetics integration to add Uptime data'
+ );
// this href value is mocked, so it doesn't correspond to the real link
// that Kibana core services will provide
- expect(addDataAnchor.getAttribute('href')).toBe('/home#/tutorial/uptimeMonitors');
+ expect(addDataAnchor.getAttribute('href')).toBe('/integrations/detail/synthetics/overview');
expect(getByText('Add data'));
});
});
diff --git a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
index bcfbf18c93cf5..789953258750b 100644
--- a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
+++ b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
@@ -99,9 +99,11 @@ export function ActionMenuContent(): React.ReactElement {
diff --git a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx
index ac129bdb327d9..60ccec84c3bb1 100644
--- a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx
+++ b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx
@@ -83,7 +83,7 @@ const createMockStore = () => {
const mockAppUrls: Record = {
uptime: '/app/uptime',
observability: '/app/observability',
- '/home#/tutorial/uptimeMonitors': '/home#/tutorial/uptimeMonitors',
+ '/integrations/detail/synthetics/overview': '/integrations/detail/synthetics/overview',
};
/* default mock core */
From a4f6988fc67e1bef37b3c153377ddbde40ddd704 Mon Sep 17 00:00:00 2001
From: Kevin Logan <56395104+kevinlog@users.noreply.github.com>
Date: Mon, 18 Oct 2021 19:27:28 -0400
Subject: [PATCH 019/204] [Security Solution] Skip flakey test Configures a new
connector.Cases connectors Configures a new connector (#115440)
---
.../cypress/integration/cases/connectors.spec.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts
index 287d86c6fba9e..69b623de0b43c 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts
@@ -20,7 +20,8 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { CASES_URL } from '../../urls/navigation';
-describe('Cases connectors', () => {
+// Skipping flakey test: https://github.com/elastic/kibana/issues/115438
+describe.skip('Cases connectors', () => {
const configureResult = {
connector: {
id: 'e271c3b8-f702-4fbc-98e0-db942b573bbd',
From 75048dc13b00d1ab8cde9e6e7d9db9829636cd05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cau=C3=AA=20Marcondes?=
<55978943+cauemarcondes@users.noreply.github.com>
Date: Mon, 18 Oct 2021 19:53:20 -0400
Subject: [PATCH 020/204] [APM] Ensure APM deprecation documentationUrl point
to correct doc branch (#115401)
* using branch in the url
* fixing TS
---
x-pack/plugins/apm/server/deprecations/deprecations.test.ts | 5 ++++-
x-pack/plugins/apm/server/deprecations/index.ts | 5 +++--
x-pack/plugins/apm/server/plugin.ts | 2 ++
3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts
index d706146faf212..43e8140fb9b3c 100644
--- a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts
+++ b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts
@@ -19,7 +19,7 @@ const deprecationContext = {
describe('getDeprecations', () => {
describe('when fleet is disabled', () => {
it('returns no deprecations', async () => {
- const deprecationsCallback = getDeprecations({});
+ const deprecationsCallback = getDeprecations({ branch: 'master' });
const deprecations = await deprecationsCallback(deprecationContext);
expect(deprecations).toEqual([]);
});
@@ -28,6 +28,7 @@ describe('getDeprecations', () => {
describe('when running on cloud with legacy apm-server', () => {
it('returns deprecations', async () => {
const deprecationsCallback = getDeprecations({
+ branch: 'master',
cloudSetup: { isCloudEnabled: true } as unknown as CloudSetup,
fleet: {
start: () => ({
@@ -43,6 +44,7 @@ describe('getDeprecations', () => {
describe('when running on cloud with fleet', () => {
it('returns no deprecations', async () => {
const deprecationsCallback = getDeprecations({
+ branch: 'master',
cloudSetup: { isCloudEnabled: true } as unknown as CloudSetup,
fleet: {
start: () => ({
@@ -58,6 +60,7 @@ describe('getDeprecations', () => {
describe('when running on prem', () => {
it('returns no deprecations', async () => {
const deprecationsCallback = getDeprecations({
+ branch: 'master',
cloudSetup: { isCloudEnabled: false } as unknown as CloudSetup,
fleet: {
start: () => ({ agentPolicyService: { get: () => undefined } }),
diff --git a/x-pack/plugins/apm/server/deprecations/index.ts b/x-pack/plugins/apm/server/deprecations/index.ts
index b592a2bf13268..76c90270abb8f 100644
--- a/x-pack/plugins/apm/server/deprecations/index.ts
+++ b/x-pack/plugins/apm/server/deprecations/index.ts
@@ -15,9 +15,11 @@ import { APMRouteHandlerResources } from '../';
export function getDeprecations({
cloudSetup,
fleet,
+ branch,
}: {
cloudSetup?: CloudSetup;
fleet?: APMRouteHandlerResources['plugins']['fleet'];
+ branch: string;
}) {
return async ({
savedObjectsClient,
@@ -46,8 +48,7 @@ export function getDeprecations({
defaultMessage:
'Running the APM Server binary directly is considered a legacy option and is deprecated since 7.16. Switch to APM Server managed by an Elastic Agent instead. Read our documentation to learn more.',
}),
- documentationUrl:
- 'https://www.elastic.co/guide/en/apm/server/current/apm-integration.html',
+ documentationUrl: `https://www.elastic.co/guide/en/apm/server/${branch}/apm-integration.html`,
level: 'warning',
correctiveActions: {
manualSteps: [
diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts
index d2d8dbf602364..72a1bc483015e 100644
--- a/x-pack/plugins/apm/server/plugin.ts
+++ b/x-pack/plugins/apm/server/plugin.ts
@@ -215,10 +215,12 @@ export class APMPlugin
);
})();
});
+
core.deprecations.registerDeprecations({
getDeprecations: getDeprecations({
cloudSetup: plugins.cloud,
fleet: resourcePlugins.fleet,
+ branch: this.initContext.env.packageInfo.branch,
}),
});
From e673383584c44f8fcb3bca767f9c38f0f7f79393 Mon Sep 17 00:00:00 2001
From: Oliver Gupte
Date: Mon, 18 Oct 2021 19:58:35 -0400
Subject: [PATCH 021/204] [APM] APM-Fleet integration version check & upgrade
message (#115297)
---
x-pack/plugins/apm/common/fleet.ts | 2 +
.../components/app/Settings/schema/index.tsx | 4 +-
.../schema/migrated/card_footer_content.tsx | 47 +++++++++++++
.../migrated/successful_migration_card.tsx | 30 ++++++++
.../migrated/upgrade_available_card.tsx | 51 ++++++++++++++
.../app/Settings/schema/schema_overview.tsx | 68 +++++--------------
.../public/components/shared/Links/kibana.ts | 11 +++
.../get_apm_package_policy_definition.ts | 7 +-
x-pack/plugins/apm/server/routes/fleet.ts | 6 +-
9 files changed, 170 insertions(+), 56 deletions(-)
create mode 100644 x-pack/plugins/apm/public/components/app/Settings/schema/migrated/card_footer_content.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/Settings/schema/migrated/successful_migration_card.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/Settings/schema/migrated/upgrade_available_card.tsx
diff --git a/x-pack/plugins/apm/common/fleet.ts b/x-pack/plugins/apm/common/fleet.ts
index 618cd20d66159..97551cc16b4be 100644
--- a/x-pack/plugins/apm/common/fleet.ts
+++ b/x-pack/plugins/apm/common/fleet.ts
@@ -6,3 +6,5 @@
*/
export const POLICY_ELASTIC_AGENT_ON_CLOUD = 'policy-elastic-agent-on-cloud';
+
+export const SUPPORTED_APM_PACKAGE_VERSION = '7.16.0';
diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx
index ac32e22fa3ded..b13046d34be94 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx
@@ -54,7 +54,8 @@ export function Schema() {
const isLoading = status !== FETCH_STATUS.SUCCESS;
const cloudApmMigrationEnabled = !!data.cloud_apm_migration_enabled;
const hasCloudAgentPolicy = !!data.has_cloud_agent_policy;
- const hasCloudApmPackagePolicy = !!data.has_cloud_apm_package_policy;
+ const cloudApmPackagePolicy = data.cloud_apm_package_policy;
+ const hasCloudApmPackagePolicy = !!cloudApmPackagePolicy;
const hasRequiredRole = !!data.has_required_role;
function updateLocalStorage(newStatus: FETCH_STATUS) {
@@ -90,6 +91,7 @@ export function Schema() {
cloudApmMigrationEnabled={cloudApmMigrationEnabled}
hasCloudAgentPolicy={hasCloudAgentPolicy}
hasRequiredRole={hasRequiredRole}
+ cloudApmPackagePolicy={cloudApmPackagePolicy}
/>
{isSwitchActive && (
+
+ {i18n.translate(
+ 'xpack.apm.settings.schema.success.viewIntegrationInFleet.buttonText',
+ { defaultMessage: 'View the APM integration in Fleet' }
+ )}
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.apm.settings.schema.success.returnText.serviceInventoryLink',
+ { defaultMessage: 'Service inventory' }
+ )}
+
+ ),
+ }}
+ />
+
+
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/migrated/successful_migration_card.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/migrated/successful_migration_card.tsx
new file mode 100644
index 0000000000000..839479fbbf652
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/Settings/schema/migrated/successful_migration_card.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiCard, EuiIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { CardFooterContent } from './card_footer_content';
+
+export function SuccessfulMigrationCard() {
+ return (
+ }
+ title={i18n.translate('xpack.apm.settings.schema.success.title', {
+ defaultMessage: 'Elastic Agent successfully setup!',
+ })}
+ description={i18n.translate(
+ 'xpack.apm.settings.schema.success.description',
+ {
+ defaultMessage:
+ 'Your APM integration is now setup and ready to receive data from your currently instrumented agents. Feel free to review the policies applied to your integtration.',
+ }
+ )}
+ footer={}
+ />
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/migrated/upgrade_available_card.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/migrated/upgrade_available_card.tsx
new file mode 100644
index 0000000000000..8c10236335961
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/Settings/schema/migrated/upgrade_available_card.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiCard, EuiIcon, EuiLink } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import React from 'react';
+import { useUpgradeApmPackagePolicyHref } from '../../../../shared/Links/kibana';
+import { CardFooterContent } from './card_footer_content';
+
+export function UpgradeAvailableCard({
+ apmPackagePolicyId,
+}: {
+ apmPackagePolicyId: string | undefined;
+}) {
+ const upgradeApmPackagePolicyHref =
+ useUpgradeApmPackagePolicyHref(apmPackagePolicyId);
+
+ return (
+ }
+ title={i18n.translate(
+ 'xpack.apm.settings.schema.upgradeAvailable.title',
+ {
+ defaultMessage: 'APM integration upgrade available!',
+ }
+ )}
+ description={
+
+ {i18n.translate(
+ 'xpack.apm.settings.schema.upgradeAvailable.upgradePackagePolicyLink',
+ { defaultMessage: 'Upgrade your APM integration' }
+ )}
+
+ ),
+ }}
+ />
+ }
+ footer={}
+ />
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/schema_overview.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/schema_overview.tsx
index 0031c102e8ae5..cead6cd8a6fb4 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/schema/schema_overview.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/schema/schema_overview.tsx
@@ -19,11 +19,14 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
-import { APMLink } from '../../../shared/Links/apm/APMLink';
+import semverLt from 'semver/functions/lt';
+import { SUPPORTED_APM_PACKAGE_VERSION } from '../../../../../common/fleet';
+import { PackagePolicy } from '../../../../../../fleet/common/types';
import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink';
-import { useFleetCloudAgentPolicyHref } from '../../../shared/Links/kibana';
import rocketLaunchGraphic from './blog-rocket-720x420.png';
import { MigrationInProgressPanel } from './migration_in_progress_panel';
+import { UpgradeAvailableCard } from './migrated/upgrade_available_card';
+import { SuccessfulMigrationCard } from './migrated/successful_migration_card';
interface Props {
onSwitch: () => void;
@@ -34,6 +37,7 @@ interface Props {
cloudApmMigrationEnabled: boolean;
hasCloudAgentPolicy: boolean;
hasRequiredRole: boolean;
+ cloudApmPackagePolicy: PackagePolicy | undefined;
}
export function SchemaOverview({
onSwitch,
@@ -44,10 +48,13 @@ export function SchemaOverview({
cloudApmMigrationEnabled,
hasCloudAgentPolicy,
hasRequiredRole,
+ cloudApmPackagePolicy,
}: Props) {
- const fleetCloudAgentPolicyHref = useFleetCloudAgentPolicyHref();
const isDisabled =
!cloudApmMigrationEnabled || !hasCloudAgentPolicy || !hasRequiredRole;
+ const packageVersion = cloudApmPackagePolicy?.package?.version;
+ const isUpgradeAvailable =
+ packageVersion && semverLt(packageVersion, SUPPORTED_APM_PACKAGE_VERSION);
if (isLoading) {
return (
@@ -76,54 +83,13 @@ export function SchemaOverview({
-
- }
- title={i18n.translate('xpack.apm.settings.schema.success.title', {
- defaultMessage: 'Elastic Agent successfully setup!',
- })}
- description={i18n.translate(
- 'xpack.apm.settings.schema.success.description',
- {
- defaultMessage:
- 'Your APM integration is now setup and ready to receive data from your currently instrumented agents. Feel free to review the policies applied to your integtration.',
- }
- )}
- footer={
-
-
- {i18n.translate(
- 'xpack.apm.settings.schema.success.viewIntegrationInFleet.buttonText',
- { defaultMessage: 'View the APM integration in Fleet' }
- )}
-
-
-
-
-
- {i18n.translate(
- 'xpack.apm.settings.schema.success.returnText.serviceInventoryLink',
- { defaultMessage: 'Service inventory' }
- )}
-
- ),
- }}
- />
-
-
-
- }
- />
+ {isUpgradeAvailable ? (
+
+ ) : (
+
+ )}
diff --git a/x-pack/plugins/apm/public/components/shared/Links/kibana.ts b/x-pack/plugins/apm/public/components/shared/Links/kibana.ts
index bfb7cf849f567..c0bdf3a98aa31 100644
--- a/x-pack/plugins/apm/public/components/shared/Links/kibana.ts
+++ b/x-pack/plugins/apm/public/components/shared/Links/kibana.ts
@@ -26,3 +26,14 @@ export function useFleetCloudAgentPolicyHref() {
} = useApmPluginContext();
return basePath.prepend('/app/fleet#/policies/policy-elastic-agent-on-cloud');
}
+
+export function useUpgradeApmPackagePolicyHref(packagePolicyId = '') {
+ const {
+ core: {
+ http: { basePath },
+ },
+ } = useApmPluginContext();
+ return basePath.prepend(
+ `/app/fleet/policies/policy-elastic-agent-on-cloud/upgrade-package-policy/${packagePolicyId}?from=integrations-policy-list`
+ );
+}
diff --git a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts
index 64b071b67d2bd..98b6a6489c47b 100644
--- a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts
+++ b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts
@@ -5,7 +5,10 @@
* 2.0.
*/
-import { POLICY_ELASTIC_AGENT_ON_CLOUD } from '../../../common/fleet';
+import {
+ POLICY_ELASTIC_AGENT_ON_CLOUD,
+ SUPPORTED_APM_PACKAGE_VERSION,
+} from '../../../common/fleet';
import { APMPluginSetupDependencies } from '../../types';
import { APM_PACKAGE_NAME } from './get_cloud_apm_package_policy';
@@ -36,7 +39,7 @@ export function getApmPackagePolicyDefinition(
],
package: {
name: APM_PACKAGE_NAME,
- version: '0.4.0',
+ version: SUPPORTED_APM_PACKAGE_VERSION,
title: 'Elastic APM',
},
};
diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts
index 2884c08ceb9a1..e18aefcd6e0d8 100644
--- a/x-pack/plugins/apm/server/routes/fleet.ts
+++ b/x-pack/plugins/apm/server/routes/fleet.ts
@@ -92,7 +92,7 @@ const fleetAgentsRoute = createApmServerRoute({
});
const saveApmServerSchemaRoute = createApmServerRoute({
- endpoint: 'POST /internal/apm/fleet/apm_server_schema',
+ endpoint: 'POST /api/apm/fleet/apm_server_schema',
options: { tags: ['access:apm', 'access:apm_write'] },
params: t.type({
body: t.type({
@@ -143,11 +143,13 @@ const getMigrationCheckRoute = createApmServerRoute({
fleetPluginStart,
})
: undefined;
+ const apmPackagePolicy = getApmPackagePolicy(cloudAgentPolicy);
return {
has_cloud_agent_policy: !!cloudAgentPolicy,
- has_cloud_apm_package_policy: !!getApmPackagePolicy(cloudAgentPolicy),
+ has_cloud_apm_package_policy: !!apmPackagePolicy,
cloud_apm_migration_enabled: cloudApmMigrationEnabled,
has_required_role: hasRequiredRole,
+ cloud_apm_package_policy: apmPackagePolicy,
};
},
});
From 5e58fbded0592b1006c289341f376d89f1f88793 Mon Sep 17 00:00:00 2001
From: Georgii Gorbachev
Date: Tue, 19 Oct 2021 02:29:14 +0200
Subject: [PATCH 022/204] [Security Solution][Detections] Fix a bug in
siem-detection-engine-rule-status Saved Object migration to SO references
(#115355)
**Ticket:** https://github.com/elastic/kibana/issues/107068
**Follow-up after:** https://github.com/elastic/kibana/pull/114585
## Summary
The existing migration function `legacyMigrateRuleAlertIdSOReferences` that migrates `alertId` fields to SO references array did not include all the other attributes of a `siem-detection-engine-rule-status` doc being migrated to the resulting doc.
This PR includes a fix and an integration test for that.
## Run the test
To run the test, in one terminal execute:
```
cd ${KIBANA_HOME} && node scripts/functional_tests_server --config x-pack/test/detection_engine_api_integration/security_and_spaces/config.ts
```
In another terminal execute:
```
cd ${KIBANA_HOME} && node scripts/functional_test_runner --config x-pack/test/detection_engine_api_integration/security_and_spaces/config.ts --include=x-pack/test/detection_engine_api_integration/security_and_spaces/tests/migrations.ts
```
### Checklist
- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
---
.../legacy_rule_status/legacy_migrations.ts | 63 ++++++++-----------
.../security_and_spaces/tests/migrations.ts | 25 ++++++++
2 files changed, 52 insertions(+), 36 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/legacy_rule_status/legacy_migrations.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/legacy_rule_status/legacy_migrations.ts
index 92d7487be0cdb..72ab4a2237ba1 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/legacy_rule_status/legacy_migrations.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/legacy_rule_status/legacy_migrations.ts
@@ -18,34 +18,19 @@ import { IRuleSavedAttributesSavedObjectAttributes } from '../types';
import { legacyGetRuleReference } from './legacy_utils';
export const truncateMessageFields: SavedObjectMigrationFn> = (doc) => {
- const { lastFailureMessage, lastSuccessMessage, ...restAttributes } = doc.attributes;
+ const { lastFailureMessage, lastSuccessMessage, ...otherAttributes } = doc.attributes;
return {
...doc,
attributes: {
+ ...otherAttributes,
lastFailureMessage: truncateMessage(lastFailureMessage),
lastSuccessMessage: truncateMessage(lastSuccessMessage),
- ...restAttributes,
},
references: doc.references ?? [],
};
};
-/**
- * This side-car rule status SO is deprecated and is to be replaced by the RuleExecutionLog on Event-Log and
- * additional fields on the Alerting Framework Rule SO.
- *
- * @deprecated Remove this once we've fully migrated to event-log and no longer require addition status SO (8.x)
- */
-export const legacyRuleStatusSavedObjectMigration = {
- '7.15.2': truncateMessageFields,
- '7.16.0': (
- doc: SavedObjectUnsanitizedDoc
- ): SavedObjectSanitizedDoc => {
- return legacyMigrateRuleAlertIdSOReferences(doc);
- },
-};
-
/**
* This migrates alertId within legacy `siem-detection-engine-rule-status` to saved object references on an upgrade.
* We only migrate alertId if we find these conditions:
@@ -62,29 +47,24 @@ export const legacyRuleStatusSavedObjectMigration = {
export const legacyMigrateRuleAlertIdSOReferences = (
doc: SavedObjectUnsanitizedDoc
): SavedObjectSanitizedDoc => {
- const { references } = doc;
-
- // Isolate alertId from the doc
- const { alertId, ...attributesWithoutAlertId } = doc.attributes;
- const existingReferences = references ?? [];
+ const { alertId, ...otherAttributes } = doc.attributes;
+ const existingReferences = doc.references ?? [];
+ // early return if alertId is not a string as expected
if (!isString(alertId)) {
- // early return if alertId is not a string as expected
return { ...doc, references: existingReferences };
- } else {
- const alertReferences = legacyMigrateAlertId({
- alertId,
- existingReferences,
- });
-
- return {
- ...doc,
- attributes: {
- ...attributesWithoutAlertId.attributes,
- },
- references: [...existingReferences, ...alertReferences],
- };
}
+
+ const alertReferences = legacyMigrateAlertId({
+ alertId,
+ existingReferences,
+ });
+
+ return {
+ ...doc,
+ attributes: otherAttributes,
+ references: [...existingReferences, ...alertReferences],
+ };
};
/**
@@ -113,3 +93,14 @@ export const legacyMigrateAlertId = ({
return [legacyGetRuleReference(alertId)];
}
};
+
+/**
+ * This side-car rule status SO is deprecated and is to be replaced by the RuleExecutionLog on Event-Log and
+ * additional fields on the Alerting Framework Rule SO.
+ *
+ * @deprecated Remove this once we've fully migrated to event-log and no longer require addition status SO (8.x)
+ */
+export const legacyRuleStatusSavedObjectMigration = {
+ '7.15.2': truncateMessageFields,
+ '7.16.0': legacyMigrateRuleAlertIdSOReferences,
+};
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/migrations.ts
index 6d1d64a04cd93..cfae7532ba496 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/migrations.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/migrations.ts
@@ -6,6 +6,7 @@
*/
import expect from '@kbn/expect';
+import { IRuleStatusSOAttributes } from '../../../../plugins/security_solution/server/lib/detection_engine/rules/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
@@ -113,6 +114,30 @@ export default ({ getService }: FtrProviderContext): void => {
undefined
);
});
+
+ it('migrates legacy siem-detection-engine-rule-status and retains other attributes as the same attributes as before', async () => {
+ const response = await es.get<{
+ 'siem-detection-engine-rule-status': IRuleStatusSOAttributes;
+ }>({
+ index: '.kibana',
+ id: 'siem-detection-engine-rule-status:d62d2980-27c4-11ec-92b0-f7b47106bb35',
+ });
+ expect(response.statusCode).to.eql(200);
+
+ expect(response.body._source?.['siem-detection-engine-rule-status']).to.eql({
+ statusDate: '2021-10-11T20:51:26.622Z',
+ status: 'succeeded',
+ lastFailureAt: '2021-10-11T18:10:08.982Z',
+ lastSuccessAt: '2021-10-11T20:51:26.622Z',
+ lastFailureMessage:
+ '4 days (323690920ms) were not queried between this rule execution and the last execution, so signals may have been missed. Consider increasing your look behind time or adding more Kibana instances. name: "Threshy" id: "fb1046a0-0452-11ec-9b15-d13d79d162f3" rule id: "b789c80f-f6d8-41f1-8b4f-b4a23342cde2" signals index: ".siem-signals-spong-default"',
+ lastSuccessMessage: 'succeeded',
+ gap: '4 days',
+ bulkCreateTimeDurations: ['34.49'],
+ searchAfterTimeDurations: ['62.58'],
+ lastLookBackDate: null,
+ });
+ });
});
});
};
From a8b616aaa29e52863526284c1b038eda1a3a5944 Mon Sep 17 00:00:00 2001
From: Thomas Neirynck
Date: Mon, 18 Oct 2021 20:31:07 -0400
Subject: [PATCH 023/204] [Fleet] Add beta flag for custom integrations
(#115447)
---
.../integrations/sections/epm/screens/home/index.tsx | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx
index 2d1077f586a1a..4270d360b9294 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx
@@ -66,6 +66,13 @@ export const mapToCard = (
uiInternalPathUrl = url;
}
+ let release: 'ga' | 'beta' | 'experimental' | undefined;
+ if ('release' in item) {
+ release = item.release;
+ } else if (item.isBeta === true) {
+ release = 'beta';
+ }
+
return {
id: `${item.type === 'ui_link' ? 'ui_link' : 'epr'}-${item.id}`,
description: item.description,
@@ -75,7 +82,7 @@ export const mapToCard = (
integration: 'integration' in item ? item.integration || '' : '',
name: 'name' in item ? item.name || '' : '',
version: 'version' in item ? item.version || '' : '',
- release: 'release' in item ? item.release : undefined,
+ release,
categories: ((item.categories || []) as string[]).filter((c: string) => !!c),
};
};
From 083a2b9523d10fa8891563274abbca360a4216b7 Mon Sep 17 00:00:00 2001
From: Brian Seeders
Date: Mon, 18 Oct 2021 21:08:50 -0400
Subject: [PATCH 024/204] skip flaky suite (#115488)
---
.../test/security_solution_endpoint_api_int/apis/metadata.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts
index 35fe0cdd6da25..2dcf36cc42ae2 100644
--- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts
+++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts
@@ -24,7 +24,8 @@ export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
- describe('test metadata api', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/115488
+ describe.skip('test metadata api', () => {
// TODO add this after endpoint package changes are merged and in snapshot
// describe('with .metrics-endpoint.metadata_united_default index', () => {
// });
From b103a544cc71d0998074b94c6a00a5da5d6e78ae Mon Sep 17 00:00:00 2001
From: Justin Kambic
Date: Mon, 18 Oct 2021 22:43:00 -0400
Subject: [PATCH 025/204] [Uptime] Fix unhandled promise rejection failure
(#114883)
* Fix unhandled promise rejection failure.
* Mock monaco to avoid editor-related errors failing test.
* Update assertion.
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../components/fleet_package/custom_fields.test.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx
index 26ee26cc8ed7f..62c6f5598adb4 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx
@@ -313,11 +313,11 @@ describe.skip('', () => {
// resolve errors
fireEvent.click(monitorType);
- waitFor(() => {
- expect(getByText('http')).toBeInTheDocument();
- expect(getByText('tcp')).toBeInTheDocument();
- expect(getByText('icmp')).toBeInTheDocument();
- expect(queryByText('browser')).not.toBeInTheDocument();
+ await waitFor(() => {
+ expect(getByText('HTTP')).toBeInTheDocument();
+ expect(getByText('TCP')).toBeInTheDocument();
+ expect(getByText('ICMP')).toBeInTheDocument();
+ expect(queryByText('Browser')).not.toBeInTheDocument();
});
});
});
From d0bc10f896a30c4b08c5fc302cd98c661d6847e3 Mon Sep 17 00:00:00 2001
From: Ashokaditya
Date: Tue, 19 Oct 2021 05:27:12 +0200
Subject: [PATCH 026/204] [Security Solution][Endpoint]Activity Log API/UX
changes (#114905)
* rename legacy actions/responses
fixes elastic/security-team/issues/1702
* use correct name for responses index
refs elastic/kibana/pull/113621
* extract helper method to utils
* append endpoint responses docs to activity log
* Show completed responses on activity log
fixes elastic/security-team/issues/1703
* remove width restriction on date picker
* add a simple test to verify endpoint responses
fixes elastic/security-team/issues/1702
* find unique action_ids from `.fleet-actions` and `.logs-endpoint.actions-default` indices
fixes elastic/security-team/issues/1702
* do not filter out endpoint only actions/responses that did not make it to Fleet
review comments
* use a constant to manage various doc types
review comments
* refactor `getActivityLog`
Simplify `getActivityLog` so it is easier to reason with.
review comments
* skip this for now
will mock this better in a new PR
* improve types
* display endpoint actions similar to fleet actions, but with success icon color
* Correctly do mocks for tests
* Include only errored endpoint actions, remove successful duplicates
fixes elastic/security-team/issues/1703
* Update tests to use non duplicate action_ids
review comments
fixes elastic/security-team/issues/1703
* show correct action title
review fixes
* statusCode constant
review change
* rename
review changes
* Update translations.ts
refs https://github.com/elastic/kibana/pull/114905/commits/74a8340b5eb2e31faba67a4fbe656f74fe52d0a2
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../common/endpoint/constants.ts | 4 +-
.../common/endpoint/types/actions.ts | 33 ++-
.../activity_log_date_range_picker/index.tsx | 1 -
.../view/details/components/log_entry.tsx | 101 ++++++-
.../components/log_entry_timeline_icon.tsx | 21 +-
.../view/details/endpoints.stories.tsx | 14 +-
.../pages/endpoint_hosts/view/index.test.tsx | 95 +++++--
.../pages/endpoint_hosts/view/translations.ts | 36 +++
.../endpoint/routes/actions/audit_log.test.ts | 213 ++++++++++++--
.../endpoint/routes/actions/isolation.ts | 29 +-
.../server/endpoint/routes/actions/mocks.ts | 32 +++
.../server/endpoint/services/actions.ts | 146 ++++------
.../endpoint/utils/audit_log_helpers.ts | 266 ++++++++++++++++++
.../server/endpoint/utils/index.ts | 2 +
.../endpoint/utils/yes_no_data_stream.test.ts | 100 +++++++
.../endpoint/utils/yes_no_data_stream.ts | 59 ++++
16 files changed, 949 insertions(+), 203 deletions(-)
create mode 100644 x-pack/plugins/security_solution/server/endpoint/utils/audit_log_helpers.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/utils/yes_no_data_stream.test.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/utils/yes_no_data_stream.ts
diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts
index 6e9123da2dd9b..178a2b68a4aab 100644
--- a/x-pack/plugins/security_solution/common/endpoint/constants.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts
@@ -10,7 +10,7 @@
export const ENDPOINT_ACTIONS_DS = '.logs-endpoint.actions';
export const ENDPOINT_ACTIONS_INDEX = `${ENDPOINT_ACTIONS_DS}-default`;
export const ENDPOINT_ACTION_RESPONSES_DS = '.logs-endpoint.action.responses';
-export const ENDPOINT_ACTION_RESPONSES_INDEX = `${ENDPOINT_ACTIONS_DS}-default`;
+export const ENDPOINT_ACTION_RESPONSES_INDEX = `${ENDPOINT_ACTION_RESPONSES_DS}-default`;
export const eventsIndexPattern = 'logs-endpoint.events.*';
export const alertsIndexPattern = 'logs-endpoint.alerts-*';
@@ -60,3 +60,5 @@ export const UNISOLATE_HOST_ROUTE = `${BASE_ENDPOINT_ROUTE}/unisolate`;
/** Endpoint Actions Log Routes */
export const ENDPOINT_ACTION_LOG_ROUTE = `/api/endpoint/action_log/{agent_id}`;
export const ACTION_STATUS_ROUTE = `/api/endpoint/action_status`;
+
+export const failedFleetActionErrorCode = '424';
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
index bc46ca2f5b451..fb29297eb5929 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
@@ -10,6 +10,13 @@ import { ActionStatusRequestSchema, HostIsolationRequestSchema } from '../schema
export type ISOLATION_ACTIONS = 'isolate' | 'unisolate';
+export const ActivityLogItemTypes = {
+ ACTION: 'action' as const,
+ RESPONSE: 'response' as const,
+ FLEET_ACTION: 'fleetAction' as const,
+ FLEET_RESPONSE: 'fleetResponse' as const,
+};
+
interface EcsError {
code?: string;
id?: string;
@@ -87,8 +94,24 @@ export interface EndpointActionResponse {
action_data: EndpointActionData;
}
+export interface EndpointActivityLogAction {
+ type: typeof ActivityLogItemTypes.ACTION;
+ item: {
+ id: string;
+ data: LogsEndpointAction;
+ };
+}
+
+export interface EndpointActivityLogActionResponse {
+ type: typeof ActivityLogItemTypes.RESPONSE;
+ item: {
+ id: string;
+ data: LogsEndpointActionResponse;
+ };
+}
+
export interface ActivityLogAction {
- type: 'action';
+ type: typeof ActivityLogItemTypes.FLEET_ACTION;
item: {
// document _id
id: string;
@@ -97,7 +120,7 @@ export interface ActivityLogAction {
};
}
export interface ActivityLogActionResponse {
- type: 'response';
+ type: typeof ActivityLogItemTypes.FLEET_RESPONSE;
item: {
// document id
id: string;
@@ -105,7 +128,11 @@ export interface ActivityLogActionResponse {
data: EndpointActionResponse;
};
}
-export type ActivityLogEntry = ActivityLogAction | ActivityLogActionResponse;
+export type ActivityLogEntry =
+ | ActivityLogAction
+ | ActivityLogActionResponse
+ | EndpointActivityLogAction
+ | EndpointActivityLogActionResponse;
export interface ActivityLog {
page: number;
pageSize: number;
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx
index 05887d82cacad..a57fa8d8e4ce5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx
@@ -32,7 +32,6 @@ interface Range {
const DatePickerWrapper = styled.div`
width: ${(props) => props.theme.eui.fractions.single.percentage};
- max-width: 350px;
`;
const StickyFlexItem = styled(EuiFlexItem)`
background: ${(props) => `${props.theme.eui.euiHeaderBackgroundColor}`};
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx
index bbe0a6f3afcd1..79af2ecb354fd 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx
@@ -9,24 +9,34 @@ import React, { memo, useMemo } from 'react';
import styled from 'styled-components';
import { EuiComment, EuiText, EuiAvatarProps, EuiCommentProps, IconType } from '@elastic/eui';
-import { Immutable, ActivityLogEntry } from '../../../../../../../common/endpoint/types';
+import {
+ Immutable,
+ ActivityLogEntry,
+ ActivityLogItemTypes,
+} from '../../../../../../../common/endpoint/types';
import { FormattedRelativePreferenceDate } from '../../../../../../common/components/formatted_date';
import { LogEntryTimelineIcon } from './log_entry_timeline_icon';
+import { useEuiTheme } from '../../../../../../common/lib/theme/use_eui_theme';
import * as i18 from '../../translations';
const useLogEntryUIProps = (
- logEntry: Immutable
+ logEntry: Immutable,
+ theme: ReturnType
): {
actionEventTitle: string;
+ avatarColor: EuiAvatarProps['color'];
+ avatarIconColor: EuiAvatarProps['iconColor'];
avatarSize: EuiAvatarProps['size'];
commentText: string;
commentType: EuiCommentProps['type'];
displayComment: boolean;
displayResponseEvent: boolean;
+ failedActionEventTitle: string;
iconType: IconType;
isResponseEvent: boolean;
isSuccessful: boolean;
+ isCompleted: boolean;
responseEventTitle: string;
username: string | React.ReactNode;
} => {
@@ -34,15 +44,19 @@ const useLogEntryUIProps = (
let iconType: IconType = 'dot';
let commentType: EuiCommentProps['type'] = 'update';
let commentText: string = '';
+ let avatarColor: EuiAvatarProps['color'] = theme.euiColorLightestShade;
+ let avatarIconColor: EuiAvatarProps['iconColor'];
let avatarSize: EuiAvatarProps['size'] = 's';
+ let failedActionEventTitle: string = '';
let isIsolateAction: boolean = false;
let isResponseEvent: boolean = false;
let isSuccessful: boolean = false;
+ let isCompleted: boolean = false;
let displayComment: boolean = false;
let displayResponseEvent: boolean = true;
let username: EuiCommentProps['username'] = '';
- if (logEntry.type === 'action') {
+ if (logEntry.type === ActivityLogItemTypes.FLEET_ACTION) {
avatarSize = 'm';
commentType = 'regular';
commentText = logEntry.item.data.data.comment?.trim() ?? '';
@@ -59,13 +73,51 @@ const useLogEntryUIProps = (
displayComment = true;
}
}
- } else if (logEntry.type === 'response') {
+ }
+ if (logEntry.type === ActivityLogItemTypes.ACTION) {
+ avatarSize = 'm';
+ commentType = 'regular';
+ commentText = logEntry.item.data.EndpointActions.data.comment?.trim() ?? '';
+ displayResponseEvent = false;
+ iconType = 'lockOpen';
+ username = logEntry.item.data.user.id;
+ avatarIconColor = theme.euiColorVis9_behindText;
+ failedActionEventTitle = i18.ACTIVITY_LOG.LogEntry.action.failedEndpointReleaseAction;
+ if (logEntry.item.data.EndpointActions.data) {
+ const data = logEntry.item.data.EndpointActions.data;
+ if (data.command === 'isolate') {
+ iconType = 'lock';
+ failedActionEventTitle = i18.ACTIVITY_LOG.LogEntry.action.failedEndpointIsolateAction;
+ }
+ if (commentText) {
+ displayComment = true;
+ }
+ }
+ } else if (logEntry.type === ActivityLogItemTypes.FLEET_RESPONSE) {
isResponseEvent = true;
if (logEntry.item.data.action_data.command === 'isolate') {
isIsolateAction = true;
}
if (!!logEntry.item.data.completed_at && !logEntry.item.data.error) {
isSuccessful = true;
+ } else {
+ avatarColor = theme.euiColorVis9_behindText;
+ }
+ } else if (logEntry.type === ActivityLogItemTypes.RESPONSE) {
+ iconType = 'check';
+ isResponseEvent = true;
+ if (logEntry.item.data.EndpointActions.data.command === 'isolate') {
+ isIsolateAction = true;
+ }
+ if (logEntry.item.data.EndpointActions.completed_at) {
+ isCompleted = true;
+ if (!logEntry.item.data.error) {
+ isSuccessful = true;
+ avatarColor = theme.euiColorVis0_behindText;
+ } else {
+ isSuccessful = false;
+ avatarColor = theme.euiColorVis9_behindText;
+ }
}
}
@@ -75,13 +127,23 @@ const useLogEntryUIProps = (
const getResponseEventTitle = () => {
if (isIsolateAction) {
- if (isSuccessful) {
+ if (isCompleted) {
+ if (isSuccessful) {
+ return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndSuccessful;
+ }
+ return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndUnsuccessful;
+ } else if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.isolationSuccessful;
} else {
return i18.ACTIVITY_LOG.LogEntry.response.isolationFailed;
}
} else {
- if (isSuccessful) {
+ if (isCompleted) {
+ if (isSuccessful) {
+ return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndSuccessful;
+ }
+ return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndUnsuccessful;
+ } else if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationSuccessful;
} else {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationFailed;
@@ -91,18 +153,22 @@ const useLogEntryUIProps = (
return {
actionEventTitle,
+ avatarColor,
+ avatarIconColor,
avatarSize,
commentText,
commentType,
displayComment,
displayResponseEvent,
+ failedActionEventTitle,
iconType,
isResponseEvent,
isSuccessful,
+ isCompleted,
responseEventTitle: getResponseEventTitle(),
username,
};
- }, [logEntry]);
+ }, [logEntry, theme]);
};
const StyledEuiComment = styled(EuiComment)`
@@ -126,28 +192,41 @@ const StyledEuiComment = styled(EuiComment)`
`;
export const LogEntry = memo(({ logEntry }: { logEntry: Immutable }) => {
+ const theme = useEuiTheme();
const {
actionEventTitle,
+ avatarColor,
+ avatarIconColor,
avatarSize,
commentText,
commentType,
displayComment,
displayResponseEvent,
+ failedActionEventTitle,
iconType,
isResponseEvent,
- isSuccessful,
responseEventTitle,
username,
- } = useLogEntryUIProps(logEntry);
+ } = useLogEntryUIProps(logEntry, theme);
return (
}
- event={{displayResponseEvent ? responseEventTitle : actionEventTitle}}
+ event={
+
+ {displayResponseEvent
+ ? responseEventTitle
+ : failedActionEventTitle
+ ? failedActionEventTitle
+ : actionEventTitle}
+
+ }
timelineIcon={
-
+
}
data-test-subj="timelineEntry"
>
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry_timeline_icon.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry_timeline_icon.tsx
index 3ff311cd8a139..25e7c7d2c4a49 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry_timeline_icon.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry_timeline_icon.tsx
@@ -7,32 +7,27 @@
import React, { memo } from 'react';
import { EuiAvatar, EuiAvatarProps } from '@elastic/eui';
-import { useEuiTheme } from '../../../../../../common/lib/theme/use_eui_theme';
export const LogEntryTimelineIcon = memo(
({
+ avatarColor,
+ avatarIconColor,
avatarSize,
- isResponseEvent,
- isSuccessful,
iconType,
+ isResponseEvent,
}: {
+ avatarColor: EuiAvatarProps['color'];
+ avatarIconColor?: EuiAvatarProps['iconColor'];
avatarSize: EuiAvatarProps['size'];
- isResponseEvent: boolean;
- isSuccessful: boolean;
iconType: EuiAvatarProps['iconType'];
+ isResponseEvent: boolean;
}) => {
- const euiTheme = useEuiTheme();
-
return (
);
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoints.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoints.stories.tsx
index 123a51e5a52bd..717368a1ff3a0 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoints.stories.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoints.stories.tsx
@@ -8,7 +8,11 @@
import React, { ComponentType } from 'react';
import moment from 'moment';
-import { ActivityLog, Immutable } from '../../../../../../common/endpoint/types';
+import {
+ ActivityLog,
+ Immutable,
+ ActivityLogItemTypes,
+} from '../../../../../../common/endpoint/types';
import { EndpointDetailsFlyoutTabs } from './components/endpoint_details_tabs';
import { EndpointActivityLog } from './endpoint_activity_log';
import { EndpointDetailsFlyout } from '.';
@@ -26,7 +30,7 @@ export const dummyEndpointActivityLog = (
endDate: moment().toString(),
data: [
{
- type: 'action',
+ type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
@@ -44,7 +48,7 @@ export const dummyEndpointActivityLog = (
},
},
{
- type: 'action',
+ type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
@@ -63,7 +67,7 @@ export const dummyEndpointActivityLog = (
},
},
{
- type: 'action',
+ type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
@@ -82,7 +86,7 @@ export const dummyEndpointActivityLog = (
},
},
{
- type: 'action',
+ type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index b2c438659b771..727c2e8a35024 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -42,6 +42,7 @@ import {
import { getCurrentIsolationRequestState } from '../store/selectors';
import { licenseService } from '../../../../common/hooks/use_license';
import { FleetActionGenerator } from '../../../../../common/endpoint/data_generators/fleet_action_generator';
+import { EndpointActionGenerator } from '../../../../../common/endpoint/data_generators/endpoint_action_generator';
import {
APP_PATH,
MANAGEMENT_PATH,
@@ -807,7 +808,7 @@ describe('when on the endpoint list page', () => {
let renderResult: ReturnType;
const agentId = 'some_agent_id';
- let getMockData: () => ActivityLog;
+ let getMockData: (option?: { hasLogsEndpointActionResponses?: boolean }) => ActivityLog;
beforeEach(async () => {
window.IntersectionObserver = jest.fn(() => ({
root: null,
@@ -828,10 +829,15 @@ describe('when on the endpoint list page', () => {
});
const fleetActionGenerator = new FleetActionGenerator('seed');
- const responseData = fleetActionGenerator.generateResponse({
+ const endpointActionGenerator = new EndpointActionGenerator('seed');
+ const endpointResponseData = endpointActionGenerator.generateResponse({
+ agent: { id: agentId },
+ });
+ const fleetResponseData = fleetActionGenerator.generateResponse({
agent_id: agentId,
});
- const actionData = fleetActionGenerator.generate({
+
+ const fleetActionData = fleetActionGenerator.generate({
agents: [agentId],
data: {
comment: 'some comment',
@@ -844,35 +850,49 @@ describe('when on the endpoint list page', () => {
},
});
- getMockData = () => ({
- page: 1,
- pageSize: 50,
- startDate: 'now-1d',
- endDate: 'now',
- data: [
- {
- type: 'response',
- item: {
- id: 'some_id_0',
- data: responseData,
+ getMockData = (hasLogsEndpointActionResponses?: {
+ hasLogsEndpointActionResponses?: boolean;
+ }) => {
+ const response: ActivityLog = {
+ page: 1,
+ pageSize: 50,
+ startDate: 'now-1d',
+ endDate: 'now',
+ data: [
+ {
+ type: 'fleetResponse',
+ item: {
+ id: 'some_id_1',
+ data: fleetResponseData,
+ },
},
- },
- {
- type: 'action',
- item: {
- id: 'some_id_1',
- data: actionData,
+ {
+ type: 'fleetAction',
+ item: {
+ id: 'some_id_2',
+ data: fleetActionData,
+ },
},
- },
- {
- type: 'action',
+ {
+ type: 'fleetAction',
+ item: {
+ id: 'some_id_3',
+ data: isolatedActionData,
+ },
+ },
+ ],
+ };
+ if (hasLogsEndpointActionResponses) {
+ response.data.unshift({
+ type: 'response',
item: {
- id: 'some_id_3',
- data: isolatedActionData,
+ id: 'some_id_0',
+ data: endpointResponseData,
},
- },
- ],
- });
+ });
+ }
+ return response;
+ };
renderResult = render();
await reactTestingLibrary.act(async () => {
@@ -912,6 +932,25 @@ describe('when on the endpoint list page', () => {
expect(`${logEntries[1]} .euiCommentTimeline__icon--regular`).not.toBe(null);
});
+ it('should display log accurately with endpoint responses', async () => {
+ const activityLogTab = await renderResult.findByTestId('activity_log');
+ reactTestingLibrary.act(() => {
+ reactTestingLibrary.fireEvent.click(activityLogTab);
+ });
+ await middlewareSpy.waitForAction('endpointDetailsActivityLogChanged');
+ reactTestingLibrary.act(() => {
+ dispatchEndpointDetailsActivityLogChanged(
+ 'success',
+ getMockData({ hasLogsEndpointActionResponses: true })
+ );
+ });
+ const logEntries = await renderResult.queryAllByTestId('timelineEntry');
+ expect(logEntries.length).toEqual(4);
+ expect(`${logEntries[0]} .euiCommentTimeline__icon--update`).not.toBe(null);
+ expect(`${logEntries[1]} .euiCommentTimeline__icon--update`).not.toBe(null);
+ expect(`${logEntries[2]} .euiCommentTimeline__icon--regular`).not.toBe(null);
+ });
+
it('should display empty state when API call has failed', async () => {
const activityLogTab = await renderResult.findByTestId('activity_log');
reactTestingLibrary.act(() => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts
index c8a29eed3fda7..9cd55a70005ec 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts
@@ -56,8 +56,44 @@ export const ACTIVITY_LOG = {
defaultMessage: 'submitted request: Release host',
}
),
+ failedEndpointReleaseAction: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.action.failedEndpointReleaseAction',
+ {
+ defaultMessage: 'failed to submit request: Release host',
+ }
+ ),
+ failedEndpointIsolateAction: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.action.failedEndpointIsolateAction',
+ {
+ defaultMessage: 'failed to submit request: Isolate host',
+ }
+ ),
},
response: {
+ isolationCompletedAndSuccessful: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.response.isolationCompletedAndSuccessful',
+ {
+ defaultMessage: 'Host isolation request completed by Endpoint',
+ }
+ ),
+ isolationCompletedAndUnsuccessful: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.response.isolationCompletedAndUnsuccessful',
+ {
+ defaultMessage: 'Host isolation request completed by Endpoint with errors',
+ }
+ ),
+ unisolationCompletedAndSuccessful: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.response.unisolationCompletedAndSuccessful',
+ {
+ defaultMessage: 'Release request completed by Endpoint',
+ }
+ ),
+ unisolationCompletedAndUnsuccessful: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.response.unisolationCompletedAndUnsuccessful',
+ {
+ defaultMessage: 'Release request completed by Endpoint with errors',
+ }
+ ),
isolationSuccessful: i18n.translate(
'xpack.securitySolution.endpointDetails.activityLog.logEntry.response.isolationSuccessful',
{
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts
index 4bd63c83169e5..5ce7962000788 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts
@@ -30,9 +30,15 @@ import {
} from '../../mocks';
import { registerActionAuditLogRoutes } from './audit_log';
import uuid from 'uuid';
-import { aMockAction, aMockResponse, MockAction, mockSearchResult, MockResponse } from './mocks';
+import { mockAuditLogSearchResult, Results } from './mocks';
import { SecuritySolutionRequestHandlerContext } from '../../../types';
-import { ActivityLog } from '../../../../common/endpoint/types';
+import {
+ ActivityLog,
+ EndpointAction,
+ EndpointActionResponse,
+} from '../../../../common/endpoint/types';
+import { FleetActionGenerator } from '../../../../common/endpoint/data_generators/fleet_action_generator';
+import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator';
describe('Action Log API', () => {
describe('schema', () => {
@@ -93,17 +99,30 @@ describe('Action Log API', () => {
});
describe('response', () => {
- const mockID = 'XYZABC-000';
- const actionID = 'some-known-actionid';
+ const mockAgentID = 'XYZABC-000';
let endpointAppContextService: EndpointAppContextService;
+ const fleetActionGenerator = new FleetActionGenerator('seed');
+ const endpointActionGenerator = new EndpointActionGenerator('seed');
// convenience for calling the route and handler for audit log
let getActivityLog: (
params: EndpointActionLogRequestParams,
query?: EndpointActionLogRequestQuery
) => Promise>;
- // convenience for injecting mock responses for actions index and responses
- let havingActionsAndResponses: (actions: MockAction[], responses: MockResponse[]) => void;
+
+ // convenience for injecting mock action requests and responses
+ // for .logs-endpoint and .fleet indices
+ let mockActions: ({
+ numActions,
+ hasFleetActions,
+ hasFleetResponses,
+ hasResponses,
+ }: {
+ numActions: number;
+ hasFleetActions?: boolean;
+ hasFleetResponses?: boolean;
+ hasResponses?: boolean;
+ }) => void;
let havingErrors: () => void;
@@ -149,12 +168,113 @@ describe('Action Log API', () => {
return mockResponse;
};
- havingActionsAndResponses = (actions: MockAction[], responses: MockResponse[]) => {
- esClientMock.asCurrentUser.search = jest.fn().mockImplementation((req) => {
- const items: any[] =
- req.index === '.fleet-actions' ? actions.splice(0, 50) : responses.splice(0, 1000);
+ // some arbitrary ids for needed actions
+ const getMockActionIds = (numAction: number): string[] => {
+ return [...Array(numAction).keys()].map(() => Math.random().toString(36).split('.')[1]);
+ };
+
+ // create as many actions as needed
+ const getEndpointActionsData = (actionIds: string[]) => {
+ const data = actionIds.map((actionId) =>
+ endpointActionGenerator.generate({
+ agent: { id: mockAgentID },
+ EndpointActions: {
+ action_id: actionId,
+ },
+ })
+ );
+ return data;
+ };
+ // create as many responses as needed
+ const getEndpointResponseData = (actionIds: string[]) => {
+ const data = actionIds.map((actionId) =>
+ endpointActionGenerator.generateResponse({
+ agent: { id: mockAgentID },
+ EndpointActions: {
+ action_id: actionId,
+ },
+ })
+ );
+ return data;
+ };
+ // create as many fleet actions as needed
+ const getFleetResponseData = (actionIds: string[]) => {
+ const data = actionIds.map((actionId) =>
+ fleetActionGenerator.generateResponse({
+ agent_id: mockAgentID,
+ action_id: actionId,
+ })
+ );
+ return data;
+ };
+ // create as many fleet responses as needed
+ const getFleetActionData = (actionIds: string[]) => {
+ const data = actionIds.map((actionId) =>
+ fleetActionGenerator.generate({
+ agents: [mockAgentID],
+ action_id: actionId,
+ data: {
+ comment: 'some comment',
+ },
+ })
+ );
+ return data;
+ };
+
+ // mock actions and responses results in a single response
+ mockActions = ({
+ numActions,
+ hasFleetActions = false,
+ hasFleetResponses = false,
+ hasResponses = false,
+ }: {
+ numActions: number;
+ hasFleetActions?: boolean;
+ hasFleetResponses?: boolean;
+ hasResponses?: boolean;
+ }) => {
+ esClientMock.asCurrentUser.search = jest.fn().mockImplementationOnce(() => {
+ let actions: Results[] = [];
+ let fleetActions: Results[] = [];
+ let responses: Results[] = [];
+ let fleetResponses: Results[] = [];
+
+ const actionIds = getMockActionIds(numActions);
+
+ actions = getEndpointActionsData(actionIds).map((e) => ({
+ _index: '.ds-.logs-endpoint.actions-default-2021.19.10-000001',
+ _source: e,
+ }));
+
+ if (hasFleetActions) {
+ fleetActions = getFleetActionData(actionIds).map((e) => ({
+ _index: '.fleet-actions-7',
+ _source: e,
+ }));
+ }
- return Promise.resolve(mockSearchResult(items.map((x) => x.build())));
+ if (hasFleetResponses) {
+ fleetResponses = getFleetResponseData(actionIds).map((e) => ({
+ _index: '.ds-.fleet-actions-results-2021.19.10-000001',
+ _source: e,
+ }));
+ }
+
+ if (hasResponses) {
+ responses = getEndpointResponseData(actionIds).map((e) => ({
+ _index: '.ds-.logs-endpoint.action.responses-default-2021.19.10-000001',
+ _source: e,
+ }));
+ }
+
+ const results = mockAuditLogSearchResult([
+ ...actions,
+ ...fleetActions,
+ ...responses,
+ ...fleetResponses,
+ ]);
+
+ return Promise.resolve(results);
});
};
@@ -172,45 +292,80 @@ describe('Action Log API', () => {
});
it('should return an empty array when nothing in audit log', async () => {
- havingActionsAndResponses([], []);
- const response = await getActivityLog({ agent_id: mockID });
+ mockActions({ numActions: 0 });
+
+ const response = await getActivityLog({ agent_id: mockAgentID });
expect(response.ok).toBeCalled();
expect((response.ok.mock.calls[0][0]?.body as ActivityLog).data).toHaveLength(0);
});
- it('should have actions and action responses', async () => {
- havingActionsAndResponses(
- [
- aMockAction().withAgent(mockID).withAction('isolate').withID(actionID),
- aMockAction().withAgent(mockID).withAction('unisolate'),
- ],
- [aMockResponse(actionID, mockID).forAction(actionID).forAgent(mockID)]
- );
- const response = await getActivityLog({ agent_id: mockID });
+ it('should return fleet actions, fleet responses and endpoint responses', async () => {
+ mockActions({
+ numActions: 2,
+ hasFleetActions: true,
+ hasFleetResponses: true,
+ hasResponses: true,
+ });
+
+ const response = await getActivityLog({ agent_id: mockAgentID });
+ const responseBody = response.ok.mock.calls[0][0]?.body as ActivityLog;
+ expect(response.ok).toBeCalled();
+ expect(responseBody.data).toHaveLength(6);
+
+ expect(
+ responseBody.data.filter((e) => (e.item.data as EndpointActionResponse).completed_at)
+ ).toHaveLength(2);
+ expect(
+ responseBody.data.filter((e) => (e.item.data as EndpointAction).expiration)
+ ).toHaveLength(2);
+ });
+
+ it('should return only fleet actions and no responses', async () => {
+ mockActions({ numActions: 2, hasFleetActions: true });
+
+ const response = await getActivityLog({ agent_id: mockAgentID });
const responseBody = response.ok.mock.calls[0][0]?.body as ActivityLog;
+ expect(response.ok).toBeCalled();
+ expect(responseBody.data).toHaveLength(2);
+
+ expect(
+ responseBody.data.filter((e) => (e.item.data as EndpointAction).expiration)
+ ).toHaveLength(2);
+ });
+
+ it('should only have fleet data', async () => {
+ mockActions({ numActions: 2, hasFleetActions: true, hasFleetResponses: true });
+ const response = await getActivityLog({ agent_id: mockAgentID });
+ const responseBody = response.ok.mock.calls[0][0]?.body as ActivityLog;
expect(response.ok).toBeCalled();
- expect(responseBody.data).toHaveLength(3);
- expect(responseBody.data.filter((e) => e.type === 'response')).toHaveLength(1);
- expect(responseBody.data.filter((e) => e.type === 'action')).toHaveLength(2);
+ expect(responseBody.data).toHaveLength(4);
+
+ expect(
+ responseBody.data.filter((e) => (e.item.data as EndpointAction).expiration)
+ ).toHaveLength(2);
+ expect(
+ responseBody.data.filter((e) => (e.item.data as EndpointActionResponse).completed_at)
+ ).toHaveLength(2);
});
it('should throw errors when no results for some agentID', async () => {
havingErrors();
try {
- await getActivityLog({ agent_id: mockID });
+ await getActivityLog({ agent_id: mockAgentID });
} catch (error) {
- expect(error.message).toEqual(`Error fetching actions log for agent_id ${mockID}`);
+ expect(error.message).toEqual(`Error fetching actions log for agent_id ${mockAgentID}`);
}
});
it('should return date ranges if present in the query', async () => {
- havingActionsAndResponses([], []);
+ mockActions({ numActions: 0 });
+
const startDate = new Date(new Date().setDate(new Date().getDate() - 1)).toISOString();
const endDate = new Date().toISOString();
const response = await getActivityLog(
- { agent_id: mockID },
+ { agent_id: mockAgentID },
{
page: 1,
page_size: 50,
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
index e12299bedbb34..02f0cb4867646 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
@@ -17,6 +17,7 @@ import {
ENDPOINT_ACTION_RESPONSES_DS,
ISOLATE_HOST_ROUTE,
UNISOLATE_HOST_ROUTE,
+ failedFleetActionErrorCode,
} from '../../../../common/endpoint/constants';
import { AGENT_ACTIONS_INDEX } from '../../../../../fleet/common';
import {
@@ -33,6 +34,7 @@ import { getMetadataForEndpoints } from '../../services';
import { EndpointAppContext } from '../../types';
import { APP_ID } from '../../../../common/constants';
import { userCanIsolate } from '../../../../common/endpoint/actions';
+import { doLogsEndpointActionDsExists } from '../../utils';
/**
* Registers the Host-(un-)isolation routes
@@ -78,7 +80,7 @@ const createFailedActionResponseEntry = async ({
body: {
...doc,
error: {
- code: '424',
+ code: failedFleetActionErrorCode,
message: 'Failed to deliver action request to fleet',
},
},
@@ -88,31 +90,6 @@ const createFailedActionResponseEntry = async ({
}
};
-const doLogsEndpointActionDsExists = async ({
- context,
- logger,
- dataStreamName,
-}: {
- context: SecuritySolutionRequestHandlerContext;
- logger: Logger;
- dataStreamName: string;
-}): Promise => {
- try {
- const esClient = context.core.elasticsearch.client.asInternalUser;
- const doesIndexTemplateExist = await esClient.indices.existsIndexTemplate({
- name: dataStreamName,
- });
- return doesIndexTemplateExist.statusCode === 404 ? false : true;
- } catch (error) {
- const errorType = error?.type ?? '';
- if (errorType !== 'resource_not_found_exception') {
- logger.error(error);
- throw error;
- }
- return false;
- }
-};
-
export const isolationRequestHandler = function (
endpointContext: EndpointAppContext,
isolate: boolean
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts
index 34f7d140a78de..b50d80a9bae71 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts
@@ -13,11 +13,43 @@ import { ApiResponse } from '@elastic/elasticsearch';
import moment from 'moment';
import uuid from 'uuid';
import {
+ LogsEndpointAction,
+ LogsEndpointActionResponse,
EndpointAction,
EndpointActionResponse,
ISOLATION_ACTIONS,
} from '../../../../common/endpoint/types';
+export interface Results {
+ _index: string;
+ _source:
+ | LogsEndpointAction
+ | LogsEndpointActionResponse
+ | EndpointAction
+ | EndpointActionResponse;
+}
+export const mockAuditLogSearchResult = (results?: Results[]) => {
+ const response = {
+ body: {
+ hits: {
+ total: { value: results?.length ?? 0, relation: 'eq' },
+ hits:
+ results?.map((a: Results) => ({
+ _index: a._index,
+ _id: Math.random().toString(36).split('.')[1],
+ _score: 0.0,
+ _source: a._source,
+ })) ?? [],
+ },
+ },
+ statusCode: 200,
+ headers: {},
+ warnings: [],
+ meta: {} as any,
+ };
+ return response;
+};
+
export const mockSearchResult = (results: any = []): ApiResponse => {
return {
body: {
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions.ts
index 711d78ba51b59..d59ecb674196c 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions.ts
@@ -6,15 +6,28 @@
*/
import { ElasticsearchClient, Logger } from 'kibana/server';
+import { SearchHit, SearchResponse } from '@elastic/elasticsearch/api/types';
+import { ApiResponse } from '@elastic/elasticsearch';
import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '../../../../fleet/common';
import { SecuritySolutionRequestHandlerContext } from '../../types';
import {
ActivityLog,
+ ActivityLogEntry,
EndpointAction,
+ LogsEndpointAction,
EndpointActionResponse,
EndpointPendingActions,
+ LogsEndpointActionResponse,
} from '../../../common/endpoint/types';
-import { catchAndWrapError } from '../utils';
+import {
+ catchAndWrapError,
+ categorizeActionResults,
+ categorizeResponseResults,
+ getActionRequestsResult,
+ getActionResponsesResult,
+ getTimeSortedData,
+ getUniqueLogData,
+} from '../utils';
import { EndpointMetadataService } from './metadata';
const PENDING_ACTION_RESPONSE_MAX_LAPSED_TIME = 300000; // 300k ms === 5 minutes
@@ -38,9 +51,9 @@ export const getAuditLogResponse = async ({
}): Promise => {
const size = Math.floor(pageSize / 2);
const from = page <= 1 ? 0 : page * size - size + 1;
- const esClient = context.core.elasticsearch.client.asCurrentUser;
+
const data = await getActivityLog({
- esClient,
+ context,
from,
size,
startDate,
@@ -59,7 +72,7 @@ export const getAuditLogResponse = async ({
};
const getActivityLog = async ({
- esClient,
+ context,
size,
from,
startDate,
@@ -67,83 +80,39 @@ const getActivityLog = async ({
elasticAgentId,
logger,
}: {
- esClient: ElasticsearchClient;
+ context: SecuritySolutionRequestHandlerContext;
elasticAgentId: string;
size: number;
from: number;
startDate: string;
endDate: string;
logger: Logger;
-}) => {
- const options = {
- headers: {
- 'X-elastic-product-origin': 'fleet',
- },
- ignore: [404],
- };
-
- let actionsResult;
- let responsesResult;
- const dateFilters = [
- { range: { '@timestamp': { gte: startDate } } },
- { range: { '@timestamp': { lte: endDate } } },
- ];
+}): Promise => {
+ let actionsResult: ApiResponse, unknown>;
+ let responsesResult: ApiResponse, unknown>;
try {
// fetch actions with matching agent_id
- const baseActionFilters = [
- { term: { agents: elasticAgentId } },
- { term: { input_type: 'endpoint' } },
- { term: { type: 'INPUT_ACTION' } },
- ];
- const actionsFilters = [...baseActionFilters, ...dateFilters];
- actionsResult = await esClient.search(
- {
- index: AGENT_ACTIONS_INDEX,
- size,
- from,
- body: {
- query: {
- bool: {
- // @ts-ignore
- filter: actionsFilters,
- },
- },
- sort: [
- {
- '@timestamp': {
- order: 'desc',
- },
- },
- ],
- },
- },
- options
- );
- const actionIds = actionsResult?.body?.hits?.hits?.map(
- (e) => (e._source as EndpointAction).action_id
- );
+ const { actionIds, actionRequests } = await getActionRequestsResult({
+ context,
+ logger,
+ elasticAgentId,
+ startDate,
+ endDate,
+ size,
+ from,
+ });
+ actionsResult = actionRequests;
- // fetch responses with matching `action_id`s
- const baseResponsesFilter = [
- { term: { agent_id: elasticAgentId } },
- { terms: { action_id: actionIds } },
- ];
- const responsesFilters = [...baseResponsesFilter, ...dateFilters];
- responsesResult = await esClient.search(
- {
- index: AGENT_ACTIONS_RESULTS_INDEX,
- size: 1000,
- body: {
- query: {
- bool: {
- filter: responsesFilters,
- },
- },
- },
- },
- options
- );
+ // fetch responses with matching unique set of `action_id`s
+ responsesResult = await getActionResponsesResult({
+ actionIds: [...new Set(actionIds)], // de-dupe `action_id`s
+ context,
+ logger,
+ elasticAgentId,
+ startDate,
+ endDate,
+ });
} catch (error) {
logger.error(error);
throw error;
@@ -153,21 +122,26 @@ const getActivityLog = async ({
throw new Error(`Error fetching actions log for agent_id ${elasticAgentId}`);
}
- const responses = responsesResult?.body?.hits?.hits?.length
- ? responsesResult?.body?.hits?.hits?.map((e) => ({
- type: 'response',
- item: { id: e._id, data: e._source },
- }))
- : [];
- const actions = actionsResult?.body?.hits?.hits?.length
- ? actionsResult?.body?.hits?.hits?.map((e) => ({
- type: 'action',
- item: { id: e._id, data: e._source },
- }))
- : [];
- const sortedData = ([...responses, ...actions] as ActivityLog['data']).sort((a, b) =>
- new Date(b.item.data['@timestamp']) > new Date(a.item.data['@timestamp']) ? 1 : -1
- );
+ // label record as `action`, `fleetAction`
+ const responses = categorizeResponseResults({
+ results: responsesResult?.body?.hits?.hits as Array<
+ SearchHit
+ >,
+ });
+
+ // label record as `response`, `fleetResponse`
+ const actions = categorizeActionResults({
+ results: actionsResult?.body?.hits?.hits as Array<
+ SearchHit
+ >,
+ });
+
+ // filter out the duplicate endpoint actions that also have fleetActions
+ // include endpoint actions that have no fleet actions
+ const uniqueLogData = getUniqueLogData([...responses, ...actions]);
+
+ // sort by @timestamp in desc order, newest first
+ const sortedData = getTimeSortedData(uniqueLogData);
return sortedData;
};
diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/audit_log_helpers.ts b/x-pack/plugins/security_solution/server/endpoint/utils/audit_log_helpers.ts
new file mode 100644
index 0000000000000..f75b265bf24d7
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/utils/audit_log_helpers.ts
@@ -0,0 +1,266 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Logger } from 'kibana/server';
+import { SearchRequest } from 'src/plugins/data/public';
+import { SearchHit, SearchResponse } from '@elastic/elasticsearch/api/types';
+import { ApiResponse } from '@elastic/elasticsearch';
+import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '../../../../fleet/common';
+import {
+ ENDPOINT_ACTIONS_INDEX,
+ ENDPOINT_ACTION_RESPONSES_INDEX,
+ failedFleetActionErrorCode,
+} from '../../../common/endpoint/constants';
+import { SecuritySolutionRequestHandlerContext } from '../../types';
+import {
+ ActivityLog,
+ ActivityLogAction,
+ EndpointActivityLogAction,
+ ActivityLogActionResponse,
+ EndpointActivityLogActionResponse,
+ ActivityLogItemTypes,
+ EndpointAction,
+ LogsEndpointAction,
+ EndpointActionResponse,
+ LogsEndpointActionResponse,
+ ActivityLogEntry,
+} from '../../../common/endpoint/types';
+import { doesLogsEndpointActionsIndexExist } from '../utils';
+
+const actionsIndices = [AGENT_ACTIONS_INDEX, ENDPOINT_ACTIONS_INDEX];
+const responseIndices = [AGENT_ACTIONS_RESULTS_INDEX, ENDPOINT_ACTION_RESPONSES_INDEX];
+export const logsEndpointActionsRegex = new RegExp(`(^\.ds-\.logs-endpoint\.actions-default-).+`);
+export const logsEndpointResponsesRegex = new RegExp(
+ `(^\.ds-\.logs-endpoint\.action\.responses-default-).+`
+);
+const queryOptions = {
+ headers: {
+ 'X-elastic-product-origin': 'fleet',
+ },
+ ignore: [404],
+};
+
+const getDateFilters = ({ startDate, endDate }: { startDate: string; endDate: string }) => {
+ return [
+ { range: { '@timestamp': { gte: startDate } } },
+ { range: { '@timestamp': { lte: endDate } } },
+ ];
+};
+
+export const getUniqueLogData = (activityLogEntries: ActivityLogEntry[]): ActivityLogEntry[] => {
+ // find the error responses for actions that didn't make it to fleet index
+ const onlyResponsesForFleetErrors = activityLogEntries
+ .filter(
+ (e) =>
+ e.type === ActivityLogItemTypes.RESPONSE &&
+ e.item.data.error?.code === failedFleetActionErrorCode
+ )
+ .map(
+ (e: ActivityLogEntry) => (e.item.data as LogsEndpointActionResponse).EndpointActions.action_id
+ );
+
+ // all actions and responses minus endpoint actions.
+ const nonEndpointActionsDocs = activityLogEntries.filter(
+ (e) => e.type !== ActivityLogItemTypes.ACTION
+ );
+
+ // only endpoint actions that match the error responses
+ const onlyEndpointActionsDocWithoutFleetActions = activityLogEntries
+ .filter((e) => e.type === ActivityLogItemTypes.ACTION)
+ .filter((e: ActivityLogEntry) =>
+ onlyResponsesForFleetErrors.includes(
+ (e.item.data as LogsEndpointAction).EndpointActions.action_id
+ )
+ );
+
+ // join the error actions and the rest
+ return [...nonEndpointActionsDocs, ...onlyEndpointActionsDocWithoutFleetActions];
+};
+
+export const categorizeResponseResults = ({
+ results,
+}: {
+ results: Array>;
+}): Array => {
+ return results?.length
+ ? results?.map((e) => {
+ const isResponseDoc: boolean = matchesIndexPattern({
+ regexPattern: logsEndpointResponsesRegex,
+ index: e._index,
+ });
+ return isResponseDoc
+ ? {
+ type: ActivityLogItemTypes.RESPONSE,
+ item: { id: e._id, data: e._source as LogsEndpointActionResponse },
+ }
+ : {
+ type: ActivityLogItemTypes.FLEET_RESPONSE,
+ item: { id: e._id, data: e._source as EndpointActionResponse },
+ };
+ })
+ : [];
+};
+
+export const categorizeActionResults = ({
+ results,
+}: {
+ results: Array>;
+}): Array => {
+ return results?.length
+ ? results?.map((e) => {
+ const isActionDoc: boolean = matchesIndexPattern({
+ regexPattern: logsEndpointActionsRegex,
+ index: e._index,
+ });
+ return isActionDoc
+ ? {
+ type: ActivityLogItemTypes.ACTION,
+ item: { id: e._id, data: e._source as LogsEndpointAction },
+ }
+ : {
+ type: ActivityLogItemTypes.FLEET_ACTION,
+ item: { id: e._id, data: e._source as EndpointAction },
+ };
+ })
+ : [];
+};
+
+export const getTimeSortedData = (data: ActivityLog['data']): ActivityLog['data'] => {
+ return data.sort((a, b) =>
+ new Date(b.item.data['@timestamp']) > new Date(a.item.data['@timestamp']) ? 1 : -1
+ );
+};
+
+export const getActionRequestsResult = async ({
+ context,
+ logger,
+ elasticAgentId,
+ startDate,
+ endDate,
+ size,
+ from,
+}: {
+ context: SecuritySolutionRequestHandlerContext;
+ logger: Logger;
+ elasticAgentId: string;
+ startDate: string;
+ endDate: string;
+ size: number;
+ from: number;
+}): Promise<{
+ actionIds: string[];
+ actionRequests: ApiResponse, unknown>;
+}> => {
+ const dateFilters = getDateFilters({ startDate, endDate });
+ const baseActionFilters = [
+ { term: { agents: elasticAgentId } },
+ { term: { input_type: 'endpoint' } },
+ { term: { type: 'INPUT_ACTION' } },
+ ];
+ const actionsFilters = [...baseActionFilters, ...dateFilters];
+
+ const hasLogsEndpointActionsIndex = await doesLogsEndpointActionsIndexExist({
+ context,
+ logger,
+ indexName: ENDPOINT_ACTIONS_INDEX,
+ });
+
+ const actionsSearchQuery: SearchRequest = {
+ index: hasLogsEndpointActionsIndex ? actionsIndices : AGENT_ACTIONS_INDEX,
+ size,
+ from,
+ body: {
+ query: {
+ bool: {
+ filter: actionsFilters,
+ },
+ },
+ sort: [
+ {
+ '@timestamp': {
+ order: 'desc',
+ },
+ },
+ ],
+ },
+ };
+
+ let actionRequests: ApiResponse, unknown>;
+ try {
+ const esClient = context.core.elasticsearch.client.asCurrentUser;
+ actionRequests = await esClient.search(actionsSearchQuery, queryOptions);
+ const actionIds = actionRequests?.body?.hits?.hits?.map((e) => {
+ return logsEndpointActionsRegex.test(e._index)
+ ? (e._source as LogsEndpointAction).EndpointActions.action_id
+ : (e._source as EndpointAction).action_id;
+ });
+
+ return { actionIds, actionRequests };
+ } catch (error) {
+ logger.error(error);
+ throw error;
+ }
+};
+
+export const getActionResponsesResult = async ({
+ context,
+ logger,
+ elasticAgentId,
+ actionIds,
+ startDate,
+ endDate,
+}: {
+ context: SecuritySolutionRequestHandlerContext;
+ logger: Logger;
+ elasticAgentId: string;
+ actionIds: string[];
+ startDate: string;
+ endDate: string;
+}): Promise, unknown>> => {
+ const dateFilters = getDateFilters({ startDate, endDate });
+ const baseResponsesFilter = [
+ { term: { agent_id: elasticAgentId } },
+ { terms: { action_id: actionIds } },
+ ];
+ const responsesFilters = [...baseResponsesFilter, ...dateFilters];
+
+ const hasLogsEndpointActionResponsesIndex = await doesLogsEndpointActionsIndexExist({
+ context,
+ logger,
+ indexName: ENDPOINT_ACTION_RESPONSES_INDEX,
+ });
+
+ const responsesSearchQuery: SearchRequest = {
+ index: hasLogsEndpointActionResponsesIndex ? responseIndices : AGENT_ACTIONS_RESULTS_INDEX,
+ size: 1000,
+ body: {
+ query: {
+ bool: {
+ filter: responsesFilters,
+ },
+ },
+ },
+ };
+
+ let actionResponses: ApiResponse, unknown>;
+ try {
+ const esClient = context.core.elasticsearch.client.asCurrentUser;
+ actionResponses = await esClient.search(responsesSearchQuery, queryOptions);
+ } catch (error) {
+ logger.error(error);
+ throw error;
+ }
+ return actionResponses;
+};
+
+const matchesIndexPattern = ({
+ regexPattern,
+ index,
+}: {
+ regexPattern: RegExp;
+ index: string;
+}): boolean => regexPattern.test(index);
diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/index.ts b/x-pack/plugins/security_solution/server/endpoint/utils/index.ts
index 34cabf79aff0e..6c40073f8c654 100644
--- a/x-pack/plugins/security_solution/server/endpoint/utils/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/utils/index.ts
@@ -7,3 +7,5 @@
export * from './fleet_agent_status_to_endpoint_host_status';
export * from './wrap_errors';
+export * from './audit_log_helpers';
+export * from './yes_no_data_stream';
diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/yes_no_data_stream.test.ts b/x-pack/plugins/security_solution/server/endpoint/utils/yes_no_data_stream.test.ts
new file mode 100644
index 0000000000000..d2894c8c64c14
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/utils/yes_no_data_stream.test.ts
@@ -0,0 +1,100 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import {
+ elasticsearchServiceMock,
+ savedObjectsClientMock,
+ loggingSystemMock,
+} from 'src/core/server/mocks';
+import { SecuritySolutionRequestHandlerContext } from '../../types';
+import { createRouteHandlerContext } from '../mocks';
+import {
+ doLogsEndpointActionDsExists,
+ doesLogsEndpointActionsIndexExist,
+} from './yes_no_data_stream';
+
+describe('Accurately answers if index template for data stream exists', () => {
+ let ctxt: jest.Mocked;
+
+ beforeEach(() => {
+ ctxt = createRouteHandlerContext(
+ elasticsearchServiceMock.createScopedClusterClient(),
+ savedObjectsClientMock.create()
+ );
+ });
+
+ const mockEsApiResponse = (response: { body: boolean; statusCode: number }) => {
+ return jest.fn().mockImplementationOnce(() => Promise.resolve(response));
+ };
+
+ it('Returns FALSE for a non-existent data stream index template', async () => {
+ ctxt.core.elasticsearch.client.asInternalUser.indices.existsIndexTemplate = mockEsApiResponse({
+ body: false,
+ statusCode: 404,
+ });
+ const doesItExist = await doLogsEndpointActionDsExists({
+ context: ctxt,
+ logger: loggingSystemMock.create().get('host-isolation'),
+ dataStreamName: '.test-stream.name',
+ });
+ expect(doesItExist).toBeFalsy();
+ });
+
+ it('Returns TRUE for an existing index', async () => {
+ ctxt.core.elasticsearch.client.asInternalUser.indices.existsIndexTemplate = mockEsApiResponse({
+ body: true,
+ statusCode: 200,
+ });
+ const doesItExist = await doLogsEndpointActionDsExists({
+ context: ctxt,
+ logger: loggingSystemMock.create().get('host-isolation'),
+ dataStreamName: '.test-stream.name',
+ });
+ expect(doesItExist).toBeTruthy();
+ });
+});
+
+describe('Accurately answers if index exists', () => {
+ let ctxt: jest.Mocked;
+
+ beforeEach(() => {
+ ctxt = createRouteHandlerContext(
+ elasticsearchServiceMock.createScopedClusterClient(),
+ savedObjectsClientMock.create()
+ );
+ });
+
+ const mockEsApiResponse = (response: { body: boolean; statusCode: number }) => {
+ return jest.fn().mockImplementationOnce(() => Promise.resolve(response));
+ };
+
+ it('Returns FALSE for a non-existent index', async () => {
+ ctxt.core.elasticsearch.client.asInternalUser.indices.exists = mockEsApiResponse({
+ body: false,
+ statusCode: 404,
+ });
+ const doesItExist = await doesLogsEndpointActionsIndexExist({
+ context: ctxt,
+ logger: loggingSystemMock.create().get('host-isolation'),
+ indexName: '.test-index.name-default',
+ });
+ expect(doesItExist).toBeFalsy();
+ });
+
+ it('Returns TRUE for an existing index', async () => {
+ ctxt.core.elasticsearch.client.asInternalUser.indices.exists = mockEsApiResponse({
+ body: true,
+ statusCode: 200,
+ });
+ const doesItExist = await doesLogsEndpointActionsIndexExist({
+ context: ctxt,
+ logger: loggingSystemMock.create().get('host-isolation'),
+ indexName: '.test-index.name-default',
+ });
+ expect(doesItExist).toBeTruthy();
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/yes_no_data_stream.ts b/x-pack/plugins/security_solution/server/endpoint/utils/yes_no_data_stream.ts
new file mode 100644
index 0000000000000..dea2e46c3c258
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/utils/yes_no_data_stream.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Logger } from 'src/core/server';
+import { SecuritySolutionRequestHandlerContext } from '../../types';
+
+export const doLogsEndpointActionDsExists = async ({
+ context,
+ logger,
+ dataStreamName,
+}: {
+ context: SecuritySolutionRequestHandlerContext;
+ logger: Logger;
+ dataStreamName: string;
+}): Promise => {
+ try {
+ const esClient = context.core.elasticsearch.client.asInternalUser;
+ const doesIndexTemplateExist = await esClient.indices.existsIndexTemplate({
+ name: dataStreamName,
+ });
+ return doesIndexTemplateExist.statusCode === 404 ? false : true;
+ } catch (error) {
+ const errorType = error?.type ?? '';
+ if (errorType !== 'resource_not_found_exception') {
+ logger.error(error);
+ throw error;
+ }
+ return false;
+ }
+};
+
+export const doesLogsEndpointActionsIndexExist = async ({
+ context,
+ logger,
+ indexName,
+}: {
+ context: SecuritySolutionRequestHandlerContext;
+ logger: Logger;
+ indexName: string;
+}): Promise => {
+ try {
+ const esClient = context.core.elasticsearch.client.asInternalUser;
+ const doesIndexExist = await esClient.indices.exists({
+ index: indexName,
+ });
+ return doesIndexExist.statusCode === 404 ? false : true;
+ } catch (error) {
+ const errorType = error?.type ?? '';
+ if (errorType !== 'index_not_found_exception') {
+ logger.error(error);
+ throw error;
+ }
+ return false;
+ }
+};
From ec3809658fdc0850106c6e2672ae46c3f6621d96 Mon Sep 17 00:00:00 2001
From: Caroline Horn <549577+cchaos@users.noreply.github.com>
Date: Mon, 18 Oct 2021 23:56:00 -0400
Subject: [PATCH 027/204] [Unified Integrations] Clean up empty states,
tutorial links and routing to prefer unified integrations (#114911)
Cleans up the integrations view and redirects all links to the integration manager.
---
.../chrome/ui/header/collapsible_nav.tsx | 2 +-
.../__snapshots__/add_data.test.tsx.snap | 16 +-
.../components/add_data/add_data.test.tsx | 4 +-
.../components/add_data/add_data.tsx | 152 +++++++++---------
.../components/sample_data/index.tsx | 4 +-
.../components/tutorial_directory.js | 56 +------
.../public/application/components/welcome.tsx | 3 +-
src/plugins/home/public/index.ts | 1 -
src/plugins/home/public/services/index.ts | 1 -
.../home/public/services/tutorials/index.ts | 1 -
.../tutorials/tutorial_service.mock.ts | 1 -
.../tutorials/tutorial_service.test.tsx | 32 ----
.../services/tutorials/tutorial_service.ts | 18 ---
.../empty_index_list_prompt.tsx | 2 +-
.../__snapshots__/overview.test.tsx.snap | 110 +++----------
.../public/components/overview/overview.tsx | 12 +-
.../public/assets/elastic_beats_card_dark.svg | 1 -
.../assets/elastic_beats_card_light.svg | 1 -
.../__snapshots__/no_data_page.test.tsx.snap | 4 +-
.../elastic_agent_card.test.tsx.snap | 55 ++++++-
.../elastic_beats_card.test.tsx.snap | 70 --------
.../no_data_card/elastic_agent_card.test.tsx | 10 +-
.../no_data_card/elastic_agent_card.tsx | 44 ++++-
.../no_data_card/elastic_beats_card.test.tsx | 45 ------
.../no_data_card/elastic_beats_card.tsx | 66 --------
.../no_data_page/no_data_card/index.ts | 1 -
.../no_data_page/no_data_page.tsx | 14 +-
.../components/app/RumDashboard/RumHome.tsx | 8 +-
.../routing/templates/no_data_config.ts | 10 +-
.../epm/components/package_list_grid.tsx | 2 +-
.../components/home_integration/index.tsx | 8 -
.../tutorial_directory_header_link.tsx | 16 +-
.../tutorial_directory_notice.tsx | 147 -----------------
x-pack/plugins/fleet/public/plugin.ts | 7 +-
.../infra/public/pages/logs/page_content.tsx | 2 +-
.../infra/public/pages/logs/page_template.tsx | 6 +-
.../logs/stream/page_no_indices_content.tsx | 4 +-
.../infra/public/pages/metrics/index.tsx | 4 +-
.../metric_detail/components/invalid_node.tsx | 4 +-
.../public/pages/metrics/page_template.tsx | 9 +-
.../components/app/header/header_menu.tsx | 2 +-
.../public/utils/no_data_config.ts | 7 +-
.../security_solution/common/constants.ts | 2 +-
.../components/overview_empty/index.test.tsx | 12 +-
.../components/overview_empty/index.tsx | 50 ++----
.../translations/translations/ja-JP.json | 11 --
.../translations/translations/zh-CN.json | 11 --
x-pack/test/accessibility/apps/home.ts | 27 ----
48 files changed, 292 insertions(+), 783 deletions(-)
delete mode 100644 src/plugins/kibana_react/public/assets/elastic_beats_card_dark.svg
delete mode 100644 src/plugins/kibana_react/public/assets/elastic_beats_card_light.svg
delete mode 100644 src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/__snapshots__/elastic_beats_card.test.tsx.snap
delete mode 100644 src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_beats_card.test.tsx
delete mode 100644 src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_beats_card.tsx
delete mode 100644 x-pack/plugins/fleet/public/components/home_integration/tutorial_directory_notice.tsx
diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx
index ad590865b9e14..ccc0e17b655b1 100644
--- a/src/core/public/chrome/ui/header/collapsible_nav.tsx
+++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx
@@ -362,7 +362,7 @@ export function CollapsibleNav({
iconType="plusInCircleFilled"
>
{i18n.translate('core.ui.primaryNav.addData', {
- defaultMessage: 'Add data',
+ defaultMessage: 'Add integrations',
})}
diff --git a/src/plugins/home/public/application/components/add_data/__snapshots__/add_data.test.tsx.snap b/src/plugins/home/public/application/components/add_data/__snapshots__/add_data.test.tsx.snap
index 26b5697f008b6..de6beab31247a 100644
--- a/src/plugins/home/public/application/components/add_data/__snapshots__/add_data.test.tsx.snap
+++ b/src/plugins/home/public/application/components/add_data/__snapshots__/add_data.test.tsx.snap
@@ -17,7 +17,7 @@ exports[`AddData render 1`] = `
id="homDataAdd__title"
>
@@ -43,17 +43,25 @@ exports[`AddData render 1`] = `
grow={false}
>
diff --git a/src/plugins/home/public/application/components/add_data/add_data.test.tsx b/src/plugins/home/public/application/components/add_data/add_data.test.tsx
index 4018ae67c19ee..3aa51f89c7d67 100644
--- a/src/plugins/home/public/application/components/add_data/add_data.test.tsx
+++ b/src/plugins/home/public/application/components/add_data/add_data.test.tsx
@@ -27,7 +27,9 @@ beforeEach(() => {
jest.clearAllMocks();
});
-const applicationStartMock = {} as unknown as ApplicationStart;
+const applicationStartMock = {
+ capabilities: { navLinks: { integrations: true } },
+} as unknown as ApplicationStart;
const addBasePathMock = jest.fn((path: string) => (path ? path : 'path'));
diff --git a/src/plugins/home/public/application/components/add_data/add_data.tsx b/src/plugins/home/public/application/components/add_data/add_data.tsx
index 97ba28a04a07e..50d6079dd8df3 100644
--- a/src/plugins/home/public/application/components/add_data/add_data.tsx
+++ b/src/plugins/home/public/application/components/add_data/add_data.tsx
@@ -22,8 +22,6 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { METRIC_TYPE } from '@kbn/analytics';
import { ApplicationStart } from 'kibana/public';
import { createAppNavigationHandler } from '../app_navigation_handler';
-// @ts-expect-error untyped component
-import { Synopsis } from '../synopsis';
import { getServices } from '../../kibana_services';
import { RedirectAppLinks } from '../../../../../kibana_react/public';
@@ -35,87 +33,91 @@ interface Props {
export const AddData: FC = ({ addBasePath, application, isDarkMode }) => {
const { trackUiMetric } = getServices();
+ const canAccessIntegrations = application.capabilities.navLinks.integrations;
+ if (canAccessIntegrations) {
+ return (
+ <>
+
+
+
+
+
+
+
+
- return (
- <>
-
-
-
-
-
-
-
-
+
-
+
+
+
+
+
-
-
-
-
-
+
-
+
+
+
+ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
+ {
+ trackUiMetric(METRIC_TYPE.CLICK, 'home_tutorial_directory');
+ createAppNavigationHandler('/app/integrations/browse')(event);
+ }}
+ >
+
+
+
+
-
-
-
- {/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
- {
- trackUiMetric(METRIC_TYPE.CLICK, 'home_tutorial_directory');
- createAppNavigationHandler('/app/home#/tutorial_directory')(event);
- }}
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
- >
- );
+
+ >
+ );
+ } else {
+ return null;
+ }
};
diff --git a/src/plugins/home/public/application/components/sample_data/index.tsx b/src/plugins/home/public/application/components/sample_data/index.tsx
index d6b9328f57e9b..b65fbb5d002b0 100644
--- a/src/plugins/home/public/application/components/sample_data/index.tsx
+++ b/src/plugins/home/public/application/components/sample_data/index.tsx
@@ -40,7 +40,7 @@ export function SampleDataCard({ urlBasePath, onDecline, onConfirm }: Props) {
image={cardGraphicURL}
textAlign="left"
title={
-
+
}
description={
-
+
{
- const notices = getServices().tutorialService.getDirectoryNotices();
- return notices.length ? (
-
- {notices.map((DirectoryNotice, index) => (
-
-
-
- ))}
-
- ) : null;
- };
-
renderHeaderLinks = () => {
const headerLinks = getServices().tutorialService.getDirectoryHeaderLinks();
return headerLinks.length ? (
@@ -245,7 +203,6 @@ class TutorialDirectoryUi extends React.Component {
render() {
const headerLinks = this.renderHeaderLinks();
const tabs = this.getTabs();
- const notices = this.renderNotices();
return (
+
),
tabs,
rightSideItems: headerLinks ? [headerLinks] : [],
}}
>
- {notices && (
- <>
- {notices}
-
- >
- )}
{this.renderTabContent()}
);
diff --git a/src/plugins/home/public/application/components/welcome.tsx b/src/plugins/home/public/application/components/welcome.tsx
index ca7e6874c75c2..03dff22c7b33f 100644
--- a/src/plugins/home/public/application/components/welcome.tsx
+++ b/src/plugins/home/public/application/components/welcome.tsx
@@ -48,8 +48,7 @@ export class Welcome extends React.Component {
};
private redirecToAddData() {
- const path = this.services.addBasePath('#/tutorial_directory');
- window.location.href = path;
+ this.services.application.navigateToApp('integrations', { path: '/browse' });
}
private onSampleDataDecline = () => {
diff --git a/src/plugins/home/public/index.ts b/src/plugins/home/public/index.ts
index dd02bf65dd8b0..7abaf5d19f008 100644
--- a/src/plugins/home/public/index.ts
+++ b/src/plugins/home/public/index.ts
@@ -23,7 +23,6 @@ export type {
FeatureCatalogueSolution,
Environment,
TutorialVariables,
- TutorialDirectoryNoticeComponent,
TutorialDirectoryHeaderLinkComponent,
TutorialModuleNoticeComponent,
} from './services';
diff --git a/src/plugins/home/public/services/index.ts b/src/plugins/home/public/services/index.ts
index 65913df6310b1..2ee68a9eef0c2 100644
--- a/src/plugins/home/public/services/index.ts
+++ b/src/plugins/home/public/services/index.ts
@@ -22,7 +22,6 @@ export { TutorialService } from './tutorials';
export type {
TutorialVariables,
TutorialServiceSetup,
- TutorialDirectoryNoticeComponent,
TutorialDirectoryHeaderLinkComponent,
TutorialModuleNoticeComponent,
} from './tutorials';
diff --git a/src/plugins/home/public/services/tutorials/index.ts b/src/plugins/home/public/services/tutorials/index.ts
index 8de12c31249d8..e007a5ea4d552 100644
--- a/src/plugins/home/public/services/tutorials/index.ts
+++ b/src/plugins/home/public/services/tutorials/index.ts
@@ -11,7 +11,6 @@ export { TutorialService } from './tutorial_service';
export type {
TutorialVariables,
TutorialServiceSetup,
- TutorialDirectoryNoticeComponent,
TutorialDirectoryHeaderLinkComponent,
TutorialModuleNoticeComponent,
} from './tutorial_service';
diff --git a/src/plugins/home/public/services/tutorials/tutorial_service.mock.ts b/src/plugins/home/public/services/tutorials/tutorial_service.mock.ts
index 0c109d61912ca..ab38a32a1a5b3 100644
--- a/src/plugins/home/public/services/tutorials/tutorial_service.mock.ts
+++ b/src/plugins/home/public/services/tutorials/tutorial_service.mock.ts
@@ -25,7 +25,6 @@ const createMock = (): jest.Mocked> => {
const service = {
setup: jest.fn(),
getVariables: jest.fn(() => ({})),
- getDirectoryNotices: jest.fn(() => []),
getDirectoryHeaderLinks: jest.fn(() => []),
getModuleNotices: jest.fn(() => []),
getCustomStatusCheck: jest.fn(),
diff --git a/src/plugins/home/public/services/tutorials/tutorial_service.test.tsx b/src/plugins/home/public/services/tutorials/tutorial_service.test.tsx
index a88cf526e3716..b90165aafb45f 100644
--- a/src/plugins/home/public/services/tutorials/tutorial_service.test.tsx
+++ b/src/plugins/home/public/services/tutorials/tutorial_service.test.tsx
@@ -27,22 +27,6 @@ describe('TutorialService', () => {
}).toThrow();
});
- test('allows multiple register directory notice calls', () => {
- const setup = new TutorialService().setup();
- expect(() => {
- setup.registerDirectoryNotice('abc', () => );
- setup.registerDirectoryNotice('def', () => );
- }).not.toThrow();
- });
-
- test('throws when same directory notice is registered twice', () => {
- const setup = new TutorialService().setup();
- expect(() => {
- setup.registerDirectoryNotice('abc', () => );
- setup.registerDirectoryNotice('abc', () => );
- }).toThrow();
- });
-
test('allows multiple register directory header link calls', () => {
const setup = new TutorialService().setup();
expect(() => {
@@ -91,22 +75,6 @@ describe('TutorialService', () => {
});
});
- describe('getDirectoryNotices', () => {
- test('returns empty array', () => {
- const service = new TutorialService();
- expect(service.getDirectoryNotices()).toEqual([]);
- });
-
- test('returns last state of register calls', () => {
- const service = new TutorialService();
- const setup = service.setup();
- const notices = [() => , () => ];
- setup.registerDirectoryNotice('abc', notices[0]);
- setup.registerDirectoryNotice('def', notices[1]);
- expect(service.getDirectoryNotices()).toEqual(notices);
- });
- });
-
describe('getDirectoryHeaderLinks', () => {
test('returns empty array', () => {
const service = new TutorialService();
diff --git a/src/plugins/home/public/services/tutorials/tutorial_service.ts b/src/plugins/home/public/services/tutorials/tutorial_service.ts
index 839b0702a499e..81b6bbe72e3e9 100644
--- a/src/plugins/home/public/services/tutorials/tutorial_service.ts
+++ b/src/plugins/home/public/services/tutorials/tutorial_service.ts
@@ -11,9 +11,6 @@ import React from 'react';
/** @public */
export type TutorialVariables = Partial>;
-/** @public */
-export type TutorialDirectoryNoticeComponent = React.FC;
-
/** @public */
export type TutorialDirectoryHeaderLinkComponent = React.FC;
@@ -27,7 +24,6 @@ type CustomComponent = () => Promise;
export class TutorialService {
private tutorialVariables: TutorialVariables = {};
- private tutorialDirectoryNotices: { [key: string]: TutorialDirectoryNoticeComponent } = {};
private tutorialDirectoryHeaderLinks: {
[key: string]: TutorialDirectoryHeaderLinkComponent;
} = {};
@@ -47,16 +43,6 @@ export class TutorialService {
this.tutorialVariables[key] = value;
},
- /**
- * Registers a component that will be rendered at the top of tutorial directory page.
- */
- registerDirectoryNotice: (id: string, component: TutorialDirectoryNoticeComponent) => {
- if (this.tutorialDirectoryNotices[id]) {
- throw new Error(`directory notice ${id} already set`);
- }
- this.tutorialDirectoryNotices[id] = component;
- },
-
/**
* Registers a component that will be rendered next to tutorial directory title/header area.
*/
@@ -94,10 +80,6 @@ export class TutorialService {
return this.tutorialVariables;
}
- public getDirectoryNotices() {
- return Object.values(this.tutorialDirectoryNotices);
- }
-
public getDirectoryHeaderLinks() {
return Object.values(this.tutorialDirectoryHeaderLinks);
}
diff --git a/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.tsx b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.tsx
index 1331eb9b7c4ac..d00f9e2368e21 100644
--- a/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.tsx
+++ b/src/plugins/index_pattern_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.tsx
@@ -91,7 +91,7 @@ export const EmptyIndexListPrompt = ({
{
- navigateToApp('home', { path: '#/tutorial_directory' });
+ navigateToApp('home', { path: '/app/integrations/browse' });
closeFlyout();
}}
icon={}
diff --git a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap
index 6da2f95fa394d..babcab15a4974 100644
--- a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap
+++ b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap
@@ -226,10 +226,7 @@ exports[`Overview render 1`] = `
[MockFunction] {
"calls": Array [
Array [
- "/app/home#/tutorial_directory",
- ],
- Array [
- "home#/tutorial_directory",
+ "/app/integrations/browse",
],
Array [
"kibana_landing_page",
@@ -259,11 +256,7 @@ exports[`Overview render 1`] = `
"results": Array [
Object {
"type": "return",
- "value": "/app/home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
Object {
"type": "return",
@@ -533,10 +526,7 @@ exports[`Overview without features 1`] = `
[MockFunction] {
"calls": Array [
Array [
- "/app/home#/tutorial_directory",
- ],
- Array [
- "home#/tutorial_directory",
+ "/app/integrations/browse",
],
Array [
"kibana_landing_page",
@@ -563,16 +553,10 @@ exports[`Overview without features 1`] = `
"/plugins/kibanaReact/assets/solutions_solution_4.svg",
],
Array [
- "/app/home#/tutorial_directory",
+ "/app/integrations/browse",
],
Array [
- "home#/tutorial_directory",
- ],
- Array [
- "/app/home#/tutorial_directory",
- ],
- Array [
- "home#/tutorial_directory",
+ "/app/integrations/browse",
],
Array [
"kibana_landing_page",
@@ -602,11 +586,7 @@ exports[`Overview without features 1`] = `
"results": Array [
Object {
"type": "return",
- "value": "/app/home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
Object {
"type": "return",
@@ -642,19 +622,11 @@ exports[`Overview without features 1`] = `
},
Object {
"type": "return",
- "value": "/app/home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "/app/home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
Object {
"type": "return",
- "value": "home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
Object {
"type": "return",
@@ -801,10 +773,7 @@ exports[`Overview without solutions 1`] = `
[MockFunction] {
"calls": Array [
Array [
- "/app/home#/tutorial_directory",
- ],
- Array [
- "home#/tutorial_directory",
+ "/app/integrations/browse",
],
Array [
"kibana_landing_page",
@@ -831,20 +800,13 @@ exports[`Overview without solutions 1`] = `
"/plugins/kibanaReact/assets/solutions_solution_4.svg",
],
Array [
- "/app/home#/tutorial_directory",
- ],
- Array [
- "home#/tutorial_directory",
+ "/app/integrations/browse",
],
],
"results": Array [
Object {
"type": "return",
- "value": "/app/home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
Object {
"type": "return",
@@ -880,11 +842,7 @@ exports[`Overview without solutions 1`] = `
},
Object {
"type": "return",
- "value": "/app/home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
],
}
@@ -898,10 +856,7 @@ exports[`Overview without solutions 1`] = `
[MockFunction] {
"calls": Array [
Array [
- "/app/home#/tutorial_directory",
- ],
- Array [
- "home#/tutorial_directory",
+ "/app/integrations/browse",
],
Array [
"kibana_landing_page",
@@ -928,20 +883,13 @@ exports[`Overview without solutions 1`] = `
"/plugins/kibanaReact/assets/solutions_solution_4.svg",
],
Array [
- "/app/home#/tutorial_directory",
- ],
- Array [
- "home#/tutorial_directory",
+ "/app/integrations/browse",
],
],
"results": Array [
Object {
"type": "return",
- "value": "/app/home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
Object {
"type": "return",
@@ -977,11 +925,7 @@ exports[`Overview without solutions 1`] = `
},
Object {
"type": "return",
- "value": "/app/home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
],
}
@@ -1001,10 +945,7 @@ exports[`Overview without solutions 1`] = `
[MockFunction] {
"calls": Array [
Array [
- "/app/home#/tutorial_directory",
- ],
- Array [
- "home#/tutorial_directory",
+ "/app/integrations/browse",
],
Array [
"kibana_landing_page",
@@ -1031,20 +972,13 @@ exports[`Overview without solutions 1`] = `
"/plugins/kibanaReact/assets/solutions_solution_4.svg",
],
Array [
- "/app/home#/tutorial_directory",
- ],
- Array [
- "home#/tutorial_directory",
+ "/app/integrations/browse",
],
],
"results": Array [
Object {
"type": "return",
- "value": "/app/home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
Object {
"type": "return",
@@ -1080,11 +1014,7 @@ exports[`Overview without solutions 1`] = `
},
Object {
"type": "return",
- "value": "/app/home#/tutorial_directory",
- },
- Object {
- "type": "return",
- "value": "home#/tutorial_directory",
+ "value": "/app/integrations/browse",
},
],
}
diff --git a/src/plugins/kibana_overview/public/components/overview/overview.tsx b/src/plugins/kibana_overview/public/components/overview/overview.tsx
index 07769e2f3c474..6a0279bd12465 100644
--- a/src/plugins/kibana_overview/public/components/overview/overview.tsx
+++ b/src/plugins/kibana_overview/public/components/overview/overview.tsx
@@ -61,7 +61,7 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) =>
const IS_DARK_THEME = uiSettings.get('theme:darkMode');
// Home does not have a locator implemented, so hard-code it here.
- const addDataHref = addBasePath('/app/home#/tutorial_directory');
+ const addDataHref = addBasePath('/app/integrations/browse');
const devToolsHref = share.url.locators.get('CONSOLE_APP_LOCATOR')?.useUrl({});
const managementHref = share.url.locators
.get('MANAGEMENT_APP_LOCATOR')
@@ -86,8 +86,14 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) =>
}),
logo: 'logoKibana',
actions: {
- beats: {
- href: addBasePath(`home#/tutorial_directory`),
+ elasticAgent: {
+ title: i18n.translate('kibanaOverview.noDataConfig.title', {
+ defaultMessage: 'Add integrations',
+ }),
+ description: i18n.translate('kibanaOverview.noDataConfig.description', {
+ defaultMessage:
+ 'Use Elastic Agent or Beats to collect data and build out Analytics solutions.',
+ }),
},
},
docsLink: docLinks.links.kibana,
diff --git a/src/plugins/kibana_react/public/assets/elastic_beats_card_dark.svg b/src/plugins/kibana_react/public/assets/elastic_beats_card_dark.svg
deleted file mode 100644
index 8652d8d921506..0000000000000
--- a/src/plugins/kibana_react/public/assets/elastic_beats_card_dark.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/plugins/kibana_react/public/assets/elastic_beats_card_light.svg b/src/plugins/kibana_react/public/assets/elastic_beats_card_light.svg
deleted file mode 100644
index f54786c1b950c..0000000000000
--- a/src/plugins/kibana_react/public/assets/elastic_beats_card_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/__snapshots__/no_data_page.test.tsx.snap b/src/plugins/kibana_react/public/page_template/no_data_page/__snapshots__/no_data_page.test.tsx.snap
index d8bc5745ec8e5..8842a3c9f5842 100644
--- a/src/plugins/kibana_react/public/page_template/no_data_page/__snapshots__/no_data_page.test.tsx.snap
+++ b/src/plugins/kibana_react/public/page_template/no_data_page/__snapshots__/no_data_page.test.tsx.snap
@@ -73,9 +73,9 @@ exports[`NoDataPage render 1`] = `
-
diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap
index 3f72ae5597a98..f66d05140b2e9 100644
--- a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap
+++ b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap
@@ -13,7 +13,36 @@ exports[`ElasticAgentCard props button 1`] = `
href="/app/integrations/browse"
image="/plugins/kibanaReact/assets/elastic_agent_card.svg"
paddingSize="l"
- title="Add Elastic Agent"
+ title={
+
+
+ Add Elastic Agent
+
+
+ }
+/>
+`;
+
+exports[`ElasticAgentCard props category 1`] = `
+
+ Add Elastic Agent
+
+ }
+ href="/app/integrations/browse/custom"
+ image="/plugins/kibanaReact/assets/elastic_agent_card.svg"
+ paddingSize="l"
+ title={
+
+
+ Add Elastic Agent
+
+
+ }
/>
`;
@@ -30,7 +59,13 @@ exports[`ElasticAgentCard props href 1`] = `
href="#"
image="/plugins/kibanaReact/assets/elastic_agent_card.svg"
paddingSize="l"
- title="Add Elastic Agent"
+ title={
+
+
+ Add Elastic Agent
+
+
+ }
/>
`;
@@ -48,7 +83,13 @@ exports[`ElasticAgentCard props recommended 1`] = `
href="/app/integrations/browse"
image="/plugins/kibanaReact/assets/elastic_agent_card.svg"
paddingSize="l"
- title="Add Elastic Agent"
+ title={
+
+
+ Add Elastic Agent
+
+
+ }
/>
`;
@@ -65,6 +106,12 @@ exports[`ElasticAgentCard renders 1`] = `
href="/app/integrations/browse"
image="/plugins/kibanaReact/assets/elastic_agent_card.svg"
paddingSize="l"
- title="Add Elastic Agent"
+ title={
+
+
+ Add Elastic Agent
+
+
+ }
/>
`;
diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/__snapshots__/elastic_beats_card.test.tsx.snap b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/__snapshots__/elastic_beats_card.test.tsx.snap
deleted file mode 100644
index af26f9e93ebac..0000000000000
--- a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/__snapshots__/elastic_beats_card.test.tsx.snap
+++ /dev/null
@@ -1,70 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ElasticBeatsCard props button 1`] = `
-
- Button
-
- }
- href="/app/home#/tutorial_directory"
- image="/plugins/kibanaReact/assets/elastic_beats_card_light.svg"
- paddingSize="l"
- title="Add data"
-/>
-`;
-
-exports[`ElasticBeatsCard props href 1`] = `
-
- Button
-
- }
- href="#"
- image="/plugins/kibanaReact/assets/elastic_beats_card_light.svg"
- paddingSize="l"
- title="Add data"
-/>
-`;
-
-exports[`ElasticBeatsCard props recommended 1`] = `
-
- Add data
-
- }
- href="/app/home#/tutorial_directory"
- image="/plugins/kibanaReact/assets/elastic_beats_card_light.svg"
- paddingSize="l"
- title="Add data"
-/>
-`;
-
-exports[`ElasticBeatsCard renders 1`] = `
-
- Add data
-
- }
- href="/app/home#/tutorial_directory"
- image="/plugins/kibanaReact/assets/elastic_beats_card_light.svg"
- paddingSize="l"
- title="Add data"
-/>
-`;
diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_agent_card.test.tsx b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_agent_card.test.tsx
index 45cc32cae06d6..b971abf06a437 100644
--- a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_agent_card.test.tsx
+++ b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_agent_card.test.tsx
@@ -14,7 +14,10 @@ jest.mock('../../../context', () => ({
...jest.requireActual('../../../context'),
useKibana: jest.fn().mockReturnValue({
services: {
- http: { basePath: { prepend: jest.fn((path: string) => (path ? path : 'path')) } },
+ http: {
+ basePath: { prepend: jest.fn((path: string) => (path ? path : 'path')) },
+ },
+ application: { capabilities: { navLinks: { integrations: true } } },
uiSettings: { get: jest.fn() },
},
}),
@@ -41,5 +44,10 @@ describe('ElasticAgentCard', () => {
const component = shallow();
expect(component).toMatchSnapshot();
});
+
+ test('category', () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ });
});
});
diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_agent_card.tsx b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_agent_card.tsx
index f071bd9fab25a..5a91e568471d1 100644
--- a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_agent_card.tsx
+++ b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_agent_card.tsx
@@ -9,7 +9,7 @@
import React, { FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import { CoreStart } from 'kibana/public';
-import { EuiButton, EuiCard } from '@elastic/eui';
+import { EuiButton, EuiCard, EuiTextColor, EuiScreenReaderOnly } from '@elastic/eui';
import { useKibana } from '../../../context';
import { NoDataPageActions, NO_DATA_RECOMMENDED } from '../no_data_page';
@@ -27,13 +27,40 @@ export const ElasticAgentCard: FunctionComponent = ({
href,
button,
layout,
+ category,
...cardRest
}) => {
const {
- services: { http },
+ services: { http, application },
} = useKibana();
const addBasePath = http.basePath.prepend;
- const basePathUrl = '/plugins/kibanaReact/assets/';
+ const image = addBasePath(`/plugins/kibanaReact/assets/elastic_agent_card.svg`);
+ const canAccessFleet = application.capabilities.navLinks.integrations;
+ const hasCategory = category ? `/${category}` : '';
+
+ if (!canAccessFleet) {
+ return (
+
+ {i18n.translate('kibana-react.noDataPage.elasticAgentCard.noPermission.title', {
+ defaultMessage: `Contact your administrator`,
+ })}
+
+ }
+ description={
+
+ {i18n.translate('kibana-react.noDataPage.elasticAgentCard.noPermission.description', {
+ defaultMessage: `This integration is not yet enabled. Your administrator has the required permissions to turn it on.`,
+ })}
+
+ }
+ isDisabled
+ />
+ );
+ }
const defaultCTAtitle = i18n.translate('kibana-react.noDataPage.elasticAgentCard.title', {
defaultMessage: 'Add Elastic Agent',
@@ -51,12 +78,17 @@ export const ElasticAgentCard: FunctionComponent = ({
return (
+ {defaultCTAtitle}
+
+ }
description={i18n.translate('kibana-react.noDataPage.elasticAgentCard.description', {
defaultMessage: `Use Elastic Agent for a simple, unified way to collect data from your machines.`,
})}
- image={addBasePath(`${basePathUrl}elastic_agent_card.svg`)}
betaBadgeLabel={recommended ? NO_DATA_RECOMMENDED : undefined}
footer={footer}
layout={layout as 'vertical' | undefined}
diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_beats_card.test.tsx b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_beats_card.test.tsx
deleted file mode 100644
index 6ea41bf6b3e1f..0000000000000
--- a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_beats_card.test.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { shallow } from 'enzyme';
-import React from 'react';
-import { ElasticBeatsCard } from './elastic_beats_card';
-
-jest.mock('../../../context', () => ({
- ...jest.requireActual('../../../context'),
- useKibana: jest.fn().mockReturnValue({
- services: {
- http: { basePath: { prepend: jest.fn((path: string) => (path ? path : 'path')) } },
- uiSettings: { get: jest.fn() },
- },
- }),
-}));
-
-describe('ElasticBeatsCard', () => {
- test('renders', () => {
- const component = shallow();
- expect(component).toMatchSnapshot();
- });
-
- describe('props', () => {
- test('recommended', () => {
- const component = shallow();
- expect(component).toMatchSnapshot();
- });
-
- test('button', () => {
- const component = shallow();
- expect(component).toMatchSnapshot();
- });
-
- test('href', () => {
- const component = shallow();
- expect(component).toMatchSnapshot();
- });
- });
-});
diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_beats_card.tsx b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_beats_card.tsx
deleted file mode 100644
index 0372d12096489..0000000000000
--- a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/elastic_beats_card.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import React, { FunctionComponent } from 'react';
-import { i18n } from '@kbn/i18n';
-import { CoreStart } from 'kibana/public';
-import { EuiButton, EuiCard } from '@elastic/eui';
-import { useKibana } from '../../../context';
-import { NoDataPageActions, NO_DATA_RECOMMENDED } from '../no_data_page';
-
-export type ElasticBeatsCardProps = NoDataPageActions & {
- solution: string;
-};
-
-export const ElasticBeatsCard: FunctionComponent = ({
- recommended,
- title,
- button,
- href,
- solution, // unused for now
- layout,
- ...cardRest
-}) => {
- const {
- services: { http, uiSettings },
- } = useKibana();
- const addBasePath = http.basePath.prepend;
- const basePathUrl = '/plugins/kibanaReact/assets/';
- const IS_DARK_THEME = uiSettings.get('theme:darkMode');
-
- const defaultCTAtitle = i18n.translate('kibana-react.noDataPage.elasticBeatsCard.title', {
- defaultMessage: 'Add data',
- });
-
- const footer =
- typeof button !== 'string' && typeof button !== 'undefined' ? (
- button
- ) : (
- // The href and/or onClick are attached to the whole Card, so the button is just for show.
- // Do not add the behavior here too or else it will propogate through
- {button || title || defaultCTAtitle}
- );
-
- return (
-
- );
-};
diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/index.ts b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/index.ts
index 3744239d9a472..e05d4d9675ca9 100644
--- a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/index.ts
+++ b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_card/index.ts
@@ -7,5 +7,4 @@
*/
export * from './elastic_agent_card';
-export * from './elastic_beats_card';
export * from './no_data_card';
diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_page.tsx b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_page.tsx
index 56eb0f34617d6..b2d9ef6ca5008 100644
--- a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_page.tsx
+++ b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_page.tsx
@@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { KibanaPageTemplateProps } from '../page_template';
-import { ElasticAgentCard, ElasticBeatsCard, NoDataCard } from './no_data_card';
+import { ElasticAgentCard, NoDataCard } from './no_data_card';
import { KibanaPageTemplateSolutionNavAvatar } from '../solution_nav';
export const NO_DATA_PAGE_MAX_WIDTH = 950;
@@ -55,6 +55,10 @@ export type NoDataPageActions = Partial & {
* Remapping `onClick` to any element
*/
onClick?: MouseEventHandler;
+ /**
+ * Category to auto-select within Fleet
+ */
+ category?: string;
};
export type NoDataPageActionsProps = Record;
@@ -107,18 +111,12 @@ export const NoDataPage: FunctionComponent = ({
const actionsKeys = Object.keys(sortedData);
const renderActions = useMemo(() => {
return Object.values(sortedData).map((action, i) => {
- if (actionsKeys[i] === 'elasticAgent') {
+ if (actionsKeys[i] === 'elasticAgent' || actionsKeys[i] === 'beats') {
return (
);
- } else if (actionsKeys[i] === 'beats') {
- return (
-
-
-
- );
} else {
return (
),
discussForumLink: (
-
+
import('./tutorial_directory_notice'));
-export const TutorialDirectoryNotice: TutorialDirectoryNoticeComponent = () => (
- }>
-
-
-);
-
const TutorialDirectoryHeaderLinkLazy = React.lazy(
() => import('./tutorial_directory_header_link')
);
diff --git a/x-pack/plugins/fleet/public/components/home_integration/tutorial_directory_header_link.tsx b/x-pack/plugins/fleet/public/components/home_integration/tutorial_directory_header_link.tsx
index 074a1c40bdb19..18fdd875c7379 100644
--- a/x-pack/plugins/fleet/public/components/home_integration/tutorial_directory_header_link.tsx
+++ b/x-pack/plugins/fleet/public/components/home_integration/tutorial_directory_header_link.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { memo, useState, useEffect } from 'react';
+import React, { memo, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiButtonEmpty } from '@elastic/eui';
import type { TutorialDirectoryHeaderLinkComponent } from 'src/plugins/home/public';
@@ -13,25 +13,15 @@ import type { TutorialDirectoryHeaderLinkComponent } from 'src/plugins/home/publ
import { RedirectAppLinks } from '../../../../../../src/plugins/kibana_react/public';
import { useLink, useCapabilities, useStartServices } from '../../hooks';
-import { tutorialDirectoryNoticeState$ } from './tutorial_directory_notice';
-
const TutorialDirectoryHeaderLink: TutorialDirectoryHeaderLinkComponent = memo(() => {
const { getHref } = useLink();
const { application } = useStartServices();
const { show: hasIngestManager } = useCapabilities();
- const [noticeState, setNoticeState] = useState({
+ const [noticeState] = useState({
settingsDataLoaded: false,
- hasSeenNotice: false,
});
- useEffect(() => {
- const subscription = tutorialDirectoryNoticeState$.subscribe((value) => setNoticeState(value));
- return () => {
- subscription.unsubscribe();
- };
- }, []);
-
- return hasIngestManager && noticeState.settingsDataLoaded && noticeState.hasSeenNotice ? (
+ return hasIngestManager && noticeState.settingsDataLoaded ? (
{
- const { getHref } = useLink();
- const { application } = useStartServices();
- const { show: hasIngestManager } = useCapabilities();
- const { data: settingsData, isLoading } = useGetSettings();
- const [dismissedNotice, setDismissedNotice] = useState(false);
-
- const dismissNotice = useCallback(async () => {
- setDismissedNotice(true);
- await sendPutSettings({
- has_seen_add_data_notice: true,
- });
- }, []);
-
- useEffect(() => {
- tutorialDirectoryNoticeState$.next({
- settingsDataLoaded: !isLoading,
- hasSeenNotice: Boolean(dismissedNotice || settingsData?.item?.has_seen_add_data_notice),
- });
- }, [isLoading, settingsData, dismissedNotice]);
-
- const hasSeenNotice =
- isLoading || settingsData?.item?.has_seen_add_data_notice || dismissedNotice;
-
- return hasIngestManager && !hasSeenNotice ? (
- <>
-
-
-
- ),
- }}
- />
- }
- >
-
-
-
-
- ),
- }}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- dismissNotice();
- }}
- >
-
-
-
-
-
-
-
- >
- ) : null;
-});
-
-// Needed for React.lazy
-// eslint-disable-next-line import/no-default-export
-export default TutorialDirectoryNotice;
diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts
index e1f263b0763e8..4a2a6900cc78c 100644
--- a/x-pack/plugins/fleet/public/plugin.ts
+++ b/x-pack/plugins/fleet/public/plugin.ts
@@ -44,11 +44,7 @@ import { CUSTOM_LOGS_INTEGRATION_NAME, INTEGRATIONS_BASE_PATH } from './constant
import { licenseService } from './hooks';
import { setHttpClient } from './hooks/use_request';
import { createPackageSearchProvider } from './search_provider';
-import {
- TutorialDirectoryNotice,
- TutorialDirectoryHeaderLink,
- TutorialModuleNotice,
-} from './components/home_integration';
+import { TutorialDirectoryHeaderLink, TutorialModuleNotice } from './components/home_integration';
import { createExtensionRegistrationCallback } from './services/ui_extensions';
import type { UIExtensionRegistrationCallback, UIExtensionsStorage } from './types';
import { LazyCustomLogsAssetsExtension } from './lazy_custom_logs_assets_extension';
@@ -197,7 +193,6 @@ export class FleetPlugin implements Plugin {
diff --git a/x-pack/plugins/infra/public/pages/logs/page_template.tsx b/x-pack/plugins/infra/public/pages/logs/page_template.tsx
index 7ee60ab84bf25..6de13b495f0ba 100644
--- a/x-pack/plugins/infra/public/pages/logs/page_template.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/page_template.tsx
@@ -44,13 +44,13 @@ export const LogsPageTemplate: React.FC = ({
actions: {
beats: {
title: i18n.translate('xpack.infra.logs.noDataConfig.beatsCard.title', {
- defaultMessage: 'Add logs with Beats',
+ defaultMessage: 'Add a logging integration',
}),
description: i18n.translate('xpack.infra.logs.noDataConfig.beatsCard.description', {
defaultMessage:
- 'Use Beats to send logs to Elasticsearch. We make it easy with modules for many popular systems and apps.',
+ 'Use the Elastic Agent or Beats to send logs to Elasticsearch. We make it easy with integrations for many popular systems and apps.',
}),
- href: basePath + `/app/home#/tutorial_directory/logging`,
+ href: basePath + `/app/integrations/browse`,
},
},
docsLink: docLinks.links.observability.guide,
diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx
index bc3bc22f3f1b2..2259a8d3528af 100644
--- a/x-pack/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx
@@ -22,8 +22,8 @@ export const LogsPageNoIndicesContent = () => {
const canConfigureSource = application?.capabilities?.logs?.configureSource ? true : false;
const tutorialLinkProps = useLinkProps({
- app: 'home',
- hash: '/tutorial_directory/logging',
+ app: 'integrations',
+ hash: '/browse',
});
return (
diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx
index ae375dc504e7a..1a79cd996087d 100644
--- a/x-pack/plugins/infra/public/pages/metrics/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx
@@ -93,9 +93,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/invalid_node.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/invalid_node.tsx
index 2a436eac30b2c..17e6382ce65cc 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/invalid_node.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/invalid_node.tsx
@@ -18,8 +18,8 @@ interface InvalidNodeErrorProps {
export const InvalidNodeError: React.FunctionComponent = ({ nodeName }) => {
const tutorialLinkProps = useLinkProps({
- app: 'home',
- hash: '/tutorial_directory/metrics',
+ app: 'integrations',
+ hash: '/browse',
});
return (
diff --git a/x-pack/plugins/infra/public/pages/metrics/page_template.tsx b/x-pack/plugins/infra/public/pages/metrics/page_template.tsx
index 41ea12c280841..4da671283644d 100644
--- a/x-pack/plugins/infra/public/pages/metrics/page_template.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/page_template.tsx
@@ -10,7 +10,6 @@ import { i18n } from '@kbn/i18n';
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
import type { LazyObservabilityPageTemplateProps } from '../../../../observability/public';
import { KibanaPageTemplateProps } from '../../../../../../src/plugins/kibana_react/public';
-import { useLinkProps } from '../../hooks/use_link_props';
interface MetricsPageTemplateProps extends LazyObservabilityPageTemplateProps {
hasData?: boolean;
@@ -30,11 +29,6 @@ export const MetricsPageTemplate: React.FC = ({
},
} = useKibanaContextForPlugin();
- const tutorialLinkProps = useLinkProps({
- app: 'home',
- hash: '/tutorial_directory/metrics',
- });
-
const noDataConfig: KibanaPageTemplateProps['noDataConfig'] = hasData
? undefined
: {
@@ -44,13 +38,12 @@ export const MetricsPageTemplate: React.FC = ({
actions: {
beats: {
title: i18n.translate('xpack.infra.metrics.noDataConfig.beatsCard.title', {
- defaultMessage: 'Add metrics with Beats',
+ defaultMessage: 'Add a metrics integration',
}),
description: i18n.translate('xpack.infra.metrics.noDataConfig.beatsCard.description', {
defaultMessage:
'Use Beats to send metrics data to Elasticsearch. We make it easy with modules for many popular systems and apps.',
}),
- ...tutorialLinkProps,
},
},
docsLink: docLinks.links.observability.guide,
diff --git a/x-pack/plugins/observability/public/components/app/header/header_menu.tsx b/x-pack/plugins/observability/public/components/app/header/header_menu.tsx
index 707cb241501fd..0ed01b7d3673e 100644
--- a/x-pack/plugins/observability/public/components/app/header/header_menu.tsx
+++ b/x-pack/plugins/observability/public/components/app/header/header_menu.tsx
@@ -26,7 +26,7 @@ export function ObservabilityHeaderMenu(): React.ReactElement | null {
{addDataLinkText}
diff --git a/x-pack/plugins/observability/public/utils/no_data_config.ts b/x-pack/plugins/observability/public/utils/no_data_config.ts
index 1e16fb145bdce..2c87b1434a0b4 100644
--- a/x-pack/plugins/observability/public/utils/no_data_config.ts
+++ b/x-pack/plugins/observability/public/utils/no_data_config.ts
@@ -24,12 +24,15 @@ export function getNoDataConfig({
defaultMessage: 'Observability',
}),
actions: {
- beats: {
+ elasticAgent: {
+ title: i18n.translate('xpack.observability.noDataConfig.beatsCard.title', {
+ defaultMessage: 'Add integrations',
+ }),
description: i18n.translate('xpack.observability.noDataConfig.beatsCard.description', {
defaultMessage:
'Use Beats and APM agents to send observability data to Elasticsearch. We make it easy with support for many popular systems, apps, and languages.',
}),
- href: basePath.prepend(`/app/home#/tutorial_directory/logging`),
+ href: basePath.prepend(`/app/integrations`),
},
},
docsLink,
diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts
index 5c41e92661e58..5a7e19e2cdd05 100644
--- a/x-pack/plugins/security_solution/common/constants.ts
+++ b/x-pack/plugins/security_solution/common/constants.ts
@@ -16,7 +16,7 @@ export const APP_NAME = 'Security';
export const APP_ICON = 'securityAnalyticsApp';
export const APP_ICON_SOLUTION = 'logoSecurity';
export const APP_PATH = `/app/security`;
-export const ADD_DATA_PATH = `/app/home#/tutorial_directory/security`;
+export const ADD_DATA_PATH = `/app/integrations/browse/security`;
export const DEFAULT_BYTES_FORMAT = 'format:bytes:defaultPattern';
export const DEFAULT_DATE_FORMAT = 'dateFormat';
export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz';
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx
index 5fa2725f9ee6f..61e9e66f1bb87 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx
@@ -45,9 +45,10 @@ describe('OverviewEmpty', () => {
expect(wrapper.find('[data-test-subj="empty-page"]').prop('noDataConfig')).toEqual({
actions: {
elasticAgent: {
+ category: 'security',
description:
- 'Use Elastic Agent to collect security events and protect your endpoints from threats. Manage your agents in Fleet and add integrations with a single click.',
- href: '/app/integrations/browse/security',
+ 'Use Elastic Agent to collect security events and protect your endpoints from threats.',
+ title: 'Add a Security integration',
},
},
docsLink: 'https://www.elastic.co/guide/en/security/mocked-test-branch/index.html',
@@ -68,8 +69,11 @@ describe('OverviewEmpty', () => {
it('render with correct actions ', () => {
expect(wrapper.find('[data-test-subj="empty-page"]').prop('noDataConfig')).toEqual({
actions: {
- beats: {
- href: '/app/home#/tutorial_directory/security',
+ elasticAgent: {
+ category: 'security',
+ description:
+ 'Use Elastic Agent to collect security events and protect your endpoints from threats.',
+ title: 'Add a Security integration',
},
},
docsLink: 'https://www.elastic.co/guide/en/security/mocked-test-branch/index.html',
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx
index bc76333943191..9b20c079002e6 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx
@@ -5,13 +5,10 @@
* 2.0.
*/
-import React, { useMemo } from 'react';
+import React from 'react';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../../common/lib/kibana';
-import { ADD_DATA_PATH } from '../../../../common/constants';
-import { pagePathGetters } from '../../../../../fleet/public';
import { SOLUTION_NAME } from '../../../../public/common/translations';
-import { useUserPrivileges } from '../../../common/components/user_privileges';
import {
KibanaPageTemplate,
@@ -19,42 +16,27 @@ import {
} from '../../../../../../../src/plugins/kibana_react/public';
const OverviewEmptyComponent: React.FC = () => {
- const { http, docLinks } = useKibana().services;
- const basePath = http.basePath.get();
- const canAccessFleet = useUserPrivileges().endpointPrivileges.canAccessFleet;
- const integrationsPathComponents = pagePathGetters.integrations_all({ category: 'security' });
-
- const agentAction: NoDataPageActionsProps = useMemo(
- () => ({
- elasticAgent: {
- href: `${basePath}${integrationsPathComponents[0]}${integrationsPathComponents[1]}`,
- description: i18n.translate(
- 'xpack.securitySolution.pages.emptyPage.beatsCard.description',
- {
- defaultMessage:
- 'Use Elastic Agent to collect security events and protect your endpoints from threats. Manage your agents in Fleet and add integrations with a single click.',
- }
- ),
- },
- }),
- [basePath, integrationsPathComponents]
- );
-
- const beatsAction: NoDataPageActionsProps = useMemo(
- () => ({
- beats: {
- href: `${basePath}${ADD_DATA_PATH}`,
- },
- }),
- [basePath]
- );
+ const { docLinks } = useKibana().services;
+
+ const agentAction: NoDataPageActionsProps = {
+ elasticAgent: {
+ category: 'security',
+ title: i18n.translate('xpack.securitySolution.pages.emptyPage.beatsCard.title', {
+ defaultMessage: 'Add a Security integration',
+ }),
+ description: i18n.translate('xpack.securitySolution.pages.emptyPage.beatsCard.description', {
+ defaultMessage:
+ 'Use Elastic Agent to collect security events and protect your endpoints from threats.',
+ }),
+ },
+ };
return (
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 3df5e4ee6c48a..852b01977b78b 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -3009,11 +3009,7 @@
"home.tutorial.savedObject.unableToAddErrorMessage": "{savedObjectsLength} 件中 {errorsLength} 件の kibana オブジェクトが追加できません。エラー:{errorMessage}",
"home.tutorial.selectionLegend": "デプロイタイプ",
"home.tutorial.selfManagedButtonLabel": "自己管理",
- "home.tutorial.tabs.allTitle": "すべて",
- "home.tutorial.tabs.loggingTitle": "ログ",
- "home.tutorial.tabs.metricsTitle": "メトリック",
"home.tutorial.tabs.sampleDataTitle": "サンプルデータ",
- "home.tutorial.tabs.securitySolutionTitle": "セキュリティ",
"home.tutorial.unexpectedStatusCheckStateErrorDescription": "予期せぬステータス確認ステータス {statusCheckState}",
"home.tutorial.unhandledInstructionTypeErrorDescription": "予期せぬ指示タイプ {visibleInstructions}",
"home.tutorialDirectory.featureCatalogueDescription": "一般的なアプリやサービスからデータを取り込みます。",
@@ -4204,8 +4200,6 @@
"kibana-react.noDataPage.cantDecide.link": "詳細については、ドキュメントをご確認ください。",
"kibana-react.noDataPage.elasticAgentCard.description": "Elasticエージェントを使用すると、シンプルで統一された方法でコンピューターからデータを収集するできます。",
"kibana-react.noDataPage.elasticAgentCard.title": "Elasticエージェントの追加",
- "kibana-react.noDataPage.elasticBeatsCard.description": "Beatsを使用して、さまざまなシステムのデータをElasticsearchに追加します。",
- "kibana-react.noDataPage.elasticBeatsCard.title": "データの追加",
"kibana-react.noDataPage.intro": "データを追加して開始するか、{solution}については{link}をご覧ください。",
"kibana-react.noDataPage.intro.link": "詳細",
"kibana-react.noDataPage.noDataPage.recommended": "推奨",
@@ -11076,12 +11070,7 @@
"xpack.fleet.fleetServerUpgradeModal.modalTitle": "エージェントをFleetサーバーに登録",
"xpack.fleet.fleetServerUpgradeModal.onPremDescriptionMessage": "Fleetサーバーが使用できます。スケーラビリティとセキュリティが改善されています。{existingAgentsMessage} Fleetを使用し続けるには、Fleetサーバーと新しいバージョンのElasticエージェントを各ホストにインストールする必要があります。詳細については、{link}をご覧ください。",
"xpack.fleet.genericActionsMenuText": "開く",
- "xpack.fleet.homeIntegration.tutorialDirectory.dismissNoticeButtonText": "メッセージを消去",
"xpack.fleet.homeIntegration.tutorialDirectory.fleetAppButtonText": "統合を試す",
- "xpack.fleet.homeIntegration.tutorialDirectory.noticeText": "Elasticエージェント統合では、シンプルかつ統合された方法で、ログ、メトリック、他の種類のデータの監視をホストに追加することができます。複数のBeatsをインストールする必要はありません。このため、インフラストラクチャ全体でのポリシーのデプロイが簡単で高速になりました。詳細については、{blogPostLink}をお読みください。",
- "xpack.fleet.homeIntegration.tutorialDirectory.noticeText.blogPostLink": "発表ブログ投稿",
- "xpack.fleet.homeIntegration.tutorialDirectory.noticeTitle": "{newPrefix} Elasticエージェント統合",
- "xpack.fleet.homeIntegration.tutorialDirectory.noticeTitle.newPrefix": "一般公開へ:",
"xpack.fleet.homeIntegration.tutorialModule.noticeText": "{notePrefix}このモジュールの新しいバージョンは{availableAsIntegrationLink}です。統合と新しいElasticエージェントの詳細については、{blogPostLink}をお読みください。",
"xpack.fleet.homeIntegration.tutorialModule.noticeText.blogPostLink": "発表ブログ投稿",
"xpack.fleet.homeIntegration.tutorialModule.noticeText.integrationLink": "Elasticエージェント統合として提供",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index d9af3edb8101d..9d88c757f1e58 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -3038,11 +3038,7 @@
"home.tutorial.savedObject.unableToAddErrorMessage": "{savedObjectsLength} 个 kibana 对象中有 {errorsLength} 个无法添加,错误:{errorMessage}",
"home.tutorial.selectionLegend": "部署类型",
"home.tutorial.selfManagedButtonLabel": "自管型",
- "home.tutorial.tabs.allTitle": "全部",
- "home.tutorial.tabs.loggingTitle": "日志",
- "home.tutorial.tabs.metricsTitle": "指标",
"home.tutorial.tabs.sampleDataTitle": "样例数据",
- "home.tutorial.tabs.securitySolutionTitle": "安全",
"home.tutorial.unexpectedStatusCheckStateErrorDescription": "意外的状态检查状态 {statusCheckState}",
"home.tutorial.unhandledInstructionTypeErrorDescription": "未处理的指令类型 {visibleInstructions}",
"home.tutorialDirectory.featureCatalogueDescription": "从热门应用和服务中采集数据。",
@@ -4244,8 +4240,6 @@
"kibana-react.noDataPage.cantDecide.link": "请参阅我们的文档以了解更多信息。",
"kibana-react.noDataPage.elasticAgentCard.description": "使用 Elastic 代理以简单统一的方式从您的计算机中收集数据。",
"kibana-react.noDataPage.elasticAgentCard.title": "添加 Elastic 代理",
- "kibana-react.noDataPage.elasticBeatsCard.description": "使用 Beats 将各种系统的数据添加到 Elasticsearch。",
- "kibana-react.noDataPage.elasticBeatsCard.title": "添加数据",
"kibana-react.noDataPage.intro": "添加您的数据以开始,或{link}{solution}。",
"kibana-react.noDataPage.intro.link": "了解详情",
"kibana-react.noDataPage.noDataPage.recommended": "推荐",
@@ -11191,12 +11185,7 @@
"xpack.fleet.fleetServerUpgradeModal.modalTitle": "将代理注册到 Fleet 服务器",
"xpack.fleet.fleetServerUpgradeModal.onPremDescriptionMessage": "Fleet 服务器现在可用且提供改善的可扩展性和安全性。{existingAgentsMessage}要继续使用 Fleet,必须在各个主机上安装 Fleet 服务器和新版 Elastic 代理。详细了解我们的 {link}。",
"xpack.fleet.genericActionsMenuText": "打开",
- "xpack.fleet.homeIntegration.tutorialDirectory.dismissNoticeButtonText": "关闭消息",
"xpack.fleet.homeIntegration.tutorialDirectory.fleetAppButtonText": "试用集成",
- "xpack.fleet.homeIntegration.tutorialDirectory.noticeText": "通过 Elastic 代理集成,可以简单统一的方式将日志、指标和其他类型数据的监测添加到主机。不再需要安装多个 Beats,这样将策略部署到整个基础架构更容易也更快速。有关更多信息,请阅读我们的{blogPostLink}。",
- "xpack.fleet.homeIntegration.tutorialDirectory.noticeText.blogPostLink": "公告博客",
- "xpack.fleet.homeIntegration.tutorialDirectory.noticeTitle": "{newPrefix}Elastic 代理集成",
- "xpack.fleet.homeIntegration.tutorialDirectory.noticeTitle.newPrefix": "已正式发布:",
"xpack.fleet.homeIntegration.tutorialModule.noticeText": "{notePrefix}此模块的较新版本{availableAsIntegrationLink}。要详细了解集成和新 Elastic 代理,请阅读我们的{blogPostLink}。",
"xpack.fleet.homeIntegration.tutorialModule.noticeText.blogPostLink": "公告博客",
"xpack.fleet.homeIntegration.tutorialModule.noticeText.integrationLink": "将作为 Elastic 代理集成来提供",
diff --git a/x-pack/test/accessibility/apps/home.ts b/x-pack/test/accessibility/apps/home.ts
index a7158d9579b60..61297859c29f8 100644
--- a/x-pack/test/accessibility/apps/home.ts
+++ b/x-pack/test/accessibility/apps/home.ts
@@ -64,33 +64,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await a11y.testAppSnapshot();
});
- it('Add data page meets a11y requirements ', async () => {
- await home.clickGoHome();
- await testSubjects.click('homeAddData');
- await a11y.testAppSnapshot();
- });
-
- it('Sample data page meets a11y requirements ', async () => {
- await testSubjects.click('homeTab-sampleData');
- await a11y.testAppSnapshot();
- });
-
- it('click on Add logs panel to open all log examples page meets a11y requirements ', async () => {
- await testSubjects.click('sampleDataSetCardlogs');
- await a11y.testAppSnapshot();
- });
-
- it('click on ActiveMQ logs panel to open tutorial meets a11y requirements', async () => {
- await testSubjects.click('homeTab-all');
- await testSubjects.click('homeSynopsisLinkactivemqlogs');
- await a11y.testAppSnapshot();
- });
-
- it('click on cloud tutorial meets a11y requirements', async () => {
- await testSubjects.click('onCloudTutorial');
- await a11y.testAppSnapshot();
- });
-
it('passes with searchbox open', async () => {
await testSubjects.click('nav-search-popover');
await a11y.testAppSnapshot();
From fd0fc770623b483a0eb7de48a4e3407446de9bd7 Mon Sep 17 00:00:00 2001
From: Frank Hassanabad
Date: Mon, 18 Oct 2021 22:24:20 -0600
Subject: [PATCH 028/204] Fixes console errors seen (#115448)
## Summary
During testing I encountered this error message:
```
[2021-10-18T13:19:07.053-06:00][ERROR][plugins.securitySolution] The notification throttle "from" and/or "to" range values could not be constructed as valid. Tried to construct the values of "from": now-null "to": 2021-10-18T19:19:00.835Z. This will cause a reset of the notification throttle. Expect either missing alert notifications or alert notifications happening earlier than expected.
```
This error was happening whenever I had a rule that was using an immediately invoked action and was encountering an error such as a non ECS compliant signal. The root cause is that I was not checking everywhere to ensure we had a throttle rule to ensure scheduling.
This fixes that by adding an `if` statement/guard around the areas of code.
I also improve the error message by adding which ruleId the error is coming from.
### Checklist
- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
---
...dule_throttle_notification_actions.test.ts | 2 +-
.../schedule_throttle_notification_actions.ts | 1 +
.../create_security_rule_type_wrapper.ts | 60 +++++++++--------
.../legacy_notifications/one_action.json | 2 +-
.../signals/signal_rule_alert_type.test.ts | 64 ++++++++++++++++++-
.../signals/signal_rule_alert_type.ts | 61 +++++++++---------
6 files changed, 129 insertions(+), 61 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts
index 81f229c636bd8..964df3c91eb08 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts
@@ -278,7 +278,7 @@ describe('schedule_throttle_notification_actions', () => {
});
expect(logger.error).toHaveBeenCalledWith(
- 'The notification throttle "from" and/or "to" range values could not be constructed as valid. Tried to construct the values of "from": now-invalid "to": 2021-08-24T19:19:22.094Z. This will cause a reset of the notification throttle. Expect either missing alert notifications or alert notifications happening earlier than expected.'
+ 'The notification throttle "from" and/or "to" range values could not be constructed as valid. Tried to construct the values of "from": now-invalid "to": 2021-08-24T19:19:22.094Z. This will cause a reset of the notification throttle. Expect either missing alert notifications or alert notifications happening earlier than expected. Check your rule with ruleId: "rule-123" for data integrity issues'
);
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.ts
index 5bf18496e6375..7b4b314cc8911 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.ts
@@ -145,6 +145,7 @@ export const scheduleThrottledNotificationActions = async ({
` "from": now-${throttle}`,
` "to": ${startedAt.toISOString()}.`,
' This will cause a reset of the notification throttle. Expect either missing alert notifications or alert notifications happening earlier than expected.',
+ ` Check your rule with ruleId: "${ruleId}" for data integrity issues`,
].join('')
);
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts
index 0ad416e86e31a..0fe7cbdc9bd9f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts
@@ -375,20 +375,22 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
);
} else {
// NOTE: Since this is throttled we have to call it even on an error condition, otherwise it will "reset" the throttle and fire early
- await scheduleThrottledNotificationActions({
- alertInstance: services.alertInstanceFactory(alertId),
- throttle: ruleSO.attributes.throttle,
- startedAt,
- id: ruleSO.id,
- kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined)
- ?.kibana_siem_app_url,
- outputIndex: ruleDataClient.indexName,
- ruleId,
- esClient: services.scopedClusterClient.asCurrentUser,
- notificationRuleParams,
- signals: result.createdSignals,
- logger,
- });
+ if (ruleSO.attributes.throttle != null) {
+ await scheduleThrottledNotificationActions({
+ alertInstance: services.alertInstanceFactory(alertId),
+ throttle: ruleSO.attributes.throttle,
+ startedAt,
+ id: ruleSO.id,
+ kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined)
+ ?.kibana_siem_app_url,
+ outputIndex: ruleDataClient.indexName,
+ ruleId,
+ esClient: services.scopedClusterClient.asCurrentUser,
+ notificationRuleParams,
+ signals: result.createdSignals,
+ logger,
+ });
+ }
const errorMessage = buildRuleMessage(
'Bulk Indexing of signals failed:',
truncateMessageList(result.errors).join()
@@ -407,20 +409,22 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
}
} catch (error) {
// NOTE: Since this is throttled we have to call it even on an error condition, otherwise it will "reset" the throttle and fire early
- await scheduleThrottledNotificationActions({
- alertInstance: services.alertInstanceFactory(alertId),
- throttle: ruleSO.attributes.throttle,
- startedAt,
- id: ruleSO.id,
- kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined)
- ?.kibana_siem_app_url,
- outputIndex: ruleDataClient.indexName,
- ruleId,
- esClient: services.scopedClusterClient.asCurrentUser,
- notificationRuleParams,
- signals: result.createdSignals,
- logger,
- });
+ if (ruleSO.attributes.throttle != null) {
+ await scheduleThrottledNotificationActions({
+ alertInstance: services.alertInstanceFactory(alertId),
+ throttle: ruleSO.attributes.throttle,
+ startedAt,
+ id: ruleSO.id,
+ kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined)
+ ?.kibana_siem_app_url,
+ outputIndex: ruleDataClient.indexName,
+ ruleId,
+ esClient: services.scopedClusterClient.asCurrentUser,
+ notificationRuleParams,
+ signals: result.createdSignals,
+ logger,
+ });
+ }
const errorMessage = error.message ?? '(no error message given)';
const message = buildRuleMessage(
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/legacy_notifications/one_action.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/legacy_notifications/one_action.json
index 1966dcf5ff53c..bf980e370e3a3 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/legacy_notifications/one_action.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/legacy_notifications/one_action.json
@@ -3,7 +3,7 @@
"interval": "1m",
"actions": [
{
- "id": "42534430-2092-11ec-99a6-05d79563c01a",
+ "id": "1fa31c30-3046-11ec-8971-1f3f7bae65af",
"group": "default",
"params": {
"message": "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts"
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts
index 88b276358a705..6a84776ccee5d 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts
@@ -536,14 +536,74 @@ describe('signal_rule_alert_type', () => {
errors: ['Error that bubbled up.'],
};
(queryExecutor as jest.Mock).mockResolvedValue(result);
- await alert.executor(payload);
+ const ruleAlert = getAlertMock(false, getQueryRuleParams());
+ ruleAlert.throttle = '1h';
+ const payLoadWithThrottle = getPayload(
+ ruleAlert,
+ alertServices
+ ) as jest.Mocked;
+ payLoadWithThrottle.rule.throttle = '1h';
+ alertServices.savedObjectsClient.get.mockResolvedValue({
+ id: 'id',
+ type: 'type',
+ references: [],
+ attributes: ruleAlert,
+ });
+ await alert.executor(payLoadWithThrottle);
expect(scheduleThrottledNotificationActions).toHaveBeenCalledTimes(1);
});
+ it('should NOT call scheduleThrottledNotificationActions if result is false and the throttle is not set', async () => {
+ const result: SearchAfterAndBulkCreateReturnType = {
+ success: false,
+ warning: false,
+ searchAfterTimes: [],
+ bulkCreateTimes: [],
+ lastLookBackDate: null,
+ createdSignalsCount: 0,
+ createdSignals: [],
+ warningMessages: [],
+ errors: ['Error that bubbled up.'],
+ };
+ (queryExecutor as jest.Mock).mockResolvedValue(result);
+ await alert.executor(payload);
+ expect(scheduleThrottledNotificationActions).toHaveBeenCalledTimes(0);
+ });
+
it('should call scheduleThrottledNotificationActions if an error was thrown to prevent the throttle from being reset', async () => {
(queryExecutor as jest.Mock).mockRejectedValue({});
- await alert.executor(payload);
+ const ruleAlert = getAlertMock(false, getQueryRuleParams());
+ ruleAlert.throttle = '1h';
+ const payLoadWithThrottle = getPayload(
+ ruleAlert,
+ alertServices
+ ) as jest.Mocked;
+ payLoadWithThrottle.rule.throttle = '1h';
+ alertServices.savedObjectsClient.get.mockResolvedValue({
+ id: 'id',
+ type: 'type',
+ references: [],
+ attributes: ruleAlert,
+ });
+ await alert.executor(payLoadWithThrottle);
expect(scheduleThrottledNotificationActions).toHaveBeenCalledTimes(1);
});
+
+ it('should NOT call scheduleThrottledNotificationActions if an error was thrown to prevent the throttle from being reset if throttle is not defined', async () => {
+ const result: SearchAfterAndBulkCreateReturnType = {
+ success: false,
+ warning: false,
+ searchAfterTimes: [],
+ bulkCreateTimes: [],
+ lastLookBackDate: null,
+ createdSignalsCount: 0,
+ createdSignals: [],
+ warningMessages: [],
+ errors: ['Error that bubbled up.'],
+ };
+ (queryExecutor as jest.Mock).mockRejectedValue(result);
+ await alert.executor(payload);
+ expect(scheduleThrottledNotificationActions).toHaveBeenCalledTimes(0);
+ });
});
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
index 4e98bee83aeb5..90220814fb928 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
@@ -474,21 +474,22 @@ export const signalRulesAlertType = ({
);
} else {
// NOTE: Since this is throttled we have to call it even on an error condition, otherwise it will "reset" the throttle and fire early
- await scheduleThrottledNotificationActions({
- alertInstance: services.alertInstanceFactory(alertId),
- throttle: savedObject.attributes.throttle,
- startedAt,
- id: savedObject.id,
- kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined)
- ?.kibana_siem_app_url,
- outputIndex,
- ruleId,
- signals: result.createdSignals,
- esClient: services.scopedClusterClient.asCurrentUser,
- notificationRuleParams,
- logger,
- });
-
+ if (savedObject.attributes.throttle != null) {
+ await scheduleThrottledNotificationActions({
+ alertInstance: services.alertInstanceFactory(alertId),
+ throttle: savedObject.attributes.throttle,
+ startedAt,
+ id: savedObject.id,
+ kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined)
+ ?.kibana_siem_app_url,
+ outputIndex,
+ ruleId,
+ signals: result.createdSignals,
+ esClient: services.scopedClusterClient.asCurrentUser,
+ notificationRuleParams,
+ logger,
+ });
+ }
const errorMessage = buildRuleMessage(
'Bulk Indexing of signals failed:',
truncateMessageList(result.errors).join()
@@ -507,20 +508,22 @@ export const signalRulesAlertType = ({
}
} catch (error) {
// NOTE: Since this is throttled we have to call it even on an error condition, otherwise it will "reset" the throttle and fire early
- await scheduleThrottledNotificationActions({
- alertInstance: services.alertInstanceFactory(alertId),
- throttle: savedObject.attributes.throttle,
- startedAt,
- id: savedObject.id,
- kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined)
- ?.kibana_siem_app_url,
- outputIndex,
- ruleId,
- signals: result.createdSignals,
- esClient: services.scopedClusterClient.asCurrentUser,
- notificationRuleParams,
- logger,
- });
+ if (savedObject.attributes.throttle != null) {
+ await scheduleThrottledNotificationActions({
+ alertInstance: services.alertInstanceFactory(alertId),
+ throttle: savedObject.attributes.throttle,
+ startedAt,
+ id: savedObject.id,
+ kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined)
+ ?.kibana_siem_app_url,
+ outputIndex,
+ ruleId,
+ signals: result.createdSignals,
+ esClient: services.scopedClusterClient.asCurrentUser,
+ notificationRuleParams,
+ logger,
+ });
+ }
const errorMessage = error.message ?? '(no error message given)';
const message = buildRuleMessage(
'An error occurred during rule execution:',
From e53f4d2f28d127935bd7fe5dfa235a81e30b9460 Mon Sep 17 00:00:00 2001
From: Frank Hassanabad
Date: Mon, 18 Oct 2021 22:37:00 -0600
Subject: [PATCH 029/204] [Security Solutions] Makes legacy
actions/notification system, legacy action status, and exception lists
multiple space shareable (#115427)
## Summary
See https://github.com/elastic/kibana/issues/114548
Makes the following saved objects multiple-isolated:
* siem-detection-engine-rule-status
* exception-list
* siem-detection-engine-rule-actions
### Checklist
Delete any items that are not applicable to this PR.
- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
---
x-pack/plugins/lists/server/saved_objects/exception_list.ts | 3 ++-
.../rule_actions/legacy_saved_object_mappings.ts | 3 ++-
.../legacy_rule_status_saved_object_mappings.ts | 3 ++-
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/lists/server/saved_objects/exception_list.ts b/x-pack/plugins/lists/server/saved_objects/exception_list.ts
index 8354e64d64a6e..3d31bdd561140 100644
--- a/x-pack/plugins/lists/server/saved_objects/exception_list.ts
+++ b/x-pack/plugins/lists/server/saved_objects/exception_list.ts
@@ -177,11 +177,12 @@ const combinedMappings: SavedObjectsType['mappings'] = {
};
export const exceptionListType: SavedObjectsType = {
+ convertToMultiNamespaceTypeVersion: '8.0.0',
hidden: false,
mappings: combinedMappings,
migrations,
name: exceptionListSavedObjectType,
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
};
export const exceptionListAgnosticType: SavedObjectsType = {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_saved_object_mappings.ts
index 3d6a405225fe6..835ccd92f9cc4 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_saved_object_mappings.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_saved_object_mappings.ts
@@ -59,9 +59,10 @@ const legacyRuleActionsSavedObjectMappings: SavedObjectsType['mappings'] = {
* @deprecated Remove this once we no longer need legacy migrations for rule actions (8.0.0)
*/
export const legacyType: SavedObjectsType = {
+ convertToMultiNamespaceTypeVersion: '8.0.0',
name: legacyRuleActionsSavedObjectType,
hidden: false,
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
mappings: legacyRuleActionsSavedObjectMappings,
migrations: legacyRuleActionsSavedObjectMigration,
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/legacy_rule_status/legacy_rule_status_saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/legacy_rule_status/legacy_rule_status_saved_object_mappings.ts
index 3fe3fc06cc7d6..298c75b8b7d51 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/legacy_rule_status/legacy_rule_status_saved_object_mappings.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/legacy_rule_status/legacy_rule_status_saved_object_mappings.ts
@@ -65,9 +65,10 @@ export const ruleStatusSavedObjectMappings: SavedObjectsType['mappings'] = {
* @deprecated Remove this once we've fully migrated to event-log and no longer require addition status SO (8.x)
*/
export const legacyRuleStatusType: SavedObjectsType = {
+ convertToMultiNamespaceTypeVersion: '8.0.0',
name: legacyRuleStatusSavedObjectType,
hidden: false,
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
mappings: ruleStatusSavedObjectMappings,
migrations: legacyRuleStatusSavedObjectMigration,
};
From 57ff4a7172bb68067df9b809d592748deeb1114c Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Tue, 19 Oct 2021 00:43:12 -0400
Subject: [PATCH 030/204] [Security Solution][Endpoint] Adds additional
endpoint privileges to the `useUserPrivileges()` hook (#115051)
* Adds new `canIsolateHost` and `canCreateArtifactsByPolicy` privileges for endpoint
* Refactor `useEndpointPrivileges` mocks to also provide a test function to return the full set of default privileges
* refactor useEndpointPrivileges tests to be more resilient to future changes
---
.../__mocks__/use_endpoint_privileges.ts | 18 ----
.../__mocks__/use_endpoint_privileges.ts | 12 +++
.../user_privileges/endpoint/index.ts | 9 ++
.../user_privileges/endpoint/mocks.ts | 29 ++++++
.../use_endpoint_privileges.test.ts | 89 ++++++++++---------
.../{ => endpoint}/use_endpoint_privileges.ts | 25 ++++--
.../user_privileges/endpoint/utils.ts | 19 ++++
.../components/user_privileges/index.tsx | 10 +--
.../components/user_info/index.test.tsx | 2 +-
.../alerts/use_alerts_privileges.test.tsx | 6 +-
.../alerts/use_signal_index.test.tsx | 2 +-
.../search_exceptions.test.tsx | 17 ++--
.../search_exceptions/search_exceptions.tsx | 2 +-
.../view/event_filters_list_page.test.tsx | 2 +-
.../host_isolation_exceptions_list.test.tsx | 3 +-
.../policy_trusted_apps_empty_unassigned.tsx | 2 +-
.../policy_trusted_apps_flyout.test.tsx | 2 +-
.../policy_trusted_apps_layout.test.tsx | 22 ++---
.../layout/policy_trusted_apps_layout.tsx | 2 +-
.../list/policy_trusted_apps_list.test.tsx | 10 +--
.../list/policy_trusted_apps_list.tsx | 2 +-
.../view/trusted_apps_page.test.tsx | 2 +-
.../public/overview/pages/overview.test.tsx | 2 +-
23 files changed, 170 insertions(+), 119 deletions(-)
delete mode 100644 x-pack/plugins/security_solution/public/common/components/user_privileges/__mocks__/use_endpoint_privileges.ts
create mode 100644 x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/__mocks__/use_endpoint_privileges.ts
create mode 100644 x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/index.ts
create mode 100644 x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/mocks.ts
rename x-pack/plugins/security_solution/public/common/components/user_privileges/{ => endpoint}/use_endpoint_privileges.test.ts (63%)
rename x-pack/plugins/security_solution/public/common/components/user_privileges/{ => endpoint}/use_endpoint_privileges.ts (71%)
create mode 100644 x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/utils.ts
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/__mocks__/use_endpoint_privileges.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/__mocks__/use_endpoint_privileges.ts
deleted file mode 100644
index 80ca534534187..0000000000000
--- a/x-pack/plugins/security_solution/public/common/components/user_privileges/__mocks__/use_endpoint_privileges.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EndpointPrivileges } from '../use_endpoint_privileges';
-
-export const useEndpointPrivileges = jest.fn(() => {
- const endpointPrivilegesMock: EndpointPrivileges = {
- loading: false,
- canAccessFleet: true,
- canAccessEndpointManagement: true,
- isPlatinumPlus: true,
- };
- return endpointPrivilegesMock;
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/__mocks__/use_endpoint_privileges.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/__mocks__/use_endpoint_privileges.ts
new file mode 100644
index 0000000000000..ae9aacaf3d55b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/__mocks__/use_endpoint_privileges.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { getEndpointPrivilegesInitialStateMock } from '../mocks';
+
+export { getEndpointPrivilegesInitialState } from '../utils';
+
+export const useEndpointPrivileges = jest.fn(getEndpointPrivilegesInitialStateMock);
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/index.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/index.ts
new file mode 100644
index 0000000000000..adea89ce1a051
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './use_endpoint_privileges';
+export { getEndpointPrivilegesInitialState } from './utils';
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/mocks.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/mocks.ts
new file mode 100644
index 0000000000000..2851c92816cea
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/mocks.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { EndpointPrivileges } from './use_endpoint_privileges';
+import { getEndpointPrivilegesInitialState } from './utils';
+
+export const getEndpointPrivilegesInitialStateMock = (
+ overrides: Partial = {}
+): EndpointPrivileges => {
+ // Get the initial state and set all permissions to `true` (enabled) for testing
+ const endpointPrivilegesMock: EndpointPrivileges = {
+ ...(
+ Object.entries(getEndpointPrivilegesInitialState()) as Array<
+ [keyof EndpointPrivileges, boolean]
+ >
+ ).reduce((mockPrivileges, [key, value]) => {
+ mockPrivileges[key] = !value;
+
+ return mockPrivileges;
+ }, {} as EndpointPrivileges),
+ ...overrides,
+ };
+
+ return endpointPrivilegesMock;
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.test.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts
similarity index 63%
rename from x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.test.ts
rename to x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts
index 82443e913499b..d4ba29a4ef950 100644
--- a/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts
@@ -6,16 +6,17 @@
*/
import { act, renderHook, RenderHookResult, RenderResult } from '@testing-library/react-hooks';
-import { useHttp, useCurrentUser } from '../../lib/kibana';
+import { useHttp, useCurrentUser } from '../../../lib/kibana';
import { EndpointPrivileges, useEndpointPrivileges } from './use_endpoint_privileges';
-import { securityMock } from '../../../../../security/public/mocks';
-import { appRoutesService } from '../../../../../fleet/common';
-import { AuthenticatedUser } from '../../../../../security/common';
-import { licenseService } from '../../hooks/use_license';
-import { fleetGetCheckPermissionsHttpMock } from '../../../management/pages/mocks';
-
-jest.mock('../../lib/kibana');
-jest.mock('../../hooks/use_license', () => {
+import { securityMock } from '../../../../../../security/public/mocks';
+import { appRoutesService } from '../../../../../../fleet/common';
+import { AuthenticatedUser } from '../../../../../../security/common';
+import { licenseService } from '../../../hooks/use_license';
+import { fleetGetCheckPermissionsHttpMock } from '../../../../management/pages/mocks';
+import { getEndpointPrivilegesInitialStateMock } from './mocks';
+
+jest.mock('../../../lib/kibana');
+jest.mock('../../../hooks/use_license', () => {
const licenseServiceInstance = {
isPlatinumPlus: jest.fn(),
};
@@ -27,6 +28,8 @@ jest.mock('../../hooks/use_license', () => {
};
});
+const licenseServiceMock = licenseService as jest.Mocked;
+
describe('When using useEndpointPrivileges hook', () => {
let authenticatedUser: AuthenticatedUser;
let fleetApiMock: ReturnType;
@@ -45,7 +48,7 @@ describe('When using useEndpointPrivileges hook', () => {
fleetApiMock = fleetGetCheckPermissionsHttpMock(
useHttp() as Parameters[0]
);
- (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(true);
+ licenseServiceMock.isPlatinumPlus.mockReturnValue(true);
render = () => {
const hookRenderResponse = renderHook(() => useEndpointPrivileges());
@@ -69,34 +72,31 @@ describe('When using useEndpointPrivileges hook', () => {
(useCurrentUser as jest.Mock).mockReturnValue(null);
const { rerender } = render();
- expect(result.current).toEqual({
- canAccessEndpointManagement: false,
- canAccessFleet: false,
- loading: true,
- isPlatinumPlus: true,
- });
+ expect(result.current).toEqual(
+ getEndpointPrivilegesInitialStateMock({
+ canAccessEndpointManagement: false,
+ canAccessFleet: false,
+ loading: true,
+ })
+ );
// Make user service available
(useCurrentUser as jest.Mock).mockReturnValue(authenticatedUser);
rerender();
- expect(result.current).toEqual({
- canAccessEndpointManagement: false,
- canAccessFleet: false,
- loading: true,
- isPlatinumPlus: true,
- });
+ expect(result.current).toEqual(
+ getEndpointPrivilegesInitialStateMock({
+ canAccessEndpointManagement: false,
+ canAccessFleet: false,
+ loading: true,
+ })
+ );
// Release the API response
await act(async () => {
fleetApiMock.waitForApi();
releaseApiResponse!();
});
- expect(result.current).toEqual({
- canAccessEndpointManagement: true,
- canAccessFleet: true,
- loading: false,
- isPlatinumPlus: true,
- });
+ expect(result.current).toEqual(getEndpointPrivilegesInitialStateMock());
});
it('should call Fleet permissions api to determine user privilege to fleet', async () => {
@@ -113,12 +113,11 @@ describe('When using useEndpointPrivileges hook', () => {
render();
await waitForNextUpdate();
await fleetApiMock.waitForApi();
- expect(result.current).toEqual({
- canAccessEndpointManagement: false,
- canAccessFleet: true, // this is only true here because I did not adjust the API mock
- loading: false,
- isPlatinumPlus: true,
- });
+ expect(result.current).toEqual(
+ getEndpointPrivilegesInitialStateMock({
+ canAccessEndpointManagement: false,
+ })
+ );
});
it('should set privileges to false if fleet api check returns failure', async () => {
@@ -130,11 +129,21 @@ describe('When using useEndpointPrivileges hook', () => {
render();
await waitForNextUpdate();
await fleetApiMock.waitForApi();
- expect(result.current).toEqual({
- canAccessEndpointManagement: false,
- canAccessFleet: false,
- loading: false,
- isPlatinumPlus: true,
- });
+ expect(result.current).toEqual(
+ getEndpointPrivilegesInitialStateMock({
+ canAccessEndpointManagement: false,
+ canAccessFleet: false,
+ })
+ );
});
+
+ it.each([['canIsolateHost'], ['canCreateArtifactsByPolicy']])(
+ 'should set %s to false if license is not PlatinumPlus',
+ async (privilege) => {
+ licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
+ render();
+ await waitForNextUpdate();
+ expect(result.current).toEqual(expect.objectContaining({ [privilege]: false }));
+ }
+ );
});
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts
similarity index 71%
rename from x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.ts
rename to x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts
index 315935104d107..36f02d22487fc 100644
--- a/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.ts
+++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.ts
@@ -6,9 +6,9 @@
*/
import { useEffect, useMemo, useRef, useState } from 'react';
-import { useCurrentUser, useHttp } from '../../lib/kibana';
-import { appRoutesService, CheckPermissionsResponse } from '../../../../../fleet/common';
-import { useLicense } from '../../hooks/use_license';
+import { useCurrentUser, useHttp } from '../../../lib/kibana';
+import { appRoutesService, CheckPermissionsResponse } from '../../../../../../fleet/common';
+import { useLicense } from '../../../hooks/use_license';
export interface EndpointPrivileges {
loading: boolean;
@@ -16,6 +16,11 @@ export interface EndpointPrivileges {
canAccessFleet: boolean;
/** If user has permissions to access Endpoint management (includes check to ensure they also have access to fleet) */
canAccessEndpointManagement: boolean;
+ /** if user has permissions to create Artifacts by Policy */
+ canCreateArtifactsByPolicy: boolean;
+ /** If user has permissions to use the Host isolation feature */
+ canIsolateHost: boolean;
+ /** @deprecated do not use. instead, use one of the other privileges defined */
isPlatinumPlus: boolean;
}
@@ -29,7 +34,7 @@ export const useEndpointPrivileges = (): EndpointPrivileges => {
const http = useHttp();
const user = useCurrentUser();
const isMounted = useRef(true);
- const license = useLicense();
+ const isPlatinumPlusLicense = useLicense().isPlatinumPlus();
const [canAccessFleet, setCanAccessFleet] = useState(false);
const [fleetCheckDone, setFleetCheckDone] = useState(false);
@@ -61,13 +66,19 @@ export const useEndpointPrivileges = (): EndpointPrivileges => {
}, [user?.roles]);
const privileges = useMemo(() => {
- return {
+ const privilegeList: EndpointPrivileges = {
loading: !fleetCheckDone || !user,
canAccessFleet,
canAccessEndpointManagement: canAccessFleet && isSuperUser,
- isPlatinumPlus: license.isPlatinumPlus(),
+ canCreateArtifactsByPolicy: isPlatinumPlusLicense,
+ canIsolateHost: isPlatinumPlusLicense,
+ // FIXME: Remove usages of the property below
+ /** @deprecated */
+ isPlatinumPlus: isPlatinumPlusLicense,
};
- }, [canAccessFleet, fleetCheckDone, isSuperUser, user, license]);
+
+ return privilegeList;
+ }, [canAccessFleet, fleetCheckDone, isSuperUser, user, isPlatinumPlusLicense]);
// Capture if component is unmounted
useEffect(
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/utils.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/utils.ts
new file mode 100644
index 0000000000000..df91314479f18
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/utils.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EndpointPrivileges } from './use_endpoint_privileges';
+
+export const getEndpointPrivilegesInitialState = (): EndpointPrivileges => {
+ return {
+ loading: true,
+ canAccessFleet: false,
+ canAccessEndpointManagement: false,
+ canIsolateHost: false,
+ canCreateArtifactsByPolicy: false,
+ isPlatinumPlus: false,
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx
index 437d27278102b..bc0640296b33d 100644
--- a/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx
@@ -11,9 +11,10 @@ import { DeepReadonly } from 'utility-types';
import { Capabilities } from '../../../../../../../src/core/public';
import { useFetchDetectionEnginePrivileges } from '../../../detections/components/user_privileges/use_fetch_detection_engine_privileges';
import { useFetchListPrivileges } from '../../../detections/components/user_privileges/use_fetch_list_privileges';
-import { EndpointPrivileges, useEndpointPrivileges } from './use_endpoint_privileges';
+import { EndpointPrivileges, useEndpointPrivileges } from './endpoint';
import { SERVER_APP_ID } from '../../../../common/constants';
+import { getEndpointPrivilegesInitialState } from './endpoint/utils';
export interface UserPrivilegesState {
listPrivileges: ReturnType;
detectionEnginePrivileges: ReturnType;
@@ -24,12 +25,7 @@ export interface UserPrivilegesState {
export const initialUserPrivilegesState = (): UserPrivilegesState => ({
listPrivileges: { loading: false, error: undefined, result: undefined },
detectionEnginePrivileges: { loading: false, error: undefined, result: undefined },
- endpointPrivileges: {
- loading: true,
- canAccessEndpointManagement: false,
- canAccessFleet: false,
- isPlatinumPlus: false,
- },
+ endpointPrivileges: getEndpointPrivilegesInitialState(),
kibanaSecuritySolutionsPrivileges: { crud: false, read: false },
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx
index 3d95dca81165e..1a8588017e4d6 100644
--- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx
@@ -17,7 +17,7 @@ import { UserPrivilegesProvider } from '../../../common/components/user_privileg
jest.mock('../../../common/lib/kibana');
jest.mock('../../containers/detection_engine/alerts/api');
-jest.mock('../../../common/components/user_privileges/use_endpoint_privileges');
+jest.mock('../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
describe('useUserInfo', () => {
beforeAll(() => {
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx
index 40894c1d01929..1dc1423606097 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx
@@ -12,6 +12,7 @@ import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock';
import { useUserPrivileges } from '../../../../common/components/user_privileges';
import { Privilege } from './types';
import { UseAlertsPrivelegesReturn, useAlertsPrivileges } from './use_alerts_privileges';
+import { getEndpointPrivilegesInitialStateMock } from '../../../../common/components/user_privileges/endpoint/mocks';
jest.mock('./api');
jest.mock('../../../../common/hooks/use_app_toasts');
@@ -86,12 +87,11 @@ const userPrivilegesInitial: ReturnType = {
result: undefined,
error: undefined,
},
- endpointPrivileges: {
+ endpointPrivileges: getEndpointPrivilegesInitialStateMock({
loading: true,
canAccessEndpointManagement: false,
canAccessFleet: false,
- isPlatinumPlus: true,
- },
+ }),
kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
};
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx
index ade83fed4fd6b..ad4ad5062c9d5 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx
@@ -13,7 +13,7 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
jest.mock('./api');
jest.mock('../../../../common/hooks/use_app_toasts');
-jest.mock('../../../../common/components/user_privileges/use_endpoint_privileges');
+jest.mock('../../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
describe('useSignalIndex', () => {
let appToastsMock: jest.Mocked>;
diff --git a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx
index 084978d35d03a..3b987a7211411 100644
--- a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx
@@ -11,11 +11,12 @@ import { AppContextTestRender, createAppRootMockRenderer } from '../../../common
import {
EndpointPrivileges,
useEndpointPrivileges,
-} from '../../../common/components/user_privileges/use_endpoint_privileges';
+} from '../../../common/components/user_privileges/endpoint/use_endpoint_privileges';
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
import { SearchExceptions, SearchExceptionsProps } from '.';
-jest.mock('../../../common/components/user_privileges/use_endpoint_privileges');
+import { getEndpointPrivilegesInitialStateMock } from '../../../common/components/user_privileges/endpoint/mocks';
+jest.mock('../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
let onSearchMock: jest.Mock;
const mockUseEndpointPrivileges = useEndpointPrivileges as jest.Mock;
@@ -29,13 +30,11 @@ describe('Search exceptions', () => {
const loadedUserEndpointPrivilegesState = (
endpointOverrides: Partial = {}
- ): EndpointPrivileges => ({
- loading: false,
- canAccessFleet: true,
- canAccessEndpointManagement: true,
- isPlatinumPlus: false,
- ...endpointOverrides,
- });
+ ): EndpointPrivileges =>
+ getEndpointPrivilegesInitialStateMock({
+ isPlatinumPlus: false,
+ ...endpointOverrides,
+ });
beforeEach(() => {
onSearchMock = jest.fn();
diff --git a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx
index 1f3eab5db2947..569916ac20315 100644
--- a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx
@@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiButton } from '@elastic/e
import { i18n } from '@kbn/i18n';
import { PolicySelectionItem, PoliciesSelector } from '../policies_selector';
import { ImmutableArray, PolicyData } from '../../../../common/endpoint/types';
-import { useEndpointPrivileges } from '../../../common/components/user_privileges/use_endpoint_privileges';
+import { useEndpointPrivileges } from '../../../common/components/user_privileges/endpoint/use_endpoint_privileges';
export interface SearchExceptionsProps {
defaultValue?: string;
diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx
index 0729f95bb44a9..02efce1ab59e8 100644
--- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx
@@ -14,7 +14,7 @@ import { isFailedResourceState, isLoadedResourceState } from '../../../state';
// Needed to mock the data services used by the ExceptionItem component
jest.mock('../../../../common/lib/kibana');
-jest.mock('../../../../common/components/user_privileges/use_endpoint_privileges');
+jest.mock('../../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
describe('When on the Event Filters List Page', () => {
let render: () => ReturnType;
diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx
index 5113457e5bccc..625da11a3644e 100644
--- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx
@@ -16,8 +16,7 @@ import { getHostIsolationExceptionItems } from '../service';
import { HostIsolationExceptionsList } from './host_isolation_exceptions_list';
import { useLicense } from '../../../../common/hooks/use_license';
-jest.mock('../../../../common/components/user_privileges/use_endpoint_privileges');
-
+jest.mock('../../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
jest.mock('../service');
jest.mock('../../../../common/hooks/use_license');
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx
index ee52e1210a481..c12bec03ada04 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx
@@ -10,7 +10,7 @@ import { EuiEmptyPrompt, EuiButton, EuiPageTemplate, EuiLink } from '@elastic/eu
import { FormattedMessage } from '@kbn/i18n/react';
import { usePolicyDetailsNavigateCallback } from '../../policy_hooks';
import { useGetLinkTo } from './use_policy_trusted_apps_empty_hooks';
-import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges';
+import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges';
interface CommonProps {
policyId: string;
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.test.tsx
index c1d00f7a3f99b..8e412d2020b72 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.test.tsx
@@ -21,7 +21,7 @@ import { createLoadedResourceState, isLoadedResourceState } from '../../../../..
import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing';
jest.mock('../../../../trusted_apps/service');
-jest.mock('../../../../../../common/components/user_privileges/use_endpoint_privileges');
+jest.mock('../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
let mockedContext: AppContextTestRender;
let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx
index 43e19c00bcc8e..dbb18a1b0f2ef 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx
@@ -19,13 +19,11 @@ import { createLoadedResourceState, isLoadedResourceState } from '../../../../..
import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing';
import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data';
import { policyListApiPathHandlers } from '../../../store/test_mock_utils';
-import {
- EndpointPrivileges,
- useEndpointPrivileges,
-} from '../../../../../../common/components/user_privileges/use_endpoint_privileges';
+import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges';
+import { getEndpointPrivilegesInitialStateMock } from '../../../../../../common/components/user_privileges/endpoint/mocks';
jest.mock('../../../../trusted_apps/service');
-jest.mock('../../../../../../common/components/user_privileges/use_endpoint_privileges');
+jest.mock('../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
const mockUseEndpointPrivileges = useEndpointPrivileges as jest.Mock;
let mockedContext: AppContextTestRender;
@@ -37,16 +35,6 @@ let http: typeof coreStart.http;
const generator = new EndpointDocGenerator();
describe('Policy trusted apps layout', () => {
- const loadedUserEndpointPrivilegesState = (
- endpointOverrides: Partial = {}
- ): EndpointPrivileges => ({
- loading: false,
- canAccessFleet: true,
- canAccessEndpointManagement: true,
- isPlatinumPlus: true,
- ...endpointOverrides,
- });
-
beforeEach(() => {
mockedContext = createAppRootMockRenderer();
http = mockedContext.coreStart.http;
@@ -137,7 +125,7 @@ describe('Policy trusted apps layout', () => {
it('should hide assign button on empty state with unassigned policies when downgraded to a gold or below license', async () => {
mockUseEndpointPrivileges.mockReturnValue(
- loadedUserEndpointPrivilegesState({
+ getEndpointPrivilegesInitialStateMock({
isPlatinumPlus: false,
})
);
@@ -155,7 +143,7 @@ describe('Policy trusted apps layout', () => {
it('should hide the `Assign trusted applications` button when there is data and the license is downgraded to gold or below', async () => {
mockUseEndpointPrivileges.mockReturnValue(
- loadedUserEndpointPrivilegesState({
+ getEndpointPrivilegesInitialStateMock({
isPlatinumPlus: false,
})
);
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx
index a3f1ed215286a..49f76ad2e02c6 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx
@@ -30,7 +30,7 @@ import {
import { usePolicyDetailsNavigateCallback, usePolicyDetailsSelector } from '../../policy_hooks';
import { PolicyTrustedAppsFlyout } from '../flyout';
import { PolicyTrustedAppsList } from '../list/policy_trusted_apps_list';
-import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges';
+import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges';
import { useAppUrl } from '../../../../../../common/lib/kibana';
import { APP_ID } from '../../../../../../../common/constants';
import { getTrustedAppsListPath } from '../../../../../common/routing';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx
index a8d3cc1505463..e18d3c01791c0 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx
@@ -24,9 +24,10 @@ import { APP_ID } from '../../../../../../../common/constants';
import {
EndpointPrivileges,
useEndpointPrivileges,
-} from '../../../../../../common/components/user_privileges/use_endpoint_privileges';
+} from '../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges';
+import { getEndpointPrivilegesInitialStateMock } from '../../../../../../common/components/user_privileges/endpoint/mocks';
-jest.mock('../../../../../../common/components/user_privileges/use_endpoint_privileges');
+jest.mock('../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
const mockUseEndpointPrivileges = useEndpointPrivileges as jest.Mock;
describe('when rendering the PolicyTrustedAppsList', () => {
@@ -43,10 +44,7 @@ describe('when rendering the PolicyTrustedAppsList', () => {
const loadedUserEndpointPrivilegesState = (
endpointOverrides: Partial = {}
): EndpointPrivileges => ({
- loading: false,
- canAccessFleet: true,
- canAccessEndpointManagement: true,
- isPlatinumPlus: true,
+ ...getEndpointPrivilegesInitialStateMock(),
...endpointOverrides,
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx
index f6afd9d502486..def0f490b7fee 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx
@@ -38,7 +38,7 @@ import { ContextMenuItemNavByRouterProps } from '../../../../../components/conte
import { ArtifactEntryCollapsibleCardProps } from '../../../../../components/artifact_entry_card';
import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator';
import { RemoveTrustedAppFromPolicyModal } from './remove_trusted_app_from_policy_modal';
-import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges';
+import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges';
const DATA_TEST_SUBJ = 'policyTrustedAppsGrid';
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
index f39fd47c78771..b4366a8922927 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
@@ -52,7 +52,7 @@ jest.mock('../../../../common/hooks/use_license', () => {
};
});
-jest.mock('../../../../common/components/user_privileges/use_endpoint_privileges');
+jest.mock('../../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
describe('When on the Trusted Apps Page', () => {
const expectedAboutInfo =
diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx
index cab02450f8886..ab5ae4f613e38 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx
@@ -30,7 +30,7 @@ import {
mockCtiLinksResponse,
} from '../components/overview_cti_links/mock';
import { useCtiDashboardLinks } from '../containers/overview_cti_links';
-import { EndpointPrivileges } from '../../common/components/user_privileges/use_endpoint_privileges';
+import { EndpointPrivileges } from '../../common/components/user_privileges/endpoint/use_endpoint_privileges';
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
import { useHostsRiskScore } from '../containers/overview_risky_host_links/use_hosts_risk_score';
From 730df8852f63bc6b652cca4f31dd56ea4e2addba Mon Sep 17 00:00:00 2001
From: "Joey F. Poon"
Date: Tue, 19 Oct 2021 00:09:21 -0500
Subject: [PATCH 031/204] [Security Solution] fix endpoint list agent status
logic (#115286)
---
.../server/endpoint/routes/metadata/handlers.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts
index 027107bcf1a59..e98cdc4f11404 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts
@@ -48,6 +48,7 @@ import {
} from './support/query_strategies';
import { NotFoundError } from '../../errors';
import { EndpointHostUnEnrolledError } from '../../services/metadata';
+import { getAgentStatus } from '../../../../../fleet/common/services/agent_status';
export interface MetadataRequestContext {
esClient?: IScopedClusterClient;
@@ -522,10 +523,11 @@ async function queryUnitedIndex(
const agentPolicy = agentPoliciesMap[agent.policy_id!];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const endpointPolicy = endpointPoliciesMap[agent.policy_id!];
+ const fleetAgentStatus = getAgentStatus(agent as Agent);
+
return {
metadata,
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- host_status: fleetAgentStatusToEndpointHostStatus(agent.last_checkin_status!),
+ host_status: fleetAgentStatusToEndpointHostStatus(fleetAgentStatus),
policy_info: {
agent: {
applied: {
From cb17ec78969b11922993d70959cfa32574b3ca81 Mon Sep 17 00:00:00 2001
From: Dario Gieselaar
Date: Tue, 19 Oct 2021 09:22:29 +0200
Subject: [PATCH 032/204] [APM] Add readme for @elastic/apm-generator (#115368)
---
packages/elastic-apm-generator/README.md | 93 ++++++++++++++++++++++++
1 file changed, 93 insertions(+)
diff --git a/packages/elastic-apm-generator/README.md b/packages/elastic-apm-generator/README.md
index e69de29bb2d1d..e43187a8155d3 100644
--- a/packages/elastic-apm-generator/README.md
+++ b/packages/elastic-apm-generator/README.md
@@ -0,0 +1,93 @@
+# @elastic/apm-generator
+
+`@elastic/apm-generator` is an experimental tool to generate synthetic APM data. It is intended to be used for development and testing of the Elastic APM app in Kibana.
+
+At a high-level, the module works by modeling APM events/metricsets with [a fluent API](https://en.wikipedia.org/wiki/Fluent_interface). The models can then be serialized and converted to Elasticsearch documents. In the future we might support APM Server as an output as well.
+
+## Usage
+
+This section assumes that you've installed Kibana's dependencies by running `yarn kbn bootstrap` in the repository's root folder.
+
+This library can currently be used in two ways:
+
+- Imported as a Node.js module, for instance to be used in Kibana's functional test suite.
+- With a command line interface, to index data based on some example scenarios.
+
+### Using the Node.js module
+
+#### Concepts
+
+- `Service`: a logical grouping for a monitored service. A `Service` object contains fields like `service.name`, `service.environment` and `agent.name`.
+- `Instance`: a single instance of a monitored service. E.g., the workload for a monitored service might be spread across multiple containers. An `Instance` object contains fields like `service.node.name` and `container.id`.
+- `Timerange`: an object that will return an array of timestamps based on an interval and a rate. These timestamps can be used to generate events/metricsets.
+- `Transaction`, `Span`, `APMError` and `Metricset`: events/metricsets that occur on an instance. For more background, see the [explanation of the APM data model](https://www.elastic.co/guide/en/apm/get-started/7.15/apm-data-model.html)
+
+
+#### Example
+
+```ts
+import { service, timerange, toElasticsearchOutput } from '@elastic/apm-generator';
+
+const instance = service('synth-go', 'production', 'go')
+ .instance('instance-a');
+
+const from = new Date('2021-01-01T12:00:00.000Z').getTime();
+const to = new Date('2021-01-01T12:00:00.000Z').getTime() - 1;
+
+const traceEvents = timerange(from, to)
+ .interval('1m')
+ .rate(10)
+ .flatMap(timestamp => instance.transaction('GET /api/product/list')
+ .timestamp(timestamp)
+ .duration(1000)
+ .success()
+ .children(
+ instance.span('GET apm-*/_search', 'db', 'elasticsearch')
+ .timestamp(timestamp + 50)
+ .duration(900)
+ .destination('elasticsearch')
+ .success()
+ ).serialize()
+ );
+
+const metricsets = timerange(from, to)
+ .interval('30s')
+ .rate(1)
+ .flatMap(timestamp => instance.appMetrics({
+ 'system.memory.actual.free': 800,
+ 'system.memory.total': 1000,
+ 'system.cpu.total.norm.pct': 0.6,
+ 'system.process.cpu.total.norm.pct': 0.7,
+ }).timestamp(timestamp)
+ .serialize()
+ );
+
+const esEvents = toElasticsearchOutput(traceEvents.concat(metricsets));
+```
+
+#### Generating metricsets
+
+`@elastic/apm-generator` can also automatically generate transaction metrics, span destination metrics and transaction breakdown metrics based on the generated trace events. If we expand on the previous example:
+
+```ts
+import { getTransactionMetrics, getSpanDestinationMetrics, getBreakdownMetrics } from '@elastic/apm-generator';
+
+const esEvents = toElasticsearchOutput([
+ ...traceEvents,
+ ...getTransactionMetrics(traceEvents),
+ ...getSpanDestinationMetrics(traceEvents),
+ ...getBreakdownMetrics(traceEvents)
+]);
+```
+
+### CLI
+
+Via the CLI, you can upload examples. The supported examples are listed in `src/lib/es.ts`. A `--target` option that specifies the Elasticsearch URL should be defined when running the `example` command. Here's an example:
+
+`$ node packages/elastic-apm-generator/src/scripts/es.js example simple-trace --target=http://admin:changeme@localhost:9200`
+
+The following options are supported:
+- `to`: the end of the time range, in ISO format. By default, the current time will be used.
+- `from`: the start of the time range, in ISO format. By default, `to` minus 15 minutes will be used.
+- `apm-server-version`: the version used in the index names bootstrapped by APM Server, e.g. `7.16.0`. __If these indices do not exist, the script will exit with an error. It will not bootstrap the indices itself.__
+
From fd4b85b1c687b0a4d837b2715bedbdfce9cba77f Mon Sep 17 00:00:00 2001
From: Pete Hampton
Date: Tue, 19 Oct 2021 08:34:34 +0100
Subject: [PATCH 033/204] Update heading on telemetry management section.
(#115425)
---
.../telemetry_management_section.test.tsx.snap | 4 ++--
.../public/components/telemetry_management_section.tsx | 8 ++++----
x-pack/plugins/translations/translations/ja-JP.json | 2 --
x-pack/plugins/translations/translations/zh-CN.json | 2 --
4 files changed, 6 insertions(+), 10 deletions(-)
diff --git a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap
index 758ecf54f4bf0..72947b1514911 100644
--- a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap
+++ b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap
@@ -53,7 +53,7 @@ exports[`TelemetryManagementSectionComponent renders as expected 1`] = `
loading={false}
setting={
Object {
- "ariaName": "Provide usage statistics",
+ "ariaName": "Provide usage data",
"category": Array [],
"defVal": true,
"description":
@@ -109,7 +109,7 @@ exports[`TelemetryManagementSectionComponent renders as expected 1`] = `
/>
,
- "displayName": "Provide usage statistics",
+ "displayName": "Provide usage data",
"isCustom": true,
"isOverridden": false,
"name": "telemetry:enabled",
diff --git a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx
index 3686cb10706bf..037603cb165d9 100644
--- a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx
+++ b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx
@@ -116,14 +116,14 @@ export class TelemetryManagementSection extends Component {
setting={{
type: 'boolean',
name: 'telemetry:enabled',
- displayName: i18n.translate('telemetry.provideUsageStatisticsTitle', {
- defaultMessage: 'Provide usage statistics',
+ displayName: i18n.translate('telemetry.provideUsageDataTitle', {
+ defaultMessage: 'Provide usage data',
}),
value: enabled,
description: this.renderDescription(),
defVal: true,
- ariaName: i18n.translate('telemetry.provideUsageStatisticsAriaName', {
- defaultMessage: 'Provide usage statistics',
+ ariaName: i18n.translate('telemetry.provideUsageDataAriaName', {
+ defaultMessage: 'Provide usage data',
}),
requiresPageReload: false,
category: [],
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 852b01977b78b..acf9daebaf552 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -4508,8 +4508,6 @@
"telemetry.optInNoticeSeenErrorToastText": "通知の消去中にエラーが発生しました",
"telemetry.optInSuccessOff": "使用状況データ収集がオフです。",
"telemetry.optInSuccessOn": "使用状況データ収集がオンです。",
- "telemetry.provideUsageStatisticsAriaName": "使用統計を提供",
- "telemetry.provideUsageStatisticsTitle": "使用統計を提供",
"telemetry.readOurUsageDataPrivacyStatementLinkText": "プライバシーポリシー",
"telemetry.securityData": "Endpoint Security データ",
"telemetry.telemetryBannerDescription": "Elastic Stackの改善にご協力ください使用状況データの収集は現在無効です。使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細については、{privacyStatementLink}をご覧ください。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 9d88c757f1e58..5890f8553e7c5 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -4553,8 +4553,6 @@
"telemetry.optInNoticeSeenErrorToastText": "关闭声明时发生错误",
"telemetry.optInSuccessOff": "使用情况数据收集已关闭。",
"telemetry.optInSuccessOn": "使用情况数据收集已打开。",
- "telemetry.provideUsageStatisticsAriaName": "提供使用情况统计",
- "telemetry.provideUsageStatisticsTitle": "提供使用情况统计",
"telemetry.readOurUsageDataPrivacyStatementLinkText": "隐私声明",
"telemetry.securityData": "终端安全数据",
"telemetry.telemetryBannerDescription": "想帮助我们改进 Elastic Stack?数据使用情况收集当前已禁用。启用使用情况数据收集可帮助我们管理并改善产品和服务。有关更多详情,请参阅我们的{privacyStatementLink}。",
From 533e5d8d393c20551f811d06632f19a44bcd25ab Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Tue, 19 Oct 2021 03:36:46 -0400
Subject: [PATCH 034/204] [Security Solution][Endpoint] Change Trusted Apps to
use `item_id` as its identifier and Enable Trusted Apps filtering by id in
the UI (#115276)
* Add `item_id` to list of searchable fields
* trusted apps api changes to use `item_id` instead of SO `id`
* Change Policy Details Trusted App "View all details" action URL to show TA list filtered by the TA id
---
.../list/policy_trusted_apps_list.test.tsx | 2 +-
.../list/policy_trusted_apps_list.tsx | 2 +-
.../pages/trusted_apps/constants.ts | 1 +
.../routes/trusted_apps/handlers.test.ts | 5 +-
.../endpoint/routes/trusted_apps/mapping.ts | 2 +-
.../routes/trusted_apps/service.test.ts | 24 +++++-
.../endpoint/routes/trusted_apps/service.ts | 77 +++++++++++++------
7 files changed, 84 insertions(+), 29 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx
index e18d3c01791c0..9165aec3bef8d 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx
@@ -205,7 +205,7 @@ describe('when rendering the PolicyTrustedAppsList', () => {
expect(appTestContext.coreStart.application.navigateToApp).toHaveBeenCalledWith(
APP_ID,
expect.objectContaining({
- path: '/administration/trusted_apps?show=edit&id=89f72d8a-05b5-4350-8cad-0dc3661d6e67',
+ path: '/administration/trusted_apps?filter=89f72d8a-05b5-4350-8cad-0dc3661d6e67',
})
);
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx
index def0f490b7fee..89ff6bd099be4 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx
@@ -113,7 +113,7 @@ export const PolicyTrustedAppsList = memo(
for (const trustedApp of trustedAppItems) {
const isGlobal = trustedApp.effectScope.type === 'global';
- const viewUrlPath = getTrustedAppsListPath({ id: trustedApp.id, show: 'edit' });
+ const viewUrlPath = getTrustedAppsListPath({ filter: trustedApp.id });
const assignedPoliciesMenuItems: ArtifactEntryCollapsibleCardProps['policies'] =
trustedApp.effectScope.type === 'global'
? undefined
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts
index 0602ae18c1408..beefb8587d787 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts
@@ -8,6 +8,7 @@
export const SEARCHABLE_FIELDS: Readonly = [
`name`,
`description`,
+ 'item_id',
`entries.value`,
`entries.entries.value`,
];
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts
index 547c1f6a2e5ff..614ad4fb548ea 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts
@@ -110,7 +110,7 @@ const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold' }
const packagePolicyClient =
createPackagePolicyServiceMock() as jest.Mocked;
-describe('handlers', () => {
+describe('TrustedApps API Handlers', () => {
beforeEach(() => {
packagePolicyClient.getByIDs.mockReset();
});
@@ -195,6 +195,7 @@ describe('handlers', () => {
const mockResponse = httpServerMock.createResponseFactory();
exceptionsListClient.deleteExceptionListItem.mockResolvedValue(null);
+ exceptionsListClient.getExceptionListItem.mockResolvedValue(null);
await deleteTrustedAppHandler(
createHandlerContextMock(),
@@ -582,7 +583,7 @@ describe('handlers', () => {
});
it('should return 404 if trusted app does not exist', async () => {
- exceptionsListClient.getExceptionListItem.mockResolvedValueOnce(null);
+ exceptionsListClient.getExceptionListItem.mockResolvedValue(null);
await updateHandler(
createHandlerContextMock(),
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts
index 2c085c14db009..08c1a3a809d4a 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts
@@ -122,7 +122,7 @@ export const exceptionListItemToTrustedApp = (
const grouped = entriesToConditionEntriesMap(exceptionListItem.entries);
return {
- id: exceptionListItem.id,
+ id: exceptionListItem.item_id,
version: exceptionListItem._version || '',
name: exceptionListItem.name,
description: exceptionListItem.description,
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts
index dce84df735929..c57416ff1c974 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts
@@ -85,9 +85,10 @@ const TRUSTED_APP: TrustedApp = {
],
};
-describe('service', () => {
+describe('TrustedApps service', () => {
beforeEach(() => {
exceptionsListClient.deleteExceptionListItem.mockReset();
+ exceptionsListClient.getExceptionListItem.mockReset();
exceptionsListClient.createExceptionListItem.mockReset();
exceptionsListClient.findExceptionListItem.mockReset();
exceptionsListClient.createTrustedAppsList.mockReset();
@@ -96,6 +97,7 @@ describe('service', () => {
describe('deleteTrustedApp', () => {
it('should delete existing trusted app', async () => {
+ exceptionsListClient.getExceptionListItem.mockResolvedValue(EXCEPTION_LIST_ITEM);
exceptionsListClient.deleteExceptionListItem.mockResolvedValue(EXCEPTION_LIST_ITEM);
expect(await deleteTrustedApp(exceptionsListClient, { id: '123' })).toBeUndefined();
@@ -107,6 +109,7 @@ describe('service', () => {
});
it('should throw for non existing trusted app', async () => {
+ exceptionsListClient.getExceptionListItem.mockResolvedValue(null);
exceptionsListClient.deleteExceptionListItem.mockResolvedValue(null);
await expect(deleteTrustedApp(exceptionsListClient, { id: '123' })).rejects.toBeInstanceOf(
@@ -393,7 +396,7 @@ describe('service', () => {
});
it('should throw a Not Found error if trusted app is not found prior to making update', async () => {
- exceptionsListClient.getExceptionListItem.mockResolvedValueOnce(null);
+ exceptionsListClient.getExceptionListItem.mockResolvedValue(null);
await expect(
updateTrustedApp(
exceptionsListClient,
@@ -489,5 +492,22 @@ describe('service', () => {
TrustedAppNotFoundError
);
});
+
+ it('should try to find trusted app by `itemId` and then by `id`', async () => {
+ exceptionsListClient.getExceptionListItem.mockResolvedValue(null);
+ await getTrustedApp(exceptionsListClient, '123').catch(() => Promise.resolve());
+
+ expect(exceptionsListClient.getExceptionListItem).toHaveBeenCalledTimes(2);
+ expect(exceptionsListClient.getExceptionListItem).toHaveBeenNthCalledWith(1, {
+ itemId: '123',
+ id: undefined,
+ namespaceType: 'agnostic',
+ });
+ expect(exceptionsListClient.getExceptionListItem).toHaveBeenNthCalledWith(2, {
+ itemId: undefined,
+ id: '123',
+ namespaceType: 'agnostic',
+ });
+ });
});
});
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.ts
index 856a615c1ffa2..7a4b2372ece8f 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.ts
@@ -15,13 +15,13 @@ import {
DeleteTrustedAppsRequestParams,
GetOneTrustedAppResponse,
GetTrustedAppsListRequest,
- GetTrustedAppsSummaryResponse,
GetTrustedAppsListResponse,
+ GetTrustedAppsSummaryRequest,
+ GetTrustedAppsSummaryResponse,
PostTrustedAppCreateRequest,
PostTrustedAppCreateResponse,
PutTrustedAppUpdateRequest,
PutTrustedAppUpdateResponse,
- GetTrustedAppsSummaryRequest,
TrustedApp,
} from '../../../../common/endpoint/types';
@@ -33,8 +33,8 @@ import {
} from './mapping';
import {
TrustedAppNotFoundError,
- TrustedAppVersionConflictError,
TrustedAppPolicyNotExistsError,
+ TrustedAppVersionConflictError,
} from './errors';
import { PackagePolicyServiceInterface } from '../../../../../fleet/server';
import { PackagePolicy } from '../../../../../fleet/common';
@@ -87,30 +87,61 @@ const isUserTryingToModifyEffectScopeWithoutPermissions = (
}
};
-export const deleteTrustedApp = async (
+/**
+ * Attempts to first fine the ExceptionItem using `item_id` and if not found, then a second attempt wil be done
+ * against the Saved Object `id`.
+ * @param exceptionsListClient
+ * @param id
+ */
+export const findTrustedAppExceptionItemByIdOrItemId = async (
exceptionsListClient: ExceptionListClient,
- { id }: DeleteTrustedAppsRequestParams
-) => {
- const exceptionListItem = await exceptionsListClient.deleteExceptionListItem({
- id,
+ id: string
+): Promise => {
+ const trustedAppExceptionItem = await exceptionsListClient.getExceptionListItem({
+ itemId: id,
+ id: undefined,
+ namespaceType: 'agnostic',
+ });
+
+ if (trustedAppExceptionItem) {
+ return trustedAppExceptionItem;
+ }
+
+ return exceptionsListClient.getExceptionListItem({
itemId: undefined,
+ id,
namespaceType: 'agnostic',
});
+};
- if (!exceptionListItem) {
+export const deleteTrustedApp = async (
+ exceptionsListClient: ExceptionListClient,
+ { id }: DeleteTrustedAppsRequestParams
+): Promise => {
+ const trustedAppExceptionItem = await findTrustedAppExceptionItemByIdOrItemId(
+ exceptionsListClient,
+ id
+ );
+
+ if (!trustedAppExceptionItem) {
throw new TrustedAppNotFoundError(id);
}
+
+ await exceptionsListClient.deleteExceptionListItem({
+ id: trustedAppExceptionItem.id,
+ itemId: undefined,
+ namespaceType: 'agnostic',
+ });
};
export const getTrustedApp = async (
exceptionsListClient: ExceptionListClient,
id: string
): Promise => {
- const trustedAppExceptionItem = await exceptionsListClient.getExceptionListItem({
- itemId: '',
- id,
- namespaceType: 'agnostic',
- });
+ const trustedAppExceptionItem = await findTrustedAppExceptionItemByIdOrItemId(
+ exceptionsListClient,
+ id
+ );
if (!trustedAppExceptionItem) {
throw new TrustedAppNotFoundError(id);
@@ -189,19 +220,18 @@ export const updateTrustedApp = async (
updatedTrustedApp: PutTrustedAppUpdateRequest,
isAtLeastPlatinum: boolean
): Promise => {
- const currentTrustedApp = await exceptionsListClient.getExceptionListItem({
- itemId: '',
- id,
- namespaceType: 'agnostic',
- });
+ const currentTrustedAppExceptionItem = await findTrustedAppExceptionItemByIdOrItemId(
+ exceptionsListClient,
+ id
+ );
- if (!currentTrustedApp) {
+ if (!currentTrustedAppExceptionItem) {
throw new TrustedAppNotFoundError(id);
}
if (
isUserTryingToModifyEffectScopeWithoutPermissions(
- exceptionListItemToTrustedApp(currentTrustedApp),
+ exceptionListItemToTrustedApp(currentTrustedAppExceptionItem),
updatedTrustedApp,
isAtLeastPlatinum
)
@@ -226,7 +256,10 @@ export const updateTrustedApp = async (
try {
updatedTrustedAppExceptionItem = await exceptionsListClient.updateExceptionListItem(
- updatedTrustedAppToUpdateExceptionListItemOptions(currentTrustedApp, updatedTrustedApp)
+ updatedTrustedAppToUpdateExceptionListItemOptions(
+ currentTrustedAppExceptionItem,
+ updatedTrustedApp
+ )
);
} catch (e) {
if (e?.output?.statusCode === 409) {
From bfe648d49606135aa52814932d7611893496c447 Mon Sep 17 00:00:00 2001
From: Pablo Machado
Date: Tue, 19 Oct 2021 09:37:01 +0200
Subject: [PATCH 035/204] Fix alerts Count table title overflow wraps
prematurely (#115364)
---
.../components/alerts_kpis/alerts_count_panel/index.tsx | 2 +-
.../components/alerts_kpis/alerts_histogram_panel/index.tsx | 6 +++++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx
index 29324d186784e..c8d45ca67068a 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx
@@ -94,7 +94,7 @@ export const AlertsCountPanel = memo(
{i18n.COUNT_TABLE_TITLE}}
titleSize="s"
hideSubtitle
>
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx
index 0613c619d89b9..07fa81f27684c 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx
@@ -257,7 +257,11 @@ export const AlertsHistogramPanel = memo(
}, [showLinkToAlerts, goToDetectionEngine, formatUrl]);
const titleText = useMemo(
- () => (onlyField == null ? title : i18n.TOP(onlyField)),
+ () => (
+
+ {onlyField == null ? title : i18n.TOP(onlyField)}
+
+ ),
[onlyField, title]
);
From db834698974ea40b452316d0ca3d37a1541bed64 Mon Sep 17 00:00:00 2001
From: Esteban Beltran
Date: Tue, 19 Oct 2021 10:00:49 +0200
Subject: [PATCH 036/204] [Security Solution] Generate host isolation
exceptions artifact (#115160)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../server/endpoint/lib/artifacts/common.ts | 3 +
.../server/endpoint/lib/artifacts/lists.ts | 20 +++
.../manifest_manager/manifest_manager.test.ts | 137 ++++++++++--------
.../manifest_manager/manifest_manager.ts | 42 ++++++
4 files changed, 140 insertions(+), 62 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts
index af5e386464305..60f91330d4558 100644
--- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts
@@ -25,6 +25,9 @@ export const ArtifactConstants = {
SUPPORTED_EVENT_FILTERS_OPERATING_SYSTEMS: ['macos', 'windows', 'linux'],
GLOBAL_EVENT_FILTERS_NAME: 'endpoint-eventfilterlist',
+
+ SUPPORTED_HOST_ISOLATION_EXCEPTIONS_OPERATING_SYSTEMS: ['macos', 'windows', 'linux'],
+ GLOBAL_HOST_ISOLATION_EXCEPTIONS_NAME: 'endpoint-hostisolationexceptionlist',
};
export const ManifestConstants = {
diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
index e27a09efd9710..e26a2c7f4b4bc 100644
--- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
@@ -15,6 +15,7 @@ import { validate } from '@kbn/securitysolution-io-ts-utils';
import {
ENDPOINT_EVENT_FILTERS_LIST_ID,
+ ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
ENDPOINT_LIST_ID,
ENDPOINT_TRUSTED_APPS_LIST_ID,
} from '@kbn/securitysolution-list-constants';
@@ -65,6 +66,7 @@ export async function getFilteredEndpointExceptionList(
| typeof ENDPOINT_LIST_ID
| typeof ENDPOINT_TRUSTED_APPS_LIST_ID
| typeof ENDPOINT_EVENT_FILTERS_LIST_ID
+ | typeof ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID
): Promise {
const exceptions: WrappedTranslatedExceptionList = { entries: [] };
let page = 1;
@@ -148,6 +150,24 @@ export async function getEndpointEventFiltersList(
);
}
+export async function getHostIsolationExceptionsList(
+ eClient: ExceptionListClient,
+ schemaVersion: string,
+ os: string,
+ policyId?: string
+): Promise {
+ const osFilter = `exception-list-agnostic.attributes.os_types:\"${os}\"`;
+ const policyFilter = `(exception-list-agnostic.attributes.tags:\"policy:all\"${
+ policyId ? ` or exception-list-agnostic.attributes.tags:\"policy:${policyId}\"` : ''
+ })`;
+
+ return getFilteredEndpointExceptionList(
+ eClient,
+ schemaVersion,
+ `${osFilter} and ${policyFilter}`,
+ ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID
+ );
+}
/**
* Translates Exception list items to Exceptions the endpoint can understand
* @param exceptions
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts
index d75e347b86bd5..0ef2abd5f50aa 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts
@@ -7,6 +7,7 @@
import { savedObjectsClientMock } from 'src/core/server/mocks';
import {
+ ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
ENDPOINT_LIST_ID,
ENDPOINT_TRUSTED_APPS_LIST_ID,
} from '@kbn/securitysolution-list-constants';
@@ -66,6 +67,12 @@ describe('ManifestManager', () => {
const ARTIFACT_NAME_EVENT_FILTERS_MACOS = 'endpoint-eventfilterlist-macos-v1';
const ARTIFACT_NAME_EVENT_FILTERS_WINDOWS = 'endpoint-eventfilterlist-windows-v1';
const ARTIFACT_NAME_EVENT_FILTERS_LINUX = 'endpoint-eventfilterlist-linux-v1';
+ const ARTIFACT_NAME_HOST_ISOLATION_EXCEPTIONS_MACOS =
+ 'endpoint-hostisolationexceptionlist-macos-v1';
+ const ARTIFACT_NAME_HOST_ISOLATION_EXCEPTIONS_WINDOWS =
+ 'endpoint-hostisolationexceptionlist-windows-v1';
+ const ARTIFACT_NAME_HOST_ISOLATION_EXCEPTIONS_LINUX =
+ 'endpoint-hostisolationexceptionlist-linux-v1';
let ARTIFACTS: InternalArtifactCompleteSchema[] = [];
let ARTIFACTS_BY_ID: { [K: string]: InternalArtifactCompleteSchema } = {};
@@ -157,31 +164,29 @@ describe('ManifestManager', () => {
const manifestManagerContext = buildManifestManagerContextMock({ savedObjectsClient });
const manifestManager = new ManifestManager(manifestManagerContext);
- savedObjectsClient.get = jest
- .fn()
- .mockImplementation(async (objectType: string, id: string) => {
- if (objectType === ManifestConstants.SAVED_OBJECT_TYPE) {
- return {
- attributes: {
- created: '20-01-2020 10:00:00.000Z',
- schemaVersion: 'v2',
- semanticVersion: '1.0.0',
- artifacts: [
- { artifactId: ARTIFACT_ID_EXCEPTIONS_MACOS, policyId: undefined },
- { artifactId: ARTIFACT_ID_EXCEPTIONS_WINDOWS, policyId: undefined },
- { artifactId: ARTIFACT_ID_EXCEPTIONS_LINUX, policyId: undefined },
- { artifactId: ARTIFACT_ID_EXCEPTIONS_WINDOWS, policyId: TEST_POLICY_ID_1 },
- { artifactId: ARTIFACT_ID_TRUSTED_APPS_MACOS, policyId: TEST_POLICY_ID_1 },
- { artifactId: ARTIFACT_ID_TRUSTED_APPS_WINDOWS, policyId: TEST_POLICY_ID_1 },
- { artifactId: ARTIFACT_ID_TRUSTED_APPS_WINDOWS, policyId: TEST_POLICY_ID_2 },
- ],
- },
- version: '2.0.0',
- };
- } else {
- return null;
- }
- });
+ savedObjectsClient.get = jest.fn().mockImplementation(async (objectType: string) => {
+ if (objectType === ManifestConstants.SAVED_OBJECT_TYPE) {
+ return {
+ attributes: {
+ created: '20-01-2020 10:00:00.000Z',
+ schemaVersion: 'v2',
+ semanticVersion: '1.0.0',
+ artifacts: [
+ { artifactId: ARTIFACT_ID_EXCEPTIONS_MACOS, policyId: undefined },
+ { artifactId: ARTIFACT_ID_EXCEPTIONS_WINDOWS, policyId: undefined },
+ { artifactId: ARTIFACT_ID_EXCEPTIONS_LINUX, policyId: undefined },
+ { artifactId: ARTIFACT_ID_EXCEPTIONS_WINDOWS, policyId: TEST_POLICY_ID_1 },
+ { artifactId: ARTIFACT_ID_TRUSTED_APPS_MACOS, policyId: TEST_POLICY_ID_1 },
+ { artifactId: ARTIFACT_ID_TRUSTED_APPS_WINDOWS, policyId: TEST_POLICY_ID_1 },
+ { artifactId: ARTIFACT_ID_TRUSTED_APPS_WINDOWS, policyId: TEST_POLICY_ID_2 },
+ ],
+ },
+ version: '2.0.0',
+ };
+ } else {
+ return null;
+ }
+ });
(
manifestManagerContext.artifactClient as jest.Mocked
@@ -218,31 +223,29 @@ describe('ManifestManager', () => {
const manifestManagerContext = buildManifestManagerContextMock({ savedObjectsClient });
const manifestManager = new ManifestManager(manifestManagerContext);
- savedObjectsClient.get = jest
- .fn()
- .mockImplementation(async (objectType: string, id: string) => {
- if (objectType === ManifestConstants.SAVED_OBJECT_TYPE) {
- return {
- attributes: {
- created: '20-01-2020 10:00:00.000Z',
- schemaVersion: 'v2',
- semanticVersion: '1.0.0',
- artifacts: [
- { artifactId: ARTIFACT_ID_EXCEPTIONS_MACOS, policyId: undefined },
- { artifactId: ARTIFACT_ID_EXCEPTIONS_WINDOWS, policyId: undefined },
- { artifactId: ARTIFACT_ID_EXCEPTIONS_LINUX, policyId: undefined },
- { artifactId: ARTIFACT_ID_EXCEPTIONS_WINDOWS, policyId: TEST_POLICY_ID_1 },
- { artifactId: ARTIFACT_ID_TRUSTED_APPS_MACOS, policyId: TEST_POLICY_ID_1 },
- { artifactId: ARTIFACT_ID_TRUSTED_APPS_WINDOWS, policyId: TEST_POLICY_ID_1 },
- { artifactId: ARTIFACT_ID_TRUSTED_APPS_WINDOWS, policyId: TEST_POLICY_ID_2 },
- ],
- },
- version: '2.0.0',
- };
- } else {
- return null;
- }
- });
+ savedObjectsClient.get = jest.fn().mockImplementation(async (objectType: string) => {
+ if (objectType === ManifestConstants.SAVED_OBJECT_TYPE) {
+ return {
+ attributes: {
+ created: '20-01-2020 10:00:00.000Z',
+ schemaVersion: 'v2',
+ semanticVersion: '1.0.0',
+ artifacts: [
+ { artifactId: ARTIFACT_ID_EXCEPTIONS_MACOS, policyId: undefined },
+ { artifactId: ARTIFACT_ID_EXCEPTIONS_WINDOWS, policyId: undefined },
+ { artifactId: ARTIFACT_ID_EXCEPTIONS_LINUX, policyId: undefined },
+ { artifactId: ARTIFACT_ID_EXCEPTIONS_WINDOWS, policyId: TEST_POLICY_ID_1 },
+ { artifactId: ARTIFACT_ID_TRUSTED_APPS_MACOS, policyId: TEST_POLICY_ID_1 },
+ { artifactId: ARTIFACT_ID_TRUSTED_APPS_WINDOWS, policyId: TEST_POLICY_ID_1 },
+ { artifactId: ARTIFACT_ID_TRUSTED_APPS_WINDOWS, policyId: TEST_POLICY_ID_2 },
+ ],
+ },
+ version: '2.0.0',
+ };
+ } else {
+ return null;
+ }
+ });
(
manifestManagerContext.artifactClient as jest.Mocked
@@ -278,6 +281,9 @@ describe('ManifestManager', () => {
ARTIFACT_NAME_EVENT_FILTERS_MACOS,
ARTIFACT_NAME_EVENT_FILTERS_WINDOWS,
ARTIFACT_NAME_EVENT_FILTERS_LINUX,
+ ARTIFACT_NAME_HOST_ISOLATION_EXCEPTIONS_MACOS,
+ ARTIFACT_NAME_HOST_ISOLATION_EXCEPTIONS_WINDOWS,
+ ARTIFACT_NAME_HOST_ISOLATION_EXCEPTIONS_LINUX,
];
const getArtifactIds = (artifacts: InternalArtifactSchema[]) => [
@@ -310,7 +316,7 @@ describe('ManifestManager', () => {
context.savedObjectsClient.create = jest
.fn()
- .mockImplementation((type: string, object: InternalManifestSchema) => ({
+ .mockImplementation((_type: string, object: InternalManifestSchema) => ({
attributes: object,
}));
const manifest = await manifestManager.buildNewManifest();
@@ -321,7 +327,7 @@ describe('ManifestManager', () => {
const artifacts = manifest.getAllArtifacts();
- expect(artifacts.length).toBe(9);
+ expect(artifacts.length).toBe(12);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);
for (const artifact of artifacts) {
@@ -336,16 +342,18 @@ describe('ManifestManager', () => {
test('Builds fully new manifest if no baseline parameter passed and present exception list items', async () => {
const exceptionListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
const trustedAppListItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
+ const hostIsolationExceptionsItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
const context = buildManifestManagerContextMock({});
const manifestManager = new ManifestManager(context);
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] },
+ [ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: { linux: [hostIsolationExceptionsItem] },
});
context.savedObjectsClient.create = jest
.fn()
- .mockImplementation((type: string, object: InternalManifestSchema) => ({
+ .mockImplementation((_type: string, object: InternalManifestSchema) => ({
attributes: object,
}));
context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]);
@@ -358,7 +366,7 @@ describe('ManifestManager', () => {
const artifacts = manifest.getAllArtifacts();
- expect(artifacts.length).toBe(9);
+ expect(artifacts.length).toBe(12);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);
expect(getArtifactObject(artifacts[0])).toStrictEqual({
@@ -374,6 +382,11 @@ describe('ManifestManager', () => {
expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[8])).toStrictEqual({ entries: [] });
+ expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] });
+ expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] });
+ expect(getArtifactObject(artifacts[11])).toStrictEqual({
+ entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'),
+ });
for (const artifact of artifacts) {
expect(manifest.isDefaultArtifact(artifact)).toBe(true);
@@ -395,7 +408,7 @@ describe('ManifestManager', () => {
context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]);
context.savedObjectsClient.create = jest
.fn()
- .mockImplementation((type: string, object: InternalManifestSchema) => ({
+ .mockImplementation((_type: string, object: InternalManifestSchema) => ({
attributes: object,
}));
const oldManifest = await manifestManager.buildNewManifest();
@@ -413,7 +426,7 @@ describe('ManifestManager', () => {
const artifacts = manifest.getAllArtifacts();
- expect(artifacts.length).toBe(9);
+ expect(artifacts.length).toBe(12);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);
expect(artifacts[0]).toStrictEqual(oldManifest.getAllArtifacts()[0]);
@@ -462,7 +475,7 @@ describe('ManifestManager', () => {
context.savedObjectsClient.create = jest
.fn()
- .mockImplementation((type: string, object: InternalManifestSchema) => ({
+ .mockImplementation((_type: string, object: InternalManifestSchema) => ({
attributes: object,
}));
@@ -474,7 +487,7 @@ describe('ManifestManager', () => {
const artifacts = manifest.getAllArtifacts();
- expect(artifacts.length).toBe(10);
+ expect(artifacts.length).toBe(13);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);
expect(getArtifactObject(artifacts[0])).toStrictEqual({
@@ -653,7 +666,7 @@ describe('ManifestManager', () => {
context.savedObjectsClient.create = jest
.fn()
- .mockImplementation((type: string, object: InternalManifestSchema) => object);
+ .mockImplementation((_type: string, object: InternalManifestSchema) => object);
await expect(manifestManager.commit(manifest)).resolves.toBeUndefined();
@@ -690,7 +703,7 @@ describe('ManifestManager', () => {
context.savedObjectsClient.update = jest
.fn()
- .mockImplementation((type: string, id: string, object: InternalManifestSchema) => object);
+ .mockImplementation((_type: string, _id: string, object: InternalManifestSchema) => object);
await expect(manifestManager.commit(manifest)).resolves.toBeUndefined();
@@ -1023,7 +1036,7 @@ describe('ManifestManager', () => {
context.savedObjectsClient.create = jest
.fn()
- .mockImplementation((type: string, object: InternalManifestSchema) => ({
+ .mockImplementation((_type: string, object: InternalManifestSchema) => ({
attributes: object,
}));
const manifest = await manifestManager.buildNewManifest();
@@ -1046,7 +1059,7 @@ describe('ManifestManager', () => {
context.savedObjectsClient.create = jest
.fn()
- .mockImplementation((type: string, object: InternalManifestSchema) => ({
+ .mockImplementation((_type: string, object: InternalManifestSchema) => ({
attributes: object,
}));
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts
index 5c1d327b1b892..736bf1c58cb90 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts
@@ -26,6 +26,7 @@ import {
getEndpointEventFiltersList,
getEndpointExceptionList,
getEndpointTrustedAppsList,
+ getHostIsolationExceptionsList,
Manifest,
} from '../../../lib/artifacts';
import {
@@ -237,6 +238,46 @@ export class ManifestManager {
);
}
+ protected async buildHostIsolationExceptionsArtifacts(): Promise {
+ const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
+ const policySpecificArtifacts: Record = {};
+
+ for (const os of ArtifactConstants.SUPPORTED_HOST_ISOLATION_EXCEPTIONS_OPERATING_SYSTEMS) {
+ defaultArtifacts.push(await this.buildHostIsolationExceptionForOs(os));
+ }
+
+ await iterateAllListItems(
+ (page) => this.listEndpointPolicyIds(page),
+ async (policyId) => {
+ for (const os of ArtifactConstants.SUPPORTED_HOST_ISOLATION_EXCEPTIONS_OPERATING_SYSTEMS) {
+ policySpecificArtifacts[policyId] = policySpecificArtifacts[policyId] || [];
+ policySpecificArtifacts[policyId].push(
+ await this.buildHostIsolationExceptionForOs(os, policyId)
+ );
+ }
+ }
+ );
+
+ return { defaultArtifacts, policySpecificArtifacts };
+ }
+
+ protected async buildHostIsolationExceptionForOs(
+ os: string,
+ policyId?: string
+ ): Promise {
+ return buildArtifact(
+ await getHostIsolationExceptionsList(
+ this.exceptionListClient,
+ this.schemaVersion,
+ os,
+ policyId
+ ),
+ this.schemaVersion,
+ os,
+ ArtifactConstants.GLOBAL_HOST_ISOLATION_EXCEPTIONS_NAME
+ );
+ }
+
/**
* Writes new artifact SO.
*
@@ -381,6 +422,7 @@ export class ManifestManager {
this.buildExceptionListArtifacts(),
this.buildTrustedAppsArtifacts(),
this.buildEventFiltersArtifacts(),
+ this.buildHostIsolationExceptionsArtifacts(),
]);
const manifest = new Manifest({
From 4d2f76974b9fd88c6fadd446024dc192528da29f Mon Sep 17 00:00:00 2001
From: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com>
Date: Tue, 19 Oct 2021 11:16:06 +0300
Subject: [PATCH 037/204] [Discover] Enable description for saved search modal
(#114257)
* [Discover] enable description for saved search
* [Discover] remove i18n translations for removed description
* [Discover] apply Tim's suggestion
* [Discover] update snapshot
* [Discover] reorder top nav buttons in tests
* [Description] fix description save action
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../components/top_nav/discover_topnav.test.tsx | 2 +-
.../components/top_nav/get_top_nav_links.test.ts | 16 +++++++++-------
.../main/components/top_nav/get_top_nav_links.ts | 4 +++-
.../main/components/top_nav/on_save_search.tsx | 10 +++++-----
.../embeddable/saved_search_embeddable.tsx | 4 ++++
.../plugins/translations/translations/ja-JP.json | 1 -
.../plugins/translations/translations/zh-CN.json | 1 -
7 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/src/plugins/discover/public/application/apps/main/components/top_nav/discover_topnav.test.tsx b/src/plugins/discover/public/application/apps/main/components/top_nav/discover_topnav.test.tsx
index 4b572f6e348b8..808346b53304c 100644
--- a/src/plugins/discover/public/application/apps/main/components/top_nav/discover_topnav.test.tsx
+++ b/src/plugins/discover/public/application/apps/main/components/top_nav/discover_topnav.test.tsx
@@ -42,7 +42,7 @@ describe('Discover topnav component', () => {
const props = getProps(true);
const component = shallowWithIntl();
const topMenuConfig = component.props().config.map((obj: TopNavMenuData) => obj.id);
- expect(topMenuConfig).toEqual(['options', 'new', 'save', 'open', 'share', 'inspect']);
+ expect(topMenuConfig).toEqual(['options', 'new', 'open', 'share', 'inspect', 'save']);
});
test('generated config of TopNavMenu config is correct when no discover save permissions are assigned', () => {
diff --git a/src/plugins/discover/public/application/apps/main/components/top_nav/get_top_nav_links.test.ts b/src/plugins/discover/public/application/apps/main/components/top_nav/get_top_nav_links.test.ts
index d31ac6e0f2fea..20c5b9bae332d 100644
--- a/src/plugins/discover/public/application/apps/main/components/top_nav/get_top_nav_links.test.ts
+++ b/src/plugins/discover/public/application/apps/main/components/top_nav/get_top_nav_links.test.ts
@@ -53,13 +53,6 @@ test('getTopNavLinks result', () => {
"run": [Function],
"testId": "discoverNewButton",
},
- Object {
- "description": "Save Search",
- "id": "save",
- "label": "Save",
- "run": [Function],
- "testId": "discoverSaveButton",
- },
Object {
"description": "Open Saved Search",
"id": "open",
@@ -81,6 +74,15 @@ test('getTopNavLinks result', () => {
"run": [Function],
"testId": "openInspectorButton",
},
+ Object {
+ "description": "Save Search",
+ "emphasize": true,
+ "iconType": "save",
+ "id": "save",
+ "label": "Save",
+ "run": [Function],
+ "testId": "discoverSaveButton",
+ },
]
`);
});
diff --git a/src/plugins/discover/public/application/apps/main/components/top_nav/get_top_nav_links.ts b/src/plugins/discover/public/application/apps/main/components/top_nav/get_top_nav_links.ts
index 81be662470306..44d2999947f41 100644
--- a/src/plugins/discover/public/application/apps/main/components/top_nav/get_top_nav_links.ts
+++ b/src/plugins/discover/public/application/apps/main/components/top_nav/get_top_nav_links.ts
@@ -76,6 +76,8 @@ export const getTopNavLinks = ({
defaultMessage: 'Save Search',
}),
testId: 'discoverSaveButton',
+ iconType: 'save',
+ emphasize: true,
run: () => onSaveSearch({ savedSearch, services, indexPattern, navigateTo, state }),
};
@@ -153,9 +155,9 @@ export const getTopNavLinks = ({
return [
...(services.capabilities.advancedSettings.save ? [options] : []),
newSearch,
- ...(services.capabilities.discover.save ? [saveSearch] : []),
openSearch,
shareSearch,
inspectSearch,
+ ...(services.capabilities.discover.save ? [saveSearch] : []),
];
};
diff --git a/src/plugins/discover/public/application/apps/main/components/top_nav/on_save_search.tsx b/src/plugins/discover/public/application/apps/main/components/top_nav/on_save_search.tsx
index 18766b5df7f33..25b04e12c650a 100644
--- a/src/plugins/discover/public/application/apps/main/components/top_nav/on_save_search.tsx
+++ b/src/plugins/discover/public/application/apps/main/components/top_nav/on_save_search.tsx
@@ -98,16 +98,19 @@ export async function onSaveSearch({
const onSave = async ({
newTitle,
newCopyOnSave,
+ newDescription,
isTitleDuplicateConfirmed,
onTitleDuplicate,
}: {
newTitle: string;
newCopyOnSave: boolean;
+ newDescription: string;
isTitleDuplicateConfirmed: boolean;
onTitleDuplicate: () => void;
}) => {
const currentTitle = savedSearch.title;
savedSearch.title = newTitle;
+ savedSearch.description = newDescription;
const saveOptions: SaveSavedSearchOptions = {
onTitleDuplicate,
copyOnSave: newCopyOnSave,
@@ -136,14 +139,11 @@ export async function onSaveSearch({
onClose={() => {}}
title={savedSearch.title ?? ''}
showCopyOnSave={!!savedSearch.id}
+ description={savedSearch.description}
objectType={i18n.translate('discover.localMenu.saveSaveSearchObjectType', {
defaultMessage: 'search',
})}
- description={i18n.translate('discover.localMenu.saveSaveSearchDescription', {
- defaultMessage:
- 'Save your Discover search so you can use it in visualizations and dashboards',
- })}
- showDescription={false}
+ showDescription={true}
/>
);
showSaveModal(saveModal, services.core.i18n.Context);
diff --git a/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx
index 8849806cf5959..89c47559d7b4c 100644
--- a/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx
+++ b/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx
@@ -402,6 +402,10 @@ export class SavedSearchEmbeddable
return this.inspectorAdapters;
}
+ public getDescription() {
+ return this.savedSearch.description;
+ }
+
public destroy() {
super.destroy();
if (this.searchProps) {
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index acf9daebaf552..59a8cdaab6413 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -2528,7 +2528,6 @@
"discover.localMenu.openSavedSearchDescription": "保存された検索を開きます",
"discover.localMenu.openTitle": "開く",
"discover.localMenu.optionsDescription": "オプション",
- "discover.localMenu.saveSaveSearchDescription": "ビジュアライゼーションとダッシュボードで使用できるように Discover の検索を保存します",
"discover.localMenu.saveSaveSearchObjectType": "検索",
"discover.localMenu.saveSearchDescription": "検索を保存します",
"discover.localMenu.saveTitle": "保存",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 5890f8553e7c5..4e901f51d54da 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -2554,7 +2554,6 @@
"discover.localMenu.openSavedSearchDescription": "打开已保存搜索",
"discover.localMenu.openTitle": "打开",
"discover.localMenu.optionsDescription": "选项",
- "discover.localMenu.saveSaveSearchDescription": "保存您的 Discover 搜索,以便可以在可视化和仪表板中使用该搜索",
"discover.localMenu.saveSaveSearchObjectType": "搜索",
"discover.localMenu.saveSearchDescription": "保存搜索",
"discover.localMenu.saveTitle": "保存",
From 32e1fc2396588b168650bd76ebd91127bc158576 Mon Sep 17 00:00:00 2001
From: Ryland Herrick
Date: Tue, 19 Oct 2021 03:23:27 -0500
Subject: [PATCH 038/204] [Security Solution][Rules] Halt Indicator Match
execution after interval has passed (#115288)
* Throw an error to stop execution if IM rule has exceeded its interval
* Extract and unit test our timeout validation
* Add integration test around timeout behavior
Configures a very slow rule to trigger a timeout and assert the
corresponding failure.
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../threat_mapping/create_threat_signals.ts | 6 ++-
.../signals/threat_mapping/utils.test.ts | 23 ++++++++++
.../signals/threat_mapping/utils.ts | 21 +++++++++
.../tests/create_threat_matching.ts | 46 +++++++++++++++++++
4 files changed, 95 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
index 169a820392a6e..677a2028acdf7 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
@@ -11,7 +11,7 @@ import { getThreatList, getThreatListCount } from './get_threat_list';
import { CreateThreatSignalsOptions } from './types';
import { createThreatSignal } from './create_threat_signal';
import { SearchAfterAndBulkCreateReturnType } from '../types';
-import { combineConcurrentResults } from './utils';
+import { buildExecutionIntervalValidator, combineConcurrentResults } from './utils';
import { buildThreatEnrichment } from './build_threat_enrichment';
export const createThreatSignals = async ({
@@ -46,6 +46,9 @@ export const createThreatSignals = async ({
const params = ruleSO.attributes.params;
logger.debug(buildRuleMessage('Indicator matching rule starting'));
const perPage = concurrentSearches * itemsPerSearch;
+ const verifyExecutionCanProceed = buildExecutionIntervalValidator(
+ ruleSO.attributes.schedule.interval
+ );
let results: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -99,6 +102,7 @@ export const createThreatSignals = async ({
});
while (threatList.hits.hits.length !== 0) {
+ verifyExecutionCanProceed();
const chunks = chunk(itemsPerSearch, threatList.hits.hits);
logger.debug(buildRuleMessage(`${chunks.length} concurrent indicator searches are starting.`));
const concurrentSearchesPerformed = chunks.map>(
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts
index ec826b44023f6..f029b02127b08 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts
@@ -10,6 +10,7 @@ import { sampleSignalHit } from '../__mocks__/es_results';
import { ThreatMatchNamedQuery } from './types';
import {
+ buildExecutionIntervalValidator,
calculateAdditiveMax,
calculateMax,
calculateMaxLookBack,
@@ -712,4 +713,26 @@ describe('utils', () => {
});
});
});
+
+ describe('buildExecutionIntervalValidator', () => {
+ it('succeeds if the validator is called within the specified interval', () => {
+ const validator = buildExecutionIntervalValidator('1m');
+ expect(() => validator()).not.toThrowError();
+ });
+
+ it('throws an error if the validator is called after the specified interval', async () => {
+ const validator = buildExecutionIntervalValidator('1s');
+
+ await new Promise((r) => setTimeout(r, 1001));
+ expect(() => validator()).toThrowError(
+ 'Current rule execution has exceeded its allotted interval (1s) and has been stopped.'
+ );
+ });
+
+ it('throws an error if the interval cannot be parsed', () => {
+ expect(() => buildExecutionIntervalValidator('badString')).toThrowError(
+ 'Unable to parse rule interval (badString); stopping rule execution since allotted duration is undefined'
+ );
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts
index 4d9fda43f032e..99f6609faec91 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts
@@ -5,7 +5,10 @@
* 2.0.
*/
+import moment from 'moment';
+
import { SearchAfterAndBulkCreateReturnType, SignalSourceHit } from '../types';
+import { parseInterval } from '../utils';
import { ThreatMatchNamedQuery } from './types';
/**
@@ -146,3 +149,21 @@ export const decodeThreatMatchNamedQuery = (encoded: string): ThreatMatchNamedQu
export const extractNamedQueries = (hit: SignalSourceHit): ThreatMatchNamedQuery[] =>
hit.matched_queries?.map((match) => decodeThreatMatchNamedQuery(match)) ?? [];
+
+export const buildExecutionIntervalValidator: (interval: string) => () => void = (interval) => {
+ const intervalDuration = parseInterval(interval);
+
+ if (intervalDuration == null) {
+ throw new Error(
+ `Unable to parse rule interval (${interval}); stopping rule execution since allotted duration is undefined.`
+ );
+ }
+
+ const executionEnd = moment().add(intervalDuration);
+ return () => {
+ if (moment().isAfter(executionEnd)) {
+ const message = `Current rule execution has exceeded its allotted interval (${interval}) and has been stopped.`;
+ throw new Error(message);
+ }
+ };
+};
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts
index 0aad3c699805a..223529fce54f6 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts
@@ -411,6 +411,52 @@ export default ({ getService }: FtrProviderContext) => {
expect(signalsOpen.hits.hits.length).equal(0);
});
+ describe('timeout behavior', () => {
+ it('will return an error if a rule execution exceeds the rule interval', async () => {
+ const rule: CreateRulesSchema = {
+ description: 'Detecting root and admin users',
+ name: 'Query with a short interval',
+ severity: 'high',
+ index: ['auditbeat-*'],
+ type: 'threat_match',
+ risk_score: 55,
+ language: 'kuery',
+ rule_id: 'rule-1',
+ from: '1900-01-01T00:00:00.000Z',
+ query: '*:*',
+ threat_query: '*:*', // broad query to take more time
+ threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
+ threat_mapping: [
+ {
+ entries: [
+ {
+ field: 'host.name',
+ value: 'host.name',
+ type: 'mapping',
+ },
+ ],
+ },
+ ],
+ threat_filters: [],
+ concurrent_searches: 1,
+ interval: '1s', // short interval
+ items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow
+ };
+
+ const { id } = await createRule(supertest, rule);
+ await waitForRuleSuccessOrStatus(supertest, id, 'failed');
+
+ const { body } = await supertest
+ .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`)
+ .set('kbn-xsrf', 'true')
+ .send({ ids: [id] })
+ .expect(200);
+ expect(body[id].current_status.last_failure_message).to.contain(
+ 'execution has exceeded its allotted interval'
+ );
+ });
+ });
+
describe('indicator enrichment', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel');
From 1fb28dcc69fe69993675cc198191dd2e91e092ea Mon Sep 17 00:00:00 2001
From: Khristinin Nikita
Date: Tue, 19 Oct 2021 10:49:58 +0200
Subject: [PATCH 039/204] IM rule default interval timeout and lookback - 1h
(#115185)
* Make 1h default value for IM rule interval and lookback time
* Fix test name
* Move value to cosntants
* Update lookback
* Change lookback to 5 minutes
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../indicator_match_rule.spec.ts | 19 +++++++++++++++++++
.../cypress/screens/create_new_rule.ts | 6 ++++++
.../rules/step_schedule_rule/index.tsx | 19 +++++++++++++++----
.../detection_engine/rules/create/index.tsx | 1 +
4 files changed, 41 insertions(+), 4 deletions(-)
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
index 871e50821b58c..8735b8d49974c 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
@@ -102,6 +102,12 @@ import {
waitForAlertsToPopulate,
waitForTheRuleToBeExecuted,
} from '../../tasks/create_new_rule';
+import {
+ SCHEDULE_INTERVAL_AMOUNT_INPUT,
+ SCHEDULE_INTERVAL_UNITS_INPUT,
+ SCHEDULE_LOOKBACK_AMOUNT_INPUT,
+ SCHEDULE_LOOKBACK_UNITS_INPUT,
+} from '../../screens/create_new_rule';
import { goBackToRuleDetails, waitForKibana } from '../../tasks/edit_rule';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
@@ -383,6 +389,19 @@ describe('indicator match', () => {
getIndicatorMappingComboField(2).should('not.exist');
});
});
+
+ describe('Schedule', () => {
+ it('IM rule has 1h time interval and lookback by default', () => {
+ selectIndicatorMatchType();
+ fillDefineIndicatorMatchRuleAndContinue(getNewThreatIndicatorRule());
+ fillAboutRuleAndContinue(getNewThreatIndicatorRule());
+
+ cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', '1');
+ cy.get(SCHEDULE_INTERVAL_UNITS_INPUT).invoke('val').should('eql', 'h');
+ cy.get(SCHEDULE_LOOKBACK_AMOUNT_INPUT).invoke('val').should('eql', '5');
+ cy.get(SCHEDULE_LOOKBACK_UNITS_INPUT).invoke('val').should('eql', 'm');
+ });
+ });
});
describe('Generating signals', () => {
diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
index 3510df6186870..aadaa5dfa0d88 100644
--- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
@@ -201,6 +201,12 @@ export const SCHEDULE_INTERVAL_AMOUNT_INPUT =
export const SCHEDULE_INTERVAL_UNITS_INPUT =
'[data-test-subj="detectionEngineStepScheduleRuleInterval"] [data-test-subj="timeType"]';
+export const SCHEDULE_LOOKBACK_AMOUNT_INPUT =
+ '[data-test-subj="detectionEngineStepScheduleRuleFrom"] [data-test-subj="interval"]';
+
+export const SCHEDULE_LOOKBACK_UNITS_INPUT =
+ '[data-test-subj="detectionEngineStepScheduleRuleFrom"] [data-test-subj="timeType"]';
+
export const SEVERITY_DROPDOWN =
'[data-test-subj="detectionEngineStepAboutRuleSeverity"] [data-test-subj="select"]';
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx
index 528494c9331ac..9d7c2b76b385f 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx
@@ -6,6 +6,7 @@
*/
import React, { FC, memo, useCallback, useEffect } from 'react';
+import { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import {
RuleStep,
@@ -16,16 +17,25 @@ import { StepRuleDescription } from '../description_step';
import { ScheduleItem } from '../schedule_item_form';
import { Form, UseField, useForm } from '../../../../shared_imports';
import { StepContentWrapper } from '../step_content_wrapper';
+import { isThreatMatchRule } from '../../../../../common/detection_engine/utils';
import { NextStep } from '../next_step';
import { schema } from './schema';
interface StepScheduleRuleProps extends RuleStepProps {
defaultValues?: ScheduleStepRule | null;
+ ruleType?: Type;
}
-const stepScheduleDefaultValue: ScheduleStepRule = {
- interval: '5m',
- from: '1m',
+const DEFAULT_INTERVAL = '5m';
+const DEFAULT_FROM = '1m';
+const THREAT_MATCH_INTERVAL = '1h';
+const THREAT_MATCH_FROM = '5m';
+
+const getStepScheduleDefaultValue = (ruleType: Type | undefined): ScheduleStepRule => {
+ return {
+ interval: isThreatMatchRule(ruleType) ? THREAT_MATCH_INTERVAL : DEFAULT_INTERVAL,
+ from: isThreatMatchRule(ruleType) ? THREAT_MATCH_FROM : DEFAULT_FROM,
+ };
};
const StepScheduleRuleComponent: FC = ({
@@ -37,8 +47,9 @@ const StepScheduleRuleComponent: FC = ({
isUpdateView = false,
onSubmit,
setForm,
+ ruleType,
}) => {
- const initialState = defaultValues ?? stepScheduleDefaultValue;
+ const initialState = defaultValues ?? getStepScheduleDefaultValue(ruleType);
const { form } = useForm({
defaultValue: initialState,
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx
index a2f4385aeeb86..d37acaeb0ffee 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx
@@ -401,6 +401,7 @@ const CreateRulePageComponent: React.FC = () => {
>
Date: Tue, 19 Oct 2021 11:03:46 +0200
Subject: [PATCH 040/204] [Security Solution][Detections] Hide building block
rules in "Security/Overview" (#105611)
* Hide building block rules in "Security/Overview"
* Add Cypress tests for alerts generated by building block rules
Co-authored-by: Dmitry Shevchenko
---
.../building_block_alerts.spec.ts | 40 +++++++++++++++++++
.../security_solution/cypress/objects/rule.ts | 20 ++++++++++
.../cypress/screens/overview.ts | 2 +
.../cypress/tasks/api_calls/rules.ts | 1 +
.../components/signals_by_category/index.tsx | 15 +++++--
.../use_filters_for_signals_by_category.ts | 37 +++++++++++++++++
6 files changed, 111 insertions(+), 4 deletions(-)
create mode 100644 x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts
create mode 100644 x-pack/plugins/security_solution/public/overview/components/signals_by_category/use_filters_for_signals_by_category.ts
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts
new file mode 100644
index 0000000000000..262ffe8163e57
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/building_block_alerts.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { getBuildingBlockRule } from '../../objects/rule';
+import { OVERVIEW_ALERTS_HISTOGRAM } from '../../screens/overview';
+import { OVERVIEW } from '../../screens/security_header';
+import { goToRuleDetails } from '../../tasks/alerts_detection_rules';
+import { createCustomRuleActivated } from '../../tasks/api_calls/rules';
+import { cleanKibana } from '../../tasks/common';
+import { waitForAlertsToPopulate, waitForTheRuleToBeExecuted } from '../../tasks/create_new_rule';
+import { loginAndWaitForPage } from '../../tasks/login';
+import { navigateFromHeaderTo } from '../../tasks/security_header';
+import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation';
+
+const EXPECTED_NUMBER_OF_ALERTS = 16;
+
+describe('Alerts generated by building block rules', () => {
+ beforeEach(() => {
+ cleanKibana();
+ });
+
+ it('Alerts should be visible on the Rule Detail page and not visible on the Overview page', () => {
+ createCustomRuleActivated(getBuildingBlockRule());
+ loginAndWaitForPage(DETECTIONS_RULE_MANAGEMENT_URL);
+ goToRuleDetails();
+ waitForTheRuleToBeExecuted();
+
+ // Check that generated events are visible on the Details page
+ waitForAlertsToPopulate(EXPECTED_NUMBER_OF_ALERTS);
+
+ navigateFromHeaderTo(OVERVIEW);
+
+ // Check that generated events are hidden on the Overview page
+ cy.get(OVERVIEW_ALERTS_HISTOGRAM).should('contain.text', 'No data to display');
+ });
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts
index 4b061865d632b..27973854097db 100644
--- a/x-pack/plugins/security_solution/cypress/objects/rule.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts
@@ -58,6 +58,7 @@ export interface CustomRule {
lookBack: Interval;
timeline: CompleteTimeline;
maxSignals: number;
+ buildingBlockType?: string;
}
export interface ThresholdRule extends CustomRule {
@@ -188,6 +189,25 @@ export const getNewRule = (): CustomRule => ({
maxSignals: 100,
});
+export const getBuildingBlockRule = (): CustomRule => ({
+ customQuery: 'host.name: *',
+ index: getIndexPatterns(),
+ name: 'Building Block Rule Test',
+ description: 'The new rule description.',
+ severity: 'High',
+ riskScore: '17',
+ tags: ['test', 'newRule'],
+ referenceUrls: ['http://example.com/', 'https://example.com/'],
+ falsePositivesExamples: ['False1', 'False2'],
+ mitre: [getMitre1(), getMitre2()],
+ note: '# test markdown',
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
+ maxSignals: 100,
+ buildingBlockType: 'default',
+});
+
export const getUnmappedRule = (): CustomRule => ({
customQuery: '*:*',
index: ['unmapped*'],
diff --git a/x-pack/plugins/security_solution/cypress/screens/overview.ts b/x-pack/plugins/security_solution/cypress/screens/overview.ts
index 1376a39e5ee79..1945b7e3ce3e7 100644
--- a/x-pack/plugins/security_solution/cypress/screens/overview.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/overview.ts
@@ -166,3 +166,5 @@ export const OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON =
export const OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT = `${OVERVIEW_RISKY_HOSTS_LINKS} [data-test-subj="header-panel-subtitle"]`;
export const OVERVIEW_RISKY_HOSTS_ENABLE_MODULE_BUTTON =
'[data-test-subj="risky-hosts-enable-module-button"]';
+
+export const OVERVIEW_ALERTS_HISTOGRAM = '[data-test-subj="alerts-histogram-panel"]';
diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts
index 04ff0fcabc081..fd2838e5b3caa 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts
@@ -114,6 +114,7 @@ export const createCustomRuleActivated = (
enabled: true,
tags: ['rule1'],
max_signals: maxSignals,
+ building_block_type: rule.buildingBlockType,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
diff --git a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx
index 321e6d00b5301..cbeb1464e1b41 100644
--- a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/index.tsx
@@ -7,19 +7,24 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
+import { Filter, Query } from '@kbn/es-query';
import { AlertsHistogramPanel } from '../../../detections/components/alerts_kpis/alerts_histogram_panel';
import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions';
-import { Filter, Query } from '../../../../../../../src/plugins/data/public';
+
import { InputsModelId } from '../../../common/store/inputs/constants';
-import * as i18n from '../../pages/translations';
import { UpdateDateRange } from '../../../common/components/charts/common';
+
import { AlertsStackByField } from '../../../detections/components/alerts_kpis/common/types';
+import * as i18n from '../../pages/translations';
+
+import { useFiltersForSignalsByCategory } from './use_filters_for_signals_by_category';
+
interface Props {
combinedQueries?: string;
- filters?: Filter[];
+ filters: Filter[];
headerChildren?: React.ReactNode;
/** Override all defaults, and only display this field */
onlyField?: AlertsStackByField;
@@ -43,6 +48,8 @@ const SignalsByCategoryComponent: React.FC = ({
}) => {
const dispatch = useDispatch();
const { signalIndexName } = useSignalIndex();
+ const filtersForSignalsByCategory = useFiltersForSignalsByCategory(filters);
+
const updateDateRangeCallback = useCallback(
({ x }) => {
if (!x) {
@@ -63,7 +70,7 @@ const SignalsByCategoryComponent: React.FC = ({
return (
{
+ // TODO: Once we are past experimental phase this code should be removed
+ const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled');
+
+ const resultingFilters = useMemo(
+ () => [
+ ...baseFilters,
+ ...(ruleRegistryEnabled
+ ? buildShowBuildingBlockFilterRuleRegistry(SHOW_BUILDING_BLOCK_ALERTS) // TODO: Once we are past experimental phase this code should be removed
+ : buildShowBuildingBlockFilter(SHOW_BUILDING_BLOCK_ALERTS)),
+ ],
+ [baseFilters, ruleRegistryEnabled]
+ );
+
+ return resultingFilters;
+};
From b9024c6ad5561ddd5dc415c1dec786e1b534c75c Mon Sep 17 00:00:00 2001
From: Kevin Lacabane
Date: Tue, 19 Oct 2021 11:08:20 +0200
Subject: [PATCH 041/204] timepicker-url sync functional test (#115173)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../test/functional/apps/monitoring/index.js | 2 +-
.../functional/apps/monitoring/time_filter.js | 26 +++++++++++++++++--
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/x-pack/test/functional/apps/monitoring/index.js b/x-pack/test/functional/apps/monitoring/index.js
index 6a5b6ea813171..a67964d325164 100644
--- a/x-pack/test/functional/apps/monitoring/index.js
+++ b/x-pack/test/functional/apps/monitoring/index.js
@@ -42,7 +42,7 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./beats/listing'));
loadTestFile(require.resolve('./beats/beat_detail'));
- // loadTestFile(require.resolve('./time_filter'));
+ loadTestFile(require.resolve('./time_filter'));
loadTestFile(require.resolve('./enable_monitoring'));
loadTestFile(require.resolve('./setup/metricbeat_migration'));
diff --git a/x-pack/test/functional/apps/monitoring/time_filter.js b/x-pack/test/functional/apps/monitoring/time_filter.js
index 910b91d07039d..76e7bc5cd043d 100644
--- a/x-pack/test/functional/apps/monitoring/time_filter.js
+++ b/x-pack/test/functional/apps/monitoring/time_filter.js
@@ -12,22 +12,44 @@ export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['header', 'timePicker']);
const testSubjects = getService('testSubjects');
const clusterList = getService('monitoringClusterList');
+ const browser = getService('browser');
+
+ const assertTimePickerRange = async (start, end) => {
+ const timeConfig = await PageObjects.timePicker.getTimeConfig();
+ expect(timeConfig.start).to.eql(start);
+ expect(timeConfig.end).to.eql(end);
+ };
describe('Timefilter', () => {
const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects);
+ const from = 'Aug 15, 2017 @ 21:00:00.000';
+ const to = 'Aug 16, 2017 @ 00:00:00.000';
+
before(async () => {
await setup('x-pack/test/functional/es_archives/monitoring/multicluster', {
- from: 'Aug 15, 2017 @ 21:00:00.000',
- to: 'Aug 16, 2017 @ 00:00:00.000',
+ from,
+ to,
});
await clusterList.assertDefaults();
+ await clusterList.closeAlertsModal();
});
after(async () => {
await tearDown();
});
+ it('syncs timepicker with url hash updates', async () => {
+ await assertTimePickerRange(from, to);
+
+ await browser.execute(() => {
+ const hash = window.location.hash;
+ window.location.hash = hash.replace(/time:\(([^)]+)\)/, 'time:(from:now-15m,to:now)');
+ });
+
+ await assertTimePickerRange('~ 15 minutes ago', 'now');
+ });
+
// FLAKY: https://github.com/elastic/kibana/issues/48910
it.skip('should send another request when clicking Refresh', async () => {
await testSubjects.click('querySubmitButton');
From 5fe9a319c001444819d0f79e5ea2e3a375eb2771 Mon Sep 17 00:00:00 2001
From: mgiota
Date: Tue, 19 Oct 2021 11:09:48 +0200
Subject: [PATCH 042/204] [RAC] [Metrics UI] Include group name in the reason
message (#115171)
* [RAC] [Metrics UI] Include group name in the reason message
* remove console log
* fix i18n errors
* fix more i18n errors
* fix i18n & check errors and move group to the end of the reason text
* add empty lines at the end of translation files
* fix more i18n tests
* try to remove manually added translations
* Revert "try to remove manually added translations"
This reverts commit 6949af2f70aff46b088bab5c942497ad46081d90.
* apply i18n_check fix and reorder values in the formatted reason
* log threshold reformat reason message and move group info at the end
---
.../server/lib/alerting/common/messages.ts | 18 ++++++----
.../inventory_metric_threshold_executor.ts | 35 +++++++++++--------
.../log_threshold/reason_formatters.ts | 4 +--
.../metric_threshold_executor.ts | 9 ++---
.../translations/translations/ja-JP.json | 3 --
.../translations/translations/zh-CN.json | 3 --
6 files changed, 40 insertions(+), 32 deletions(-)
diff --git a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts
index 084043f357bb1..23c89abf4a7aa 100644
--- a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts
@@ -109,15 +109,17 @@ const thresholdToI18n = ([a, b]: Array) => {
};
export const buildFiredAlertReason: (alertResult: {
+ group: string;
metric: string;
comparator: Comparator;
threshold: Array;
currentValue: number | string;
-}) => string = ({ metric, comparator, threshold, currentValue }) =>
+}) => string = ({ group, metric, comparator, threshold, currentValue }) =>
i18n.translate('xpack.infra.metrics.alerting.threshold.firedAlertReason', {
defaultMessage:
- '{metric} is {comparator} a threshold of {threshold} (current value is {currentValue})',
+ '{metric} is {comparator} a threshold of {threshold} (current value is {currentValue}) for {group}',
values: {
+ group,
metric,
comparator: comparatorToI18n(comparator, threshold.map(toNumber), toNumber(currentValue)),
threshold: thresholdToI18n(threshold),
@@ -126,14 +128,15 @@ export const buildFiredAlertReason: (alertResult: {
});
export const buildRecoveredAlertReason: (alertResult: {
+ group: string;
metric: string;
comparator: Comparator;
threshold: Array;
currentValue: number | string;
-}) => string = ({ metric, comparator, threshold, currentValue }) =>
+}) => string = ({ group, metric, comparator, threshold, currentValue }) =>
i18n.translate('xpack.infra.metrics.alerting.threshold.recoveredAlertReason', {
defaultMessage:
- '{metric} is now {comparator} a threshold of {threshold} (current value is {currentValue})',
+ '{metric} is now {comparator} a threshold of {threshold} (current value is {currentValue}) for {group}',
values: {
metric,
comparator: recoveredComparatorToI18n(
@@ -143,19 +146,22 @@ export const buildRecoveredAlertReason: (alertResult: {
),
threshold: thresholdToI18n(threshold),
currentValue,
+ group,
},
});
export const buildNoDataAlertReason: (alertResult: {
+ group: string;
metric: string;
timeSize: number;
timeUnit: string;
-}) => string = ({ metric, timeSize, timeUnit }) =>
+}) => string = ({ group, metric, timeSize, timeUnit }) =>
i18n.translate('xpack.infra.metrics.alerting.threshold.noDataAlertReason', {
- defaultMessage: '{metric} has reported no data over the past {interval}',
+ defaultMessage: '{metric} has reported no data over the past {interval} for {group}',
values: {
metric,
interval: `${timeSize}${timeUnit}`,
+ group,
},
});
diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts
index 5cd093c6f1472..3dd702126735d 100644
--- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts
@@ -102,18 +102,18 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
)
);
const inventoryItems = Object.keys(first(results)!);
- for (const item of inventoryItems) {
+ for (const group of inventoryItems) {
// AND logic; all criteria must be across the threshold
const shouldAlertFire = results.every((result) => {
// Grab the result of the most recent bucket
- return last(result[item].shouldFire);
+ return last(result[group].shouldFire);
});
- const shouldAlertWarn = results.every((result) => last(result[item].shouldWarn));
+ const shouldAlertWarn = results.every((result) => last(result[group].shouldWarn));
// AND logic; because we need to evaluate all criteria, if one of them reports no data then the
// whole alert is in a No Data/Error state
- const isNoData = results.some((result) => last(result[item].isNoData));
- const isError = results.some((result) => result[item].isError);
+ const isNoData = results.some((result) => last(result[group].isNoData));
+ const isError = results.some((result) => result[group].isError);
const nextState = isError
? AlertStates.ERROR
@@ -129,7 +129,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
reason = results
.map((result) =>
buildReasonWithVerboseMetricName(
- result[item],
+ group,
+ result[group],
buildFiredAlertReason,
nextState === AlertStates.WARNING
)
@@ -142,19 +143,23 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
*/
// } else if (nextState === AlertStates.OK && prevState?.alertState === AlertStates.ALERT) {
// reason = results
- // .map((result) => buildReasonWithVerboseMetricName(result[item], buildRecoveredAlertReason))
+ // .map((result) => buildReasonWithVerboseMetricName(group, result[group], buildRecoveredAlertReason))
// .join('\n');
}
if (alertOnNoData) {
if (nextState === AlertStates.NO_DATA) {
reason = results
- .filter((result) => result[item].isNoData)
- .map((result) => buildReasonWithVerboseMetricName(result[item], buildNoDataAlertReason))
+ .filter((result) => result[group].isNoData)
+ .map((result) =>
+ buildReasonWithVerboseMetricName(group, result[group], buildNoDataAlertReason)
+ )
.join('\n');
} else if (nextState === AlertStates.ERROR) {
reason = results
- .filter((result) => result[item].isError)
- .map((result) => buildReasonWithVerboseMetricName(result[item], buildErrorAlertReason))
+ .filter((result) => result[group].isError)
+ .map((result) =>
+ buildReasonWithVerboseMetricName(group, result[group], buildErrorAlertReason)
+ )
.join('\n');
}
}
@@ -166,7 +171,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
? WARNING_ACTIONS.id
: FIRED_ACTIONS.id;
- const alertInstance = alertInstanceFactory(`${item}`, reason);
+ const alertInstance = alertInstanceFactory(`${group}`, reason);
alertInstance.scheduleActions(
/**
* TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on
@@ -174,12 +179,12 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
*/
actionGroupId as unknown as InventoryMetricThresholdAllowedActionGroups,
{
- group: item,
+ group,
alertState: stateToAlertMessage[nextState],
reason,
timestamp: moment().toISOString(),
value: mapToConditionsLookup(results, (result) =>
- formatMetric(result[item].metric, result[item].currentValue)
+ formatMetric(result[group].metric, result[group].currentValue)
),
threshold: mapToConditionsLookup(criteria, (c) => c.threshold),
metric: mapToConditionsLookup(criteria, (c) => c.metric),
@@ -190,6 +195,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
});
const buildReasonWithVerboseMetricName = (
+ group: string,
resultItem: any,
buildReason: (r: any) => string,
useWarningThreshold?: boolean
@@ -197,6 +203,7 @@ const buildReasonWithVerboseMetricName = (
if (!resultItem) return '';
const resultWithVerboseMetricName = {
...resultItem,
+ group,
metric:
toMetricOpt(resultItem.metric)?.text ||
(resultItem.metric === 'custom'
diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/reason_formatters.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/reason_formatters.ts
index cd579b9965b66..f70e0a0140ce8 100644
--- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/reason_formatters.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/reason_formatters.ts
@@ -34,7 +34,7 @@ export const getReasonMessageForGroupedCountAlert = (
) =>
i18n.translate('xpack.infra.logs.alerting.threshold.groupedCountAlertReasonDescription', {
defaultMessage:
- '{groupName}: {actualCount, plural, one {{actualCount} log entry} other {{actualCount} log entries} } ({translatedComparator} {expectedCount}) match the conditions.',
+ '{actualCount, plural, one {{actualCount} log entry} other {{actualCount} log entries} } ({translatedComparator} {expectedCount}) match the conditions for {groupName}.',
values: {
actualCount,
expectedCount,
@@ -66,7 +66,7 @@ export const getReasonMessageForGroupedRatioAlert = (
) =>
i18n.translate('xpack.infra.logs.alerting.threshold.groupedRatioAlertReasonDescription', {
defaultMessage:
- '{groupName}: The log entries ratio is {actualRatio} ({translatedComparator} {expectedRatio}).',
+ 'The log entries ratio is {actualRatio} ({translatedComparator} {expectedRatio}) for {groupName}.',
values: {
actualRatio,
expectedRatio,
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
index af5f945eeb4bb..e4887e922bb66 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
@@ -143,9 +143,10 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) {
reason = alertResults
.map((result) =>
- buildFiredAlertReason(
- formatAlertResult(result[group], nextState === AlertStates.WARNING)
- )
+ buildFiredAlertReason({
+ ...formatAlertResult(result[group], nextState === AlertStates.WARNING),
+ group,
+ })
)
.join('\n');
/*
@@ -181,7 +182,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
if (nextState === AlertStates.NO_DATA) {
reason = alertResults
.filter((result) => result[group].isNoData)
- .map((result) => buildNoDataAlertReason(result[group]))
+ .map((result) => buildNoDataAlertReason({ ...result[group], group }))
.join('\n');
} else if (nextState === AlertStates.ERROR) {
reason = alertResults
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 59a8cdaab6413..aac0f651b8dee 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -13446,15 +13446,12 @@
"xpack.infra.metrics.alerting.threshold.errorAlertReason": "{metric}のデータのクエリを試行しているときに、Elasticsearchが失敗しました",
"xpack.infra.metrics.alerting.threshold.errorState": "エラー",
"xpack.infra.metrics.alerting.threshold.fired": "アラート",
- "xpack.infra.metrics.alerting.threshold.firedAlertReason": "{metric}は{comparator} {threshold}のしきい値です(現在の値は{currentValue})",
"xpack.infra.metrics.alerting.threshold.gtComparator": "より大きい",
"xpack.infra.metrics.alerting.threshold.ltComparator": "より小さい",
- "xpack.infra.metrics.alerting.threshold.noDataAlertReason": "{metric}は過去{interval}にデータを報告していません",
"xpack.infra.metrics.alerting.threshold.noDataFormattedValue": "[データなし]",
"xpack.infra.metrics.alerting.threshold.noDataState": "データなし",
"xpack.infra.metrics.alerting.threshold.okState": "OK [回復済み]",
"xpack.infra.metrics.alerting.threshold.outsideRangeComparator": "の間にない",
- "xpack.infra.metrics.alerting.threshold.recoveredAlertReason": "{metric}は{comparator} {threshold}のしきい値です(現在の値は{currentValue})",
"xpack.infra.metrics.alerting.threshold.thresholdRange": "{a}と{b}",
"xpack.infra.metrics.alerting.threshold.warning": "警告",
"xpack.infra.metrics.alerting.threshold.warningState": "警告",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 4e901f51d54da..50d90f5144585 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -13634,15 +13634,12 @@
"xpack.infra.metrics.alerting.threshold.errorAlertReason": "Elasticsearch 尝试查询 {metric} 的数据时出现故障",
"xpack.infra.metrics.alerting.threshold.errorState": "错误",
"xpack.infra.metrics.alerting.threshold.fired": "告警",
- "xpack.infra.metrics.alerting.threshold.firedAlertReason": "{metric} {comparator}阈值 {threshold}(当前值为 {currentValue})",
"xpack.infra.metrics.alerting.threshold.gtComparator": "大于",
"xpack.infra.metrics.alerting.threshold.ltComparator": "小于",
- "xpack.infra.metrics.alerting.threshold.noDataAlertReason": "{metric} 在过去 {interval}中未报告数据",
"xpack.infra.metrics.alerting.threshold.noDataFormattedValue": "[无数据]",
"xpack.infra.metrics.alerting.threshold.noDataState": "无数据",
"xpack.infra.metrics.alerting.threshold.okState": "正常 [已恢复]",
"xpack.infra.metrics.alerting.threshold.outsideRangeComparator": "不介于",
- "xpack.infra.metrics.alerting.threshold.recoveredAlertReason": "{metric} 现在{comparator}阈值 {threshold}(当前值为 {currentValue})",
"xpack.infra.metrics.alerting.threshold.thresholdRange": "{a} 和 {b}",
"xpack.infra.metrics.alerting.threshold.warning": "警告",
"xpack.infra.metrics.alerting.threshold.warningState": "警告",
From f9afe67f1e554cf3b295c4c43bf2b3f68c103120 Mon Sep 17 00:00:00 2001
From: Andrew Goldstein
Date: Tue, 19 Oct 2021 04:10:14 -0600
Subject: [PATCH 043/204] [Security Solution] Improves the formatting of array
values and JSON in the Event and Alert Details panels (#115141)
## [Security Solution] Improves the formatting of array values and JSON in the Event and Alert Details panels
This PR improves the formatting of array values and JSON in the Event and Alert details panels by:
- in the `Table` tab, formatting array values such that each value appears on a separate line, (instead of joining the values on a single line)
- in the `JSON` tab, displaying the raw search hit JSON, instead displaying a JSON representation based on the `Fields` API
### Table value formatting
In the Event and Alert details `Table` tab, array values were joined on a single line, as shown in the _before_ screenshot below:
![event-details-value-formatting-before](https://user-images.githubusercontent.com/4459398/137524968-6450cd73-3154-457d-b850-32a3e7faaab2.png)
_Above: (before) array values were joined on a single line_
Array values are now formatted such that each value appears on a separate line, as shown in the _after_ screenshot below:
![event-details-value-formatting-after](https://user-images.githubusercontent.com/4459398/137436705-b0bec735-5a83-402e-843a-2776e1c80da9.png)
_Above: (after) array values each appear on a separte line_
### JSON formatting
The `JSON` tab previously displayed a JSON representation based on the `Fields` API. Array values were previously represented as a joined string, as shown in the _before_ screenshot below:
![event-details-json-formatting-before](https://user-images.githubusercontent.com/4459398/137525039-d1b14f21-5f9c-4201-905e-8b08f00bb5a0.png)
_Above: (before) array values were previously represented as a joined string_
The `JSON` tab now displays the raw search hit JSON, per the _after_ screenshot below:
![event-details-json-formatting-after](https://user-images.githubusercontent.com/4459398/137437257-330c5b49-a4ad-418e-a976-923f7a35c0cf.png)
_Above: (after) the `JSON` tab displays the raw search hit_
CC @monina-n @paulewing
---
.../detection_alerts/alerts_details.spec.ts | 20 +-
.../detection_alerts/cti_enrichments.spec.ts | 49 ++-
.../cypress/screens/alerts_details.ts | 2 +
.../alert_summary_view.test.tsx.snap | 120 ++++--
.../__snapshots__/json_view.test.tsx.snap | 343 ++++++++++++++++--
.../event_details/event_details.test.tsx | 3 +-
.../event_details/event_details.tsx | 6 +-
.../event_details/json_view.test.tsx | 49 +--
.../components/event_details/json_view.tsx | 23 +-
.../table/field_value_cell.test.tsx | 193 ++++++++++
.../event_details/table/field_value_cell.tsx | 26 +-
.../public/common/mock/mock_detail_item.ts | 188 ++++++++++
.../event_details/expandable_event.tsx | 3 +
.../side_panel/event_details/index.tsx | 4 +-
.../timelines/containers/details/index.tsx | 7 +-
.../timeline/events/details/index.ts | 1 +
.../timeline/factory/events/details/index.ts | 4 +
17 files changed, 872 insertions(+), 169 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
index 674114188632b..7b792f8d560f1 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
@@ -5,14 +5,14 @@
* 2.0.
*/
-import { ALERT_FLYOUT, CELL_TEXT, JSON_LINES, TABLE_ROWS } from '../../screens/alerts_details';
+import { ALERT_FLYOUT, CELL_TEXT, JSON_TEXT, TABLE_ROWS } from '../../screens/alerts_details';
import {
expandFirstAlert,
waitForAlertsIndexToBeCreated,
waitForAlertsPanelToBeLoaded,
} from '../../tasks/alerts';
-import { openJsonView, openTable, scrollJsonViewToBottom } from '../../tasks/alerts_details';
+import { openJsonView, openTable } from '../../tasks/alerts_details';
import { createCustomRuleActivated } from '../../tasks/api_calls/rules';
import { cleanKibana } from '../../tasks/common';
import { esArchiverLoad } from '../../tasks/es_archiver';
@@ -36,20 +36,14 @@ describe('Alert details with unmapped fields', () => {
});
it('Displays the unmapped field on the JSON view', () => {
- const expectedUnmappedField = { line: 2, text: ' "unmapped": "This is the unmapped field"' };
+ const expectedUnmappedValue = 'This is the unmapped field';
openJsonView();
- scrollJsonViewToBottom();
- cy.get(ALERT_FLYOUT)
- .find(JSON_LINES)
- .then((elements) => {
- const length = elements.length;
- cy.wrap(elements)
- .eq(length - expectedUnmappedField.line)
- .invoke('text')
- .should('include', expectedUnmappedField.text);
- });
+ cy.get(JSON_TEXT).then((x) => {
+ const parsed = JSON.parse(x.text());
+ expect(parsed._source.unmapped).to.equal(expectedUnmappedValue);
+ });
});
it('Displays the unmapped field on the table', () => {
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
index b3c6abcd8e426..f15e7adbbca44 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
@@ -10,7 +10,7 @@ import { cleanKibana, reload } from '../../tasks/common';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import {
- JSON_LINES,
+ JSON_TEXT,
TABLE_CELL,
TABLE_ROWS,
THREAT_DETAILS_VIEW,
@@ -28,11 +28,7 @@ import {
viewThreatIntelTab,
} from '../../tasks/alerts';
import { createCustomIndicatorRule } from '../../tasks/api_calls/rules';
-import {
- openJsonView,
- openThreatIndicatorDetails,
- scrollJsonViewToBottom,
-} from '../../tasks/alerts_details';
+import { openJsonView, openThreatIndicatorDetails } from '../../tasks/alerts_details';
import { ALERTS_URL } from '../../urls/navigation';
import { addsFieldsToTimeline } from '../../tasks/rule_details';
@@ -76,26 +72,39 @@ describe('CTI Enrichment', () => {
it('Displays persisted enrichments on the JSON view', () => {
const expectedEnrichment = [
- { line: 4, text: ' "threat": {' },
{
- line: 3,
- text: ' "enrichments": "{\\"indicator\\":{\\"first_seen\\":\\"2021-03-10T08:02:14.000Z\\",\\"file\\":{\\"size\\":80280,\\"pe\\":{},\\"type\\":\\"elf\\",\\"hash\\":{\\"sha256\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"tlsh\\":\\"6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE\\",\\"ssdeep\\":\\"1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL\\",\\"md5\\":\\"9b6c3518a91d23ed77504b5416bfb5b3\\"}},\\"type\\":\\"file\\"},\\"matched\\":{\\"atomic\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"field\\":\\"myhash.mysha256\\",\\"id\\":\\"84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f\\",\\"index\\":\\"logs-ti_abusech.malware\\",\\"type\\":\\"indicator_match_rule\\"}}"',
+ indicator: {
+ first_seen: '2021-03-10T08:02:14.000Z',
+ file: {
+ size: 80280,
+ pe: {},
+ type: 'elf',
+ hash: {
+ sha256: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
+ tlsh: '6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE',
+ ssdeep:
+ '1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL',
+ md5: '9b6c3518a91d23ed77504b5416bfb5b3',
+ },
+ },
+ type: 'file',
+ },
+ matched: {
+ atomic: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
+ field: 'myhash.mysha256',
+ id: '84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f',
+ index: 'logs-ti_abusech.malware',
+ type: 'indicator_match_rule',
+ },
},
- { line: 2, text: ' }' },
];
expandFirstAlert();
openJsonView();
- scrollJsonViewToBottom();
-
- cy.get(JSON_LINES).then((elements) => {
- const length = elements.length;
- expectedEnrichment.forEach((enrichment) => {
- cy.wrap(elements)
- .eq(length - enrichment.line)
- .invoke('text')
- .should('include', enrichment.text);
- });
+
+ cy.get(JSON_TEXT).then((x) => {
+ const parsed = JSON.parse(x.text());
+ expect(parsed._source.threat.enrichments).to.deep.equal(expectedEnrichment);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts
index c740a669d059a..584fba05452f0 100644
--- a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts
@@ -28,6 +28,8 @@ export const JSON_LINES = '.euiCodeBlock__line';
export const JSON_VIEW_TAB = '[data-test-subj="jsonViewTab"]';
+export const JSON_TEXT = '[data-test-subj="jsonView"]';
+
export const TABLE_CELL = '.euiTableRowCell';
export const TABLE_TAB = '[data-test-subj="tableTab"]';
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap
index d367c68586be1..930e1282ebca5 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/alert_summary_view.test.tsx.snap
@@ -138,12 +138,17 @@ exports[`AlertSummaryView Behavior event code renders additional summary rows 1`
class="euiTableCellContent flyoutOverviewDescription euiTableCellContent--overflowingContent"
>
{
- "_id": "pEMaMmkBUV60JmNWmWVi",
- "_index": "filebeat-8.0.0-2019.02.19-000001",
+ "_index": ".ds-logs-endpoint.events.network-default-2021.09.28-000001",
+ "_id": "TUWyf3wBFCFU0qRJTauW",
"_score": 1,
- "_type": "_doc",
- "@timestamp": "2019-02-28T16:50:54.621Z",
- "agent": {
- "ephemeral_id": "9d391ef2-a734-4787-8891-67031178c641",
- "hostname": "siem-kibana",
- "id": "5de03d5f-52f3-482e-91d4-853c7de073c3",
- "type": "filebeat",
- "version": "8.0.0"
- },
- "cloud": {
- "availability_zone": "projects/189716325846/zones/us-east1-b",
- "instance": {
- "id": "5412578377715150143",
- "name": "siem-kibana"
+ "_source": {
+ "agent": {
+ "id": "2ac9e9b3-f6d5-4ce6-915d-8f1f8f413624",
+ "type": "endpoint",
+ "version": "8.0.0-SNAPSHOT"
},
- "machine": {
- "type": "projects/189716325846/machineTypes/n1-standard-1"
+ "process": {
+ "Ext": {
+ "ancestry": [
+ "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyMzY0LTEzMjc4NjA2NTAyLjA=",
+ "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTEtMTMyNzA3Njg2OTIuMA=="
+ ]
+ },
+ "name": "filebeat",
+ "pid": 22535,
+ "entity_id": "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyNTM1LTEzMjc4NjA2NTI4LjA=",
+ "executable": "/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat"
},
- "project": {
- "id": "elastic-beats"
+ "destination": {
+ "address": "127.0.0.1",
+ "port": 9200,
+ "ip": "127.0.0.1"
},
- "provider": "gce"
- },
- "destination": {
- "bytes": 584,
- "ip": "10.47.8.200",
- "packets": 4,
- "port": 902
+ "source": {
+ "address": "127.0.0.1",
+ "port": 54146,
+ "ip": "127.0.0.1"
+ },
+ "message": "Endpoint network event",
+ "network": {
+ "transport": "tcp",
+ "type": "ipv4"
+ },
+ "@timestamp": "2021-10-14T16:45:58.0310772Z",
+ "ecs": {
+ "version": "1.11.0"
+ },
+ "data_stream": {
+ "namespace": "default",
+ "type": "logs",
+ "dataset": "endpoint.events.network"
+ },
+ "elastic": {
+ "agent": {
+ "id": "12345"
+ }
+ },
+ "host": {
+ "hostname": "test-linux-1",
+ "os": {
+ "Ext": {
+ "variant": "Debian"
+ },
+ "kernel": "4.19.0-17-cloud-amd64 #1 SMP Debian 4.19.194-2 (2021-06-21)",
+ "name": "Linux",
+ "family": "debian",
+ "type": "linux",
+ "version": "10",
+ "platform": "debian",
+ "full": "Debian 10"
+ },
+ "ip": [
+ "127.0.0.1",
+ "::1",
+ "10.1.2.3",
+ "2001:0DB8:AC10:FE01::"
+ ],
+ "name": "test-linux-1",
+ "id": "76ea303129f249aa7382338e4263eac1",
+ "mac": [
+ "aa:bb:cc:dd:ee:ff"
+ ],
+ "architecture": "x86_64"
+ },
+ "event": {
+ "agent_id_status": "verified",
+ "sequence": 44872,
+ "ingested": "2021-10-14T16:46:04Z",
+ "created": "2021-10-14T16:45:58.0310772Z",
+ "kind": "event",
+ "module": "endpoint",
+ "action": "connection_attempted",
+ "id": "MKPXftjGeHiQzUNj++++nn6R",
+ "category": [
+ "network"
+ ],
+ "type": [
+ "start"
+ ],
+ "dataset": "endpoint.events.network",
+ "outcome": "unknown"
+ },
+ "user": {
+ "Ext": {
+ "real": {
+ "name": "root",
+ "id": 0
+ }
+ },
+ "name": "root",
+ "id": 0
+ },
+ "group": {
+ "Ext": {
+ "real": {
+ "name": "root",
+ "id": 0
+ }
+ },
+ "name": "root",
+ "id": 0
+ }
},
- "event": {
- "kind": "event"
+ "fields": {
+ "host.os.full.text": [
+ "Debian 10"
+ ],
+ "event.category": [
+ "network"
+ ],
+ "process.name.text": [
+ "filebeat"
+ ],
+ "host.os.name.text": [
+ "Linux"
+ ],
+ "host.os.full": [
+ "Debian 10"
+ ],
+ "host.hostname": [
+ "test-linux-1"
+ ],
+ "process.pid": [
+ 22535
+ ],
+ "host.mac": [
+ "42:01:0a:c8:00:32"
+ ],
+ "elastic.agent.id": [
+ "abcdefg-f6d5-4ce6-915d-8f1f8f413624"
+ ],
+ "host.os.version": [
+ "10"
+ ],
+ "host.os.name": [
+ "Linux"
+ ],
+ "source.ip": [
+ "127.0.0.1"
+ ],
+ "destination.address": [
+ "127.0.0.1"
+ ],
+ "host.name": [
+ "test-linux-1"
+ ],
+ "event.agent_id_status": [
+ "verified"
+ ],
+ "event.kind": [
+ "event"
+ ],
+ "event.outcome": [
+ "unknown"
+ ],
+ "group.name": [
+ "root"
+ ],
+ "user.id": [
+ "0"
+ ],
+ "host.os.type": [
+ "linux"
+ ],
+ "process.Ext.ancestry": [
+ "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyMzY0LTEzMjc4NjA2NTAyLjA=",
+ "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTEtMTMyNzA3Njg2OTIuMA=="
+ ],
+ "user.Ext.real.id": [
+ "0"
+ ],
+ "data_stream.type": [
+ "logs"
+ ],
+ "host.architecture": [
+ "x86_64"
+ ],
+ "process.name": [
+ "filebeat"
+ ],
+ "agent.id": [
+ "2ac9e9b3-f6d5-4ce6-915d-8f1f8f413624"
+ ],
+ "source.port": [
+ 54146
+ ],
+ "ecs.version": [
+ "1.11.0"
+ ],
+ "event.created": [
+ "2021-10-14T16:45:58.031Z"
+ ],
+ "agent.version": [
+ "8.0.0-SNAPSHOT"
+ ],
+ "host.os.family": [
+ "debian"
+ ],
+ "destination.port": [
+ 9200
+ ],
+ "group.id": [
+ "0"
+ ],
+ "user.name": [
+ "root"
+ ],
+ "source.address": [
+ "127.0.0.1"
+ ],
+ "process.entity_id": [
+ "MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyNTM1LTEzMjc4NjA2NTI4LjA="
+ ],
+ "host.ip": [
+ "127.0.0.1",
+ "::1",
+ "10.1.2.3",
+ "2001:0DB8:AC10:FE01::"
+ ],
+ "process.executable.caseless": [
+ "/opt/elastic/agent/data/elastic-agent-058c40/install/filebeat-8.0.0-snapshot-linux-x86_64/filebeat"
+ ],
+ "event.sequence": [
+ 44872
+ ],
+ "agent.type": [
+ "endpoint"
+ ],
+ "process.executable.text": [
+ "/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat"
+ ],
+ "group.Ext.real.name": [
+ "root"
+ ],
+ "event.module": [
+ "endpoint"
+ ],
+ "host.os.kernel": [
+ "4.19.0-17-cloud-amd64 #1 SMP Debian 4.19.194-2 (2021-06-21)"
+ ],
+ "host.os.full.caseless": [
+ "debian 10"
+ ],
+ "host.id": [
+ "76ea303129f249aa7382338e4263eac1"
+ ],
+ "process.name.caseless": [
+ "filebeat"
+ ],
+ "network.type": [
+ "ipv4"
+ ],
+ "process.executable": [
+ "/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat"
+ ],
+ "user.Ext.real.name": [
+ "root"
+ ],
+ "data_stream.namespace": [
+ "default"
+ ],
+ "message": [
+ "Endpoint network event"
+ ],
+ "destination.ip": [
+ "127.0.0.1"
+ ],
+ "network.transport": [
+ "tcp"
+ ],
+ "host.os.Ext.variant": [
+ "Debian"
+ ],
+ "group.Ext.real.id": [
+ "0"
+ ],
+ "event.ingested": [
+ "2021-10-14T16:46:04.000Z"
+ ],
+ "event.action": [
+ "connection_attempted"
+ ],
+ "@timestamp": [
+ "2021-10-14T16:45:58.031Z"
+ ],
+ "host.os.platform": [
+ "debian"
+ ],
+ "data_stream.dataset": [
+ "endpoint.events.network"
+ ],
+ "event.type": [
+ "start"
+ ],
+ "event.id": [
+ "MKPXftjGeHiQzUNj++++nn6R"
+ ],
+ "host.os.name.caseless": [
+ "linux"
+ ],
+ "event.dataset": [
+ "endpoint.events.network"
+ ],
+ "user.name.text": [
+ "root"
+ ]
}
}
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx
index a8ba536a75541..37ca3b0b897a6 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx
@@ -11,7 +11,7 @@ import React from 'react';
import '../../mock/match_media';
import '../../mock/react_beautiful_dnd';
-import { mockDetailItemData, mockDetailItemDataId, TestProviders } from '../../mock';
+import { mockDetailItemData, mockDetailItemDataId, rawEventData, TestProviders } from '../../mock';
import { EventDetails, EventsViewType } from './event_details';
import { mockBrowserFields } from '../../containers/source/mock';
@@ -48,6 +48,7 @@ describe('EventDetails', () => {
timelineId: 'test',
eventView: EventsViewType.summaryView,
hostRisk: { fields: [], loading: true },
+ rawEventData,
};
const alertsProps = {
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx
index e7092d9d6f466..a8305a635f157 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx
@@ -61,6 +61,7 @@ interface Props {
id: string;
isAlert: boolean;
isDraggable?: boolean;
+ rawEventData: object | undefined;
timelineTabType: TimelineTabs | 'flyout';
timelineId: string;
hostRisk: HostRisk | null;
@@ -106,6 +107,7 @@ const EventDetailsComponent: React.FC = ({
id,
isAlert,
isDraggable,
+ rawEventData,
timelineId,
timelineTabType,
hostRisk,
@@ -278,12 +280,12 @@ const EventDetailsComponent: React.FC = ({
<>
-
+
>
),
}),
- [data]
+ [rawEventData]
);
const tabs = useMemo(() => {
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx
index 696fac6016603..b20270266602d 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx
@@ -8,58 +8,15 @@
import { shallow } from 'enzyme';
import React from 'react';
-import { mockDetailItemData } from '../../mock';
+import { rawEventData } from '../../mock';
-import { buildJsonView, JsonView } from './json_view';
+import { JsonView } from './json_view';
describe('JSON View', () => {
describe('rendering', () => {
test('should match snapshot', () => {
- const wrapper = shallow();
+ const wrapper = shallow();
expect(wrapper).toMatchSnapshot();
});
});
-
- describe('buildJsonView', () => {
- test('should match a json', () => {
- const expectedData = {
- '@timestamp': '2019-02-28T16:50:54.621Z',
- _id: 'pEMaMmkBUV60JmNWmWVi',
- _index: 'filebeat-8.0.0-2019.02.19-000001',
- _score: 1,
- _type: '_doc',
- agent: {
- ephemeral_id: '9d391ef2-a734-4787-8891-67031178c641',
- hostname: 'siem-kibana',
- id: '5de03d5f-52f3-482e-91d4-853c7de073c3',
- type: 'filebeat',
- version: '8.0.0',
- },
- cloud: {
- availability_zone: 'projects/189716325846/zones/us-east1-b',
- instance: {
- id: '5412578377715150143',
- name: 'siem-kibana',
- },
- machine: {
- type: 'projects/189716325846/machineTypes/n1-standard-1',
- },
- project: {
- id: 'elastic-beats',
- },
- provider: 'gce',
- },
- destination: {
- bytes: 584,
- ip: '10.47.8.200',
- packets: 4,
- port: 902,
- },
- event: {
- kind: 'event',
- },
- };
- expect(buildJsonView(mockDetailItemData)).toEqual(expectedData);
- });
- });
});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx
index 0614f131bcd10..0227d44f32305 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.tsx
@@ -6,15 +6,13 @@
*/
import { EuiCodeBlock } from '@elastic/eui';
-import { set } from '@elastic/safer-lodash-set/fp';
import React, { useMemo } from 'react';
import styled from 'styled-components';
-import { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
import { omitTypenameAndEmpty } from '../../../timelines/components/timeline/body/helpers';
interface Props {
- data: TimelineEventsDetailsItem[];
+ rawEventData: object | undefined;
}
const EuiCodeEditorContainer = styled.div`
@@ -23,15 +21,15 @@ const EuiCodeEditorContainer = styled.div`
}
`;
-export const JsonView = React.memo(({ data }) => {
+export const JsonView = React.memo(({ rawEventData }) => {
const value = useMemo(
() =>
JSON.stringify(
- buildJsonView(data),
+ rawEventData,
omitTypenameAndEmpty,
2 // indent level
),
- [data]
+ [rawEventData]
);
return (
@@ -50,16 +48,3 @@ export const JsonView = React.memo(({ data }) => {
});
JsonView.displayName = 'JsonView';
-
-export const buildJsonView = (data: TimelineEventsDetailsItem[]) =>
- data
- .sort((a, b) => a.field.localeCompare(b.field))
- .reduce(
- (accumulator, item) =>
- set(
- item.field,
- Array.isArray(item.originalValue) ? item.originalValue.join() : item.originalValue,
- accumulator
- ),
- {}
- );
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx
new file mode 100644
index 0000000000000..f6c43da2da8ac
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx
@@ -0,0 +1,193 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+
+import { BrowserField } from '../../../containers/source';
+import { FieldValueCell } from './field_value_cell';
+import { TestProviders } from '../../../mock';
+import { EventFieldsData } from '../types';
+
+const contextId = 'test';
+
+const eventId = 'TUWyf3wBFCFU0qRJTauW';
+
+const hostIpData: EventFieldsData = {
+ aggregatable: true,
+ ariaRowindex: 35,
+ category: 'host',
+ description: 'Host ip addresses.',
+ example: '127.0.0.1',
+ field: 'host.ip',
+ fields: {},
+ format: '',
+ indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'],
+ isObjectArray: false,
+ name: 'host.ip',
+ originalValue: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'],
+ searchable: true,
+ type: 'ip',
+ values: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'],
+};
+const hostIpValues = ['127.0.0.1', '::1', '10.1.2.3', 'fe80::4001:aff:fec8:32'];
+
+describe('FieldValueCell', () => {
+ describe('common behavior', () => {
+ beforeEach(() => {
+ render(
+
+
+
+ );
+ });
+
+ test('it formats multiple values such that each value is displayed on a single line', () => {
+ expect(screen.getByTestId(`event-field-${hostIpData.field}`)).toHaveClass(
+ 'euiFlexGroup--directionColumn'
+ );
+ });
+ });
+
+ describe('when `BrowserField` metadata is NOT available', () => {
+ beforeEach(() => {
+ render(
+
+
+
+ );
+ });
+
+ test('it renders each of the expected values when `fieldFromBrowserField` is undefined', () => {
+ hostIpValues.forEach((value) => {
+ expect(screen.getByText(value)).toBeInTheDocument();
+ });
+ });
+
+ test('it renders values formatted as plain text (without `eventFieldsTable__fieldValue` formatting)', () => {
+ expect(screen.getByTestId(`event-field-${hostIpData.field}`).firstChild).not.toHaveClass(
+ 'eventFieldsTable__fieldValue'
+ );
+ });
+ });
+
+ describe('`message` field formatting', () => {
+ const messageData: EventFieldsData = {
+ aggregatable: false,
+ ariaRowindex: 50,
+ category: 'base',
+ description:
+ 'For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.',
+ example: 'Hello World',
+ field: 'message',
+ fields: {},
+ format: '',
+ indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'],
+ isObjectArray: false,
+ name: 'message',
+ originalValue: ['Endpoint network event'],
+ searchable: true,
+ type: 'string',
+ values: ['Endpoint network event'],
+ };
+ const messageValues = ['Endpoint network event'];
+
+ const messageFieldFromBrowserField: BrowserField = {
+ aggregatable: false,
+ category: 'base',
+ description:
+ 'For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.',
+ example: 'Hello World',
+ fields: {},
+ format: '',
+ indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'],
+ name: 'message',
+ searchable: true,
+ type: 'string',
+ };
+
+ beforeEach(() => {
+ render(
+
+
+
+ );
+ });
+
+ test('it renders special formatting for the `message` field', () => {
+ expect(screen.getByTestId('event-field-message')).toBeInTheDocument();
+ });
+
+ test('it renders the expected message value', () => {
+ messageValues.forEach((value) => {
+ expect(screen.getByText(value)).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('when `BrowserField` metadata IS available', () => {
+ const hostIpFieldFromBrowserField: BrowserField = {
+ aggregatable: true,
+ category: 'host',
+ description: 'Host ip addresses.',
+ example: '127.0.0.1',
+ fields: {},
+ format: '',
+ indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'],
+ name: 'host.ip',
+ searchable: true,
+ type: 'ip',
+ };
+
+ beforeEach(() => {
+ render(
+
+
+
+ );
+ });
+
+ test('it renders values formatted with the expected class', () => {
+ expect(screen.getByTestId(`event-field-${hostIpData.field}`).firstChild).toHaveClass(
+ 'eventFieldsTable__fieldValue'
+ );
+ });
+
+ test('it renders link buttons for each of the host ip addresses', () => {
+ expect(screen.getAllByRole('button').length).toBe(hostIpValues.length);
+ });
+
+ test('it renders each of the expected values when `fieldFromBrowserField` is provided', () => {
+ hostIpValues.forEach((value) => {
+ expect(screen.getByText(value)).toBeInTheDocument();
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx
index fc20f84d3650d..dc6c84b8138fe 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiText } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { BrowserField } from '../../../containers/source';
import { OverflowField } from '../../tables/helpers';
import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field';
@@ -36,18 +36,28 @@ export const FieldValueCell = React.memo(
values,
}: FieldValueCellProps) => {
return (
-
+
{values != null &&
values.map((value, i) => {
if (fieldFromBrowserField == null) {
return (
-
- {value}
-
+
+
+ {value}
+
+
);
}
return (
-
+
{data.field === MESSAGE_FIELD_NAME ? (
) : (
@@ -63,10 +73,10 @@ export const FieldValueCell = React.memo(
linkValue={(getLinkValue && getLinkValue(data.field)) ?? linkValue}
/>
)}
-
+
);
})}
-
+
);
}
);
diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts
index 3712d389edeb1..035bdbbceff88 100644
--- a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts
@@ -139,3 +139,191 @@ export const generateMockDetailItemData = (): TimelineEventsDetailsItem[] => [
];
export const mockDetailItemData: TimelineEventsDetailsItem[] = generateMockDetailItemData();
+
+export const rawEventData = {
+ _index: '.ds-logs-endpoint.events.network-default-2021.09.28-000001',
+ _id: 'TUWyf3wBFCFU0qRJTauW',
+ _score: 1,
+ _source: {
+ agent: {
+ id: '2ac9e9b3-f6d5-4ce6-915d-8f1f8f413624',
+ type: 'endpoint',
+ version: '8.0.0-SNAPSHOT',
+ },
+ process: {
+ Ext: {
+ ancestry: [
+ 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyMzY0LTEzMjc4NjA2NTAyLjA=',
+ 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTEtMTMyNzA3Njg2OTIuMA==',
+ ],
+ },
+ name: 'filebeat',
+ pid: 22535,
+ entity_id: 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyNTM1LTEzMjc4NjA2NTI4LjA=',
+ executable:
+ '/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat',
+ },
+ destination: {
+ address: '127.0.0.1',
+ port: 9200,
+ ip: '127.0.0.1',
+ },
+ source: {
+ address: '127.0.0.1',
+ port: 54146,
+ ip: '127.0.0.1',
+ },
+ message: 'Endpoint network event',
+ network: {
+ transport: 'tcp',
+ type: 'ipv4',
+ },
+ '@timestamp': '2021-10-14T16:45:58.0310772Z',
+ ecs: {
+ version: '1.11.0',
+ },
+ data_stream: {
+ namespace: 'default',
+ type: 'logs',
+ dataset: 'endpoint.events.network',
+ },
+ elastic: {
+ agent: {
+ id: '12345',
+ },
+ },
+ host: {
+ hostname: 'test-linux-1',
+ os: {
+ Ext: {
+ variant: 'Debian',
+ },
+ kernel: '4.19.0-17-cloud-amd64 #1 SMP Debian 4.19.194-2 (2021-06-21)',
+ name: 'Linux',
+ family: 'debian',
+ type: 'linux',
+ version: '10',
+ platform: 'debian',
+ full: 'Debian 10',
+ },
+ ip: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'],
+ name: 'test-linux-1',
+ id: '76ea303129f249aa7382338e4263eac1',
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: 'x86_64',
+ },
+ event: {
+ agent_id_status: 'verified',
+ sequence: 44872,
+ ingested: '2021-10-14T16:46:04Z',
+ created: '2021-10-14T16:45:58.0310772Z',
+ kind: 'event',
+ module: 'endpoint',
+ action: 'connection_attempted',
+ id: 'MKPXftjGeHiQzUNj++++nn6R',
+ category: ['network'],
+ type: ['start'],
+ dataset: 'endpoint.events.network',
+ outcome: 'unknown',
+ },
+ user: {
+ Ext: {
+ real: {
+ name: 'root',
+ id: 0,
+ },
+ },
+ name: 'root',
+ id: 0,
+ },
+ group: {
+ Ext: {
+ real: {
+ name: 'root',
+ id: 0,
+ },
+ },
+ name: 'root',
+ id: 0,
+ },
+ },
+ fields: {
+ 'host.os.full.text': ['Debian 10'],
+ 'event.category': ['network'],
+ 'process.name.text': ['filebeat'],
+ 'host.os.name.text': ['Linux'],
+ 'host.os.full': ['Debian 10'],
+ 'host.hostname': ['test-linux-1'],
+ 'process.pid': [22535],
+ 'host.mac': ['42:01:0a:c8:00:32'],
+ 'elastic.agent.id': ['abcdefg-f6d5-4ce6-915d-8f1f8f413624'],
+ 'host.os.version': ['10'],
+ 'host.os.name': ['Linux'],
+ 'source.ip': ['127.0.0.1'],
+ 'destination.address': ['127.0.0.1'],
+ 'host.name': ['test-linux-1'],
+ 'event.agent_id_status': ['verified'],
+ 'event.kind': ['event'],
+ 'event.outcome': ['unknown'],
+ 'group.name': ['root'],
+ 'user.id': ['0'],
+ 'host.os.type': ['linux'],
+ 'process.Ext.ancestry': [
+ 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyMzY0LTEzMjc4NjA2NTAyLjA=',
+ 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTEtMTMyNzA3Njg2OTIuMA==',
+ ],
+ 'user.Ext.real.id': ['0'],
+ 'data_stream.type': ['logs'],
+ 'host.architecture': ['x86_64'],
+ 'process.name': ['filebeat'],
+ 'agent.id': ['2ac9e9b3-f6d5-4ce6-915d-8f1f8f413624'],
+ 'source.port': [54146],
+ 'ecs.version': ['1.11.0'],
+ 'event.created': ['2021-10-14T16:45:58.031Z'],
+ 'agent.version': ['8.0.0-SNAPSHOT'],
+ 'host.os.family': ['debian'],
+ 'destination.port': [9200],
+ 'group.id': ['0'],
+ 'user.name': ['root'],
+ 'source.address': ['127.0.0.1'],
+ 'process.entity_id': [
+ 'MmFjOWU5YjMtZjZkNS00Y2U2LTkxNWQtOGYxZjhmNDEzNjI0LTIyNTM1LTEzMjc4NjA2NTI4LjA=',
+ ],
+ 'host.ip': ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'],
+ 'process.executable.caseless': [
+ '/opt/elastic/agent/data/elastic-agent-058c40/install/filebeat-8.0.0-snapshot-linux-x86_64/filebeat',
+ ],
+ 'event.sequence': [44872],
+ 'agent.type': ['endpoint'],
+ 'process.executable.text': [
+ '/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat',
+ ],
+ 'group.Ext.real.name': ['root'],
+ 'event.module': ['endpoint'],
+ 'host.os.kernel': ['4.19.0-17-cloud-amd64 #1 SMP Debian 4.19.194-2 (2021-06-21)'],
+ 'host.os.full.caseless': ['debian 10'],
+ 'host.id': ['76ea303129f249aa7382338e4263eac1'],
+ 'process.name.caseless': ['filebeat'],
+ 'network.type': ['ipv4'],
+ 'process.executable': [
+ '/opt/Elastic/Agent/data/elastic-agent-058c40/install/filebeat-8.0.0-SNAPSHOT-linux-x86_64/filebeat',
+ ],
+ 'user.Ext.real.name': ['root'],
+ 'data_stream.namespace': ['default'],
+ message: ['Endpoint network event'],
+ 'destination.ip': ['127.0.0.1'],
+ 'network.transport': ['tcp'],
+ 'host.os.Ext.variant': ['Debian'],
+ 'group.Ext.real.id': ['0'],
+ 'event.ingested': ['2021-10-14T16:46:04.000Z'],
+ 'event.action': ['connection_attempted'],
+ '@timestamp': ['2021-10-14T16:45:58.031Z'],
+ 'host.os.platform': ['debian'],
+ 'data_stream.dataset': ['endpoint.events.network'],
+ 'event.type': ['start'],
+ 'event.id': ['MKPXftjGeHiQzUNj++++nn6R'],
+ 'host.os.name.caseless': ['linux'],
+ 'event.dataset': ['endpoint.events.network'],
+ 'user.name.text': ['root'],
+ },
+};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx
index 17d43d80a5a9a..6a7f0602c3675 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx
@@ -33,6 +33,7 @@ interface Props {
isDraggable?: boolean;
loading: boolean;
messageHeight?: number;
+ rawEventData: object | undefined;
timelineTabType: TimelineTabs | 'flyout';
timelineId: string;
hostRisk: HostRisk | null;
@@ -93,6 +94,7 @@ export const ExpandableEvent = React.memo(
loading,
detailsData,
hostRisk,
+ rawEventData,
}) => {
if (!event.eventId) {
return {i18n.EVENT_DETAILS_PLACEHOLDER};
@@ -111,6 +113,7 @@ export const ExpandableEvent = React.memo(
id={event.eventId}
isAlert={isAlert}
isDraggable={isDraggable}
+ rawEventData={rawEventData}
timelineId={timelineId}
timelineTabType={timelineTabType}
hostRisk={hostRisk}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx
index f8786e0706834..b9d7e0a8c024f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx
@@ -79,7 +79,7 @@ const EventDetailsPanelComponent: React.FC = ({
tabType,
timelineId,
}) => {
- const [loading, detailsData] = useTimelineEventsDetails({
+ const [loading, detailsData, rawEventData] = useTimelineEventsDetails({
docValueFields,
entityType,
indexName: expandedEvent.indexName ?? '',
@@ -195,6 +195,7 @@ const EventDetailsPanelComponent: React.FC = ({
isAlert={isAlert}
isDraggable={isDraggable}
loading={loading}
+ rawEventData={rawEventData}
timelineId={timelineId}
timelineTabType="flyout"
hostRisk={hostRisk}
@@ -228,6 +229,7 @@ const EventDetailsPanelComponent: React.FC = ({
isAlert={isAlert}
isDraggable={isDraggable}
loading={loading}
+ rawEventData={rawEventData}
timelineId={timelineId}
timelineTabType={tabType}
hostRisk={hostRisk}
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx
index e59eaeed4f2a6..f05966bd97870 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx
@@ -42,7 +42,7 @@ export const useTimelineEventsDetails = ({
indexName,
eventId,
skip,
-}: UseTimelineEventsDetailsProps): [boolean, EventsArgs['detailsData']] => {
+}: UseTimelineEventsDetailsProps): [boolean, EventsArgs['detailsData'], object | undefined] => {
const { data } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
@@ -55,6 +55,8 @@ export const useTimelineEventsDetails = ({
const [timelineDetailsResponse, setTimelineDetailsResponse] =
useState(null);
+ const [rawEventData, setRawEventData] = useState