From ea8791b3876fea585be3c4cbd297b6c1aa8ed8d5 Mon Sep 17 00:00:00 2001
From: Constance
Date: Mon, 21 Sep 2020 16:44:37 -0700
Subject: [PATCH 01/22] [Enterprise Search] Set up Workplace Search header
action menu, create reusable renderHeaderActions (#78050)
* Create reusable renderHeaderActions
- I put it in the same file as renderApp, since both functions behave similarly (one renders in the main Kibana app/body, the other renders in Kibana's header)
- slightly opinionated: renamed renderActionMenu to renderHeaderActions from Kibana's examples
* Add example renderHeaderActions usage
* Add example WorkplaceSearchHeaderActions component
- not sure if this should be where it lives or is exported from, feel free to change @scottybollinger
* Update WorkplaceSearchHeaderActions with realistic functionality
- pass required externalUrl helper as arg/prop
- TODO: Consider refactoring KibanaContext to a Kea store so that we can share it across our renderApp and renderHeaderActions
* Fix type errors
---
.../public/applications/index.test.tsx | 16 +++++++++-
.../public/applications/index.tsx | 19 ++++++++++++
.../components/layout/index.ts | 1 +
.../layout/kibana_header_actions.test.tsx | 29 +++++++++++++++++++
.../layout/kibana_header_actions.tsx | 28 ++++++++++++++++++
.../enterprise_search/public/plugin.ts | 9 +++++-
6 files changed, 100 insertions(+), 2 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx
diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
index e0cf2814b46b4..053c450ab925e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
@@ -10,7 +10,7 @@ import { AppMountParameters } from 'src/core/public';
import { coreMock } from 'src/core/public/mocks';
import { licensingMock } from '../../../licensing/public/mocks';
-import { renderApp } from './';
+import { renderApp, renderHeaderActions } from './';
import { AppSearch } from './app_search';
import { WorkplaceSearch } from './workplace_search';
@@ -33,6 +33,7 @@ describe('renderApp', () => {
const unmount = renderApp(MockApp, params, core, plugins, config, data);
expect(params.element.querySelector('.hello-world')).not.toBeNull();
+
unmount();
expect(params.element.innerHTML).toEqual('');
});
@@ -47,3 +48,16 @@ describe('renderApp', () => {
expect(params.element.querySelector('.setupGuide')).not.toBeNull();
});
});
+
+describe('renderHeaderActions', () => {
+ it('mounts and unmounts any HeaderActions component', () => {
+ const mockHeaderEl = document.createElement('header');
+ const MockHeaderActions = () => ;
+
+ const unmount = renderHeaderActions(MockHeaderActions, mockHeaderEl, {} as any);
+ expect(mockHeaderEl.querySelector('.hello-world')).not.toBeNull();
+
+ unmount();
+ expect(mockHeaderEl.innerHTML).toEqual('');
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx
index 82f884644be4a..43056f2f65538 100644
--- a/x-pack/plugins/enterprise_search/public/applications/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx
@@ -88,3 +88,22 @@ export const renderApp = (
ReactDOM.unmountComponentAtNode(params.element);
};
};
+
+/**
+ * Render function for Kibana's header action menu chrome -
+ * reusable by any Enterprise Search plugin simply by passing in
+ * a custom HeaderActions component (e.g., WorkplaceSearchHeaderActions)
+ * @see https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md
+ */
+interface IHeaderActionsProps {
+ externalUrl: IExternalUrl;
+}
+
+export const renderHeaderActions = (
+ HeaderActions: React.FC,
+ kibanaHeaderEl: HTMLElement,
+ externalUrl: IExternalUrl
+) => {
+ ReactDOM.render(, kibanaHeaderEl);
+ return () => ReactDOM.unmountComponentAtNode(kibanaHeaderEl);
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts
index 41861a8ee2dc5..915638246c00e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts
@@ -5,3 +5,4 @@
*/
export { WorkplaceSearchNav } from './nav';
+export { WorkplaceSearchHeaderActions } from './kibana_header_actions';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx
new file mode 100644
index 0000000000000..a006c5e3775d5
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { EuiButtonEmpty } from '@elastic/eui';
+import { ExternalUrl } from '../../../shared/enterprise_search_url';
+
+import { WorkplaceSearchHeaderActions } from './';
+
+describe('WorkplaceSearchHeaderActions', () => {
+ const externalUrl = new ExternalUrl('http://localhost:3002');
+
+ it('renders a link to the search application', () => {
+ const wrapper = shallow();
+
+ expect(wrapper.find(EuiButtonEmpty).prop('href')).toEqual('http://localhost:3002/ws/search');
+ });
+
+ it('does not render without an Enterprise Search host URL set', () => {
+ const wrapper = shallow();
+
+ expect(wrapper.isEmptyRender()).toBe(true);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx
new file mode 100644
index 0000000000000..fa32d598f848d
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiButtonEmpty } from '@elastic/eui';
+
+import { IExternalUrl } from '../../../shared/enterprise_search_url';
+
+interface IProps {
+ externalUrl: IExternalUrl;
+}
+
+export const WorkplaceSearchHeaderActions: React.FC = ({ externalUrl }) => {
+ const { enterpriseSearchUrl, getWorkplaceSearchUrl } = externalUrl;
+ if (!enterpriseSearchUrl) return null;
+
+ return (
+
+ {i18n.translate('xpack.enterpriseSearch.workplaceSearch.headerActions.searchApplication', {
+ defaultMessage: 'Go to search application',
+ })}
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts
index 0ef58a7c03f10..c23bb23be3979 100644
--- a/x-pack/plugins/enterprise_search/public/plugin.ts
+++ b/x-pack/plugins/enterprise_search/public/plugin.ts
@@ -103,9 +103,16 @@ export class EnterpriseSearchPlugin implements Plugin {
await this.getInitialData(coreStart.http);
- const { renderApp } = await import('./applications');
+ const { renderApp, renderHeaderActions } = await import('./applications');
const { WorkplaceSearch } = await import('./applications/workplace_search');
+ const { WorkplaceSearchHeaderActions } = await import(
+ './applications/workplace_search/components/layout'
+ );
+ params.setHeaderActionMenu((element) =>
+ renderHeaderActions(WorkplaceSearchHeaderActions, element, this.data.externalUrl)
+ );
+
return renderApp(WorkplaceSearch, params, coreStart, plugins, this.config, this.data);
},
});
From 8d7aae0c214c9ed8f7e5daa98a7f24f9f131cfa3 Mon Sep 17 00:00:00 2001
From: spalger
Date: Mon, 21 Sep 2020 16:55:08 -0700
Subject: [PATCH 02/22] Revert "Accessibility dashboard edit panel tests
(#77829)"
This reverts commit e15b4c43d4b570822ba31801931069b1bbb87217.
---
test/accessibility/apps/dashboard_panel.ts | 2 +-
.../apps/dashboard_edit_panel.ts | 118 ------------------
x-pack/test/accessibility/config.ts | 1 -
3 files changed, 1 insertion(+), 120 deletions(-)
delete mode 100644 x-pack/test/accessibility/apps/dashboard_edit_panel.ts
diff --git a/test/accessibility/apps/dashboard_panel.ts b/test/accessibility/apps/dashboard_panel.ts
index 1a817ce6b7a1c..03fa76387da1f 100644
--- a/test/accessibility/apps/dashboard_panel.ts
+++ b/test/accessibility/apps/dashboard_panel.ts
@@ -18,6 +18,7 @@
*/
import { FtrProviderContext } from '../ftr_provider_context';
+
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'home', 'settings']);
const a11y = getService('a11y');
@@ -30,7 +31,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
useActualUrl: true,
});
-
await PageObjects.home.addSampleDataSet('flights');
await PageObjects.common.navigateToApp('dashboard');
await testSubjects.click('dashboardListingTitleLink-[Flights]-Global-Flight-Dashboard');
diff --git a/x-pack/test/accessibility/apps/dashboard_edit_panel.ts b/x-pack/test/accessibility/apps/dashboard_edit_panel.ts
deleted file mode 100644
index 9c0827e4128cf..0000000000000
--- a/x-pack/test/accessibility/apps/dashboard_edit_panel.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { FtrProviderContext } from '../ftr_provider_context';
-
-export default function ({ getService, getPageObjects }: FtrProviderContext) {
- const { dashboard } = getPageObjects(['dashboard']);
- const a11y = getService('a11y');
- const dashboardPanelActions = getService('dashboardPanelActions');
- const testSubjects = getService('testSubjects');
- const esArchiver = getService('esArchiver');
- const drilldowns = getService('dashboardDrilldownsManage');
- const kibanaServer = getService('kibanaServer');
- const PageObjects = getPageObjects(['security', 'common']);
- const toasts = getService('toasts');
-
- describe('Dashboard Edit Panel', () => {
- before(async () => {
- await esArchiver.load('dashboard/drilldowns');
- await esArchiver.loadIfNeeded('logstash_functional');
- await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' });
- await PageObjects.common.navigateToApp('dashboard');
- await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME);
- await testSubjects.click('dashboardEditMode');
- });
-
- after(async () => {
- await esArchiver.unload('dashboard/drilldowns');
- });
-
- // embeddable edit panel
- it(' A11y test on dashboard edit panel menu options', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await a11y.testAppSnapshot();
- });
-
- // clone panel
- it(' A11y test on dashboard embeddable clone panel', async () => {
- await testSubjects.click('embeddablePanelAction-clonePanel');
- await a11y.testAppSnapshot();
- await toasts.dismissAllToasts();
- await dashboardPanelActions.removePanelByTitle('Visualization PieChart (copy)');
- });
-
- // edit dashboard title
- it(' A11y test on dashboard embeddable edit dashboard title', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await testSubjects.click('embeddablePanelAction-ACTION_CUSTOMIZE_PANEL');
- await a11y.testAppSnapshot();
- await testSubjects.click('customizePanelHideTitle');
- await a11y.testAppSnapshot();
- await testSubjects.click('saveNewTitleButton');
- });
-
- // https://github.com/elastic/kibana/issues/77931
- it.skip('A11y test for edit visualization and save', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await testSubjects.click('embeddablePanelAction-editPanel');
- await testSubjects.click('visualizesaveAndReturnButton');
- await a11y.testAppSnapshot();
- });
-
- // https://github.com/elastic/kibana/issues/77422
- it.skip('A11y test on dashboard embeddable custom time range', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await testSubjects.click('embeddablePanelAction-CUSTOM_TIME_RANGE');
- await a11y.testAppSnapshot();
- });
-
- // inspector panel
- it('A11y test on dashboard embeddable open inspector', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await testSubjects.click('embeddablePanelAction-openInspector');
- await a11y.testAppSnapshot();
- await testSubjects.click('euiFlyoutCloseButton');
- });
-
- // create drilldown
- it('A11y test on dashboard embeddable open flyout and drilldown', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await testSubjects.click('embeddablePanelAction-OPEN_FLYOUT_ADD_DRILLDOWN');
- await a11y.testAppSnapshot();
- await testSubjects.click('flyoutCloseButton');
- });
-
- // fullscreen
- it('A11y test on dashboard embeddable fullscreen', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await testSubjects.click('embeddablePanelAction-togglePanel');
- await a11y.testAppSnapshot();
- });
-
- // minimize fullscreen panel
- it('A11y test on dashboard embeddable fullscreen minimize ', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await testSubjects.click('embeddablePanelAction-togglePanel');
- await a11y.testAppSnapshot();
- });
-
- // replace panel
- it('A11y test on dashboard embeddable replace panel', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await testSubjects.click('embeddablePanelAction-replacePanel');
- await a11y.testAppSnapshot();
- await testSubjects.click('euiFlyoutCloseButton');
- });
-
- // delete from dashboard
- it('A11y test on dashboard embeddable delete panel', async () => {
- await testSubjects.click('embeddablePanelToggleMenuIcon');
- await testSubjects.click('embeddablePanelAction-deletePanel');
- await a11y.testAppSnapshot();
- });
- });
-}
diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts
index 9382a7186db54..bae7b688fd28c 100644
--- a/x-pack/test/accessibility/config.ts
+++ b/x-pack/test/accessibility/config.ts
@@ -21,7 +21,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('./apps/search_profiler'),
require.resolve('./apps/uptime'),
require.resolve('./apps/spaces'),
- require.resolve('./apps/dashboard_edit_panel'),
],
pageObjects,
services,
From 05df9efc912cf262d068933e7aae4523c2705932 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Mon, 21 Sep 2020 20:45:30 -0400
Subject: [PATCH 03/22] [Mappings editor] Add support for point field type
(#77543)
---
.../datatypes/point_datatype.test.tsx | 158 ++++++++++++++++++
.../helpers/mappings_editor.helpers.tsx | 9 +-
.../ignore_z_value_parameter.tsx | 13 +-
.../field_parameters/meta_parameter.tsx | 2 +
.../fields/field_types/index.ts | 2 +
.../fields/field_types/point_type.tsx | 76 +++++++++
.../constants/data_types_definition.tsx | 21 +++
.../constants/parameters_definition.tsx | 62 ++++++-
.../mappings_editor/types/document_fields.ts | 2 +
9 files changed, 335 insertions(+), 10 deletions(-)
create mode 100644 x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx
create mode 100644 x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/point_type.tsx
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx
new file mode 100644
index 0000000000000..0ee70d63ba667
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx
@@ -0,0 +1,158 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { act } from 'react-dom/test-utils';
+
+import { componentHelpers, MappingsEditorTestBed } from '../helpers';
+
+const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor;
+
+// Parameters automatically added to the point datatype when saved (with the default values)
+export const defaultPointParameters = {
+ type: 'point',
+ ignore_malformed: false,
+ ignore_z_value: true,
+};
+
+describe('Mappings editor: point datatype', () => {
+ /**
+ * Variable to store the mappings data forwarded to the consumer component
+ */
+ let data: any;
+ let onChangeHandler: jest.Mock = jest.fn();
+ let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler);
+ let testBed: MappingsEditorTestBed;
+
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ beforeEach(() => {
+ onChangeHandler = jest.fn();
+ getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler);
+ });
+
+ test('initial view and default parameters values', async () => {
+ const defaultMappings = {
+ properties: {
+ myField: {
+ type: 'point',
+ },
+ },
+ };
+
+ const updatedMappings = { ...defaultMappings };
+
+ await act(async () => {
+ testBed = setup({ value: defaultMappings, onChange: onChangeHandler });
+ });
+ testBed.component.update();
+
+ const {
+ component,
+ actions: { startEditField, updateFieldAndCloseFlyout },
+ } = testBed;
+
+ // Open the flyout to edit the field
+ await startEditField('myField');
+
+ // Save the field and close the flyout
+ await updateFieldAndCloseFlyout();
+
+ // It should have the default parameters values added
+ updatedMappings.properties.myField = defaultPointParameters;
+
+ ({ data } = await getMappingsEditorData(component));
+ expect(data).toEqual(updatedMappings);
+ });
+
+ describe('meta parameter', () => {
+ const defaultMappings = {
+ properties: {
+ myField: {
+ type: 'point',
+ },
+ },
+ };
+
+ const updatedMappings = { ...defaultMappings };
+
+ const metaParameter = {
+ meta: {
+ my_metadata: 'foobar',
+ },
+ };
+
+ beforeEach(async () => {
+ await act(async () => {
+ testBed = setup({ value: defaultMappings, onChange: onChangeHandler });
+ });
+ testBed.component.update();
+ });
+
+ test('valid meta object', async () => {
+ const {
+ component,
+ actions: {
+ startEditField,
+ updateFieldAndCloseFlyout,
+ showAdvancedSettings,
+ toggleFormRow,
+ updateJsonEditor,
+ },
+ } = testBed;
+
+ // Open the flyout to edit the field
+ await startEditField('myField');
+ await showAdvancedSettings();
+
+ // Enable the meta parameter and add value
+ toggleFormRow('metaParameter');
+ await act(async () => {
+ updateJsonEditor('metaParameterEditor', metaParameter.meta);
+ });
+ component.update();
+
+ // Save the field and close the flyout
+ await updateFieldAndCloseFlyout();
+
+ // It should have the default parameters values added, plus metadata
+ updatedMappings.properties.myField = {
+ ...defaultPointParameters,
+ ...metaParameter,
+ };
+
+ ({ data } = await getMappingsEditorData(component));
+ expect(data).toEqual(updatedMappings);
+ });
+
+ test('strip empty string', async () => {
+ const {
+ component,
+ actions: { startEditField, updateFieldAndCloseFlyout, showAdvancedSettings, toggleFormRow },
+ } = testBed;
+
+ // Open the flyout to edit the field
+ await startEditField('myField');
+ await showAdvancedSettings();
+
+ // Enable the meta parameter
+ toggleFormRow('metaParameter');
+
+ // Save the field and close the flyout without adding any values to meta parameter
+ await updateFieldAndCloseFlyout();
+
+ // It should have the default parameters values added
+ updatedMappings.properties.myField = defaultPointParameters;
+
+ ({ data } = await getMappingsEditorData(component));
+ expect(data).toEqual(updatedMappings);
+ });
+ });
+});
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx
index 2a4af89c46559..e123dea6ff2ff 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx
@@ -239,6 +239,10 @@ const createActions = (testBed: TestBed) => {
const getCheckboxValue = (testSubject: TestSubjects): boolean =>
find(testSubject).props().checked;
+ const toggleFormRow = (formRowName: string) => {
+ form.toggleEuiSwitch(`${formRowName}.formRowToggle`);
+ };
+
return {
selectTab,
getFieldAt,
@@ -252,6 +256,7 @@ const createActions = (testBed: TestBed) => {
getComboBoxValue,
getToggleValue,
getCheckboxValue,
+ toggleFormRow,
};
};
@@ -365,4 +370,6 @@ export type TestSubjects =
| 'searchQuoteAnalyzer-custom'
| 'searchQuoteAnalyzer-toggleCustomButton'
| 'searchQuoteAnalyzer-custom.input'
- | 'useSameAnalyzerForSearchCheckBox.input';
+ | 'useSameAnalyzerForSearchCheckBox.input'
+ | 'metaParameterEditor'
+ | string;
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/ignore_z_value_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/ignore_z_value_parameter.tsx
index bd118ac08964f..ce58a264db968 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/ignore_z_value_parameter.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/ignore_z_value_parameter.tsx
@@ -10,15 +10,18 @@ import { i18n } from '@kbn/i18n';
import { EditFieldFormRow } from '../fields/edit_field';
-export const IgnoreZValueParameter = () => (
+export const IgnoreZValueParameter = ({ description }: { description?: string }) => (
);
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/meta_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/meta_parameter.tsx
index c8af296318b61..a950ba82d0eac 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/meta_parameter.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/meta_parameter.tsx
@@ -32,6 +32,7 @@ export const MetaParameter: FunctionComponent = ({ defaultToggleValue })
}),
href: documentationService.getMetaLink(),
}}
+ data-test-subj="metaParameter"
>
= ({ defaultToggleValue })
component={JsonEditorField}
componentProps={{
euiCodeEditorProps: {
+ ['data-test-subj']: 'metaParameterEditor',
height: '300px',
'aria-label': i18n.translate('xpack.idxMgmt.mappingsEditor.metaParameterAriaLabel', {
defaultMessage: 'metadata field data editor',
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts
index 8fcd02e4a362e..6b092c5561b3b 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts
@@ -32,6 +32,7 @@ import { HistogramType } from './histogram_type';
import { ConstantKeywordType } from './constant_keyword_type';
import { RankFeatureType } from './rank_feature_type';
import { WildcardType } from './wildcard_type';
+import { PointType } from './point_type';
const typeToParametersFormMap: { [key in DataType]?: ComponentType } = {
alias: AliasType,
@@ -60,6 +61,7 @@ const typeToParametersFormMap: { [key in DataType]?: ComponentType } = {
constant_keyword: ConstantKeywordType,
rank_feature: RankFeatureType,
wildcard: WildcardType,
+ point: PointType,
};
export const getParametersFormForType = (
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/point_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/point_type.tsx
new file mode 100644
index 0000000000000..9108c56e4496b
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/point_type.tsx
@@ -0,0 +1,76 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { FunctionComponent } from 'react';
+
+import { i18n } from '@kbn/i18n';
+
+import { NormalizedField, Field as FieldType, ParameterName } from '../../../../types';
+import { UseField, TextAreaField } from '../../../../shared_imports';
+import { getFieldConfig } from '../../../../lib';
+import {
+ IgnoreMalformedParameter,
+ IgnoreZValueParameter,
+ NullValueParameter,
+ MetaParameter,
+} from '../../field_parameters';
+import { AdvancedParametersSection, BasicParametersSection } from '../edit_field';
+
+interface Props {
+ field: NormalizedField;
+}
+
+const getDefaultToggleValue = (param: ParameterName, field: FieldType) => {
+ return field[param] !== undefined && field[param] !== getFieldConfig(param).defaultValue;
+};
+
+export const PointType: FunctionComponent = ({ field }) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx
index a4d3bf3832d5c..293ae56d57ace 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx
@@ -821,6 +821,26 @@ export const TYPE_DEFINITION: { [key in DataType]: DataTypeDefinition } = {
),
},
+ point: {
+ label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.pointDescription', {
+ defaultMessage: 'Point',
+ }),
+ value: 'point',
+ documentation: {
+ main: '/point.html',
+ },
+ description: () => (
+
+ {'x,y'},
+ }}
+ />
+
+ ),
+ },
wildcard: {
label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.wildcardDescription', {
defaultMessage: 'Wildcard',
@@ -882,6 +902,7 @@ export const MAIN_TYPES: MainType[] = [
'token_count',
'histogram',
'wildcard',
+ 'point',
'other',
];
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx
index fd17dc1b8fd1e..4ffedc8ca114d 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx
@@ -382,6 +382,50 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio
},
schema: t.any,
},
+ null_value_point: {
+ fieldConfig: {
+ defaultValue: '',
+ label: nullValueLabel,
+ helpText: () => (
+
+ {i18n.translate(
+ 'xpack.idxMgmt.mappingsEditor.parameters.pointWellKnownTextDocumentationLink',
+ {
+ defaultMessage: 'Well-Known Text',
+ }
+ )}
+
+ ),
+ }}
+ />
+ ),
+ validations: [
+ {
+ validator: nullValueValidateEmptyField,
+ },
+ ],
+ deserializer: (value: any) => {
+ if (value === '') {
+ return value;
+ }
+ return JSON.stringify(value);
+ },
+ serializer: (value: string) => {
+ try {
+ return JSON.parse(value);
+ } catch (error) {
+ // swallow error and return non-parsed value;
+ return value;
+ }
+ },
+ },
+ schema: t.any,
+ },
copy_to: {
fieldConfig: {
defaultValue: '',
@@ -476,12 +520,22 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio
return JSON.stringify(value, null, 2);
},
serializer: (value: string) => {
- const parsed = JSON.parse(value);
- // If an empty object was passed, strip out this value entirely.
- if (!Object.keys(parsed).length) {
+ // Strip out empty strings
+ if (value.trim() === '') {
return undefined;
}
- return parsed;
+
+ try {
+ const parsed = JSON.parse(value);
+ // If an empty object was passed, strip out this value entirely.
+ if (!Object.keys(parsed).length) {
+ return undefined;
+ }
+ return parsed;
+ } catch (error) {
+ // swallow error and return non-parsed value;
+ return value;
+ }
},
},
schema: t.any,
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts
index 97dca49fc93ed..ca38a8d1e6c33 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts
@@ -59,6 +59,7 @@ export type MainType =
| 'geo_point'
| 'geo_shape'
| 'token_count'
+ | 'point'
| 'histogram'
| 'constant_keyword'
| 'wildcard'
@@ -109,6 +110,7 @@ export type ParameterName =
| 'null_value_boolean'
| 'null_value_geo_point'
| 'null_value_ip'
+ | 'null_value_point'
| 'copy_to'
| 'dynamic'
| 'dynamic_toggle'
From b778e9c0a9b84edc84402125962b22c9b5046875 Mon Sep 17 00:00:00 2001
From: Kevin Logan <56395104+kevinlog@users.noreply.github.com>
Date: Mon, 21 Sep 2020 21:26:39 -0400
Subject: [PATCH 04/22] [SECURITY_SOLUTION] list UI is backwards compatible
(#77956)
---
.../pages/endpoint_hosts/store/index.test.ts | 1 +
.../pages/endpoint_hosts/store/middleware.ts | 37 ++++++++++---------
.../store/mock_endpoint_result_list.ts | 17 +++++++--
.../pages/endpoint_hosts/store/reducer.ts | 3 ++
.../pages/endpoint_hosts/store/selectors.ts | 8 ++++
.../management/pages/endpoint_hosts/types.ts | 3 ++
.../pages/endpoint_hosts/view/index.test.tsx | 35 ++++++++++++++++--
.../pages/endpoint_hosts/view/index.tsx | 11 ++++--
8 files changed, 86 insertions(+), 29 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
index 4faef85afbdc8..61bcd222b1b1e 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
@@ -58,6 +58,7 @@ describe('EndpointList store concerns', () => {
patternsError: undefined,
isAutoRefreshEnabled: true,
autoRefreshInterval: DEFAULT_POLL_INTERVAL,
+ queryStrategyVersion: undefined,
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
index 7673702f54370..7872c8824a8ee 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
@@ -17,6 +17,7 @@ import {
nonExistingPolicies,
patterns,
searchBarQuery,
+ isTransformEnabled,
} from './selectors';
import { EndpointState, PolicyIds } from '../types';
import {
@@ -70,24 +71,6 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory HostResultList = (options = {}) => {
const {
total = 1,
request_page_size: requestPageSize = 10,
request_page_index: requestPageIndex = 0,
+ query_strategy_version: queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2,
} = options;
// Skip any that are before the page we're on
@@ -50,7 +52,7 @@ export const mockEndpointResultList: (options?: {
hosts.push({
metadata: generator.generateHostMetadata(),
host_status: HostStatus.ERROR,
- query_strategy_version: MetadataQueryStrategyVersions.VERSION_2,
+ query_strategy_version: queryStrategyVersion,
});
}
const mock: HostResultList = {
@@ -58,7 +60,7 @@ export const mockEndpointResultList: (options?: {
total,
request_page_size: requestPageSize,
request_page_index: requestPageIndex,
- query_strategy_version: MetadataQueryStrategyVersions.VERSION_2,
+ query_strategy_version: queryStrategyVersion,
};
return mock;
};
@@ -84,6 +86,7 @@ const endpointListApiPathHandlerMocks = ({
endpointPackagePolicies = [],
policyResponse = generator.generatePolicyResponse(),
agentPolicy = generator.generateAgentPolicy(),
+ queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2,
}: {
/** route handlers will be setup for each individual host in this array */
endpointsResults?: HostResultList['hosts'];
@@ -91,6 +94,7 @@ const endpointListApiPathHandlerMocks = ({
endpointPackagePolicies?: GetPolicyListResponse['items'];
policyResponse?: HostPolicyResponse;
agentPolicy?: GetAgentPoliciesResponseItem;
+ queryStrategyVersion?: MetadataQueryStrategyVersions;
} = {}) => {
const apiHandlers = {
// endpoint package info
@@ -107,7 +111,7 @@ const endpointListApiPathHandlerMocks = ({
request_page_size: 10,
request_page_index: 0,
total: endpointsResults?.length || 0,
- query_strategy_version: MetadataQueryStrategyVersions.VERSION_2,
+ query_strategy_version: queryStrategyVersion,
};
},
@@ -164,11 +168,16 @@ export const setEndpointListApiMockImplementation: (
apiResponses?: Parameters[0]
) => void = (
mockedHttpService,
- { endpointsResults = mockEndpointResultList({ total: 3 }).hosts, ...pathHandlersOptions } = {}
+ {
+ endpointsResults = mockEndpointResultList({ total: 3 }).hosts,
+ queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2,
+ ...pathHandlersOptions
+ } = {}
) => {
const apiHandlers = endpointListApiPathHandlerMocks({
...pathHandlersOptions,
endpointsResults,
+ queryStrategyVersion,
});
mockedHttpService.post
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
index 99a1df7eb4002..0f948f74a48e4 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
@@ -36,6 +36,7 @@ export const initialEndpointListState: Immutable = {
patternsError: undefined,
isAutoRefreshEnabled: true,
autoRefreshInterval: DEFAULT_POLL_INTERVAL,
+ queryStrategyVersion: undefined,
};
/* eslint-disable-next-line complexity */
@@ -49,6 +50,7 @@ export const endpointListReducer: ImmutableReducer = (
total,
request_page_size: pageSize,
request_page_index: pageIndex,
+ query_strategy_version: queryStrategyVersion,
} = action.payload;
return {
...state,
@@ -56,6 +58,7 @@ export const endpointListReducer: ImmutableReducer = (
total,
pageSize,
pageIndex,
+ queryStrategyVersion,
loading: false,
error: undefined,
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
index a3eee8063d6f7..fe47d60afc339 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
@@ -14,6 +14,7 @@ import {
HostPolicyResponseAppliedAction,
HostPolicyResponseConfiguration,
HostPolicyResponseActionStatus,
+ MetadataQueryStrategyVersions,
} from '../../../../../common/endpoint/types';
import { EndpointState, EndpointIndexUIQueryParams } from '../types';
import { extractListPaginationParams } from '../../../common/routing';
@@ -54,11 +55,18 @@ export const isAutoRefreshEnabled = (state: Immutable) => state.i
export const autoRefreshInterval = (state: Immutable) => state.autoRefreshInterval;
+const queryStrategyVersion = (state: Immutable) => state.queryStrategyVersion;
+
export const endpointPackageVersion = createSelector(
endpointPackageInfo,
(info) => info?.version ?? undefined
);
+export const isTransformEnabled = createSelector(
+ queryStrategyVersion,
+ (version) => version !== MetadataQueryStrategyVersions.VERSION_1
+);
+
/**
* Returns the index patterns for the SearchBar to use for autosuggest
*/
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
index 77f21243ea120..bdd0d5e942cef 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
@@ -11,6 +11,7 @@ import {
HostPolicyResponse,
AppLocation,
PolicyData,
+ MetadataQueryStrategyVersions,
} from '../../../../common/endpoint/types';
import { ServerApiError } from '../../../common/types';
import { GetPackagesResponse } from '../../../../../ingest_manager/common';
@@ -65,6 +66,8 @@ export interface EndpointState {
isAutoRefreshEnabled: boolean;
/** The current auto refresh interval for data in ms */
autoRefreshInterval: number;
+ /** The query strategy version that informs whether the transform for KQL is enabled or not */
+ queryStrategyVersion?: MetadataQueryStrategyVersions;
}
/**
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 bd8344f41fe3a..51a6be18471aa 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
@@ -108,6 +108,31 @@ describe('when on the list page', () => {
});
});
+ describe('when loading data with the query_strategy_version is `v1`', () => {
+ beforeEach(() => {
+ reactTestingLibrary.act(() => {
+ const mockedEndpointListData = mockEndpointResultList({
+ total: 4,
+ query_strategy_version: MetadataQueryStrategyVersions.VERSION_1,
+ });
+ setEndpointListApiMockImplementation(coreStart.http, {
+ endpointsResults: mockedEndpointListData.hosts,
+ queryStrategyVersion: mockedEndpointListData.query_strategy_version,
+ });
+ });
+ });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+ it('should not display the KQL bar', async () => {
+ const renderResult = render();
+ await reactTestingLibrary.act(async () => {
+ await middlewareSpy.waitForAction('serverReturnedEndpointList');
+ });
+ expect(renderResult.queryByTestId('adminSearchBar')).toBeNull();
+ });
+ });
+
describe('when there is no selected host in the url', () => {
it('should not show the flyout', () => {
const renderResult = render();
@@ -123,7 +148,9 @@ describe('when on the list page', () => {
let firstPolicyID: string;
beforeEach(() => {
reactTestingLibrary.act(() => {
- const hostListData = mockEndpointResultList({ total: 4 }).hosts;
+ const mockedEndpointData = mockEndpointResultList({ total: 4 });
+ const hostListData = mockedEndpointData.hosts;
+ const queryStrategyVersion = mockedEndpointData.query_strategy_version;
firstPolicyID = hostListData[0].metadata.Endpoint.policy.applied.id;
@@ -132,7 +159,7 @@ describe('when on the list page', () => {
hostListData[index] = {
metadata: hostListData[index].metadata,
host_status: status,
- query_strategy_version: MetadataQueryStrategyVersions.VERSION_2,
+ query_strategy_version: queryStrategyVersion,
};
}
);
@@ -682,11 +709,11 @@ describe('when on the list page', () => {
let renderAndWaitForData: () => Promise>;
const mockEndpointListApi = () => {
- const { hosts } = mockEndpointResultList();
+ const { hosts, query_strategy_version: queryStrategyVersion } = mockEndpointResultList();
hostInfo = {
host_status: hosts[0].host_status,
metadata: hosts[0].metadata,
- query_strategy_version: MetadataQueryStrategyVersions.VERSION_2,
+ query_strategy_version: queryStrategyVersion,
};
const packagePolicy = docGenerator.generatePolicyPackagePolicy();
packagePolicy.id = hosts[0].metadata.Endpoint.policy.applied.id;
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
index ecee2bee0c58b..3e1f08eee7b94 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
@@ -135,6 +135,7 @@ export const EndpointList = () => {
autoRefreshInterval,
isAutoRefreshEnabled,
patternsError,
+ isTransformEnabled,
} = useEndpointSelector(selector);
const { formatUrl, search } = useFormatUrl(SecurityPageName.administration);
@@ -532,8 +533,8 @@ export const EndpointList = () => {
const hasListData = listData && listData.length > 0;
const refreshStyle = useMemo(() => {
- return { display: endpointsExist ? 'flex' : 'none', maxWidth: 200 };
- }, [endpointsExist]);
+ return { display: endpointsExist && isTransformEnabled ? 'flex' : 'none', maxWidth: 200 };
+ }, [endpointsExist, isTransformEnabled]);
const refreshIsPaused = useMemo(() => {
return !endpointsExist ? false : hasSelectedEndpoint ? true : !isAutoRefreshEnabled;
@@ -543,6 +544,10 @@ export const EndpointList = () => {
return !endpointsExist ? DEFAULT_POLL_INTERVAL : autoRefreshInterval;
}, [endpointsExist, autoRefreshInterval]);
+ const shouldShowKQLBar = useMemo(() => {
+ return endpointsExist && !patternsError && isTransformEnabled;
+ }, [endpointsExist, patternsError, isTransformEnabled]);
+
return (
{
{hasSelectedEndpoint && }
<>
- {endpointsExist && !patternsError && (
+ {shouldShowKQLBar && (
From 4d115d50140b0f57f7975b3465501373cfc456bc Mon Sep 17 00:00:00 2001
From: Spencer
Date: Mon, 21 Sep 2020 19:09:54 -0700
Subject: [PATCH 05/22] [baseline capture] switch to large workers (#78109)
Co-authored-by: spalger
---
.ci/Jenkinsfile_baseline_capture | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.ci/Jenkinsfile_baseline_capture b/.ci/Jenkinsfile_baseline_capture
index 33ecfcd84fd3e..791cacf7abb4c 100644
--- a/.ci/Jenkinsfile_baseline_capture
+++ b/.ci/Jenkinsfile_baseline_capture
@@ -12,12 +12,12 @@ kibanaPipeline(timeoutMinutes: 120) {
]) {
parallel([
'oss-baseline': {
- workers.ci(name: 'oss-baseline', size: 's-highmem', ramDisk: true, runErrorReporter: false) {
+ workers.ci(name: 'oss-baseline', size: 'l', ramDisk: true, runErrorReporter: false) {
kibanaPipeline.functionalTestProcess('oss-baseline', './test/scripts/jenkins_baseline.sh')()
}
},
'xpack-baseline': {
- workers.ci(name: 'xpack-baseline', size: 's-highmem', ramDisk: true, runErrorReporter: false) {
+ workers.ci(name: 'xpack-baseline', size: 'l', ramDisk: true, runErrorReporter: false) {
kibanaPipeline.functionalTestProcess('xpack-baseline', './test/scripts/jenkins_xpack_baseline.sh')()
}
},
From 4dc0c128ce36a255a87325295652804c1c1a10c4 Mon Sep 17 00:00:00 2001
From: DeDe Morton
Date: Mon, 21 Sep 2020 19:31:57 -0700
Subject: [PATCH 06/22] Edit UI text strings in Integrations and Fleet tabs
(#75837)
Co-authored-by: Elastic Machine
---
.../create_package_policy_page/components/layout.tsx | 2 +-
.../agent_policy/create_package_policy_page/index.tsx | 4 ++--
.../components/confirm_delete_modal.tsx | 7 ++++---
.../components/new_enrollment_key_flyout.tsx | 2 +-
.../sections/fleet/enrollment_token_list_page/index.tsx | 4 ++--
.../ingest_manager/sections/fleet/setup_page/index.tsx | 3 +--
6 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx
index 3bcf0aab9a5c8..6edce74d162bb 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx
@@ -111,7 +111,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{
) : (
);
}, [from]);
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx
index a02214a6fe7fa..39f35fed56ef5 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx
@@ -242,7 +242,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
notifications.toasts.addSuccess({
title: i18n.translate('xpack.ingestManager.createPackagePolicy.addedNotificationTitle', {
- defaultMessage: `Successfully added '{packagePolicyName}'`,
+ defaultMessage: `'{packagePolicyName}' integration added.`,
values: {
packagePolicyName: packagePolicy.name,
},
@@ -250,7 +250,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
text:
agentCount && agentPolicy
? i18n.translate('xpack.ingestManager.createPackagePolicy.addedNotificationMessage', {
- defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy`,
+ defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`,
values: {
agentPolicyName: agentPolicy.name,
},
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx
index d2092f070a22a..a115e03a369a2 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx
@@ -21,7 +21,7 @@ export const ConfirmEnrollmentTokenDelete = (props: Props) => {
{
confirmButtonText={i18n.translate(
'xpack.ingestManager.enrollmentTokenDeleteModal.deleteButton',
{
- defaultMessage: 'Delete',
+ defaultMessage: 'Revoke enrollment token',
}
)}
defaultFocusedButton="confirm"
@@ -42,7 +42,8 @@ export const ConfirmEnrollmentTokenDelete = (props: Props) => {
>
= ({
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx
index d85a6e8b5b833..f447469a02df2 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx
@@ -268,7 +268,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => {
@@ -290,7 +290,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => {
setFlyoutOpen(true)}>
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx
index eeade9036df00..fbd74f8b03e72 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx
@@ -95,8 +95,7 @@ export const SetupPage: React.FunctionComponent<{
From 13bd34e2da9da1178d88868f18c22a215d4d73ec Mon Sep 17 00:00:00 2001
From: Christos Nasikas
Date: Tue, 22 Sep 2020 10:12:24 +0300
Subject: [PATCH 07/22] [ES-ARCHIVER] Fix bug when query flag is empty (#77983)
---
packages/kbn-es-archiver/src/cli.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/kbn-es-archiver/src/cli.ts b/packages/kbn-es-archiver/src/cli.ts
index 41abe83c148cd..87df07fe865bd 100644
--- a/packages/kbn-es-archiver/src/cli.ts
+++ b/packages/kbn-es-archiver/src/cli.ts
@@ -144,7 +144,7 @@ export function runCli() {
const query = flags.query;
let parsedQuery;
- if (typeof query === 'string') {
+ if (typeof query === 'string' && query.length > 0) {
try {
parsedQuery = JSON.parse(query);
} catch (err) {
From 7053ad1a47abc1770a5acac5c3f9444c3cd5d103 Mon Sep 17 00:00:00 2001
From: Dario Gieselaar
Date: Tue, 22 Sep 2020 09:16:58 +0200
Subject: [PATCH 08/22] [APM] Use transaction metrics for transaction error
rate (#78009)
Closes #77716.
---
.../get_service_map_service_node_info.ts | 3 +
.../__snapshots__/queries.test.ts.snap | 14 +++++
.../get_services/get_services_items_stats.ts | 21 +++++--
.../lib/transaction_groups/get_error_rate.ts | 58 ++++++++++++++-----
.../apm/server/routes/transaction_groups.ts | 6 ++
5 files changed, 82 insertions(+), 20 deletions(-)
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts
index 5c183fd9150dd..7c2137ce65d83 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts
@@ -96,10 +96,12 @@ async function getErrorStats({
setup,
serviceName,
environment,
+ searchAggregatedTransactions,
}: {
setup: Options['setup'];
serviceName: string;
environment?: string;
+ searchAggregatedTransactions: boolean;
}) {
const setupWithBlankUiFilters = {
...setup,
@@ -108,6 +110,7 @@ async function getErrorStats({
const { noHits, average } = await getErrorRate({
setup: setupWithBlankUiFilters,
serviceName,
+ searchAggregatedTransactions,
});
return { avgErrorRate: noHits ? null : average };
}
diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
index a65536df37bc8..431f11066aaff 100644
--- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
@@ -277,6 +277,13 @@ Array [
"services": Object {
"aggs": Object {
"outcomes": Object {
+ "aggs": Object {
+ "count": Object {
+ "value_count": Object {
+ "field": "transaction.duration.us",
+ },
+ },
+ },
"terms": Object {
"field": "event.outcome",
},
@@ -284,6 +291,13 @@ Array [
"timeseries": Object {
"aggs": Object {
"outcomes": Object {
+ "aggs": Object {
+ "count": Object {
+ "value_count": Object {
+ "field": "transaction.duration.us",
+ },
+ },
+ },
"terms": Object {
"field": "event.outcome",
},
diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts
index 17799203fe73b..65bc3f7e47171 100644
--- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts
@@ -14,7 +14,6 @@ import {
EVENT_OUTCOME,
} from '../../../../common/elasticsearch_fieldnames';
import { mergeProjection } from '../../../projections/util/merge_projection';
-import { ProcessorEvent } from '../../../../common/processor_event';
import {
ServicesItemsSetup,
ServicesItemsProjection,
@@ -258,6 +257,7 @@ export const getTransactionRates = async ({
export const getTransactionErrorRates = async ({
setup,
projection,
+ searchAggregatedTransactions,
}: AggregationParams) => {
const { apmEventClient, start, end } = setup;
@@ -265,12 +265,25 @@ export const getTransactionErrorRates = async ({
terms: {
field: EVENT_OUTCOME,
},
+ aggs: {
+ count: {
+ value_count: {
+ field: getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ },
+ },
+ },
};
const response = await apmEventClient.search(
mergeProjection(projection, {
apm: {
- events: [ProcessorEvent.transaction],
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
},
body: {
size: 0,
@@ -319,11 +332,11 @@ export const getTransactionErrorRates = async ({
const successfulTransactions =
outcomeResponse.buckets.find(
(bucket) => bucket.key === EventOutcome.success
- )?.doc_count ?? 0;
+ )?.count.value ?? 0;
const failedTransactions =
outcomeResponse.buckets.find(
(bucket) => bucket.key === EventOutcome.failure
- )?.doc_count ?? 0;
+ )?.count.value ?? 0;
return failedTransactions / (successfulTransactions + failedTransactions);
}
diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts
index 82595317342f1..3dc126c45d328 100644
--- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts
+++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts
@@ -11,7 +11,6 @@ import {
SERVICE_NAME,
EVENT_OUTCOME,
} from '../../../common/elasticsearch_fieldnames';
-import { ProcessorEvent } from '../../../common/processor_event';
import { rangeFilter } from '../../../common/utils/range_filter';
import {
Setup,
@@ -19,17 +18,23 @@ import {
SetupUIFilters,
} from '../helpers/setup_request';
import { getBucketSize } from '../helpers/get_bucket_size';
+import {
+ getProcessorEventForAggregatedTransactions,
+ getTransactionDurationFieldForAggregatedTransactions,
+} from '../helpers/aggregated_transactions';
export async function getErrorRate({
serviceName,
transactionType,
transactionName,
setup,
+ searchAggregatedTransactions,
}: {
serviceName: string;
transactionType?: string;
transactionName?: string;
setup: Setup & SetupTimeRange & SetupUIFilters;
+ searchAggregatedTransactions: boolean;
}) {
const { start, end, uiFiltersES, apmEventClient } = setup;
@@ -53,7 +58,11 @@ export async function getErrorRate({
const params = {
apm: {
- events: [ProcessorEvent.transaction],
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
},
body: {
size: 0,
@@ -67,8 +76,19 @@ export async function getErrorRate({
extended_bounds: { min: start, max: end },
},
aggs: {
- erroneous_transactions: {
- filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } },
+ [EVENT_OUTCOME]: {
+ terms: {
+ field: EVENT_OUTCOME,
+ },
+ aggs: {
+ count: {
+ value_count: {
+ field: getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ },
+ },
+ },
},
},
},
@@ -81,18 +101,24 @@ export async function getErrorRate({
const noHits = resp.hits.total.value === 0;
const erroneousTransactionsRate =
- resp.aggregations?.total_transactions.buckets.map(
- ({
- key,
- doc_count: totalTransactions,
- erroneous_transactions: erroneousTransactions,
- }) => {
- return {
- x: key,
- y: erroneousTransactions.doc_count / totalTransactions,
- };
- }
- ) || [];
+ resp.aggregations?.total_transactions.buckets.map((bucket) => {
+ const successful =
+ bucket[EVENT_OUTCOME].buckets.find(
+ (eventOutcomeBucket) =>
+ eventOutcomeBucket.key === EventOutcome.success
+ )?.count.value ?? 0;
+
+ const failed =
+ bucket[EVENT_OUTCOME].buckets.find(
+ (eventOutcomeBucket) =>
+ eventOutcomeBucket.key === EventOutcome.failure
+ )?.count.value ?? 0;
+
+ return {
+ x: bucket.key,
+ y: failed / (successful + failed),
+ };
+ }) || [];
const average = mean(
erroneousTransactionsRate
diff --git a/x-pack/plugins/apm/server/routes/transaction_groups.ts b/x-pack/plugins/apm/server/routes/transaction_groups.ts
index 888c4363f77b9..10e917f385e71 100644
--- a/x-pack/plugins/apm/server/routes/transaction_groups.ts
+++ b/x-pack/plugins/apm/server/routes/transaction_groups.ts
@@ -210,11 +210,17 @@ export const transactionGroupsErrorRateRoute = createRoute(() => ({
const { params } = context;
const { serviceName } = params.path;
const { transactionType, transactionName } = params.query;
+
+ const searchAggregatedTransactions = await getSearchAggregatedTransactions(
+ setup
+ );
+
return getErrorRate({
serviceName,
transactionType,
transactionName,
setup,
+ searchAggregatedTransactions,
});
},
}));
From 8cf508fb6b1e998606710220f2c494d88e4710d6 Mon Sep 17 00:00:00 2001
From: Anton Dosov
Date: Tue, 22 Sep 2020 09:40:46 +0200
Subject: [PATCH 09/22] [UiActions] Remove duplicate apply filter action
(#77485)
Removing unused action
---
src/plugins/embeddable/public/bootstrap.ts | 8 -
src/plugins/embeddable/public/index.ts | 1 -
.../lib/actions/apply_filter_action.test.ts | 134 ---------------
.../public/lib/actions/apply_filter_action.ts | 69 --------
.../embeddable/public/lib/actions/index.ts | 1 -
.../public/tests/apply_filter_action.test.ts | 153 ------------------
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
8 files changed, 368 deletions(-)
delete mode 100644 src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts
delete mode 100644 src/plugins/embeddable/public/lib/actions/apply_filter_action.ts
delete mode 100644 src/plugins/embeddable/public/tests/apply_filter_action.test.ts
diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts
index 33cf210763b10..5c95214ef591b 100644
--- a/src/plugins/embeddable/public/bootstrap.ts
+++ b/src/plugins/embeddable/public/bootstrap.ts
@@ -19,7 +19,6 @@
import { UiActionsSetup } from '../../ui_actions/public';
import {
contextMenuTrigger,
- createFilterAction,
panelBadgeTrigger,
EmbeddableContext,
CONTEXT_MENU_TRIGGER,
@@ -29,8 +28,6 @@ import {
ACTION_INSPECT_PANEL,
REMOVE_PANEL_ACTION,
ACTION_EDIT_PANEL,
- FilterActionContext,
- ACTION_APPLY_FILTER,
panelNotificationTrigger,
PANEL_NOTIFICATION_TRIGGER,
} from './lib';
@@ -48,7 +45,6 @@ declare module '../../ui_actions/public' {
[ACTION_INSPECT_PANEL]: EmbeddableContext;
[REMOVE_PANEL_ACTION]: EmbeddableContext;
[ACTION_EDIT_PANEL]: EmbeddableContext;
- [ACTION_APPLY_FILTER]: FilterActionContext;
}
}
@@ -60,8 +56,4 @@ export const bootstrap = (uiActions: UiActionsSetup) => {
uiActions.registerTrigger(contextMenuTrigger);
uiActions.registerTrigger(panelBadgeTrigger);
uiActions.registerTrigger(panelNotificationTrigger);
-
- const actionApplyFilter = createFilterAction();
-
- uiActions.registerAction(actionApplyFilter);
};
diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts
index c5d8853ada5e8..7609f07d660bc 100644
--- a/src/plugins/embeddable/public/index.ts
+++ b/src/plugins/embeddable/public/index.ts
@@ -24,7 +24,6 @@ import { EmbeddablePublicPlugin } from './plugin';
export {
ACTION_ADD_PANEL,
- ACTION_APPLY_FILTER,
ACTION_EDIT_PANEL,
Adapters,
AddPanelAction,
diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts
deleted file mode 100644
index 88c1a5917e609..0000000000000
--- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { createFilterAction } from './apply_filter_action';
-import { expectErrorAsync } from '../../tests/helpers';
-import { defaultTrigger } from '../../../../ui_actions/public/triggers';
-
-test('has ACTION_APPLY_FILTER type and id', () => {
- const action = createFilterAction();
- expect(action.id).toBe('ACTION_APPLY_FILTER');
- expect(action.type).toBe('ACTION_APPLY_FILTER');
-});
-
-test('has expected display name', () => {
- const action = createFilterAction();
- expect(action.getDisplayName({} as any)).toMatchInlineSnapshot(`"Apply filter to current view"`);
-});
-
-describe('getIconType()', () => {
- test('returns "filter" icon', async () => {
- const action = createFilterAction();
- const result = action.getIconType({} as any);
- expect(result).toBe('filter');
- });
-});
-
-describe('isCompatible()', () => {
- test('when embeddable filters and filters exist, returns true', async () => {
- const action = createFilterAction();
- const result = await action.isCompatible({
- embeddable: {
- getRoot: () => ({
- getInput: () => ({
- filters: [],
- }),
- }),
- } as any,
- filters: [],
- trigger: defaultTrigger,
- });
- expect(result).toBe(true);
- });
-
- test('when embeddable filters not set, returns false', async () => {
- const action = createFilterAction();
- const result = await action.isCompatible({
- embeddable: {
- getRoot: () => ({
- getInput: () => ({
- // filters: [],
- }),
- }),
- } as any,
- filters: [],
- trigger: defaultTrigger,
- });
- expect(result).toBe(false);
- });
-
- test('when triggerContext or filters are not set, returns false', async () => {
- const action = createFilterAction();
-
- const result1 = await action.isCompatible({
- embeddable: {
- getRoot: () => ({
- getInput: () => ({
- filters: [],
- }),
- }),
- } as any,
- } as any);
- expect(result1).toBe(false);
- });
-});
-
-const getEmbeddable = () => {
- const root = {
- getInput: jest.fn(() => ({
- filters: [],
- })),
- updateInput: jest.fn(),
- };
- const embeddable = {
- getRoot: () => root,
- } as any;
- return [embeddable, root];
-};
-
-describe('execute()', () => {
- describe('when no filters are given', () => {
- test('throws an error', async () => {
- const action = createFilterAction();
- const error = await expectErrorAsync(() =>
- action.execute({
- embeddable: getEmbeddable(),
- } as any)
- );
- expect(error).toBeInstanceOf(Error);
- expect(error.message).toBe('Applying a filter requires a filter and embeddable as context');
- });
-
- test('updates filter input on success', async () => {
- const action = createFilterAction();
- const [embeddable, root] = getEmbeddable();
-
- await action.execute({
- embeddable,
- filters: ['FILTER' as any],
- trigger: defaultTrigger,
- });
-
- expect(root.updateInput).toHaveBeenCalledTimes(1);
- expect(root.updateInput.mock.calls[0][0]).toMatchObject({
- filters: ['FILTER'],
- });
- });
- });
-});
diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts
deleted file mode 100644
index 3460203aac29c..0000000000000
--- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { i18n } from '@kbn/i18n';
-import { ActionByType, createAction, IncompatibleActionError } from '../ui_actions';
-import { IEmbeddable, EmbeddableInput } from '../embeddables';
-import { Filter } from '../../../../../plugins/data/public';
-
-export const ACTION_APPLY_FILTER = 'ACTION_APPLY_FILTER';
-
-type RootEmbeddable = IEmbeddable;
-export interface FilterActionContext {
- embeddable: IEmbeddable;
- filters: Filter[];
-}
-
-async function isCompatible(context: FilterActionContext) {
- if (context.embeddable === undefined) {
- return false;
- }
- const root = context.embeddable.getRoot() as RootEmbeddable;
- return Boolean(root.getInput().filters !== undefined && context.filters !== undefined);
-}
-
-export function createFilterAction(): ActionByType {
- return createAction({
- type: ACTION_APPLY_FILTER,
- id: ACTION_APPLY_FILTER,
- order: 100,
- getIconType: () => 'filter',
- getDisplayName: () => {
- return i18n.translate('embeddableApi.actions.applyFilterActionTitle', {
- defaultMessage: 'Apply filter to current view',
- });
- },
- isCompatible,
- execute: async ({ embeddable, filters }) => {
- if (!filters || !embeddable) {
- throw new Error('Applying a filter requires a filter and embeddable as context');
- }
-
- if (!(await isCompatible({ embeddable, filters }))) {
- throw new IncompatibleActionError();
- }
-
- const root = embeddable.getRoot() as RootEmbeddable;
-
- root.updateInput({
- filters,
- });
- },
- });
-}
diff --git a/src/plugins/embeddable/public/lib/actions/index.ts b/src/plugins/embeddable/public/lib/actions/index.ts
index ea32c6aa2d455..8be2c3f5df450 100644
--- a/src/plugins/embeddable/public/lib/actions/index.ts
+++ b/src/plugins/embeddable/public/lib/actions/index.ts
@@ -17,5 +17,4 @@
* under the License.
*/
-export * from './apply_filter_action';
export * from './edit_panel_action';
diff --git a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
deleted file mode 100644
index f8c4a4a7e4b72..0000000000000
--- a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { testPlugin } from './test_plugin';
-import { EmbeddableOutput, isErrorEmbeddable, createFilterAction } from '../lib';
-import {
- FilterableContainer,
- FilterableContainerInput,
- FILTERABLE_CONTAINER,
- FilterableEmbeddableFactory,
- HelloWorldContainer,
- FILTERABLE_EMBEDDABLE,
- FilterableEmbeddable,
- FilterableContainerFactory,
- FilterableEmbeddableInput,
-} from '../lib/test_samples';
-import { esFilters } from '../../../data/public';
-import { applyFilterTrigger } from '../../../ui_actions/public';
-
-test('ApplyFilterAction applies the filter to the root of the container tree', async () => {
- const { doStart, setup } = testPlugin();
-
- const factory2 = new FilterableEmbeddableFactory();
- const factory1 = new FilterableContainerFactory(async () => await api.getEmbeddableFactory);
- setup.registerEmbeddableFactory(factory2.type, factory2);
- setup.registerEmbeddableFactory(factory1.type, factory1);
-
- const api = doStart();
-
- const applyFilterAction = createFilterAction();
-
- const root = new FilterableContainer(
- { id: 'root', panels: {}, filters: [] },
- api.getEmbeddableFactory
- );
-
- const node1 = await root.addNewEmbeddable<
- FilterableContainerInput,
- EmbeddableOutput,
- FilterableContainer
- >(FILTERABLE_CONTAINER, { panels: {}, id: 'node1' });
-
- const node2 = await root.addNewEmbeddable<
- FilterableContainerInput,
- EmbeddableOutput,
- FilterableContainer
- >(FILTERABLE_CONTAINER, { panels: {}, id: 'Node2' });
-
- if (isErrorEmbeddable(node1) || isErrorEmbeddable(node2)) {
- throw new Error();
- }
-
- const embeddable = await node2.addNewEmbeddable<
- FilterableEmbeddableInput,
- EmbeddableOutput,
- FilterableEmbeddable
- >(FILTERABLE_EMBEDDABLE, { id: 'leaf' });
-
- if (isErrorEmbeddable(embeddable)) {
- throw new Error();
- }
-
- const filter: any = {
- $state: { store: esFilters.FilterStateStore.APP_STATE },
- meta: {
- disabled: false,
- negate: false,
- alias: '',
- },
- query: { match: { extension: { query: 'foo' } } },
- };
-
- await applyFilterAction.execute({ embeddable, filters: [filter], trigger: applyFilterTrigger });
- expect(root.getInput().filters.length).toBe(1);
- expect(node1.getInput().filters.length).toBe(1);
- expect(embeddable.getInput().filters.length).toBe(1);
- expect(node2.getInput().filters.length).toBe(1);
-});
-
-test('ApplyFilterAction is incompatible if the root container does not accept a filter as input', async () => {
- const { doStart, setup } = testPlugin();
-
- const factory = new FilterableEmbeddableFactory();
- setup.registerEmbeddableFactory(factory.type, factory);
- const api = doStart();
- const applyFilterAction = createFilterAction();
-
- const parent = new HelloWorldContainer({ id: 'root', panels: {} }, {
- getEmbeddableFactory: api.getEmbeddableFactory,
- } as any);
- const embeddable = await parent.addNewEmbeddable<
- FilterableContainerInput,
- EmbeddableOutput,
- FilterableContainer
- >(FILTERABLE_EMBEDDABLE, { id: 'leaf' });
-
- if (isErrorEmbeddable(embeddable)) {
- throw new Error();
- }
-
- // @ts-ignore
- expect(await applyFilterAction.isCompatible({ embeddable })).toBe(false);
-});
-
-test('trying to execute on incompatible context throws an error ', async () => {
- const { doStart, setup } = testPlugin();
-
- const factory = new FilterableEmbeddableFactory();
- setup.registerEmbeddableFactory(factory.type, factory);
-
- const api = doStart();
- const applyFilterAction = createFilterAction();
-
- const parent = new HelloWorldContainer({ id: 'root', panels: {} }, {
- getEmbeddableFactory: api.getEmbeddableFactory,
- } as any);
-
- const embeddable = await parent.addNewEmbeddable<
- FilterableContainerInput,
- EmbeddableOutput,
- FilterableContainer
- >(FILTERABLE_EMBEDDABLE, { id: 'leaf' });
-
- if (isErrorEmbeddable(embeddable)) {
- throw new Error();
- }
-
- async function check() {
- await applyFilterAction.execute({ embeddable } as any);
- }
- await expect(check()).rejects.toThrow(Error);
-});
-
-test('gets title', async () => {
- const applyFilterAction = createFilterAction();
- expect(applyFilterAction.getDisplayName({} as any)).toBeDefined();
-});
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 781f351d7d241..790d1152f0846 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -1465,7 +1465,6 @@
"discover.uninitializedText": "クエリを作成、フィルターを追加、または[更新]をクリックして、現在のクエリの結果を取得します。",
"discover.uninitializedTitle": "検索開始",
"discover.valueIsNotConfiguredIndexPatternIDWarningTitle": "{stateVal}は設定されたインデックスパターンIDではありません",
- "embeddableApi.actions.applyFilterActionTitle": "現在のビューにフィルターを適用",
"embeddableApi.addPanel.createNewDefaultOption": "新規作成...",
"embeddableApi.addPanel.displayName": "パネルの追加",
"embeddableApi.addPanel.noMatchingObjectsMessage": "一致するオブジェクトが見つかりませんでした。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index fd749ba4709ec..d93c8054268e7 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -1466,7 +1466,6 @@
"discover.uninitializedText": "编写查询,添加一些筛选,或只需单击“刷新”来检索当前查询的结果。",
"discover.uninitializedTitle": "开始搜索",
"discover.valueIsNotConfiguredIndexPatternIDWarningTitle": "{stateVal} 不是配置的索引模式 ID",
- "embeddableApi.actions.applyFilterActionTitle": "将筛选应用于当前视图",
"embeddableApi.addPanel.createNewDefaultOption": "新建",
"embeddableApi.addPanel.displayName": "添加面板",
"embeddableApi.addPanel.noMatchingObjectsMessage": "未找到任何匹配对象。",
From ed7a6307105b5562827c61145ebd4aba9a40a59e Mon Sep 17 00:00:00 2001
From: Diana Derevyankina
<54894989+DziyanaDzeraviankina@users.noreply.github.com>
Date: Tue, 22 Sep 2020 11:13:18 +0300
Subject: [PATCH 10/22] [TSVB] Fields dropdowns are not populated if one of the
indices is missing (#77363)
* Fields dropdowns are not populated if one of the indices is missing
* Fix tests and accept api changes
* Add indexPatternsService to get default index pattern
* Replace simple error message string with i18n translation
* Remove unnecessary displaying error code
Co-authored-by: Elastic Machine
---
...dexpatternsfetcher.getfieldsforwildcard.md | 5 ++++-
.../fetcher/index_patterns_fetcher.ts | 5 +++--
.../index_patterns/fetcher/lib/es_api.ts | 9 ++++++--
.../field_capabilities.test.js | 2 +-
.../field_capabilities/field_capabilities.ts | 10 +++++++--
src/plugins/data/server/server.api.md | 3 +++
.../server/lib/get_fields.ts | 21 +++++++++++++------
.../abstract_search_strategy.test.js | 1 +
.../strategies/abstract_search_strategy.ts | 1 +
.../vis_data/get_interval_and_timefield.js | 4 +---
10 files changed, 44 insertions(+), 17 deletions(-)
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md
index 6bd3bbf2433cd..52382372d6d96 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md
@@ -12,6 +12,9 @@ Get a list of field objects for an index pattern that may contain wildcards
getFieldsForWildcard(options: {
pattern: string | string[];
metaFields?: string[];
+ fieldCapsOptions?: {
+ allowNoIndices: boolean;
+ };
}): Promise;
```
@@ -19,7 +22,7 @@ getFieldsForWildcard(options: {
| Parameter | Type | Description |
| --- | --- | --- |
-| options | {
pattern: string | string[];
metaFields?: string[];
}
| |
+| options | {
pattern: string | string[];
metaFields?: string[];
fieldCapsOptions?: {
allowNoIndices: boolean;
};
}
| |
Returns:
diff --git a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts
index ff9d67152e268..57c636a9e3c69 100644
--- a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts
+++ b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts
@@ -55,9 +55,10 @@ export class IndexPatternsFetcher {
async getFieldsForWildcard(options: {
pattern: string | string[];
metaFields?: string[];
+ fieldCapsOptions?: { allowNoIndices: boolean };
}): Promise {
- const { pattern, metaFields } = options;
- return await getFieldCapabilities(this._callDataCluster, pattern, metaFields);
+ const { pattern, metaFields, fieldCapsOptions } = options;
+ return await getFieldCapabilities(this._callDataCluster, pattern, metaFields, fieldCapsOptions);
}
/**
diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts
index 0738a16034d46..27ce14f9a3597 100644
--- a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts
+++ b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts
@@ -69,15 +69,20 @@ export async function callIndexAliasApi(
*
* @param {Function} callCluster bound function for accessing an es client
* @param {Array|String} indices
+ * @param {Object} fieldCapsOptions
* @return {Promise}
*/
-export async function callFieldCapsApi(callCluster: LegacyAPICaller, indices: string[] | string) {
+export async function callFieldCapsApi(
+ callCluster: LegacyAPICaller,
+ indices: string[] | string,
+ fieldCapsOptions: { allowNoIndices: boolean } = { allowNoIndices: false }
+) {
try {
return (await callCluster('fieldCaps', {
index: indices,
fields: '*',
ignoreUnavailable: true,
- allowNoIndices: false,
+ ...fieldCapsOptions,
})) as FieldCapsResponse;
} catch (error) {
throw convertEsError(indices, error);
diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js
index a0af7582ac6f3..0e5757b7b782b 100644
--- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js
+++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js
@@ -61,7 +61,7 @@ describe('index_patterns/field_capabilities/field_capabilities', () => {
await getFieldCapabilities(footballs[0], footballs[1]);
sinon.assert.calledOnce(callFieldCapsApi);
- calledWithExactly(callFieldCapsApi, [footballs[0], footballs[1]]);
+ calledWithExactly(callFieldCapsApi, [footballs[0], footballs[1], undefined]);
});
});
diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts
index 6b26c82dc95e7..62e77e0adad66 100644
--- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts
+++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts
@@ -32,14 +32,20 @@ import { FieldDescriptor } from '../../index_patterns_fetcher';
* @param {Function} callCluster bound function for accessing an es client
* @param {Array} [indices=[]] the list of indexes to check
* @param {Array} [metaFields=[]] the list of internal fields to include
+ * @param {Object} fieldCapsOptions
* @return {Promise>}
*/
export async function getFieldCapabilities(
callCluster: LegacyAPICaller,
indices: string | string[] = [],
- metaFields: string[] = []
+ metaFields: string[] = [],
+ fieldCapsOptions?: { allowNoIndices: boolean }
) {
- const esFieldCaps: FieldCapsResponse = await callFieldCapsApi(callCluster, indices);
+ const esFieldCaps: FieldCapsResponse = await callFieldCapsApi(
+ callCluster,
+ indices,
+ fieldCapsOptions
+ );
const fieldsFromFieldCapsByName = keyBy(readFieldCapsResponse(esFieldCaps), 'name');
const allFieldsUnsorted = Object.keys(fieldsFromFieldCapsByName)
diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md
index c48aa8397dc83..2024e9e7f2974 100644
--- a/src/plugins/data/server/server.api.md
+++ b/src/plugins/data/server/server.api.md
@@ -687,6 +687,9 @@ export class IndexPatternsFetcher {
getFieldsForWildcard(options: {
pattern: string | string[];
metaFields?: string[];
+ fieldCapsOptions?: {
+ allowNoIndices: boolean;
+ };
}): Promise;
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
index 777de89672bbe..26a1792e3ec70 100644
--- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
@@ -16,15 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { uniqBy } from 'lodash';
+import { uniqBy, get } from 'lodash';
import { first, map } from 'rxjs/operators';
import { KibanaRequest, RequestHandlerContext } from 'kibana/server';
-// @ts-ignore
-import { getIndexPatternObject } from './vis_data/helpers/get_index_pattern';
-import { indexPatterns } from '../../../data/server';
import { Framework } from '../plugin';
-import { IndexPatternFieldDescriptor, IndexPatternsFetcher } from '../../../data/server';
+import {
+ indexPatterns,
+ IndexPatternFieldDescriptor,
+ IndexPatternsFetcher,
+} from '../../../data/server';
import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy';
export async function getFields(
@@ -58,7 +59,15 @@ export async function getFields(
.toPromise();
},
};
- const { indexPatternString } = await getIndexPatternObject(reqFacade, indexPattern);
+ let indexPatternString = indexPattern;
+
+ if (!indexPatternString) {
+ const [, { data }] = await framework.core.getStartServices();
+ const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory(request);
+ const defaultIndexPattern = await indexPatternsService.getDefault();
+ indexPatternString = get(defaultIndexPattern, 'title', '');
+ }
+
const {
searchStrategy,
capabilities,
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js
index 6773ee482b098..4dcc67dc46976 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js
@@ -49,6 +49,7 @@ describe('AbstractSearchStrategy', () => {
expect(fields).toBe(mockedFields);
expect(req.pre.indexPatternsService.getFieldsForWildcard).toHaveBeenCalledWith({
pattern: indexPattern,
+ fieldCapsOptions: { allowNoIndices: true },
});
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
index 92b7e6976962e..2eb92b2b777e8 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
@@ -84,6 +84,7 @@ export class AbstractSearchStrategy {
return await indexPatternsService!.getFieldsForWildcard({
pattern: indexPattern,
+ fieldCapsOptions: { allowNoIndices: true },
});
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
index e4bda194299df..82a2ef66cb1c0 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
@@ -17,12 +17,10 @@
* under the License.
*/
-import { get } from 'lodash';
-
const DEFAULT_TIME_FIELD = '@timestamp';
export function getIntervalAndTimefield(panel, series = {}, indexPatternObject) {
- const getDefaultTimeField = () => get(indexPatternObject, 'timeFieldName', DEFAULT_TIME_FIELD);
+ const getDefaultTimeField = () => indexPatternObject?.timeFieldName ?? DEFAULT_TIME_FIELD;
const timeField =
(series.override_index_pattern && series.series_time_field) ||
From d3dd50520b12e0bcadf682771afc9f0a62d792f7 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Tue, 22 Sep 2020 11:06:34 +0200
Subject: [PATCH 11/22] [Uptime] Improve ping chart axis (#77992)
---
.../components/common/charts/duration_line_series_list.tsx | 3 ++-
.../uptime/public/components/common/charts/ping_histogram.tsx | 3 +++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/uptime/public/components/common/charts/duration_line_series_list.tsx b/x-pack/plugins/uptime/public/components/common/charts/duration_line_series_list.tsx
index 4223e918393b6..edb7e13ed064f 100644
--- a/x-pack/plugins/uptime/public/components/common/charts/duration_line_series_list.tsx
+++ b/x-pack/plugins/uptime/public/components/common/charts/duration_line_series_list.tsx
@@ -5,7 +5,7 @@
*/
import React from 'react';
-import { LineSeries, CurveType } from '@elastic/charts';
+import { LineSeries, CurveType, Fit } from '@elastic/charts';
import { LocationDurationLine } from '../../../../common/types';
import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper';
@@ -28,6 +28,7 @@ export const DurationLineSeriesList = ({ lines }: Props) => (
yAccessors={[1]}
yScaleToDataExtent={false}
yScaleType="linear"
+ fit={Fit.Linear}
/>
))}
>
diff --git a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx
index 39b8a38f60982..3f252c2a436ae 100644
--- a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx
+++ b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx
@@ -17,6 +17,7 @@ import { EuiTitle, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useContext } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
+import numeral from '@elastic/numeral';
import moment from 'moment';
import { getChartDateLabel } from '../../../lib/helper';
import { ChartWrapper } from './chart_wrapper';
@@ -144,6 +145,8 @@ export const PingHistogramComponent: React.FC = ({
defaultMessage: 'Ping Y Axis',
})}
position="left"
+ tickFormat={(d) => numeral(d).format('0')}
+ labelFormat={(d) => numeral(d).format('0a')}
title={i18n.translate('xpack.uptime.snapshotHistogram.yAxis.title', {
defaultMessage: 'Pings',
description:
From 41a7f1a1c280914006704d46573956c9598ec3ed Mon Sep 17 00:00:00 2001
From: Gidi Meir Morris
Date: Tue, 22 Sep 2020 11:18:33 +0100
Subject: [PATCH 12/22] [Actions] adds a Test Connector tab in the Connectors
list (#77365)
Adds a tab in the _Edit Alert_ flyout which allows the user to _test_ their connector by executing it using an example action. The execution relies on the connector being updated, so is only enabled when there are no saved changes in the Connector form itself.
---
x-pack/plugins/actions/common/types.ts | 10 +
.../builtin_action_types/es_index.test.ts | 43 ++++
.../server/builtin_action_types/es_index.ts | 46 ++--
x-pack/plugins/actions/server/types.ts | 12 +-
.../components/add_message_variables.tsx | 1 +
.../es_index/es_index_params.tsx | 79 +++---
.../lib/action_connector_api.test.ts | 30 +++
.../application/lib/action_connector_api.ts | 15 ++
.../connector_edit_flyout.scss | 3 +
.../connector_edit_flyout.test.tsx | 2 +-
.../connector_edit_flyout.tsx | 223 ++++++++++++-----
.../test_connector_form.test.tsx | 212 +++++++++++++++++
.../test_connector_form.tsx | 224 ++++++++++++++++++
.../components/actions_connectors_list.tsx | 149 ++++++------
.../apps/triggers_actions_ui/connectors.ts | 79 +++++-
15 files changed, 929 insertions(+), 199 deletions(-)
create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss
create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx
create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx
diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts
index 49e8f3e80b14a..41ec4d2a88e9f 100644
--- a/x-pack/plugins/actions/common/types.ts
+++ b/x-pack/plugins/actions/common/types.ts
@@ -24,3 +24,13 @@ export interface ActionResult {
config: Record;
isPreconfigured: boolean;
}
+
+// the result returned from an action type executor function
+export interface ActionTypeExecutorResult {
+ actionId: string;
+ status: 'ok' | 'error';
+ message?: string;
+ serviceMessage?: string;
+ data?: Data;
+ retry?: null | boolean | Date;
+}
diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts
index 7a0e24521a1c6..3d92d5ebf33fc 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts
@@ -284,4 +284,47 @@ describe('execute()', () => {
]
`);
});
+
+ test('resolves with an error when an error occurs in the indexing operation', async () => {
+ const secrets = {};
+ // minimal params
+ const config = { index: 'index-value', refresh: false, executionTimeField: null };
+ const params = {
+ documents: [{ '': 'bob' }],
+ };
+
+ const actionId = 'some-id';
+
+ services.callCluster.mockResolvedValue({
+ took: 0,
+ errors: true,
+ items: [
+ {
+ index: {
+ _index: 'indexme',
+ _id: '7buTjHQB0SuNSiS9Hayt',
+ status: 400,
+ error: {
+ type: 'mapper_parsing_exception',
+ reason: 'failed to parse',
+ caused_by: {
+ type: 'illegal_argument_exception',
+ reason: 'field name cannot be an empty string',
+ },
+ },
+ },
+ },
+ ],
+ });
+
+ expect(await actionType.executor({ actionId, config, secrets, params, services }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "actionId": "some-id",
+ "message": "error indexing documents",
+ "serviceMessage": "failed to parse (field name cannot be an empty string)",
+ "status": "error",
+ }
+ `);
+ });
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts
index 53bf75651b1e5..868c07b775c78 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { curry } from 'lodash';
+import { curry, find } from 'lodash';
import { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
@@ -85,21 +85,39 @@ async function executor(
refresh: config.refresh,
};
- let result;
try {
- result = await services.callCluster('bulk', bulkParams);
+ const result = await services.callCluster('bulk', bulkParams);
+
+ const err = find(result.items, 'index.error.reason');
+ if (err) {
+ return wrapErr(
+ `${err.index.error!.reason}${
+ err.index.error?.caused_by ? ` (${err.index.error?.caused_by?.reason})` : ''
+ }`,
+ actionId,
+ logger
+ );
+ }
+
+ return { status: 'ok', data: result, actionId };
} catch (err) {
- const message = i18n.translate('xpack.actions.builtin.esIndex.errorIndexingErrorMessage', {
- defaultMessage: 'error indexing documents',
- });
- logger.error(`error indexing documents: ${err.message}`);
- return {
- status: 'error',
- actionId,
- message,
- serviceMessage: err.message,
- };
+ return wrapErr(err.message, actionId, logger);
}
+}
- return { status: 'ok', data: result, actionId };
+function wrapErr(
+ errMessage: string,
+ actionId: string,
+ logger: Logger
+): ActionTypeExecutorResult {
+ const message = i18n.translate('xpack.actions.builtin.esIndex.errorIndexingErrorMessage', {
+ defaultMessage: 'error indexing documents',
+ });
+ logger.error(`error indexing documents: ${errMessage}`);
+ return {
+ status: 'error',
+ actionId,
+ message,
+ serviceMessage: errMessage,
+ };
}
diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts
index 3e92ca331bb93..a23a2b0893261 100644
--- a/x-pack/plugins/actions/server/types.ts
+++ b/x-pack/plugins/actions/server/types.ts
@@ -15,6 +15,8 @@ import {
SavedObjectsClientContract,
SavedObjectAttributes,
} from '../../../../src/core/server';
+import { ActionTypeExecutorResult } from '../common';
+export { ActionTypeExecutorResult } from '../common';
export type WithoutQueryAndParams = Pick>;
export type GetServicesFunction = (request: KibanaRequest) => Services;
@@ -80,16 +82,6 @@ export interface FindActionResult extends ActionResult {
referencedByCount: number;
}
-// the result returned from an action type executor function
-export interface ActionTypeExecutorResult {
- actionId: string;
- status: 'ok' | 'error';
- message?: string;
- serviceMessage?: string;
- data?: Data;
- retry?: null | boolean | Date;
-}
-
// signature of the action type executor function
export type ExecutorType = (
options: ActionTypeExecutorOptions
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx
index 0742ed8a778ef..2bcd87830901b 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx
@@ -61,6 +61,7 @@ export const AddMessageVariables: React.FunctionComponent = ({
setIsVariablesPopoverOpen(true)}
iconType="indexOpen"
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx
index 495707db4975c..0a04db1b5ddfa 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx
@@ -32,48 +32,47 @@ export const IndexParamsFields = ({
};
return (
- <>
- 0 ? ((documents[0] as unknown) as string) : undefined
+ 0 ? ((documents[0] as unknown) as string) : undefined
+ }
+ label={i18n.translate(
+ 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel',
+ {
+ defaultMessage: 'Document to index',
}
- label={i18n.translate(
- 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel',
- {
- defaultMessage: 'Document to index',
- }
- )}
- aria-label={i18n.translate(
- 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel',
- {
- defaultMessage: 'Code editor',
- }
- )}
- errors={errors.documents as string[]}
- onDocumentsChange={onDocumentsChange}
- helpText={
-
-
-
+ )}
+ aria-label={i18n.translate(
+ 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel',
+ {
+ defaultMessage: 'Code editor',
}
- onBlur={() => {
- if (
- !(documents && documents.length > 0 ? ((documents[0] as unknown) as string) : undefined)
- ) {
- // set document as empty to turn on the validation for non empty valid JSON object
- onDocumentsChange('{}');
- }
- }}
- />
- >
+ )}
+ errors={errors.documents as string[]}
+ onDocumentsChange={onDocumentsChange}
+ helpText={
+
+
+
+ }
+ onBlur={() => {
+ if (
+ !(documents && documents.length > 0 ? ((documents[0] as unknown) as string) : undefined)
+ ) {
+ // set document as empty to turn on the validation for non empty valid JSON object
+ onDocumentsChange('{}');
+ }
+ }}
+ />
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts
index 43b22361aea36..ad3a5b40bd00d 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts
@@ -12,6 +12,7 @@ import {
loadActionTypes,
loadAllActions,
updateActionConnector,
+ executeAction,
} from './action_connector_api';
const http = httpServiceMock.createStartContract();
@@ -128,3 +129,32 @@ describe('deleteActions', () => {
`);
});
});
+
+describe('executeAction', () => {
+ test('should call execute API', async () => {
+ const id = '123';
+ const params = {
+ stringParams: 'someString',
+ numericParams: 123,
+ };
+
+ http.post.mockResolvedValueOnce({
+ actionId: id,
+ status: 'ok',
+ });
+
+ const result = await executeAction({ id, http, params });
+ expect(result).toEqual({
+ actionId: id,
+ status: 'ok',
+ });
+ expect(http.post.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+ "/api/actions/action/123/_execute",
+ Object {
+ "body": "{\\"params\\":{\\"stringParams\\":\\"someString\\",\\"numericParams\\":123}}",
+ },
+ ]
+ `);
+ });
+});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts
index 46a676ac06539..c2c7139d13bf0 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts
@@ -7,6 +7,7 @@
import { HttpSetup } from 'kibana/public';
import { BASE_ACTION_API_PATH } from '../constants';
import { ActionConnector, ActionConnectorWithoutId, ActionType } from '../../types';
+import { ActionTypeExecutorResult } from '../../../../../plugins/actions/common';
export async function loadActionTypes({ http }: { http: HttpSetup }): Promise {
return await http.get(`${BASE_ACTION_API_PATH}/list_action_types`);
@@ -65,3 +66,17 @@ export async function deleteActions({
);
return { successes, errors };
}
+
+export async function executeAction({
+ id,
+ params,
+ http,
+}: {
+ id: string;
+ http: HttpSetup;
+ params: Record;
+}): Promise> {
+ return await http.post(`${BASE_ACTION_API_PATH}/action/${id}/_execute`, {
+ body: JSON.stringify({ params }),
+ });
+}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss
new file mode 100644
index 0000000000000..873a3ceb762cd
--- /dev/null
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss
@@ -0,0 +1,3 @@
+.connectorEditFlyoutTabs {
+ margin-bottom: '-25px';
+}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx
index dd9eeae266987..0c2f4df0ca52b 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx
@@ -152,6 +152,6 @@ describe('connector_edit_flyout', () => {
const preconfiguredBadge = wrapper.find('[data-test-subj="preconfiguredBadge"]');
expect(preconfiguredBadge.exists()).toBeTruthy();
- expect(wrapper.find('[data-test-subj="saveEditedActionButton"]').exists()).toBeFalsy();
+ expect(wrapper.find('[data-test-subj="saveAndCloseEditedActionButton"]').exists()).toBeFalsy();
});
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx
index ca75e730062ab..fc902a4fabcd8 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx
@@ -19,15 +19,21 @@ import {
EuiBetaBadge,
EuiText,
EuiLink,
+ EuiTabs,
+ EuiTab,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { Option, none, some } from 'fp-ts/lib/Option';
import { ActionConnectorForm, validateBaseProperties } from './action_connector_form';
+import { TestConnectorForm } from './test_connector_form';
import { ActionConnectorTableItem, ActionConnector, IErrorObject } from '../../../types';
import { connectorReducer } from './connector_reducer';
-import { updateActionConnector } from '../../lib/action_connector_api';
+import { updateActionConnector, executeAction } from '../../lib/action_connector_api';
import { hasSaveActionsCapability } from '../../lib/capabilities';
import { useActionsConnectorsContext } from '../../context/actions_connectors_context';
import { PLUGIN } from '../../constants/plugin';
+import { ActionTypeExecutorResult } from '../../../../../actions/common';
+import './connector_edit_flyout.scss';
export interface ConnectorEditProps {
initialConnector: ActionConnectorTableItem;
@@ -40,7 +46,6 @@ export const ConnectorEditFlyout = ({
editFlyoutVisible,
setEditFlyoutVisibility,
}: ConnectorEditProps) => {
- let hasErrors = false;
const {
http,
toastNotifications,
@@ -56,13 +61,26 @@ export const ConnectorEditFlyout = ({
connector: { ...initialConnector, secrets: {} },
});
const [isSaving, setIsSaving] = useState(false);
+ const [selectedTab, setTab] = useState<'config' | 'test'>('config');
+
+ const [hasChanges, setHasChanges] = useState(false);
const setConnector = (key: string, value: any) => {
dispatch({ command: { type: 'setConnector' }, payload: { key, value } });
};
+ const [testExecutionActionParams, setTestExecutionActionParams] = useState<
+ Record
+ >({});
+ const [testExecutionResult, setTestExecutionResult] = useState<
+ Option>
+ >(none);
+ const [isExecutingAction, setIsExecutinAction] = useState(false);
+
const closeFlyout = useCallback(() => {
setEditFlyoutVisibility(false);
setConnector('connector', { ...initialConnector, secrets: {} });
+ setHasChanges(false);
+ setTestExecutionResult(none);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setEditFlyoutVisibility]);
@@ -71,11 +89,13 @@ export const ConnectorEditFlyout = ({
}
const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId);
- const errors = {
+ const errorsInConnectorConfig = {
...actionTypeModel?.validateConnector(connector).errors,
...validateBaseProperties(connector).errors,
} as IErrorObject;
- hasErrors = !!Object.keys(errors).find((errorKey) => errors[errorKey].length >= 1);
+ const hasErrorsInConnectorConfig = !!Object.keys(errorsInConnectorConfig).find(
+ (errorKey) => errorsInConnectorConfig[errorKey].length >= 1
+ );
const onActionConnectorSave = async (): Promise =>
await updateActionConnector({ http, connector, id: connector.id })
@@ -173,6 +193,32 @@ export const ConnectorEditFlyout = ({
);
+ const onExecutAction = () => {
+ setIsExecutinAction(true);
+ return executeAction({ id: connector.id, params: testExecutionActionParams, http }).then(
+ (result) => {
+ setIsExecutinAction(false);
+ setTestExecutionResult(some(result));
+ return result;
+ }
+ );
+ };
+
+ const onSaveClicked = async (closeAfterSave: boolean = true) => {
+ setIsSaving(true);
+ const savedAction = await onActionConnectorSave();
+ setIsSaving(false);
+ if (savedAction) {
+ setHasChanges(false);
+ if (closeAfterSave) {
+ closeFlyout();
+ }
+ if (reloadConnectors) {
+ reloadConnectors();
+ }
+ }
+ };
+
return (
@@ -184,40 +230,78 @@ export const ConnectorEditFlyout = ({
) : null}
{flyoutTitle}
+
+ setTab('config')}
+ data-test-subj="configureConnectorTab"
+ isSelected={'config' === selectedTab}
+ >
+ {i18n.translate('xpack.triggersActionsUI.sections.editConnectorForm.tabText', {
+ defaultMessage: 'Configuration',
+ })}
+
+ setTab('test')}
+ data-test-subj="testConnectorTab"
+ isSelected={'test' === selectedTab}
+ >
+ {i18n.translate('xpack.triggersActionsUI.sections.testConnectorForm.tabText', {
+ defaultMessage: 'Test',
+ })}
+
+
- {!connector.isPreconfigured ? (
- {
+ setHasChanges(true);
+ // if the user changes the connector, "forget" the last execution
+ // so the user comes back to a clean form ready to run a fresh test
+ setTestExecutionResult(none);
+ dispatch(changes);
+ }}
+ actionTypeRegistry={actionTypeRegistry}
+ http={http}
+ docLinks={docLinks}
+ capabilities={capabilities}
+ consumer={consumer}
+ />
+ ) : (
+
+
+ {i18n.translate(
+ 'xpack.triggersActionsUI.sections.editConnectorForm.descriptionText',
+ {
+ defaultMessage: 'This connector is readonly.',
+ }
+ )}
+
+
+
+
+
+ )
+ ) : (
+
- ) : (
-
-
- {i18n.translate(
- 'xpack.triggersActionsUI.sections.editConnectorForm.descriptionText',
- {
- defaultMessage: 'This connector is readonly.',
- }
- )}
-
-
-
-
-
)}
@@ -232,35 +316,48 @@ export const ConnectorEditFlyout = ({
)}
- {canSave && actionTypeModel && !connector.isPreconfigured ? (
-
- {
- setIsSaving(true);
- const savedAction = await onActionConnectorSave();
- setIsSaving(false);
- if (savedAction) {
- closeFlyout();
- if (reloadConnectors) {
- reloadConnectors();
- }
- }
- }}
- >
-
-
-
- ) : null}
+
+
+ {canSave && actionTypeModel && !connector.isPreconfigured ? (
+
+
+ {
+ await onSaveClicked(false);
+ }}
+ >
+
+
+
+
+ {
+ await onSaveClicked();
+ }}
+ >
+
+
+
+
+ ) : null}
+
+
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx
new file mode 100644
index 0000000000000..482bccb5517f1
--- /dev/null
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx
@@ -0,0 +1,212 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { lazy } from 'react';
+import { I18nProvider } from '@kbn/i18n/react';
+import { coreMock } from '../../../../../../../src/core/public/mocks';
+import TestConnectorForm from './test_connector_form';
+import { none, some } from 'fp-ts/lib/Option';
+import { ActionConnector, ValidationResult } from '../../../types';
+import { actionTypeRegistryMock } from '../../action_type_registry.mock';
+import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context';
+import { EuiFormRow, EuiFieldText, EuiText, EuiLink, EuiForm, EuiSelect } from '@elastic/eui';
+import { mountWithIntl } from 'test_utils/enzyme_helpers';
+
+const mockedActionParamsFields = lazy(async () => ({
+ default() {
+ return (
+
+
+
+
+
+
+ Link to some help
+
+ }
+ >
+
+
+
+ );
+ },
+}));
+
+const actionType = {
+ id: 'my-action-type',
+ iconClass: 'test',
+ selectMessage: 'test',
+ validateConnector: (): ValidationResult => {
+ return { errors: {} };
+ },
+ validateParams: (): ValidationResult => {
+ const validationResult = { errors: {} };
+ return validationResult;
+ },
+ actionConnectorFields: null,
+ actionParamsFields: mockedActionParamsFields,
+};
+
+describe('test_connector_form', () => {
+ let deps: any;
+ let actionTypeRegistry;
+ beforeAll(async () => {
+ actionTypeRegistry = actionTypeRegistryMock.create();
+
+ const mocks = coreMock.createSetup();
+ const [
+ {
+ application: { capabilities },
+ },
+ ] = await mocks.getStartServices();
+ deps = {
+ http: mocks.http,
+ toastNotifications: mocks.notifications.toasts,
+ docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' },
+ actionTypeRegistry,
+ capabilities,
+ };
+ actionTypeRegistry.get.mockReturnValue(actionType);
+ });
+
+ it('renders initially as the action form and execute button and no result', async () => {
+ const connector = {
+ actionTypeId: actionType.id,
+ config: {},
+ secrets: {},
+ } as ActionConnector;
+ const wrapper = mountWithIntl(
+
+ {
+ return new Promise(() => {});
+ },
+ docLinks: deps!.docLinks,
+ }}
+ >
+ {}}
+ isExecutingAction={false}
+ onExecutAction={async () => ({
+ actionId: '',
+ status: 'ok',
+ })}
+ executionResult={none}
+ />
+
+
+ );
+ const executeActionButton = wrapper?.find('[data-test-subj="executeActionButton"]');
+ expect(executeActionButton?.exists()).toBeTruthy();
+ expect(executeActionButton?.first().prop('isDisabled')).toBe(false);
+
+ const result = wrapper?.find('[data-test-subj="executionAwaiting"]');
+ expect(result?.exists()).toBeTruthy();
+ });
+
+ it('renders successful results', async () => {
+ const connector = {
+ actionTypeId: actionType.id,
+ config: {},
+ secrets: {},
+ } as ActionConnector;
+ const wrapper = mountWithIntl(
+
+ {
+ return new Promise(() => {});
+ },
+ docLinks: deps!.docLinks,
+ }}
+ >
+ {}}
+ isExecutingAction={false}
+ onExecutAction={async () => ({
+ actionId: '',
+ status: 'ok',
+ })}
+ executionResult={some({
+ actionId: '',
+ status: 'ok',
+ })}
+ />
+
+
+ );
+ const result = wrapper?.find('[data-test-subj="executionSuccessfulResult"]');
+ expect(result?.exists()).toBeTruthy();
+ });
+
+ it('renders failure results', async () => {
+ const connector = {
+ actionTypeId: actionType.id,
+ config: {},
+ secrets: {},
+ } as ActionConnector;
+ const wrapper = mountWithIntl(
+
+ {
+ return new Promise(() => {});
+ },
+ docLinks: deps!.docLinks,
+ }}
+ >
+ {}}
+ isExecutingAction={false}
+ onExecutAction={async () => ({
+ actionId: '',
+ status: 'error',
+ message: 'Error Message',
+ })}
+ executionResult={some({
+ actionId: '',
+ status: 'error',
+ message: 'Error Message',
+ })}
+ />
+
+
+ );
+ const result = wrapper?.find('[data-test-subj="executionFailureResult"]');
+ expect(result?.exists()).toBeTruthy();
+ });
+});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx
new file mode 100644
index 0000000000000..a73fd4e22e637
--- /dev/null
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx
@@ -0,0 +1,224 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { Fragment, Suspense } from 'react';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiText,
+ EuiButton,
+ EuiSteps,
+ EuiLoadingSpinner,
+ EuiDescriptionList,
+ EuiCallOut,
+ EuiSpacer,
+} from '@elastic/eui';
+import { Option, map, getOrElse } from 'fp-ts/lib/Option';
+import { pipe } from 'fp-ts/lib/pipeable';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import { ActionConnector } from '../../../types';
+import { useActionsConnectorsContext } from '../../context/actions_connectors_context';
+import { ActionTypeExecutorResult } from '../../../../../actions/common';
+
+export interface ConnectorAddFlyoutProps {
+ connector: ActionConnector;
+ executeEnabled: boolean;
+ isExecutingAction: boolean;
+ setActionParams: (params: Record) => void;
+ actionParams: Record;
+ onExecutAction: () => Promise>;
+ executionResult: Option>;
+}
+
+export const TestConnectorForm = ({
+ connector,
+ executeEnabled,
+ executionResult,
+ actionParams,
+ setActionParams,
+ onExecutAction,
+ isExecutingAction,
+}: ConnectorAddFlyoutProps) => {
+ const { actionTypeRegistry, docLinks } = useActionsConnectorsContext();
+ const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId);
+ const ParamsFieldsComponent = actionTypeModel.actionParamsFields;
+
+ const actionErrors = actionTypeModel?.validateParams(actionParams);
+ const hasErrors = !!Object.values(actionErrors.errors).find((errors) => errors.length > 0);
+
+ const steps = [
+ {
+ title: 'Create an action',
+ children: ParamsFieldsComponent ? (
+
+
+
+
+
+ }
+ >
+
+ setActionParams({
+ ...actionParams,
+ [field]: value,
+ })
+ }
+ messageVariables={[]}
+ docLinks={docLinks}
+ actionConnector={connector}
+ />
+
+ ) : (
+
+ This Connector does not require any Action Parameter.
+
+ ),
+ },
+ {
+ title: 'Run the action',
+ children: (
+
+ {executeEnabled ? null : (
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ ),
+ },
+ {
+ title: 'Results',
+ children: pipe(
+ executionResult,
+ map((result) =>
+ result.status === 'ok' ? (
+
+ ) : (
+
+ )
+ ),
+ getOrElse(() => )
+ ),
+ },
+ ];
+
+ return ;
+};
+
+const AwaitingExecution = () => (
+
+
+
+
+
+);
+
+const SuccessfulExecution = () => (
+
+
+
+
+
+);
+
+const FailedExecussion = ({
+ executionResult: { message, serviceMessage },
+}: {
+ executionResult: ActionTypeExecutorResult;
+}) => {
+ const items = [
+ {
+ title: i18n.translate(
+ 'xpack.triggersActionsUI.sections.testConnectorForm.executionFailureDescription',
+ {
+ defaultMessage: 'The following error was found:',
+ }
+ ),
+ description:
+ message ??
+ i18n.translate(
+ 'xpack.triggersActionsUI.sections.testConnectorForm.executionFailureUnknownReason',
+ {
+ defaultMessage: 'Unknown reason',
+ }
+ ),
+ },
+ ];
+ if (serviceMessage) {
+ items.push({
+ title: i18n.translate(
+ 'xpack.triggersActionsUI.sections.testConnectorForm.executionFailureAdditionalDetails',
+ {
+ defaultMessage: 'Details:',
+ }
+ ),
+ description: serviceMessage,
+ });
+ }
+ return (
+
+
+
+ );
+};
+
+// eslint-disable-next-line import/no-default-export
+export { TestConnectorForm as default };
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
index 837529bfc938d..352c9a67bbee2 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
@@ -194,55 +194,15 @@ export const ActionsConnectorsList: React.FunctionComponent = () => {
truncateText: true,
},
{
- field: 'isPreconfigured',
name: '',
- render: (value: number, item: ActionConnectorTableItem) => {
- if (item.isPreconfigured) {
- return (
-
-
-
-
-
- );
- }
+ render: (item: ActionConnectorTableItem) => {
return (
-
-
- setConnectorsToDelete([item.id])}
- iconType={'trash'}
- />
-
-
+ setConnectorsToDelete([item.id])}
+ />
);
},
@@ -344,28 +304,6 @@ export const ActionsConnectorsList: React.FunctionComponent = () => {
/>
);
- const noPermissionPrompt = (
-
-
-
- }
- body={
-
-
-
- }
- />
- );
-
return (
{
{data.length === 0 && canSave && !isLoadingActions && !isLoadingActionTypes && (
setAddFlyoutVisibility(true)} />
)}
- {data.length === 0 && !canSave && noPermissionPrompt}
+ {data.length === 0 && !canSave && }
{
function getActionsCountByActionType(actions: ActionConnector[], actionTypeId: string) {
return actions.filter((action) => action.actionTypeId === actionTypeId).length;
}
+
+const DeleteOperation: React.FunctionComponent<{
+ item: ActionConnectorTableItem;
+ canDelete: boolean;
+ onDelete: () => void;
+}> = ({ item, canDelete, onDelete }) => {
+ if (item.isPreconfigured) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+
+
+
+ );
+};
+
+const NoPermissionPrompt: React.FunctionComponent<{}> = () => (
+
+
+
+ }
+ body={
+
+
+
+ }
+ />
+);
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
index 86e355988da0b..151c837640228 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
@@ -17,6 +17,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
const find = getService('find');
+ const retry = getService('retry');
+ const comboBox = getService('comboBox');
describe('Connectors', function () {
before(async () => {
@@ -76,7 +78,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.setValue('slackWebhookUrlInput', 'https://test');
- await find.clickByCssSelector('[data-test-subj="saveEditedActionButton"]:not(disabled)');
+ await find.clickByCssSelector(
+ '[data-test-subj="saveAndCloseEditedActionButton"]:not(disabled)'
+ );
const toastTitle = await pageObjects.common.closeToast();
expect(toastTitle).to.eql(`Updated '${updatedConnectorName}'`);
@@ -92,6 +96,64 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
]);
});
+ it('should test a connector and display a successful result', async () => {
+ const connectorName = generateUniqueKey();
+ const indexName = generateUniqueKey();
+ await createIndexConnector(connectorName, indexName);
+
+ await pageObjects.triggersActionsUI.searchConnectors(connectorName);
+
+ const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList();
+ expect(searchResultsBeforeEdit.length).to.eql(1);
+
+ await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
+
+ await find.clickByCssSelector('[data-test-subj="testConnectorTab"]');
+
+ // test success
+ await testSubjects.setValue('documentsJsonEditor', '{ "key": "value" }');
+
+ await find.clickByCssSelector('[data-test-subj="executeActionButton"]:not(disabled)');
+
+ await retry.try(async () => {
+ await testSubjects.find('executionSuccessfulResult');
+ });
+
+ await find.clickByCssSelector(
+ '[data-test-subj="cancelSaveEditedConnectorButton"]:not(disabled)'
+ );
+ });
+
+ it('should test a connector and display a failure result', async () => {
+ const connectorName = generateUniqueKey();
+ const indexName = generateUniqueKey();
+ await createIndexConnector(connectorName, indexName);
+
+ await pageObjects.triggersActionsUI.searchConnectors(connectorName);
+
+ const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList();
+ expect(searchResultsBeforeEdit.length).to.eql(1);
+
+ await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
+
+ await find.clickByCssSelector('[data-test-subj="testConnectorTab"]');
+
+ await testSubjects.setValue('documentsJsonEditor', '{ "": "value" }');
+
+ await find.clickByCssSelector('[data-test-subj="executeActionButton"]:not(disabled)');
+
+ await retry.try(async () => {
+ const executionFailureResultCallout = await testSubjects.find('executionFailureResult');
+ expect(await executionFailureResultCallout.getVisibleText()).to.match(
+ /error indexing documents/
+ );
+ });
+
+ await find.clickByCssSelector(
+ '[data-test-subj="cancelSaveEditedConnectorButton"]:not(disabled)'
+ );
+ });
+
it('should reset connector when canceling an edit', async () => {
const connectorName = generateUniqueKey();
await createConnector(connectorName);
@@ -193,7 +255,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
expect(await testSubjects.exists('preconfiguredBadge')).to.be(true);
- expect(await testSubjects.exists('saveEditedActionButton')).to.be(false);
+ expect(await testSubjects.exists('saveAndCloseEditedActionButton')).to.be(false);
});
});
@@ -209,4 +271,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)');
await pageObjects.common.closeToast();
}
+
+ async function createIndexConnector(connectorName: string, indexName: string) {
+ await pageObjects.triggersActionsUI.clickCreateConnectorButton();
+
+ await testSubjects.click('.index-card');
+
+ await testSubjects.setValue('nameInput', connectorName);
+
+ await comboBox.set('connectorIndexesComboBox', indexName);
+
+ await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)');
+ await pageObjects.common.closeToast();
+ }
};
From c63ee1b31e73049ba1f926fec19de018d38276a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Tue, 22 Sep 2020 12:36:10 +0200
Subject: [PATCH 13/22] Bump backport to 5.6.0 (#78097)
---
package.json | 2 +-
yarn.lock | 281 ++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 247 insertions(+), 36 deletions(-)
diff --git a/package.json b/package.json
index 1f2749ea44a90..57f5ac16059c9 100644
--- a/package.json
+++ b/package.json
@@ -350,7 +350,7 @@
"babel-eslint": "^10.0.3",
"babel-jest": "^25.5.1",
"babel-plugin-istanbul": "^6.0.0",
- "backport": "5.5.1",
+ "backport": "5.6.0",
"brace": "0.11.1",
"chai": "3.5.0",
"chance": "1.0.18",
diff --git a/yarn.lock b/yarn.lock
index cec2697f6c15c..9e96158771cde 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2458,6 +2458,25 @@
jsonwebtoken "^8.3.0"
lru-cache "^5.1.1"
+"@octokit/auth-token@^2.4.0":
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.2.tgz#10d0ae979b100fa6b72fa0e8e63e27e6d0dbff8a"
+ integrity sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ==
+ dependencies:
+ "@octokit/types" "^5.0.0"
+
+"@octokit/core@^3.0.0":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.1.2.tgz#c937d5f9621b764573068fcd2e5defcc872fd9cc"
+ integrity sha512-AInOFULmwOa7+NFi9F8DlDkm5qtZVmDQayi7TUgChE3yeIGPq0Y+6cAEXPexQ3Ea+uZy66hKEazR7DJyU+4wfw==
+ dependencies:
+ "@octokit/auth-token" "^2.4.0"
+ "@octokit/graphql" "^4.3.1"
+ "@octokit/request" "^5.4.0"
+ "@octokit/types" "^5.0.0"
+ before-after-hook "^2.1.0"
+ universal-user-agent "^6.0.0"
+
"@octokit/endpoint@^3.2.0":
version "3.2.3"
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-3.2.3.tgz#bd9aea60cd94ce336656b57a5c9cb7f10be8f4f3"
@@ -2468,6 +2487,44 @@
universal-user-agent "^2.0.1"
url-template "^2.0.8"
+"@octokit/endpoint@^6.0.1":
+ version "6.0.6"
+ resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.6.tgz#4f09f2b468976b444742a1d5069f6fa45826d999"
+ integrity sha512-7Cc8olaCoL/mtquB7j/HTbPM+sY6Ebr4k2X2y4JoXpVKQ7r5xB4iGQE0IoO58wIPsUk4AzoT65AMEpymSbWTgQ==
+ dependencies:
+ "@octokit/types" "^5.0.0"
+ is-plain-object "^5.0.0"
+ universal-user-agent "^6.0.0"
+
+"@octokit/graphql@^4.3.1":
+ version "4.5.6"
+ resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.6.tgz#708143ba15cf7c1879ed6188266e7f270be805d4"
+ integrity sha512-Rry+unqKTa3svswT2ZAuqenpLrzJd+JTv89LTeVa5UM/5OX8o4KTkPL7/1ABq4f/ZkELb0XEK/2IEoYwykcLXg==
+ dependencies:
+ "@octokit/request" "^5.3.0"
+ "@octokit/types" "^5.0.0"
+ universal-user-agent "^6.0.0"
+
+"@octokit/plugin-paginate-rest@^2.2.0":
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.4.0.tgz#92f951ddc8a1cd505353fa07650752ca25ed7e93"
+ integrity sha512-YT6Klz3LLH6/nNgi0pheJnUmTFW4kVnxGft+v8Itc41IIcjl7y1C8TatmKQBbCSuTSNFXO5pCENnqg6sjwpJhg==
+ dependencies:
+ "@octokit/types" "^5.5.0"
+
+"@octokit/plugin-request-log@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e"
+ integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==
+
+"@octokit/plugin-rest-endpoint-methods@4.2.0":
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.2.0.tgz#c5a0691b3aba5d8b4ef5dffd6af3649608f167ba"
+ integrity sha512-1/qn1q1C1hGz6W/iEDm9DoyNoG/xdFDt78E3eZ5hHeUfJTLJgyAMdj9chL/cNBHjcjd+FH5aO1x0VCqR2RE0mw==
+ dependencies:
+ "@octokit/types" "^5.5.0"
+ deprecation "^2.3.1"
+
"@octokit/plugin-retry@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-2.2.0.tgz#11f3957a46ccdb7b7f33caabf8c17e57b25b80b2"
@@ -2475,6 +2532,15 @@
dependencies:
bottleneck "^2.15.3"
+"@octokit/request-error@^2.0.0":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0"
+ integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw==
+ dependencies:
+ "@octokit/types" "^5.0.1"
+ deprecation "^2.0.0"
+ once "^1.4.0"
+
"@octokit/request@2.4.2", "@octokit/request@^2.1.2", "@octokit/request@^2.4.2":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-2.4.2.tgz#87c36e820dd1e43b1629f4f35c95b00cd456320b"
@@ -2487,6 +2553,20 @@
once "^1.4.0"
universal-user-agent "^2.0.1"
+"@octokit/request@^5.3.0", "@octokit/request@^5.4.0":
+ version "5.4.9"
+ resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.9.tgz#0a46f11b82351b3416d3157261ad9b1558c43365"
+ integrity sha512-CzwVvRyimIM1h2n9pLVYfTDmX9m+KHSgCpqPsY8F1NdEK8IaWqXhSBXsdjOBFZSpEcxNEeg4p0UO9cQ8EnOCLA==
+ dependencies:
+ "@octokit/endpoint" "^6.0.1"
+ "@octokit/request-error" "^2.0.0"
+ "@octokit/types" "^5.0.0"
+ deprecation "^2.0.0"
+ is-plain-object "^5.0.0"
+ node-fetch "^2.6.1"
+ once "^1.4.0"
+ universal-user-agent "^6.0.0"
+
"@octokit/rest@^16.23.2":
version "16.23.2"
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.23.2.tgz#975e84610427c4ab6c41bec77c24aed9b7563db4"
@@ -2505,6 +2585,23 @@
universal-user-agent "^2.0.0"
url-template "^2.0.8"
+"@octokit/rest@^18.0.6":
+ version "18.0.6"
+ resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.6.tgz#76c274f1a68f40741a131768ef483f041e7b98b6"
+ integrity sha512-ES4lZBKPJMX/yUoQjAZiyFjei9pJ4lTTfb9k7OtYoUzKPDLl/M8jiHqt6qeSauyU4eZGLw0sgP1WiQl9FYeM5w==
+ dependencies:
+ "@octokit/core" "^3.0.0"
+ "@octokit/plugin-paginate-rest" "^2.2.0"
+ "@octokit/plugin-request-log" "^1.0.0"
+ "@octokit/plugin-rest-endpoint-methods" "4.2.0"
+
+"@octokit/types@^5.0.0", "@octokit/types@^5.0.1", "@octokit/types@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b"
+ integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ==
+ dependencies:
+ "@types/node" ">= 8"
+
"@percy/agent@^0.26.0":
version "0.26.0"
resolved "https://registry.yarnpkg.com/@percy/agent/-/agent-0.26.0.tgz#9f06849d752df7368198835d0b5edc16c2d69a0c"
@@ -4112,16 +4209,30 @@
"@types/node" "*"
"@types/webpack" "*"
-"@types/lodash@^4.14.159":
- version "4.14.159"
- resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065"
- integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg==
+"@types/lodash.difference@^4.5.6":
+ version "4.5.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.difference/-/lodash.difference-4.5.6.tgz#41ec5c4e684eeacf543848a9a1b2a4856ccf9853"
+ integrity sha512-wXH53r+uoUCrKhmh7S5Gf6zo3vpsx/zH2R4pvkmDlmopmMTCROAUXDpPMXATGCWkCjE6ik3VZzZUxBgMjZho9Q==
+ dependencies:
+ "@types/lodash" "*"
-"@types/lodash@^4.14.160":
+"@types/lodash.intersection@^4.4.6":
+ version "4.4.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.intersection/-/lodash.intersection-4.4.6.tgz#0fb241badf6edbb2a7d194a70c50e950e2486e68"
+ integrity sha512-6ewsKax7+HgT+7mEhzXT6tIyIHc/mjCwZJnarvLbCrtW21qmDQHWbaJj4Ht4DQDBmMdnvZe8APuVlsMpZ5E5mQ==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash@*", "@types/lodash@^4.14.160":
version "4.14.161"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18"
integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA==
+"@types/lodash@^4.14.159":
+ version "4.14.159"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065"
+ integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg==
+
"@types/log-symbols@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/log-symbols/-/log-symbols-2.0.0.tgz#7919e2ec3c8d13879bfdcab310dd7a3f7fc9466d"
@@ -4269,7 +4380,7 @@
dependencies:
"@types/node" "*"
-"@types/node@*", "@types/node@8.10.54", "@types/node@>=10.17.17 <10.20.0", "@types/node@>=8.9.0", "@types/node@^12.0.2":
+"@types/node@*", "@types/node@8.10.54", "@types/node@>= 8", "@types/node@>=10.17.17 <10.20.0", "@types/node@>=8.9.0", "@types/node@^12.0.2":
version "10.17.26"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.26.tgz#a8a119960bff16b823be4c617da028570779bcfd"
integrity sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw==
@@ -7213,26 +7324,31 @@ bach@^1.0.0:
async-settle "^1.0.0"
now-and-later "^2.0.0"
-backport@5.5.1:
- version "5.5.1"
- resolved "https://registry.yarnpkg.com/backport/-/backport-5.5.1.tgz#2eeddbdc4cfc0530119bdb2b0c3c30bc7ef574dd"
- integrity sha512-vQuGrxxMx9H64ywqsIYUHL8+/xvPeP0nnBa0YQt5S+XqW7etaqOoa5dFW0c77ADdqjfLlGUIvtc2i6UrmqeFUQ==
+backport@5.6.0:
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/backport/-/backport-5.6.0.tgz#6dcc0485e5eecf66bb6f950983fd0b018217ec20"
+ integrity sha512-wz7Ve3uslhGUMtHuctqIEtZFItTGKRRMiNANYso0iw1ar81ILsczDGgxeOlzmmnIQFi1ZvEs6lX3cgypGfef9A==
dependencies:
- axios "^0.19.2"
+ "@octokit/rest" "^18.0.6"
+ "@types/lodash.difference" "^4.5.6"
+ "@types/lodash.intersection" "^4.4.6"
+ axios "^0.19.0"
dedent "^0.7.0"
del "^5.1.0"
- find-up "^4.1.0"
- inquirer "^7.3.1"
+ find-up "^5.0.0"
+ inquirer "^7.3.3"
+ lodash.difference "^4.5.0"
lodash.flatmap "^4.5.0"
+ lodash.intersection "^4.4.0"
lodash.isempty "^4.4.0"
lodash.isstring "^4.0.1"
lodash.uniq "^4.5.0"
make-dir "^3.1.0"
- ora "^4.0.4"
+ ora "^5.1.0"
safe-json-stringify "^1.2.0"
- strip-json-comments "^3.1.0"
+ strip-json-comments "^3.1.1"
winston "^3.3.3"
- yargs "^15.4.0"
+ yargs "^16.0.3"
bail@^1.0.0:
version "1.0.2"
@@ -7306,6 +7422,11 @@ before-after-hook@^1.4.0:
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.4.0.tgz#2b6bf23dca4f32e628fd2747c10a37c74a4b484d"
integrity sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg==
+before-after-hook@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
+ integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
+
big-integer@^1.6.16:
version "1.6.48"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
@@ -8622,10 +8743,10 @@ cli-spinners@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.1.0.tgz#22c34b4d51f573240885b201efda4e4ec9fff3c7"
integrity sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA==
-cli-spinners@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.2.0.tgz#e8b988d9206c692302d8ee834e7a85c0144d8f77"
- integrity sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==
+cli-spinners@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f"
+ integrity sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA==
cli-table3@0.5.1:
version "0.5.1"
@@ -8744,6 +8865,15 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
+cliui@^7.0.0:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3"
+ integrity sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^7.0.0"
+
clone-buffer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
@@ -10639,6 +10769,11 @@ deprecation@^1.0.1:
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-1.0.1.tgz#2df79b79005752180816b7b6e079cbd80490d711"
integrity sha512-ccVHpE72+tcIKaGMql33x5MAjKQIZrk+3x2GbJ7TeraUCZWHoT+KSZpoC+JQFsUBlSTXUrBaGiF0j6zVTepPLg==
+deprecation@^2.0.0, deprecation@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
+ integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
+
des.js@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
@@ -11755,6 +11890,11 @@ es6-weak-map@^2.0.1, es6-weak-map@^2.0.2:
es6-iterator "^2.0.1"
es6-symbol "^3.1.1"
+escalade@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e"
+ integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==
+
escape-goat@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
@@ -13059,6 +13199,14 @@ find-up@^4.0.0, find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
find-versions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-2.0.0.tgz#2ad90d490f6828c1aa40292cf709ac3318210c3c"
@@ -13694,7 +13842,7 @@ get-caller-file@^1.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
-get-caller-file@^2.0.1:
+get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@@ -16006,7 +16154,7 @@ inquirer@^6.0.0:
strip-ansi "^5.1.0"
through "^2.3.6"
-inquirer@^7.0.0, inquirer@^7.3.1, inquirer@^7.3.3:
+inquirer@^7.0.0, inquirer@^7.3.3:
version "7.3.3"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003"
integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==
@@ -16677,6 +16825,11 @@ is-plain-object@3.0.0, is-plain-object@^3.0.0:
dependencies:
isobject "^4.0.0"
+is-plain-object@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
+ integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
+
is-promise@^2.1, is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
@@ -18711,6 +18864,13 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
locutus@^2.0.5:
version "2.0.10"
resolved "https://registry.yarnpkg.com/locutus/-/locutus-2.0.10.tgz#f903619466a98a4ab76e8b87a5854b55a743b917"
@@ -19011,7 +19171,7 @@ log-symbols@2.2.0, log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0:
dependencies:
chalk "^2.0.1"
-log-symbols@3.0.0, log-symbols@^3.0.0:
+log-symbols@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==
@@ -21460,16 +21620,16 @@ ora@^3.0.0:
strip-ansi "^5.2.0"
wcwidth "^1.0.1"
-ora@^4.0.4:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/ora/-/ora-4.0.4.tgz#e8da697cc5b6a47266655bf68e0fb588d29a545d"
- integrity sha512-77iGeVU1cIdRhgFzCK8aw1fbtT1B/iZAvWjS+l/o1x0RShMgxHUZaD2yDpWsNCPwXg9z1ZA78Kbdvr8kBmG/Ww==
+ora@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/ora/-/ora-5.1.0.tgz#b188cf8cd2d4d9b13fd25383bc3e5cba352c94f8"
+ integrity sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w==
dependencies:
- chalk "^3.0.0"
+ chalk "^4.1.0"
cli-cursor "^3.1.0"
- cli-spinners "^2.2.0"
+ cli-spinners "^2.4.0"
is-interactive "^1.0.0"
- log-symbols "^3.0.0"
+ log-symbols "^4.0.0"
mute-stream "0.0.8"
strip-ansi "^6.0.0"
wcwidth "^1.0.1"
@@ -21640,6 +21800,13 @@ p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0:
dependencies:
p-try "^2.0.0"
+p-limit@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe"
+ integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==
+ dependencies:
+ p-try "^2.0.0"
+
p-locate@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
@@ -21661,6 +21828,13 @@ p-locate@^4.1.0:
dependencies:
p-limit "^2.2.0"
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
p-map@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
@@ -26947,10 +27121,10 @@ strip-json-comments@^3.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
-strip-json-comments@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180"
- integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==
+strip-json-comments@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
strip-json-comments@~1.0.1:
version "1.0.4"
@@ -28688,6 +28862,11 @@ universal-user-agent@^2.0.0, universal-user-agent@^2.0.1:
dependencies:
os-name "^3.0.0"
+universal-user-agent@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
+ integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
+
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
@@ -30272,6 +30451,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -30521,6 +30709,11 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
+y18n@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.1.tgz#1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571"
+ integrity sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg==
+
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
@@ -30582,6 +30775,11 @@ yargs-parser@^18.1.1, yargs-parser@^18.1.2, yargs-parser@^18.1.3:
camelcase "^5.0.0"
decamelize "^1.2.0"
+yargs-parser@^20.0.0:
+ version "20.2.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.0.tgz#944791ca2be2e08ddadd3d87e9de4c6484338605"
+ integrity sha512-2agPoRFPoIcFzOIp6656gcvsg2ohtscpw2OINr/q46+Sq41xz2OYLqx5HRHabmFU1OARIPAYH5uteICE7mn/5A==
+
yargs-unparser@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f"
@@ -30624,7 +30822,7 @@ yargs@13.3.2, yargs@^13.2.2, yargs@^13.3.0, yargs@^13.3.2:
y18n "^4.0.0"
yargs-parser "^13.1.2"
-yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.0, yargs@^15.4.1:
+yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
@@ -30641,6 +30839,19 @@ yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.0, yargs@^15.4.1:
y18n "^4.0.0"
yargs-parser "^18.1.2"
+yargs@^16.0.3:
+ version "16.0.3"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c"
+ integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA==
+ dependencies:
+ cliui "^7.0.0"
+ escalade "^3.0.2"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.0"
+ y18n "^5.0.1"
+ yargs-parser "^20.0.0"
+
yargs@^3.15.0:
version "3.32.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
From c03c7b3cf4a50693ab8a152c377857e51fcd3ad3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?=
Date: Tue, 22 Sep 2020 12:45:51 +0200
Subject: [PATCH 14/22] [Security Solution] Refactor Hosts Kpi to use Search
Strategy (#77606)
---
.../security_solution/hosts/index.ts | 1 +
.../hosts/kpi/authentications/index.ts | 24 +++
.../hosts/kpi/common/index.ts | 29 +++
.../hosts/kpi/hosts/index.ts | 18 ++
.../security_solution/hosts/kpi/index.ts | 25 +++
.../hosts/kpi/unique_ips/index.ts | 20 +++
.../security_solution/index.ts | 20 +++
.../components/stat_items/index.test.tsx | 8 +-
.../common/components/stat_items/index.tsx | 14 +-
.../authentications_table/translations.ts | 2 +-
.../__snapshots__/index.test.tsx.snap | 155 ----------------
.../kpi_hosts/authentications/index.tsx | 73 ++++++++
.../{ => authentications}/translations.ts | 37 +---
.../components/kpi_hosts/common/index.tsx | 69 +++++++
.../components/kpi_hosts/hosts/index.tsx | 62 +++++++
.../kpi_hosts/hosts/translations.ts | 11 ++
.../hosts/components/kpi_hosts/index.test.tsx | 107 -----------
.../hosts/components/kpi_hosts/index.tsx | 137 +++++++-------
.../kpi_hosts/kpi_host_details_mapping.ts | 64 -------
.../components/kpi_hosts/kpi_hosts_mapping.ts | 79 --------
.../hosts/components/kpi_hosts/mock.tsx | 145 ---------------
.../hosts/components/kpi_hosts/types.ts | 18 +-
.../components/kpi_hosts/unique_ips/index.tsx | 73 ++++++++
.../kpi_hosts/unique_ips/translations.ts | 39 ++++
.../kpi_hosts/authentications/index.tsx | 170 ++++++++++++++++++
.../kpi_hosts/authentications/translations.ts | 21 +++
.../containers/kpi_hosts/hosts/index.tsx | 158 ++++++++++++++++
.../kpi_hosts/hosts/translations.ts | 21 +++
.../hosts/containers/kpi_hosts/index.tsx | 82 +--------
.../containers/kpi_hosts/unique_ips/index.tsx | 167 +++++++++++++++++
.../kpi_hosts/unique_ips/translations.ts | 21 +++
.../public/hosts/pages/details/index.tsx | 29 +--
.../public/hosts/pages/hosts.tsx | 31 +---
.../authentications_query_tab_body.tsx | 31 ++--
.../__snapshots__/index.test.tsx.snap | 4 +-
.../components/kpi_network/common/index.tsx | 6 +-
.../components/kpi_network/dns/index.tsx | 4 +-
.../components/kpi_network/index.test.tsx | 8 +-
.../network/components/kpi_network/index.tsx | 4 +-
.../kpi_network/network_events/index.tsx | 4 +-
.../kpi_network/tls_handshakes/index.tsx | 4 +-
.../kpi_network/unique_flows/index.tsx | 4 +-
.../kpi_network/unique_private_ips/index.tsx | 4 +-
.../public/network/pages/network.tsx | 4 +-
.../hosts/authentications/dsl/query.dsl.ts | 4 +-
.../factory/hosts/authentications/helpers.ts | 4 +-
.../factory/hosts/authentications/index.tsx | 4 +-
.../factory/hosts/index.test.ts | 11 +-
.../security_solution/factory/hosts/index.ts | 12 +-
.../hosts/kpi/authentications/helpers.ts | 21 +++
.../hosts/kpi/authentications/index.ts | 63 +++++++
.../query.hosts_kpi_authentications.dsl.ts | 101 +++++++++++
.../factory/hosts/kpi/common/index.ts | 21 +++
.../factory/hosts/kpi/hosts/index.ts | 42 +++++
.../kpi/hosts/query.hosts_kpi_hosts.dsl.ts | 64 +++++++
.../factory/hosts/kpi/index.ts | 10 ++
.../factory/hosts/kpi/unique_ips/index.ts | 55 ++++++
.../query.hosts_kpi_unique_ips.dsl.ts | 82 +++++++++
.../translations/translations/ja-JP.json | 2 +-
.../translations/translations/zh-CN.json | 2 +-
60 files changed, 1663 insertions(+), 842 deletions(-)
create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts
create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.ts
create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts
create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts
create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts
delete mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap
create mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx
rename x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/{ => authentications}/translations.ts (55%)
create mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts
delete mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts
delete mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts
delete mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx
create mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts
create mode 100644 x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts
create mode 100644 x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts
create mode 100644 x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts
index 63a57c20a8593..a39638e48892d 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts
@@ -9,6 +9,7 @@ export * from './authentications';
export * from './common';
export * from './details';
export * from './first_last_seen';
+export * from './kpi';
export * from './overview';
export * from './uncommon_processes';
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts
new file mode 100644
index 0000000000000..cbf1f32c3b5fa
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
+import { Inspect, Maybe } from '../../../../common';
+import { RequestBasicOptions } from '../../..';
+import { HostsKpiHistogramData } from '../common';
+
+export interface HostsKpiAuthenticationsHistogramCount {
+ doc_count: number;
+}
+
+export type HostsKpiAuthenticationsRequestOptions = RequestBasicOptions;
+
+export interface HostsKpiAuthenticationsStrategyResponse extends IEsSearchResponse {
+ authenticationsSuccess: Maybe;
+ authenticationsSuccessHistogram: Maybe;
+ authenticationsFailure: Maybe;
+ authenticationsFailureHistogram: Maybe;
+ inspect?: Maybe;
+}
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.ts
new file mode 100644
index 0000000000000..52e65bb995796
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Maybe } from '../../../../common';
+
+export interface HostsKpiHistogramData {
+ x?: Maybe;
+ y?: Maybe;
+}
+
+export interface HostsKpiHistogram {
+ key_as_string: string;
+ key: number;
+ doc_count: number;
+ count: T;
+}
+
+export interface HostsKpiGeneralHistogramCount {
+ value: number;
+}
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts
new file mode 100644
index 0000000000000..8e8bd97c9b60b
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
+import { Inspect, Maybe } from '../../../../common';
+import { RequestBasicOptions } from '../../..';
+import { HostsKpiHistogramData } from '../common';
+
+export type HostsKpiHostsRequestOptions = RequestBasicOptions;
+
+export interface HostsKpiHostsStrategyResponse extends IEsSearchResponse {
+ hosts: Maybe;
+ hostsHistogram: Maybe;
+ inspect?: Maybe;
+}
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts
new file mode 100644
index 0000000000000..dc34f619e0362
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export * from './authentications';
+export * from './common';
+export * from './hosts';
+export * from './unique_ips';
+
+import { HostsKpiAuthenticationsStrategyResponse } from './authentications';
+import { HostsKpiHostsStrategyResponse } from './hosts';
+import { HostsKpiUniqueIpsStrategyResponse } from './unique_ips';
+
+export enum HostsKpiQueries {
+ kpiAuthentications = 'hostsKpiAuthentications',
+ kpiHosts = 'hostsKpiHosts',
+ kpiUniqueIps = 'hostsKpiUniqueIps',
+}
+
+export type HostsKpiStrategyResponse =
+ | Omit
+ | Omit
+ | Omit;
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts
new file mode 100644
index 0000000000000..18a603725f401
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
+import { Inspect, Maybe } from '../../../../common';
+import { RequestBasicOptions } from '../../..';
+import { HostsKpiHistogramData } from '../common';
+
+export type HostsKpiUniqueIpsRequestOptions = RequestBasicOptions;
+
+export interface HostsKpiUniqueIpsStrategyResponse extends IEsSearchResponse {
+ uniqueSourceIps: Maybe;
+ uniqueSourceIpsHistogram: Maybe;
+ uniqueDestinationIps: Maybe;
+ uniqueDestinationIpsHistogram: Maybe;
+ inspect?: Maybe;
+}
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts
index 95f3cd4fd7da7..cfcf613b662bc 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts
@@ -20,6 +20,13 @@ import {
HostsStrategyResponse,
HostUncommonProcessesStrategyResponse,
HostUncommonProcessesRequestOptions,
+ HostsKpiQueries,
+ HostsKpiAuthenticationsStrategyResponse,
+ HostsKpiAuthenticationsRequestOptions,
+ HostsKpiHostsStrategyResponse,
+ HostsKpiHostsRequestOptions,
+ HostsKpiUniqueIpsStrategyResponse,
+ HostsKpiUniqueIpsRequestOptions,
} from './hosts';
import {
NetworkQueries,
@@ -70,6 +77,7 @@ export * from './network';
export type FactoryQueryTypes =
| HostsQueries
+ | HostsKpiQueries
| NetworkQueries
| NetworkKpiQueries
| typeof MatrixHistogramQuery;
@@ -106,6 +114,12 @@ export type StrategyResponseType = T extends HostsQ
? HostFirstLastSeenStrategyResponse
: T extends HostsQueries.uncommonProcesses
? HostUncommonProcessesStrategyResponse
+ : T extends HostsKpiQueries.kpiAuthentications
+ ? HostsKpiAuthenticationsStrategyResponse
+ : T extends HostsKpiQueries.kpiHosts
+ ? HostsKpiHostsStrategyResponse
+ : T extends HostsKpiQueries.kpiUniqueIps
+ ? HostsKpiUniqueIpsStrategyResponse
: T extends NetworkQueries.details
? NetworkDetailsStrategyResponse
: T extends NetworkQueries.dns
@@ -148,6 +162,12 @@ export type StrategyRequestType = T extends HostsQu
? HostFirstLastSeenRequestOptions
: T extends HostsQueries.uncommonProcesses
? HostUncommonProcessesRequestOptions
+ : T extends HostsKpiQueries.kpiAuthentications
+ ? HostsKpiAuthenticationsRequestOptions
+ : T extends HostsKpiQueries.kpiHosts
+ ? HostsKpiHostsRequestOptions
+ : T extends HostsKpiQueries.kpiUniqueIps
+ ? HostsKpiUniqueIpsRequestOptions
: T extends NetworkQueries.details
? NetworkDetailsRequestOptions
: T extends NetworkQueries.dns
diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx
index 664d8b2ff5598..310d4c52ec5bc 100644
--- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx
@@ -39,8 +39,10 @@ import {
} from '../../mock';
import { State, createStore } from '../../store';
import { Provider as ReduxStoreProvider } from 'react-redux';
-import { KpiHostsData } from '../../../graphql/types';
-import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy';
+import {
+ HostsKpiStrategyResponse,
+ NetworkKpiStrategyResponse,
+} from '../../../../common/search_strategy';
const from = '2019-06-15T06:00:00.000Z';
const to = '2019-06-18T06:00:00.000Z';
@@ -242,7 +244,7 @@ describe('useKpiMatrixStatus', () => {
data,
}: {
fieldsMapping: Readonly;
- data: NetworkKpiStrategyResponse | KpiHostsData;
+ data: NetworkKpiStrategyResponse | HostsKpiStrategyResponse;
}) => {
const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(
fieldsMapping,
diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx
index 13a93a784a2c9..34fb344eed3c4 100644
--- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx
@@ -18,8 +18,10 @@ import { get, getOr } from 'lodash/fp';
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
-import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy';
-import { KpiHostsData } from '../../../graphql/types';
+import {
+ HostsKpiStrategyResponse,
+ NetworkKpiStrategyResponse,
+} from '../../../../common/search_strategy';
import { AreaChart } from '../charts/areachart';
import { BarChart } from '../charts/barchart';
import { ChartSeriesData, ChartData, ChartSeriesConfigs, UpdateDateRange } from '../charts/common';
@@ -113,12 +115,12 @@ export const barchartConfigs = (config?: { onElementClick?: ElementClickListener
export const addValueToFields = (
fields: StatItem[],
- data: KpiHostsData | NetworkKpiStrategyResponse
+ data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse
): StatItem[] => fields.map((field) => ({ ...field, value: get(field.key, data) }));
export const addValueToAreaChart = (
fields: StatItem[],
- data: KpiHostsData | NetworkKpiStrategyResponse
+ data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse
): ChartSeriesData[] =>
fields
.filter((field) => get(`${field.key}Histogram`, data) != null)
@@ -130,7 +132,7 @@ export const addValueToAreaChart = (
export const addValueToBarChart = (
fields: StatItem[],
- data: KpiHostsData | NetworkKpiStrategyResponse
+ data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse
): ChartSeriesData[] => {
if (fields.length === 0) return [];
return fields.reduce((acc: ChartSeriesData[], field: StatItem, idx: number) => {
@@ -159,7 +161,7 @@ export const addValueToBarChart = (
export const useKpiMatrixStatus = (
mappings: Readonly,
- data: KpiHostsData | NetworkKpiStrategyResponse,
+ data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse,
id: string,
from: string,
to: string,
diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts
index 0e918275e2b18..d437a6b73f71a 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts
+++ b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts
@@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
export const AUTHENTICATIONS = i18n.translate(
- 'xpack.securitySolution.authenticationsTable.authenticationFailures',
+ 'xpack.securitySolution.authenticationsTable.authentications',
{
defaultMessage: 'Authentications',
}
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 2b2a35945bdf1..0000000000000
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,155 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`kpiHostsComponent render it should render KpiHostDetailsData 1`] = `
-
-
-
-
-`;
-
-exports[`kpiHostsComponent render it should render KpiHostsData 1`] = `
-
-
-
-
-
-`;
-
-exports[`kpiHostsComponent render it should render spinner if it is loading 1`] = `
-
-
-
-
-
-`;
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx
new file mode 100644
index 0000000000000..0949616827470
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { StatItems } from '../../../../common/components/stat_items';
+import { useHostsKpiAuthentications } from '../../../containers/kpi_hosts/authentications';
+import { HostsKpiBaseComponentManage } from '../common';
+import { HostsKpiProps, HostsKpiChartColors } from '../types';
+import * as i18n from './translations';
+
+export const fieldsMapping: Readonly = [
+ {
+ key: 'authentication',
+ fields: [
+ {
+ key: 'authenticationsSuccess',
+ name: i18n.SUCCESS_CHART_LABEL,
+ description: i18n.SUCCESS_UNIT_LABEL,
+ value: null,
+ color: HostsKpiChartColors.authenticationsSuccess,
+ icon: 'check',
+ },
+ {
+ key: 'authenticationsFailure',
+ name: i18n.FAIL_CHART_LABEL,
+ description: i18n.FAIL_UNIT_LABEL,
+ value: null,
+ color: HostsKpiChartColors.authenticationsFailure,
+ icon: 'cross',
+ },
+ ],
+ enableAreaChart: true,
+ enableBarChart: true,
+ description: i18n.USER_AUTHENTICATIONS,
+ },
+];
+
+const HostsKpiAuthenticationsComponent: React.FC = ({
+ filterQuery,
+ from,
+ to,
+ narrowDateRange,
+ setQuery,
+ skip,
+}) => {
+ const [loading, { refetch, id, inspect, ...data }] = useHostsKpiAuthentications({
+ filterQuery,
+ endDate: to,
+ startDate: from,
+ skip,
+ });
+
+ return (
+
+ );
+};
+
+export const HostsKpiAuthentications = React.memo(HostsKpiAuthenticationsComponent);
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts
similarity index 55%
rename from x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts
rename to x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts
index 82543e6f106fa..5175781159c91 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts
@@ -3,11 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { i18n } from '@kbn/i18n';
-export const HOSTS = i18n.translate('xpack.securitySolution.kpiHosts.hosts.title', {
- defaultMessage: 'Hosts',
-});
+import { i18n } from '@kbn/i18n';
export const USER_AUTHENTICATIONS = i18n.translate(
'xpack.securitySolution.kpiHosts.userAuthentications.title',
@@ -43,35 +40,3 @@ export const FAIL_CHART_LABEL = i18n.translate(
defaultMessage: 'Fail',
}
);
-
-export const UNIQUE_IPS = i18n.translate('xpack.securitySolution.kpiHosts.uniqueIps.title', {
- defaultMessage: 'Unique IPs',
-});
-
-export const SOURCE_UNIT_LABEL = i18n.translate(
- 'xpack.securitySolution.kpiHosts.uniqueIps.sourceUnitLabel',
- {
- defaultMessage: 'source',
- }
-);
-
-export const DESTINATION_UNIT_LABEL = i18n.translate(
- 'xpack.securitySolution.kpiHosts.uniqueIps.destinationUnitLabel',
- {
- defaultMessage: 'destination',
- }
-);
-
-export const SOURCE_CHART_LABEL = i18n.translate(
- 'xpack.securitySolution.kpiHosts.uniqueIps.sourceChartLabel',
- {
- defaultMessage: 'Src.',
- }
-);
-
-export const DESTINATION_CHART_LABEL = i18n.translate(
- 'xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel',
- {
- defaultMessage: 'Dest.',
- }
-);
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx
new file mode 100644
index 0000000000000..7c51a503092af
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx
@@ -0,0 +1,69 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { EuiFlexItem, EuiLoadingSpinner, EuiFlexGroup } from '@elastic/eui';
+import styled from 'styled-components';
+
+import { manageQuery } from '../../../../common/components/page/manage_query';
+import { HostsKpiStrategyResponse } from '../../../../../common/search_strategy';
+import {
+ StatItemsComponent,
+ StatItemsProps,
+ useKpiMatrixStatus,
+ StatItems,
+} from '../../../../common/components/stat_items';
+import { UpdateDateRange } from '../../../../common/components/charts/common';
+
+const kpiWidgetHeight = 247;
+
+export const FlexGroup = styled(EuiFlexGroup)`
+ min-height: ${kpiWidgetHeight}px;
+`;
+
+FlexGroup.displayName = 'FlexGroup';
+
+export const HostsKpiBaseComponent = React.memo<{
+ fieldsMapping: Readonly;
+ data: HostsKpiStrategyResponse;
+ loading?: boolean;
+ id: string;
+ from: string;
+ to: string;
+ narrowDateRange: UpdateDateRange;
+}>(({ fieldsMapping, data, id, loading = false, from, to, narrowDateRange }) => {
+ const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(
+ fieldsMapping,
+ data,
+ id,
+ from,
+ to,
+ narrowDateRange
+ );
+
+ if (loading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+ {statItemsProps.map((mappedStatItemProps) => (
+
+ ))}
+
+ );
+});
+
+HostsKpiBaseComponent.displayName = 'HostsKpiBaseComponent';
+
+export const HostsKpiBaseComponentManage = manageQuery(HostsKpiBaseComponent);
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx
new file mode 100644
index 0000000000000..b1c4d6331e450
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { StatItems } from '../../../../common/components/stat_items';
+import { useHostsKpiHosts } from '../../../containers/kpi_hosts/hosts';
+import { HostsKpiBaseComponentManage } from '../common';
+import { HostsKpiProps, HostsKpiChartColors } from '../types';
+import * as i18n from './translations';
+
+export const fieldsMapping: Readonly = [
+ {
+ key: 'hosts',
+ fields: [
+ {
+ key: 'hosts',
+ value: null,
+ color: HostsKpiChartColors.hosts,
+ icon: 'storage',
+ },
+ ],
+ enableAreaChart: true,
+ description: i18n.HOSTS,
+ },
+];
+
+const HostsKpiHostsComponent: React.FC = ({
+ filterQuery,
+ from,
+ to,
+ narrowDateRange,
+ setQuery,
+ skip,
+}) => {
+ const [loading, { refetch, id, inspect, ...data }] = useHostsKpiHosts({
+ filterQuery,
+ endDate: to,
+ startDate: from,
+ skip,
+ });
+
+ return (
+
+ );
+};
+
+export const HostsKpiHosts = React.memo(HostsKpiHostsComponent);
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts
new file mode 100644
index 0000000000000..7754591ab415b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const HOSTS = i18n.translate('xpack.securitySolution.kpiHosts.hosts.title', {
+ defaultMessage: 'Hosts',
+});
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx
deleted file mode 100644
index 7731881df6d2c..0000000000000
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { mockKpiHostsData, mockKpiHostDetailsData } from './mock';
-import React from 'react';
-import { shallow, ShallowWrapper } from 'enzyme';
-import '../../../common/mock/match_media';
-import { KpiHostsComponentBase } from '.';
-import * as statItems from '../../../common/components/stat_items';
-import { kpiHostsMapping } from './kpi_hosts_mapping';
-import { kpiHostDetailsMapping } from './kpi_host_details_mapping';
-
-describe('kpiHostsComponent', () => {
- const ID = 'kpiHost';
- const from = '2019-06-15T06:00:00.000Z';
- const to = '2019-06-18T06:00:00.000Z';
- const narrowDateRange = () => {};
- describe('render', () => {
- test('it should render spinner if it is loading', () => {
- const wrapper: ShallowWrapper = shallow(
-
- );
- expect(wrapper).toMatchSnapshot();
- });
-
- test('it should render KpiHostsData', () => {
- const wrapper: ShallowWrapper = shallow(
-
- );
- expect(wrapper).toMatchSnapshot();
- });
-
- test('it should render KpiHostDetailsData', () => {
- const wrapper: ShallowWrapper = shallow(
-
- );
- expect(wrapper).toMatchSnapshot();
- });
- });
-
- const table = [
- [mockKpiHostsData, kpiHostsMapping] as [typeof mockKpiHostsData, typeof kpiHostsMapping],
- [mockKpiHostDetailsData, kpiHostDetailsMapping] as [
- typeof mockKpiHostDetailsData,
- typeof kpiHostDetailsMapping
- ],
- ];
-
- describe.each(table)(
- 'it should handle KpiHostsProps and KpiHostDetailsProps',
- (data, mapping) => {
- let mockUseKpiMatrixStatus: jest.SpyInstance;
- beforeAll(() => {
- mockUseKpiMatrixStatus = jest.spyOn(statItems, 'useKpiMatrixStatus');
- });
-
- beforeEach(() => {
- shallow(
-
- );
- });
-
- afterEach(() => {
- mockUseKpiMatrixStatus.mockClear();
- });
-
- afterAll(() => {
- mockUseKpiMatrixStatus.mockRestore();
- });
-
- test(`it should apply correct mapping by given data type`, () => {
- expect(mockUseKpiMatrixStatus).toBeCalledWith(mapping, data, ID, from, to, narrowDateRange);
- });
- }
- );
-});
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx
index c39e86591013f..fff4c64900a8b 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx
@@ -4,81 +4,78 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import React from 'react';
-import styled from 'styled-components';
+import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
-import { KpiHostsData, KpiHostDetailsData } from '../../../graphql/types';
-import {
- StatItemsComponent,
- StatItemsProps,
- useKpiMatrixStatus,
-} from '../../../common/components/stat_items';
-import { kpiHostsMapping } from './kpi_hosts_mapping';
-import { kpiHostDetailsMapping } from './kpi_host_details_mapping';
-import { UpdateDateRange } from '../../../common/components/charts/common';
+import { HostsKpiAuthentications } from './authentications';
+import { HostsKpiHosts } from './hosts';
+import { HostsKpiUniqueIps } from './unique_ips';
+import { HostsKpiProps } from './types';
-const kpiWidgetHeight = 247;
-
-interface GenericKpiHostProps {
- from: string;
- id: string;
- loading: boolean;
- to: string;
- narrowDateRange: UpdateDateRange;
-}
-
-interface KpiHostsProps extends GenericKpiHostProps {
- data: KpiHostsData;
-}
-
-interface KpiHostDetailsProps extends GenericKpiHostProps {
- data: KpiHostDetailsData;
-}
-
-const FlexGroupSpinner = styled(EuiFlexGroup)`
- {
- min-height: ${kpiWidgetHeight}px;
- }
-`;
-
-FlexGroupSpinner.displayName = 'FlexGroupSpinner';
-
-export const KpiHostsComponentBase = ({
- data,
- from,
- loading,
- id,
- to,
- narrowDateRange,
-}: KpiHostsProps | KpiHostDetailsProps) => {
- const mappings =
- (data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping;
- const statItemsProps: StatItemsProps[] = useKpiMatrixStatus(
- mappings,
- data,
- id,
- from,
- to,
- narrowDateRange
- );
- return loading ? (
-
-
-
+export const HostsKpiComponent = React.memo(
+ ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => (
+
+
+
+
+
+
+
+
+
-
- ) : (
-
- {statItemsProps.map((mappedStatItemProps, idx) => {
- return ;
- })}
- );
-};
+ )
+);
-KpiHostsComponentBase.displayName = 'KpiHostsComponentBase';
+HostsKpiComponent.displayName = 'HostsKpiComponent';
-export const KpiHostsComponent = React.memo(KpiHostsComponentBase);
+export const HostsDetailsKpiComponent = React.memo(
+ ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => (
+
+
+
+
+
+
+
+
+ )
+);
-KpiHostsComponent.displayName = 'KpiHostsComponent';
+HostsDetailsKpiComponent.displayName = 'HostsDetailsKpiComponent';
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts
deleted file mode 100644
index b3e98b70c4cb0..0000000000000
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import * as i18n from './translations';
-import { StatItems } from '../../../common/components/stat_items';
-import { KpiHostsChartColors } from './types';
-
-export const kpiHostDetailsMapping: Readonly = [
- {
- key: 'authentication',
- index: 0,
- fields: [
- {
- key: 'authSuccess',
- name: i18n.SUCCESS_CHART_LABEL,
- description: i18n.SUCCESS_UNIT_LABEL,
- value: null,
- color: KpiHostsChartColors.authSuccess,
- icon: 'check',
- },
- {
- key: 'authFailure',
- name: i18n.FAIL_CHART_LABEL,
- description: i18n.FAIL_UNIT_LABEL,
- value: null,
- color: KpiHostsChartColors.authFailure,
- icon: 'cross',
- },
- ],
- enableAreaChart: true,
- enableBarChart: true,
- grow: 1,
- description: i18n.USER_AUTHENTICATIONS,
- },
- {
- key: 'uniqueIps',
- index: 1,
- fields: [
- {
- key: 'uniqueSourceIps',
- name: i18n.SOURCE_CHART_LABEL,
- description: i18n.SOURCE_UNIT_LABEL,
- value: null,
- color: KpiHostsChartColors.uniqueSourceIps,
- icon: 'visMapCoordinate',
- },
- {
- key: 'uniqueDestinationIps',
- name: i18n.DESTINATION_CHART_LABEL,
- description: i18n.DESTINATION_UNIT_LABEL,
- value: null,
- color: KpiHostsChartColors.uniqueDestinationIps,
- icon: 'visMapCoordinate',
- },
- ],
- enableAreaChart: true,
- enableBarChart: true,
- grow: 1,
- description: i18n.UNIQUE_IPS,
- },
-];
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts
deleted file mode 100644
index 78a9fd5b84d1f..0000000000000
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts
+++ /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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import * as i18n from './translations';
-import { KpiHostsChartColors } from './types';
-import { StatItems } from '../../../common/components/stat_items';
-
-export const kpiHostsMapping: Readonly = [
- {
- key: 'hosts',
- index: 0,
- fields: [
- {
- key: 'hosts',
- value: null,
- color: KpiHostsChartColors.hosts,
- icon: 'storage',
- },
- ],
- enableAreaChart: true,
- grow: 2,
- description: i18n.HOSTS,
- },
- {
- key: 'authentication',
- index: 1,
- fields: [
- {
- key: 'authSuccess',
- name: i18n.SUCCESS_CHART_LABEL,
- description: i18n.SUCCESS_UNIT_LABEL,
- value: null,
- color: KpiHostsChartColors.authSuccess,
- icon: 'check',
- },
- {
- key: 'authFailure',
- name: i18n.FAIL_CHART_LABEL,
- description: i18n.FAIL_UNIT_LABEL,
- value: null,
- color: KpiHostsChartColors.authFailure,
- icon: 'cross',
- },
- ],
- enableAreaChart: true,
- enableBarChart: true,
- grow: 4,
- description: i18n.USER_AUTHENTICATIONS,
- },
- {
- key: 'uniqueIps',
- index: 2,
- fields: [
- {
- key: 'uniqueSourceIps',
- name: i18n.SOURCE_CHART_LABEL,
- description: i18n.SOURCE_UNIT_LABEL,
- value: null,
- color: KpiHostsChartColors.uniqueSourceIps,
- icon: 'visMapCoordinate',
- },
- {
- key: 'uniqueDestinationIps',
- name: i18n.DESTINATION_CHART_LABEL,
- description: i18n.DESTINATION_UNIT_LABEL,
- value: null,
- color: KpiHostsChartColors.uniqueDestinationIps,
- icon: 'visMapCoordinate',
- },
- ],
- enableAreaChart: true,
- enableBarChart: true,
- grow: 4,
- description: i18n.UNIQUE_IPS,
- },
-];
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx
deleted file mode 100644
index a1d081af20435..0000000000000
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export const mockKpiHostsData = {
- hosts: 986,
- hostsHistogram: [
- {
- x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
- y: 919,
- },
- {
- x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
- y: 82,
- },
- {
- x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
- y: 4,
- },
- ],
- authSuccess: 61,
- authSuccessHistogram: [
- {
- x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
- y: 8,
- },
- {
- x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
- y: 52,
- },
- {
- x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
- y: 1,
- },
- ],
- authFailure: 15722,
- authFailureHistogram: [
- {
- x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
- y: 11731,
- },
- {
- x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
- y: 3979,
- },
- {
- x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
- y: 12,
- },
- ],
- uniqueSourceIps: 1407,
- uniqueSourceIpsHistogram: [
- {
- x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
- y: 1182,
- },
- {
- x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
- y: 364,
- },
- {
- x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
- y: 63,
- },
- ],
- uniqueDestinationIps: 1954,
- uniqueDestinationIpsHistogram: [
- {
- x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
- y: 1809,
- },
- {
- x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
- y: 407,
- },
- {
- x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
- y: 64,
- },
- ],
-};
-export const mockKpiHostDetailsData = {
- authSuccess: 61,
- authSuccessHistogram: [
- {
- x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
- y: 8,
- },
- {
- x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
- y: 52,
- },
- {
- x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
- y: 1,
- },
- ],
- authFailure: 15722,
- authFailureHistogram: [
- {
- x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
- y: 11731,
- },
- {
- x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
- y: 3979,
- },
- {
- x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
- y: 12,
- },
- ],
- uniqueSourceIps: 1407,
- uniqueSourceIpsHistogram: [
- {
- x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
- y: 1182,
- },
- {
- x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
- y: 364,
- },
- {
- x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
- y: 63,
- },
- ],
- uniqueDestinationIps: 1954,
- uniqueDestinationIpsHistogram: [
- {
- x: new Date('2019-05-03T13:00:00.000Z').valueOf(),
- y: 1809,
- },
- {
- x: new Date('2019-05-04T01:00:00.000Z').valueOf(),
- y: 407,
- },
- {
- x: new Date('2019-05-04T13:00:00.000Z').valueOf(),
- y: 64,
- },
- ],
-};
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts
index fd48368124795..7cdbb16ee348c 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts
@@ -4,9 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export enum KpiHostsChartColors {
- authSuccess = '#54B399',
- authFailure = '#E7664C',
+import { UpdateDateRange } from '../../../common/components/charts/common';
+import { GlobalTimeArgs } from '../../../common/containers/use_global_time';
+
+export interface HostsKpiProps {
+ filterQuery: string;
+ from: string;
+ to: string;
+ narrowDateRange: UpdateDateRange;
+ setQuery: GlobalTimeArgs['setQuery'];
+ skip: boolean;
+}
+
+export enum HostsKpiChartColors {
+ authenticationsSuccess = '#54B399',
+ authenticationsFailure = '#E7664C',
uniqueSourceIps = '#D36086',
uniqueDestinationIps = '#9170B8',
hosts = '#6092C0',
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx
new file mode 100644
index 0000000000000..c6f430faacccb
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { StatItems } from '../../../../common/components/stat_items';
+import { useHostsKpiUniqueIps } from '../../../containers/kpi_hosts/unique_ips';
+import { HostsKpiBaseComponentManage } from '../common';
+import { HostsKpiProps, HostsKpiChartColors } from '../types';
+import * as i18n from './translations';
+
+export const fieldsMapping: Readonly = [
+ {
+ key: 'uniqueIps',
+ fields: [
+ {
+ key: 'uniqueSourceIps',
+ name: i18n.SOURCE_CHART_LABEL,
+ description: i18n.SOURCE_UNIT_LABEL,
+ value: null,
+ color: HostsKpiChartColors.uniqueSourceIps,
+ icon: 'visMapCoordinate',
+ },
+ {
+ key: 'uniqueDestinationIps',
+ name: i18n.DESTINATION_CHART_LABEL,
+ description: i18n.DESTINATION_UNIT_LABEL,
+ value: null,
+ color: HostsKpiChartColors.uniqueDestinationIps,
+ icon: 'visMapCoordinate',
+ },
+ ],
+ enableAreaChart: true,
+ enableBarChart: true,
+ description: i18n.UNIQUE_IPS,
+ },
+];
+
+const HostsKpiUniqueIpsComponent: React.FC = ({
+ filterQuery,
+ from,
+ to,
+ narrowDateRange,
+ setQuery,
+ skip,
+}) => {
+ const [loading, { refetch, id, inspect, ...data }] = useHostsKpiUniqueIps({
+ filterQuery,
+ endDate: to,
+ startDate: from,
+ skip,
+ });
+
+ return (
+
+ );
+};
+
+export const HostsKpiUniqueIps = React.memo(HostsKpiUniqueIpsComponent);
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts
new file mode 100644
index 0000000000000..6cc651880be7b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const UNIQUE_IPS = i18n.translate('xpack.securitySolution.kpiHosts.uniqueIps.title', {
+ defaultMessage: 'Unique IPs',
+});
+
+export const SOURCE_UNIT_LABEL = i18n.translate(
+ 'xpack.securitySolution.kpiHosts.uniqueIps.sourceUnitLabel',
+ {
+ defaultMessage: 'source',
+ }
+);
+
+export const DESTINATION_UNIT_LABEL = i18n.translate(
+ 'xpack.securitySolution.kpiHosts.uniqueIps.destinationUnitLabel',
+ {
+ defaultMessage: 'destination',
+ }
+);
+
+export const SOURCE_CHART_LABEL = i18n.translate(
+ 'xpack.securitySolution.kpiHosts.uniqueIps.sourceChartLabel',
+ {
+ defaultMessage: 'Src.',
+ }
+);
+
+export const DESTINATION_CHART_LABEL = i18n.translate(
+ 'xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel',
+ {
+ defaultMessage: 'Dest.',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx
new file mode 100644
index 0000000000000..0d90b73e0a584
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx
@@ -0,0 +1,170 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import deepEqual from 'fast-deep-equal';
+import { noop } from 'lodash/fp';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
+import { inputsModel } from '../../../../common/store';
+import { createFilter } from '../../../../common/containers/helpers';
+import { useKibana } from '../../../../common/lib/kibana';
+import {
+ HostsKpiQueries,
+ HostsKpiAuthenticationsRequestOptions,
+ HostsKpiAuthenticationsStrategyResponse,
+} from '../../../../../common/search_strategy';
+import { ESTermQuery } from '../../../../../common/typed_json';
+
+import * as i18n from './translations';
+import { AbortError } from '../../../../../../../../src/plugins/data/common';
+import { getInspectResponse } from '../../../../helpers';
+import { InspectResponse } from '../../../../types';
+
+const ID = 'hostsKpiAuthenticationsQuery';
+
+export interface HostsKpiAuthenticationsArgs
+ extends Omit {
+ id: string;
+ inspect: InspectResponse;
+ isInspected: boolean;
+ refetch: inputsModel.Refetch;
+}
+
+interface UseHostsKpiAuthentications {
+ filterQuery?: ESTermQuery | string;
+ endDate: string;
+ skip?: boolean;
+ startDate: string;
+}
+
+export const useHostsKpiAuthentications = ({
+ filterQuery,
+ endDate,
+ skip = false,
+ startDate,
+}: UseHostsKpiAuthentications): [boolean, HostsKpiAuthenticationsArgs] => {
+ const { data, notifications, uiSettings } = useKibana().services;
+ const refetch = useRef(noop);
+ const abortCtrl = useRef(new AbortController());
+ const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
+ const [loading, setLoading] = useState(false);
+ const [hostsKpiAuthenticationsRequest, setHostsKpiAuthenticationsRequest] = useState<
+ HostsKpiAuthenticationsRequestOptions
+ >({
+ defaultIndex,
+ factoryQueryType: HostsKpiQueries.kpiAuthentications,
+ filterQuery: createFilter(filterQuery),
+ id: ID,
+ timerange: {
+ interval: '12h',
+ from: startDate,
+ to: endDate,
+ },
+ });
+
+ const [hostsKpiAuthenticationsResponse, setHostsKpiAuthenticationsResponse] = useState<
+ HostsKpiAuthenticationsArgs
+ >({
+ authenticationsSuccess: 0,
+ authenticationsSuccessHistogram: [],
+ authenticationsFailure: 0,
+ authenticationsFailureHistogram: [],
+ id: ID,
+ inspect: {
+ dsl: [],
+ response: [],
+ },
+ isInspected: false,
+ refetch: refetch.current,
+ });
+
+ const hostsKpiAuthenticationsSearch = useCallback(
+ (request: HostsKpiAuthenticationsRequestOptions) => {
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+
+ const searchSubscription$ = data.search
+ .search(
+ request,
+ {
+ strategy: 'securitySolutionSearchStrategy',
+ abortSignal: abortCtrl.current.signal,
+ }
+ )
+ .subscribe({
+ next: (response) => {
+ if (!response.isPartial && !response.isRunning) {
+ if (!didCancel) {
+ setLoading(false);
+ setHostsKpiAuthenticationsResponse((prevResponse) => ({
+ ...prevResponse,
+ authenticationsSuccess: response.authenticationsSuccess,
+ authenticationsSuccessHistogram: response.authenticationsSuccessHistogram,
+ authenticationsFailure: response.authenticationsFailure,
+ authenticationsFailureHistogram: response.authenticationsFailureHistogram,
+ inspect: getInspectResponse(response, prevResponse.inspect),
+ refetch: refetch.current,
+ }));
+ }
+ searchSubscription$.unsubscribe();
+ } else if (response.isPartial && !response.isRunning) {
+ if (!didCancel) {
+ setLoading(false);
+ }
+ // TODO: Make response error status clearer
+ notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_AUTHENTICATIONS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({
+ title: i18n.FAIL_HOSTS_KPI_AUTHENTICATIONS,
+ text: msg.message,
+ });
+ }
+ },
+ });
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ refetch.current = asyncSearch;
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, notifications.toasts]
+ );
+
+ useEffect(() => {
+ setHostsKpiAuthenticationsRequest((prevRequest) => {
+ const myRequest = {
+ ...prevRequest,
+ defaultIndex,
+ filterQuery: createFilter(filterQuery),
+ timerange: {
+ interval: '12h',
+ from: startDate,
+ to: endDate,
+ },
+ };
+ if (!skip && !deepEqual(prevRequest, myRequest)) {
+ return myRequest;
+ }
+ return prevRequest;
+ });
+ }, [defaultIndex, endDate, filterQuery, skip, startDate]);
+
+ useEffect(() => {
+ hostsKpiAuthenticationsSearch(hostsKpiAuthenticationsRequest);
+ }, [hostsKpiAuthenticationsRequest, hostsKpiAuthenticationsSearch]);
+
+ return [loading, hostsKpiAuthenticationsResponse];
+};
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts
new file mode 100644
index 0000000000000..fb5af83d0acef
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const ERROR_HOSTS_KPI_AUTHENTICATIONS = i18n.translate(
+ 'xpack.securitySolution.hostsKpiAuthentications.errorSearchDescription',
+ {
+ defaultMessage: `An error has occurred on hosts kpi authentications search`,
+ }
+);
+
+export const FAIL_HOSTS_KPI_AUTHENTICATIONS = i18n.translate(
+ 'xpack.securitySolution.hostsKpiAuthentications.failSearchDescription',
+ {
+ defaultMessage: `Failed to run search on hosts kpi authentications`,
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx
new file mode 100644
index 0000000000000..190ce1aa7eae1
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx
@@ -0,0 +1,158 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import deepEqual from 'fast-deep-equal';
+import { noop } from 'lodash/fp';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
+import { inputsModel } from '../../../../common/store';
+import { createFilter } from '../../../../common/containers/helpers';
+import { useKibana } from '../../../../common/lib/kibana';
+import {
+ HostsKpiQueries,
+ HostsKpiHostsRequestOptions,
+ HostsKpiHostsStrategyResponse,
+} from '../../../../../common/search_strategy';
+import { ESTermQuery } from '../../../../../common/typed_json';
+
+import * as i18n from './translations';
+import { AbortError } from '../../../../../../../../src/plugins/data/common';
+import { getInspectResponse } from '../../../../helpers';
+import { InspectResponse } from '../../../../types';
+
+const ID = 'hostsKpiHostsQuery';
+
+export interface HostsKpiHostsArgs extends Omit {
+ id: string;
+ inspect: InspectResponse;
+ isInspected: boolean;
+ refetch: inputsModel.Refetch;
+}
+
+interface UseHostsKpiHosts {
+ filterQuery?: ESTermQuery | string;
+ endDate: string;
+ skip?: boolean;
+ startDate: string;
+}
+
+export const useHostsKpiHosts = ({
+ filterQuery,
+ endDate,
+ skip = false,
+ startDate,
+}: UseHostsKpiHosts): [boolean, HostsKpiHostsArgs] => {
+ const { data, notifications, uiSettings } = useKibana().services;
+ const refetch = useRef(noop);
+ const abortCtrl = useRef(new AbortController());
+ const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
+ const [loading, setLoading] = useState(false);
+ const [hostsKpiHostsRequest, setHostsKpiHostsRequest] = useState({
+ defaultIndex,
+ factoryQueryType: HostsKpiQueries.kpiHosts,
+ filterQuery: createFilter(filterQuery),
+ id: ID,
+ timerange: {
+ interval: '12h',
+ from: startDate,
+ to: endDate,
+ },
+ });
+
+ const [hostsKpiHostsResponse, setHostsKpiHostsResponse] = useState({
+ hosts: 0,
+ hostsHistogram: [],
+ id: ID,
+ inspect: {
+ dsl: [],
+ response: [],
+ },
+ isInspected: false,
+ refetch: refetch.current,
+ });
+
+ const hostsKpiHostsSearch = useCallback(
+ (request: HostsKpiHostsRequestOptions) => {
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+
+ const searchSubscription$ = data.search
+ .search(request, {
+ strategy: 'securitySolutionSearchStrategy',
+ abortSignal: abortCtrl.current.signal,
+ })
+ .subscribe({
+ next: (response) => {
+ if (!response.isPartial && !response.isRunning) {
+ if (!didCancel) {
+ setLoading(false);
+ setHostsKpiHostsResponse((prevResponse) => ({
+ ...prevResponse,
+ hosts: response.hosts,
+ hostsHistogram: response.hostsHistogram,
+ inspect: getInspectResponse(response, prevResponse.inspect),
+ refetch: refetch.current,
+ }));
+ }
+ searchSubscription$.unsubscribe();
+ } else if (response.isPartial && !response.isRunning) {
+ if (!didCancel) {
+ setLoading(false);
+ }
+ // TODO: Make response error status clearer
+ notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_HOSTS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({
+ title: i18n.FAIL_HOSTS_KPI_HOSTS,
+ text: msg.message,
+ });
+ }
+ },
+ });
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ refetch.current = asyncSearch;
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, notifications.toasts]
+ );
+
+ useEffect(() => {
+ setHostsKpiHostsRequest((prevRequest) => {
+ const myRequest = {
+ ...prevRequest,
+ defaultIndex,
+ filterQuery: createFilter(filterQuery),
+ timerange: {
+ interval: '12h',
+ from: startDate,
+ to: endDate,
+ },
+ };
+ if (!skip && !deepEqual(prevRequest, myRequest)) {
+ return myRequest;
+ }
+ return prevRequest;
+ });
+ }, [defaultIndex, endDate, filterQuery, skip, startDate]);
+
+ useEffect(() => {
+ hostsKpiHostsSearch(hostsKpiHostsRequest);
+ }, [hostsKpiHostsRequest, hostsKpiHostsSearch]);
+
+ return [loading, hostsKpiHostsResponse];
+};
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts
new file mode 100644
index 0000000000000..2a15563a4b1cd
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const ERROR_HOSTS_KPI_HOSTS = i18n.translate(
+ 'xpack.securitySolution.hostsKpiHosts.errorSearchDescription',
+ {
+ defaultMessage: `An error has occurred on hosts kpi hosts search`,
+ }
+);
+
+export const FAIL_HOSTS_KPI_HOSTS = i18n.translate(
+ 'xpack.securitySolution.hostsKpiHosts.failSearchDescription',
+ {
+ defaultMessage: `Failed to run search on hosts kpi hosts`,
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx
index 1a6df58f04597..c0ae767219aae 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx
@@ -4,82 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getOr } from 'lodash/fp';
-import React from 'react';
-import { Query } from 'react-apollo';
-import { connect, ConnectedProps } from 'react-redux';
-
-import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
-import { GetKpiHostsQuery, KpiHostsData } from '../../../graphql/types';
-import { inputsModel, inputsSelectors, State } from '../../../common/store';
-import { useUiSetting } from '../../../common/lib/kibana';
-import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers';
-import { QueryTemplateProps } from '../../../common/containers/query_template';
-
-import { kpiHostsQuery } from './index.gql_query';
-
-const ID = 'kpiHostsQuery';
-
-export interface KpiHostsArgs {
- id: string;
- inspect: inputsModel.InspectQuery;
- kpiHosts: KpiHostsData;
- loading: boolean;
- refetch: inputsModel.Refetch;
-}
-
-export interface KpiHostsProps extends QueryTemplateProps {
- children: (args: KpiHostsArgs) => React.ReactNode;
-}
-
-const KpiHostsComponentQuery = React.memo(
- ({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => (
-
- query={kpiHostsQuery}
- fetchPolicy={getDefaultFetchPolicy()}
- notifyOnNetworkStatusChange
- skip={skip}
- variables={{
- sourceId,
- timerange: {
- interval: '12h',
- from: startDate!,
- to: endDate!,
- },
- filterQuery: createFilter(filterQuery),
- defaultIndex: useUiSetting(DEFAULT_INDEX_KEY),
- inspect: isInspected,
- }}
- >
- {({ data, loading, refetch }) => {
- const kpiHosts = getOr({}, `source.KpiHosts`, data);
- return children({
- id,
- inspect: getOr(null, 'source.KpiHosts.inspect', data),
- kpiHosts,
- loading,
- refetch,
- });
- }}
-
- )
-);
-
-KpiHostsComponentQuery.displayName = 'KpiHostsComponentQuery';
-
-const makeMapStateToProps = () => {
- const getQuery = inputsSelectors.globalQueryByIdSelector();
- const mapStateToProps = (state: State, { id = ID }: KpiHostsProps) => {
- const { isInspected } = getQuery(state, id);
- return {
- isInspected,
- };
- };
- return mapStateToProps;
-};
-
-const connector = connect(makeMapStateToProps);
-
-type PropsFromRedux = ConnectedProps;
-
-export const KpiHostsQuery = connector(KpiHostsComponentQuery);
+export * from './authentications';
+export * from './hosts';
+export * from './unique_ips';
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx
new file mode 100644
index 0000000000000..ac5cc12807f00
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx
@@ -0,0 +1,167 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import deepEqual from 'fast-deep-equal';
+import { noop } from 'lodash/fp';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
+import { inputsModel } from '../../../../common/store';
+import { createFilter } from '../../../../common/containers/helpers';
+import { useKibana } from '../../../../common/lib/kibana';
+import {
+ HostsKpiQueries,
+ HostsKpiUniqueIpsRequestOptions,
+ HostsKpiUniqueIpsStrategyResponse,
+} from '../../../../../common/search_strategy';
+import { ESTermQuery } from '../../../../../common/typed_json';
+
+import * as i18n from './translations';
+import { AbortError } from '../../../../../../../../src/plugins/data/common';
+import { getInspectResponse } from '../../../../helpers';
+import { InspectResponse } from '../../../../types';
+
+const ID = 'hostsKpiUniqueIpsQuery';
+
+export interface HostsKpiUniqueIpsArgs
+ extends Omit {
+ id: string;
+ inspect: InspectResponse;
+ isInspected: boolean;
+ refetch: inputsModel.Refetch;
+}
+
+interface UseHostsKpiUniqueIps {
+ filterQuery?: ESTermQuery | string;
+ endDate: string;
+ skip?: boolean;
+ startDate: string;
+}
+
+export const useHostsKpiUniqueIps = ({
+ filterQuery,
+ endDate,
+ skip = false,
+ startDate,
+}: UseHostsKpiUniqueIps): [boolean, HostsKpiUniqueIpsArgs] => {
+ const { data, notifications, uiSettings } = useKibana().services;
+ const refetch = useRef(noop);
+ const abortCtrl = useRef(new AbortController());
+ const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
+ const [loading, setLoading] = useState(false);
+ const [hostsKpiUniqueIpsRequest, setHostsKpiUniqueIpsRequest] = useState<
+ HostsKpiUniqueIpsRequestOptions
+ >({
+ defaultIndex,
+ factoryQueryType: HostsKpiQueries.kpiUniqueIps,
+ filterQuery: createFilter(filterQuery),
+ id: ID,
+ timerange: {
+ interval: '12h',
+ from: startDate,
+ to: endDate,
+ },
+ });
+
+ const [hostsKpiUniqueIpsResponse, setHostsKpiUniqueIpsResponse] = useState(
+ {
+ uniqueSourceIps: 0,
+ uniqueSourceIpsHistogram: [],
+ uniqueDestinationIps: 0,
+ uniqueDestinationIpsHistogram: [],
+ id: ID,
+ inspect: {
+ dsl: [],
+ response: [],
+ },
+ isInspected: false,
+ refetch: refetch.current,
+ }
+ );
+
+ const hostsKpiUniqueIpsSearch = useCallback(
+ (request: HostsKpiUniqueIpsRequestOptions) => {
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+
+ const searchSubscription$ = data.search
+ .search(request, {
+ strategy: 'securitySolutionSearchStrategy',
+ abortSignal: abortCtrl.current.signal,
+ })
+ .subscribe({
+ next: (response) => {
+ if (!response.isPartial && !response.isRunning) {
+ if (!didCancel) {
+ setLoading(false);
+ setHostsKpiUniqueIpsResponse((prevResponse) => ({
+ ...prevResponse,
+ uniqueSourceIps: response.uniqueSourceIps,
+ uniqueSourceIpsHistogram: response.uniqueSourceIpsHistogram,
+ uniqueDestinationIps: response.uniqueDestinationIps,
+ uniqueDestinationIpsHistogram: response.uniqueDestinationIpsHistogram,
+ inspect: getInspectResponse(response, prevResponse.inspect),
+ refetch: refetch.current,
+ }));
+ }
+ searchSubscription$.unsubscribe();
+ } else if (response.isPartial && !response.isRunning) {
+ if (!didCancel) {
+ setLoading(false);
+ }
+ // TODO: Make response error status clearer
+ notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_UNIQUE_IPS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({
+ title: i18n.FAIL_HOSTS_KPI_UNIQUE_IPS,
+ text: msg.message,
+ });
+ }
+ },
+ });
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ refetch.current = asyncSearch;
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, notifications.toasts]
+ );
+
+ useEffect(() => {
+ setHostsKpiUniqueIpsRequest((prevRequest) => {
+ const myRequest = {
+ ...prevRequest,
+ defaultIndex,
+ filterQuery: createFilter(filterQuery),
+ timerange: {
+ interval: '12h',
+ from: startDate,
+ to: endDate,
+ },
+ };
+ if (!skip && !deepEqual(prevRequest, myRequest)) {
+ return myRequest;
+ }
+ return prevRequest;
+ });
+ }, [defaultIndex, endDate, filterQuery, skip, startDate]);
+
+ useEffect(() => {
+ hostsKpiUniqueIpsSearch(hostsKpiUniqueIpsRequest);
+ }, [hostsKpiUniqueIpsRequest, hostsKpiUniqueIpsSearch]);
+
+ return [loading, hostsKpiUniqueIpsResponse];
+};
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts
new file mode 100644
index 0000000000000..2d1574b080ac1
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const ERROR_HOSTS_KPI_UNIQUE_IPS = i18n.translate(
+ 'xpack.securitySolution.hostsKpiUniqueIps.errorSearchDescription',
+ {
+ defaultMessage: `An error has occurred on hosts kpi unique ips search`,
+ }
+);
+
+export const FAIL_HOSTS_KPI_UNIQUE_IPS = i18n.translate(
+ 'xpack.securitySolution.hostsKpiUniqueIps.failSearchDescription',
+ {
+ defaultMessage: `Failed to run search on hosts kpi unique ips`,
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
index d8cd59f119d52..57e1b128ce64d 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
@@ -21,13 +21,12 @@ import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml
import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities';
import { scoreIntervalToDateTime } from '../../../common/components/ml/score/score_interval_to_datetime';
import { SiemNavigation } from '../../../common/components/navigation';
-import { KpiHostsComponent } from '../../components/kpi_hosts';
+import { HostsDetailsKpiComponent } from '../../components/kpi_hosts';
import { HostOverview } from '../../../overview/components/host_overview';
import { manageQuery } from '../../../common/components/page/manage_query';
import { SiemSearchBar } from '../../../common/components/search_bar';
import { WrapperPage } from '../../../common/components/wrapper_page';
import { HostOverviewByNameQuery } from '../../containers/hosts/details';
-import { KpiHostDetailsQuery } from '../../containers/kpi_host_details';
import { useGlobalTime } from '../../../common/containers/use_global_time';
import { useWithSource } from '../../../common/containers/source';
import { LastEventIndexKey } from '../../../graphql/types';
@@ -54,7 +53,6 @@ import { TimelineId } from '../../../../common/types/timeline';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
const HostOverviewManage = manageQuery(HostOverview);
-const KpiHostDetailsManage = manageQuery(KpiHostsComponent);
const HostDetailsComponent = React.memo(
({
@@ -160,27 +158,14 @@ const HostDetailsComponent = React.memo(
-
- {({ kpiHostDetails, id, inspect, loading, refetch }) => (
-
- )}
-
+ />
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
index ef88c255b1735..4b8e3cc6987ac 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
@@ -17,11 +17,9 @@ import { HeaderPage } from '../../common/components/header_page';
import { LastEventTime } from '../../common/components/last_event_time';
import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions';
import { SiemNavigation } from '../../common/components/navigation';
-import { KpiHostsComponent } from '../components/kpi_hosts';
-import { manageQuery } from '../../common/components/page/manage_query';
+import { HostsKpiComponent } from '../components/kpi_hosts';
import { SiemSearchBar } from '../../common/components/search_bar';
import { WrapperPage } from '../../common/components/wrapper_page';
-import { KpiHostsQuery } from '../containers/kpi_hosts';
import { useFullScreen } from '../../common/containers/use_full_screen';
import { useGlobalTime } from '../../common/containers/use_global_time';
import { useWithSource } from '../../common/containers/source';
@@ -49,8 +47,6 @@ import { timelineSelectors } from '../../timelines/store/timeline';
import { timelineDefaults } from '../../timelines/store/timeline/defaults';
import { TimelineModel } from '../../timelines/store/timeline/model';
-const KpiHostsComponentManage = manageQuery(KpiHostsComponent);
-
export const HostsComponent = React.memo(
({ filters, graphEventId, query, setAbsoluteRangeDatePicker, hostsPagePath }) => {
const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime();
@@ -109,27 +105,14 @@ export const HostsComponent = React.memo(
title={i18n.PAGE_TITLE}
/>
-
- {({ kpiHosts, loading, id, inspect, refetch }) => (
-
- )}
-
+ narrowDateRange={narrowDateRange}
+ />
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx
index 65ddb9305f607..d3fc68874ce91 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx
@@ -16,7 +16,7 @@ import {
MatrixHistogramConfigs,
} from '../../../common/components/matrix_histogram/types';
import { MatrixHistogram } from '../../../common/components/matrix_histogram';
-import { KpiHostsChartColors } from '../../components/kpi_hosts/types';
+import { HostsKpiChartColors } from '../../components/kpi_hosts/types';
import * as i18n from '../translations';
import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution';
@@ -24,7 +24,7 @@ const AuthenticationTableManage = manageQuery(AuthenticationTable);
const ID = 'authenticationsHistogramQuery';
-const authStackByOptions: MatrixHistogramOption[] = [
+const authenticationsStackByOptions: MatrixHistogramOption[] = [
{
text: 'event.outcome',
value: 'event.outcome',
@@ -32,31 +32,32 @@ const authStackByOptions: MatrixHistogramOption[] = [
];
const DEFAULT_STACK_BY = 'event.outcome';
-enum AuthMatrixDataGroup {
- authSuccess = 'success',
- authFailure = 'failure',
+enum AuthenticationsMatrixDataGroup {
+ authenticationsSuccess = 'success',
+ authenticationsFailure = 'failure',
}
-export const authMatrixDataMappingFields: MatrixHistogramMappingTypes = {
- [AuthMatrixDataGroup.authSuccess]: {
- key: AuthMatrixDataGroup.authSuccess,
+export const authenticationsMatrixDataMappingFields: MatrixHistogramMappingTypes = {
+ [AuthenticationsMatrixDataGroup.authenticationsSuccess]: {
+ key: AuthenticationsMatrixDataGroup.authenticationsSuccess,
value: null,
- color: KpiHostsChartColors.authSuccess,
+ color: HostsKpiChartColors.authenticationsSuccess,
},
- [AuthMatrixDataGroup.authFailure]: {
- key: AuthMatrixDataGroup.authFailure,
+ [AuthenticationsMatrixDataGroup.authenticationsFailure]: {
+ key: AuthenticationsMatrixDataGroup.authenticationsFailure,
value: null,
- color: KpiHostsChartColors.authFailure,
+ color: HostsKpiChartColors.authenticationsFailure,
},
};
const histogramConfigs: MatrixHistogramConfigs = {
defaultStackByOption:
- authStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? authStackByOptions[0],
+ authenticationsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ??
+ authenticationsStackByOptions[0],
errorMessage: i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA,
histogramType: MatrixHistogramType.authentications,
- mapping: authMatrixDataMappingFields,
- stackByOptions: authStackByOptions,
+ mapping: authenticationsMatrixDataMappingFields,
+ stackByOptions: authenticationsStackByOptions,
title: i18n.NAVIGATION_AUTHENTICATIONS_TITLE,
};
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap
index 49562162e94a8..a03d7c2317517 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`KpiNetwork Component rendering it renders the default widget 1`] = `
-;
data: NetworkKpiStrategyResponse;
loading?: boolean;
@@ -64,6 +64,6 @@ export const KpiNetworkBaseComponent = React.memo<{
);
});
-KpiNetworkBaseComponent.displayName = 'KpiNetworkBaseComponent';
+NetworkKpiBaseComponent.displayName = 'NetworkKpiBaseComponent';
-export const KpiNetworkBaseComponentManage = manageQuery(KpiNetworkBaseComponent);
+export const NetworkKpiBaseComponentManage = manageQuery(NetworkKpiBaseComponent);
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx
index 889f3dacc2d98..0f13b0e8f874e 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx
@@ -8,7 +8,7 @@ import React from 'react';
import { StatItems } from '../../../../common/components/stat_items';
import { useNetworkKpiDns } from '../../../containers/kpi_network/dns';
-import { KpiNetworkBaseComponentManage } from '../common';
+import { NetworkKpiBaseComponentManage } from '../common';
import { NetworkKpiProps } from '../types';
import * as i18n from './translations';
@@ -41,7 +41,7 @@ const NetworkKpiDnsComponent: React.FC = ({
});
return (
- {
+describe('NetworkKpiComponent', () => {
const state: State = mockGlobalState;
const props = {
from: '2019-06-15T06:00:00.000Z',
@@ -53,11 +53,11 @@ describe('KpiNetwork Component', () => {
test('it renders the default widget', () => {
const wrapper = shallow(
-
+
);
- expect(wrapper.find('KpiNetworkComponent')).toMatchSnapshot();
+ expect(wrapper.find('NetworkKpiComponent')).toMatchSnapshot();
});
});
});
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx
index 674e592940fa6..95534e1a61988 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx
@@ -14,7 +14,7 @@ import { NetworkKpiUniqueFlows } from './unique_flows';
import { NetworkKpiUniquePrivateIps } from './unique_private_ips';
import { NetworkKpiProps } from './types';
-export const KpiNetworkComponent = React.memo(
+export const NetworkKpiComponent = React.memo(
({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => (
@@ -78,4 +78,4 @@ export const KpiNetworkComponent = React.memo(
)
);
-KpiNetworkComponent.displayName = 'KpiNetworkComponent';
+NetworkKpiComponent.displayName = 'NetworkKpiComponent';
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx
index 3ee2acf1a115c..18217e41f2a27 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx
@@ -9,7 +9,7 @@ import { euiPaletteColorBlind } from '@elastic/eui';
import { StatItems } from '../../../../common/components/stat_items';
import { useNetworkKpiNetworkEvents } from '../../../containers/kpi_network/network_events';
-import { KpiNetworkBaseComponentManage } from '../common';
+import { NetworkKpiBaseComponentManage } from '../common';
import { NetworkKpiProps } from '../types';
import * as i18n from './translations';
@@ -46,7 +46,7 @@ const NetworkKpiNetworkEventsComponent: React.FC = ({
});
return (
- = ({
});
return (
- = ({
});
return (
- = ({
});
return (
- (
- > = {
@@ -32,7 +32,7 @@ export const buildQuery = ({
defaultIndex,
docValueFields,
}: HostAuthenticationsRequestOptions) => {
- const esFields = reduceFields(authenticationFields, { ...hostFieldsMap, ...sourceFieldsMap });
+ const esFields = reduceFields(authenticationsFields, { ...hostFieldsMap, ...sourceFieldsMap });
const filter = [
...createQueryFilterClauses(filterQuery),
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts
index d61914fda7d06..ce8900a578102 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts
@@ -15,7 +15,7 @@ import {
StrategyResponseType,
} from '../../../../../../common/search_strategy/security_solution';
-export const authenticationFields = [
+export const authenticationsFields = [
'_id',
'failures',
'successes',
@@ -31,7 +31,7 @@ export const authenticationFields = [
];
export const formatAuthenticationData = (
- fields: readonly string[] = authenticationFields,
+ fields: readonly string[] = authenticationsFields,
hit: AuthenticationHit,
fieldMap: Readonly>
): AuthenticationsEdges =>
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx
index a43f53880587a..e09d8de7ba945 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx
@@ -20,7 +20,7 @@ import {
import { inspectStringifyObject } from '../../../../../utils/build_query';
import { SecuritySolutionFactory } from '../../types';
import { auditdFieldsMap, buildQuery as buildAuthenticationQuery } from './dsl/query.dsl';
-import { authenticationFields, formatAuthenticationData, getHits } from './helpers';
+import { authenticationsFields, formatAuthenticationData, getHits } from './helpers';
export const authentications: SecuritySolutionFactory = {
buildDsl: (options: HostAuthenticationsRequestOptions) => {
@@ -40,7 +40,7 @@ export const authentications: SecuritySolutionFactory
- formatAuthenticationData(authenticationFields, hit, auditdFieldsMap)
+ formatAuthenticationData(authenticationsFields, hit, auditdFieldsMap)
);
const edges = authenticationEdges.splice(cursorStart, querySize - cursorStart);
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts
index edcba88a0cd89..44c55ab6e7c9d 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts
@@ -5,13 +5,16 @@
*/
import { hostsFactory } from '.';
-import { HostsQueries } from '../../../../../common/search_strategy';
+import { HostsQueries, HostsKpiQueries } from '../../../../../common/search_strategy';
import { allHosts } from './all';
import { hostDetails } from './details';
import { hostOverview } from './overview';
import { firstLastSeenHost } from './last_first_seen';
import { uncommonProcesses } from './uncommon_processes';
import { authentications } from './authentications';
+import { hostsKpiAuthentications } from './kpi/authentications';
+import { hostsKpiHosts } from './kpi/hosts';
+import { hostsKpiUniqueIps } from './kpi/unique_ips';
jest.mock('./all');
jest.mock('./details');
@@ -19,6 +22,9 @@ jest.mock('./overview');
jest.mock('./last_first_seen');
jest.mock('./uncommon_processes');
jest.mock('./authentications');
+jest.mock('./kpi/authentications');
+jest.mock('./kpi/hosts');
+jest.mock('./kpi/unique_ips');
describe('hostsFactory', () => {
test('should include correct apis', () => {
@@ -29,6 +35,9 @@ describe('hostsFactory', () => {
[HostsQueries.firstLastSeen]: firstLastSeenHost,
[HostsQueries.uncommonProcesses]: uncommonProcesses,
[HostsQueries.authentications]: authentications,
+ [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications,
+ [HostsKpiQueries.kpiHosts]: hostsKpiHosts,
+ [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps,
};
expect(hostsFactory).toEqual(expectedHostsFactory);
});
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts
index 85619cfec62ce..ad6a6182d331b 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts
@@ -7,6 +7,7 @@
import {
FactoryQueryTypes,
HostsQueries,
+ HostsKpiQueries,
} from '../../../../../common/search_strategy/security_solution';
import { SecuritySolutionFactory } from '../types';
@@ -16,12 +17,21 @@ import { hostOverview } from './overview';
import { firstLastSeenHost } from './last_first_seen';
import { uncommonProcesses } from './uncommon_processes';
import { authentications } from './authentications';
+import { hostsKpiAuthentications } from './kpi/authentications';
+import { hostsKpiHosts } from './kpi/hosts';
+import { hostsKpiUniqueIps } from './kpi/unique_ips';
-export const hostsFactory: Record> = {
+export const hostsFactory: Record<
+ HostsQueries | HostsKpiQueries,
+ SecuritySolutionFactory
+> = {
[HostsQueries.details]: hostDetails,
[HostsQueries.hosts]: allHosts,
[HostsQueries.overview]: hostOverview,
[HostsQueries.firstLastSeen]: firstLastSeenHost,
[HostsQueries.uncommonProcesses]: uncommonProcesses,
[HostsQueries.authentications]: authentications,
+ [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications,
+ [HostsKpiQueries.kpiHosts]: hostsKpiHosts,
+ [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps,
};
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts
new file mode 100644
index 0000000000000..513e361b5be05
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ HostsKpiHistogram,
+ HostsKpiAuthenticationsHistogramCount,
+ HostsKpiHistogramData,
+} from '../../../../../../../common/search_strategy';
+
+export const formatAuthenticationsHistogramData = (
+ data: Array>
+): HostsKpiHistogramData[] | null =>
+ data && data.length > 0
+ ? data.map(({ key, count }) => ({
+ x: key,
+ y: count.doc_count,
+ }))
+ : null;
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts
new file mode 100644
index 0000000000000..bafc9a3accc6e
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getOr } from 'lodash/fp';
+
+import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common';
+import {
+ HostsKpiQueries,
+ HostsKpiAuthenticationsStrategyResponse,
+ HostsKpiAuthenticationsRequestOptions,
+} from '../../../../../../../common/search_strategy/security_solution/hosts';
+import { inspectStringifyObject } from '../../../../../../utils/build_query';
+import { SecuritySolutionFactory } from '../../../types';
+import { buildHostsKpiAuthenticationsQuery } from './query.hosts_kpi_authentications.dsl';
+import { formatAuthenticationsHistogramData } from './helpers';
+
+export const hostsKpiAuthentications: SecuritySolutionFactory = {
+ buildDsl: (options: HostsKpiAuthenticationsRequestOptions) =>
+ buildHostsKpiAuthenticationsQuery(options),
+ parse: async (
+ options: HostsKpiAuthenticationsRequestOptions,
+ response: IEsSearchResponse
+ ): Promise => {
+ const inspect = {
+ dsl: [inspectStringifyObject(buildHostsKpiAuthenticationsQuery(options))],
+ };
+
+ const authenticationsSuccessHistogram = getOr(
+ null,
+ 'aggregations.authentication_success_histogram.buckets',
+ response.rawResponse
+ );
+ const authenticationsFailureHistogram = getOr(
+ null,
+ 'aggregations.authentication_failure_histogram.buckets',
+ response.rawResponse
+ );
+
+ return {
+ ...response,
+ inspect,
+ authenticationsSuccess: getOr(
+ null,
+ 'aggregations.authentication_success.doc_count',
+ response.rawResponse
+ ),
+ authenticationsSuccessHistogram: formatAuthenticationsHistogramData(
+ authenticationsSuccessHistogram
+ ),
+ authenticationsFailure: getOr(
+ null,
+ 'aggregations.authentication_failure.doc_count',
+ response.rawResponse
+ ),
+ authenticationsFailureHistogram: formatAuthenticationsHistogramData(
+ authenticationsFailureHistogram
+ ),
+ };
+ },
+};
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts
new file mode 100644
index 0000000000000..8da5f7f95c5d1
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts
@@ -0,0 +1,101 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { HostsKpiAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts';
+import { createQueryFilterClauses } from '../../../../../../utils/build_query';
+
+export const buildHostsKpiAuthenticationsQuery = ({
+ filterQuery,
+ timerange: { from, to },
+ defaultIndex,
+}: HostsKpiAuthenticationsRequestOptions) => {
+ const filter = [
+ ...createQueryFilterClauses(filterQuery),
+ {
+ bool: {
+ filter: [
+ {
+ term: {
+ 'event.category': 'authentication',
+ },
+ },
+ ],
+ },
+ },
+ {
+ range: {
+ '@timestamp': {
+ gte: from,
+ lte: to,
+ format: 'strict_date_optional_time',
+ },
+ },
+ },
+ ];
+
+ const dslQuery = {
+ index: defaultIndex,
+ allowNoIndices: true,
+ ignoreUnavailable: true,
+ body: {
+ aggs: {
+ authentication_success: {
+ filter: {
+ term: {
+ 'event.outcome': 'success',
+ },
+ },
+ },
+ authentication_success_histogram: {
+ auto_date_histogram: {
+ field: '@timestamp',
+ buckets: '6',
+ },
+ aggs: {
+ count: {
+ filter: {
+ term: {
+ 'event.outcome': 'success',
+ },
+ },
+ },
+ },
+ },
+ authentication_failure: {
+ filter: {
+ term: {
+ 'event.outcome': 'failure',
+ },
+ },
+ },
+ authentication_failure_histogram: {
+ auto_date_histogram: {
+ field: '@timestamp',
+ buckets: '6',
+ },
+ aggs: {
+ count: {
+ filter: {
+ term: {
+ 'event.outcome': 'failure',
+ },
+ },
+ },
+ },
+ },
+ },
+ query: {
+ bool: {
+ filter,
+ },
+ },
+ size: 0,
+ track_total_hits: false,
+ },
+ };
+
+ return dslQuery;
+};
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts
new file mode 100644
index 0000000000000..080ef05c99136
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ HostsKpiHistogram,
+ HostsKpiGeneralHistogramCount,
+ HostsKpiHistogramData,
+} from '../../../../../../../common/search_strategy';
+
+export const formatGeneralHistogramData = (
+ data: Array>
+): HostsKpiHistogramData[] | null =>
+ data && data.length > 0
+ ? data.map(({ key, count }) => ({
+ x: key,
+ y: count.value,
+ }))
+ : null;
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts
new file mode 100644
index 0000000000000..6d91ebf09895e
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getOr } from 'lodash/fp';
+
+import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common';
+import {
+ HostsKpiQueries,
+ HostsKpiHostsStrategyResponse,
+ HostsKpiHostsRequestOptions,
+} from '../../../../../../../common/search_strategy/security_solution/hosts';
+import { inspectStringifyObject } from '../../../../../../utils/build_query';
+import { SecuritySolutionFactory } from '../../../types';
+import { buildHostsKpiHostsQuery } from './query.hosts_kpi_hosts.dsl';
+import { formatGeneralHistogramData } from '../common';
+
+export const hostsKpiHosts: SecuritySolutionFactory = {
+ buildDsl: (options: HostsKpiHostsRequestOptions) => buildHostsKpiHostsQuery(options),
+ parse: async (
+ options: HostsKpiHostsRequestOptions,
+ response: IEsSearchResponse
+ ): Promise => {
+ const inspect = {
+ dsl: [inspectStringifyObject(buildHostsKpiHostsQuery(options))],
+ };
+
+ const hostsHistogram = getOr(
+ null,
+ 'aggregations.hosts_histogram.buckets',
+ response.rawResponse
+ );
+ return {
+ ...response,
+ inspect,
+ hosts: getOr(null, 'aggregations.hosts.value', response.rawResponse),
+ hostsHistogram: formatGeneralHistogramData(hostsHistogram),
+ };
+ },
+};
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts
new file mode 100644
index 0000000000000..704743cc434ed
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { HostsKpiHostsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts';
+import { createQueryFilterClauses } from '../../../../../../utils/build_query';
+
+export const buildHostsKpiHostsQuery = ({
+ filterQuery,
+ timerange: { from, to },
+ defaultIndex,
+}: HostsKpiHostsRequestOptions) => {
+ const filter = [
+ ...createQueryFilterClauses(filterQuery),
+ {
+ range: {
+ '@timestamp': {
+ gte: from,
+ lte: to,
+ format: 'strict_date_optional_time',
+ },
+ },
+ },
+ ];
+
+ const dslQuery = {
+ index: defaultIndex,
+ allowNoIndices: true,
+ ignoreUnavailable: true,
+ body: {
+ aggregations: {
+ hosts: {
+ cardinality: {
+ field: 'host.name',
+ },
+ },
+ hosts_histogram: {
+ auto_date_histogram: {
+ field: '@timestamp',
+ buckets: '6',
+ },
+ aggs: {
+ count: {
+ cardinality: {
+ field: 'host.name',
+ },
+ },
+ },
+ },
+ },
+ query: {
+ bool: {
+ filter,
+ },
+ },
+ size: 0,
+ track_total_hits: false,
+ },
+ };
+
+ return dslQuery;
+};
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts
new file mode 100644
index 0000000000000..f4793ecd53f8f
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export * from './authentications';
+export * from './common';
+export * from './hosts';
+export * from './unique_ips';
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts
new file mode 100644
index 0000000000000..2f890e6fdacca
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getOr } from 'lodash/fp';
+
+import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common';
+import {
+ HostsKpiQueries,
+ HostsKpiUniqueIpsStrategyResponse,
+ HostsKpiUniqueIpsRequestOptions,
+} from '../../../../../../../common/search_strategy/security_solution/hosts';
+import { inspectStringifyObject } from '../../../../../../utils/build_query';
+import { SecuritySolutionFactory } from '../../../types';
+import { buildHostsKpiUniqueIpsQuery } from './query.hosts_kpi_unique_ips.dsl';
+import { formatGeneralHistogramData } from '../common';
+
+export const hostsKpiUniqueIps: SecuritySolutionFactory = {
+ buildDsl: (options: HostsKpiUniqueIpsRequestOptions) => buildHostsKpiUniqueIpsQuery(options),
+ parse: async (
+ options: HostsKpiUniqueIpsRequestOptions,
+ response: IEsSearchResponse
+ ): Promise => {
+ const inspect = {
+ dsl: [inspectStringifyObject(buildHostsKpiUniqueIpsQuery(options))],
+ };
+
+ const uniqueSourceIpsHistogram = getOr(
+ null,
+ 'aggregations.unique_source_ips_histogram.buckets',
+ response.rawResponse
+ );
+
+ const uniqueDestinationIpsHistogram = getOr(
+ null,
+ 'aggregations.unique_destination_ips_histogram.buckets',
+ response.rawResponse
+ );
+
+ return {
+ ...response,
+ inspect,
+ uniqueSourceIps: getOr(null, 'aggregations.unique_source_ips.value', response.rawResponse),
+ uniqueSourceIpsHistogram: formatGeneralHistogramData(uniqueSourceIpsHistogram),
+ uniqueDestinationIps: getOr(
+ null,
+ 'aggregations.unique_destination_ips.value',
+ response.rawResponse
+ ),
+ uniqueDestinationIpsHistogram: formatGeneralHistogramData(uniqueDestinationIpsHistogram),
+ };
+ },
+};
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts
new file mode 100644
index 0000000000000..618c6cb51f666
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts
@@ -0,0 +1,82 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { HostsKpiUniqueIpsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts';
+import { createQueryFilterClauses } from '../../../../../../utils/build_query';
+
+export const buildHostsKpiUniqueIpsQuery = ({
+ filterQuery,
+ timerange: { from, to },
+ defaultIndex,
+}: HostsKpiUniqueIpsRequestOptions) => {
+ const filter = [
+ ...createQueryFilterClauses(filterQuery),
+ {
+ range: {
+ '@timestamp': {
+ gte: from,
+ lte: to,
+ format: 'strict_date_optional_time',
+ },
+ },
+ },
+ ];
+
+ const dslQuery = {
+ index: defaultIndex,
+ allowNoIndices: true,
+ ignoreUnavailable: true,
+ body: {
+ aggregations: {
+ unique_source_ips: {
+ cardinality: {
+ field: 'source.ip',
+ },
+ },
+ unique_source_ips_histogram: {
+ auto_date_histogram: {
+ field: '@timestamp',
+ buckets: '6',
+ },
+ aggs: {
+ count: {
+ cardinality: {
+ field: 'source.ip',
+ },
+ },
+ },
+ },
+ unique_destination_ips: {
+ cardinality: {
+ field: 'destination.ip',
+ },
+ },
+ unique_destination_ips_histogram: {
+ auto_date_histogram: {
+ field: '@timestamp',
+ buckets: '6',
+ },
+ aggs: {
+ count: {
+ cardinality: {
+ field: 'destination.ip',
+ },
+ },
+ },
+ },
+ },
+ query: {
+ bool: {
+ filter,
+ },
+ },
+ size: 0,
+ track_total_hits: false,
+ },
+ };
+
+ return dslQuery;
+};
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 790d1152f0846..b86d59762c8b8 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -14890,7 +14890,7 @@
"xpack.securitySolution.auditd.violatedSeLinuxPolicyDescription": "selinuxポリシーに違反しました",
"xpack.securitySolution.auditd.wasAuthorizedToUseDescription": "が以下の使用を承認されました。",
"xpack.securitySolution.auditd.withResultDescription": "結果付き",
- "xpack.securitySolution.authenticationsTable.authenticationFailures": "認証",
+ "xpack.securitySolution.authenticationsTable.authentications": "認証",
"xpack.securitySolution.authenticationsTable.failures": "失敗",
"xpack.securitySolution.authenticationsTable.lastFailedDestination": "前回失敗したデスティネーション",
"xpack.securitySolution.authenticationsTable.lastFailedSource": "前回失敗したソース",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index d93c8054268e7..28d9cfa4aaf0d 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -14899,7 +14899,7 @@
"xpack.securitySolution.auditd.violatedSeLinuxPolicyDescription": "已违反 selinux 策略",
"xpack.securitySolution.auditd.wasAuthorizedToUseDescription": "有权使用",
"xpack.securitySolution.auditd.withResultDescription": ",结果为",
- "xpack.securitySolution.authenticationsTable.authenticationFailures": "身份验证",
+ "xpack.securitySolution.authenticationsTable.authentications": "身份验证",
"xpack.securitySolution.authenticationsTable.failures": "错误",
"xpack.securitySolution.authenticationsTable.lastFailedDestination": "上一失败目标",
"xpack.securitySolution.authenticationsTable.lastFailedSource": "上一失败源",
From d7107e4c67c75cb7f8d967fbaa1fdfd8da7eafd5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?=
Date: Tue, 22 Sep 2020 13:41:23 +0100
Subject: [PATCH 15/22] [Upgrade Assistant] Rename "telemetry" to "stats"
(#78127)
---
.../public/application/components/tabs.tsx | 2 +-
.../tabs/checkup/deprecations/reindex/button.tsx | 2 +-
.../server/routes/telemetry.test.ts | 16 ++++++++--------
.../upgrade_assistant/server/routes/telemetry.ts | 4 ++--
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx
index 146cebabbb382..110eff36e3df9 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx
@@ -239,7 +239,7 @@ export class UpgradeAssistantTabs extends React.Component {
this.setState({ telemetryState: TelemetryState.Running });
- await this.props.http.fetch('/api/upgrade_assistant/telemetry/ui_open', {
+ await this.props.http.fetch('/api/upgrade_assistant/stats/ui_open', {
method: 'PUT',
body: JSON.stringify(set({}, tabName, true)),
});
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx
index a20f4117f693d..747430f455f22 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx
@@ -239,7 +239,7 @@ export class ReindexButton extends React.Component {
});
afterEach(() => jest.clearAllMocks());
- describe('PUT /api/upgrade_assistant/telemetry/ui_open', () => {
+ describe('PUT /api/upgrade_assistant/stats/ui_open', () => {
it('returns correct payload with single option', async () => {
const returnPayload = {
overview: true,
@@ -51,7 +51,7 @@ describe('Upgrade Assistant Telemetry API', () => {
const resp = await routeDependencies.router.getHandler({
method: 'put',
- pathPattern: '/api/upgrade_assistant/telemetry/ui_open',
+ pathPattern: '/api/upgrade_assistant/stats/ui_open',
})(
routeHandlerContextMock,
createRequestMock({ body: returnPayload }),
@@ -72,7 +72,7 @@ describe('Upgrade Assistant Telemetry API', () => {
const resp = await routeDependencies.router.getHandler({
method: 'put',
- pathPattern: '/api/upgrade_assistant/telemetry/ui_open',
+ pathPattern: '/api/upgrade_assistant/stats/ui_open',
})(
routeHandlerContextMock,
createRequestMock({
@@ -93,7 +93,7 @@ describe('Upgrade Assistant Telemetry API', () => {
const resp = await routeDependencies.router.getHandler({
method: 'put',
- pathPattern: '/api/upgrade_assistant/telemetry/ui_open',
+ pathPattern: '/api/upgrade_assistant/stats/ui_open',
})(
routeHandlerContextMock,
createRequestMock({
@@ -108,7 +108,7 @@ describe('Upgrade Assistant Telemetry API', () => {
});
});
- describe('PUT /api/upgrade_assistant/telemetry/ui_reindex', () => {
+ describe('PUT /api/upgrade_assistant/stats/ui_reindex', () => {
it('returns correct payload with single option', async () => {
const returnPayload = {
close: false,
@@ -121,7 +121,7 @@ describe('Upgrade Assistant Telemetry API', () => {
const resp = await routeDependencies.router.getHandler({
method: 'put',
- pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex',
+ pathPattern: '/api/upgrade_assistant/stats/ui_reindex',
})(
routeHandlerContextMock,
createRequestMock({
@@ -147,7 +147,7 @@ describe('Upgrade Assistant Telemetry API', () => {
const resp = await routeDependencies.router.getHandler({
method: 'put',
- pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex',
+ pathPattern: '/api/upgrade_assistant/stats/ui_reindex',
})(
routeHandlerContextMock,
createRequestMock({
@@ -169,7 +169,7 @@ describe('Upgrade Assistant Telemetry API', () => {
const resp = await routeDependencies.router.getHandler({
method: 'put',
- pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex',
+ pathPattern: '/api/upgrade_assistant/stats/ui_reindex',
})(
routeHandlerContextMock,
createRequestMock({
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts
index 900a5e64c55c3..71f5de01f6a44 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts
@@ -12,7 +12,7 @@ import { RouteDependencies } from '../types';
export function registerTelemetryRoutes({ router, getSavedObjectsService }: RouteDependencies) {
router.put(
{
- path: '/api/upgrade_assistant/telemetry/ui_open',
+ path: '/api/upgrade_assistant/stats/ui_open',
validate: {
body: schema.object({
overview: schema.boolean({ defaultValue: false }),
@@ -40,7 +40,7 @@ export function registerTelemetryRoutes({ router, getSavedObjectsService }: Rout
router.put(
{
- path: '/api/upgrade_assistant/telemetry/ui_reindex',
+ path: '/api/upgrade_assistant/stats/ui_reindex',
validate: {
body: schema.object({
close: schema.boolean({ defaultValue: false }),
From 6d819b7a1d95e8ecc50eef364ef1448544bf21da Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Tue, 22 Sep 2020 08:46:00 -0400
Subject: [PATCH 16/22] [Ingest Manager] Fix agent action acknowledgement
(#78089)
---
.../server/services/agents/acks.test.ts | 110 +++++++++++++++++-
.../server/services/agents/acks.ts | 8 +-
2 files changed, 111 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts
index 866aa587b8a56..c7b4098803827 100644
--- a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts
+++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts
@@ -57,7 +57,7 @@ describe('test agent acks services', () => {
);
});
- it('should update config field on the agent if a policy change is acknowledged', async () => {
+ it('should update config field on the agent if a policy change is acknowledged with an agent without policy', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
const actionAttributes = {
@@ -116,6 +116,114 @@ describe('test agent acks services', () => {
`);
});
+ it('should update config field on the agent if a policy change is acknowledged with a higher revision than the agent one', async () => {
+ const mockSavedObjectsClient = savedObjectsClientMock.create();
+
+ const actionAttributes = {
+ type: 'CONFIG_CHANGE',
+ policy_id: 'policy1',
+ policy_revision: 4,
+ sent_at: '2020-03-14T19:45:02.620Z',
+ timestamp: '2019-01-04T14:32:03.36764-05:00',
+ created_at: '2020-03-14T19:45:02.620Z',
+ ack_data: JSON.stringify({ packages: ['system'] }),
+ };
+
+ mockSavedObjectsClient.bulkGet.mockReturnValue(
+ Promise.resolve({
+ saved_objects: [
+ {
+ id: 'action2',
+ references: [],
+ type: AGENT_ACTION_SAVED_OBJECT_TYPE,
+ attributes: actionAttributes,
+ },
+ ],
+ } as SavedObjectsBulkResponse)
+ );
+
+ await acknowledgeAgentActions(
+ mockSavedObjectsClient,
+ ({
+ id: 'id',
+ type: AGENT_TYPE_PERMANENT,
+ policy_id: 'policy1',
+ policy_revision: 3,
+ } as unknown) as Agent,
+ [
+ {
+ type: 'ACTION_RESULT',
+ subtype: 'CONFIG',
+ timestamp: '2019-01-04T14:32:03.36764-05:00',
+ action_id: 'action2',
+ agent_id: 'id',
+ } as AgentEvent,
+ ]
+ );
+ expect(mockSavedObjectsClient.bulkUpdate).toBeCalled();
+ expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0]).toHaveLength(1);
+ expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0][0]).toMatchInlineSnapshot(`
+ Object {
+ "attributes": Object {
+ "packages": Array [
+ "system",
+ ],
+ "policy_revision": 4,
+ },
+ "id": "id",
+ "type": "fleet-agents",
+ }
+ `);
+ });
+
+ it('should not update config field on the agent if a policy change is acknowledged with a lower revision than the agent one', async () => {
+ const mockSavedObjectsClient = savedObjectsClientMock.create();
+
+ const actionAttributes = {
+ type: 'CONFIG_CHANGE',
+ policy_id: 'policy1',
+ policy_revision: 4,
+ sent_at: '2020-03-14T19:45:02.620Z',
+ timestamp: '2019-01-04T14:32:03.36764-05:00',
+ created_at: '2020-03-14T19:45:02.620Z',
+ ack_data: JSON.stringify({ packages: ['system'] }),
+ };
+
+ mockSavedObjectsClient.bulkGet.mockReturnValue(
+ Promise.resolve({
+ saved_objects: [
+ {
+ id: 'action2',
+ references: [],
+ type: AGENT_ACTION_SAVED_OBJECT_TYPE,
+ attributes: actionAttributes,
+ },
+ ],
+ } as SavedObjectsBulkResponse)
+ );
+
+ await acknowledgeAgentActions(
+ mockSavedObjectsClient,
+ ({
+ id: 'id',
+ type: AGENT_TYPE_PERMANENT,
+ policy_id: 'policy1',
+ policy_revision: 5,
+ } as unknown) as Agent,
+ [
+ {
+ type: 'ACTION_RESULT',
+ subtype: 'CONFIG',
+ timestamp: '2019-01-04T14:32:03.36764-05:00',
+ action_id: 'action2',
+ agent_id: 'id',
+ } as AgentEvent,
+ ]
+ );
+ expect(mockSavedObjectsClient.bulkUpdate).toBeCalled();
+ expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0]).toHaveLength(0);
+ });
+
it('should not update config field on the agent if a policy change for an old revision is acknowledged', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts
index d29dfcec7ef30..1392710eb0eff 100644
--- a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts
+++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts
@@ -139,16 +139,12 @@ function getLatestConfigChangePolicyActionIfUpdated(
!isAgentPolicyAction(action) ||
action.type !== 'CONFIG_CHANGE' ||
action.policy_id !== agent.policy_id ||
- (acc?.policy_revision ?? 0) < (agent.policy_revision || 0)
+ (action?.policy_revision ?? 0) < (agent.policy_revision || 0)
) {
return acc;
}
- if (action.policy_revision > (acc?.policy_revision ?? 0)) {
- return action;
- }
-
- return acc;
+ return action;
}, null);
}
From ef86fbc7802008f3a9cee8ef609d964749d15e3a Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Tue, 22 Sep 2020 15:31:07 +0200
Subject: [PATCH 17/22] call .destroy on ace when react component unmounts
(#78132)
---
.../containers/editor/legacy/console_editor/editor.tsx | 3 +++
.../models/legacy_core_editor/legacy_core_editor.ts | 4 ++++
src/plugins/console/public/types/core_editor.ts | 5 +++++
.../searchprofiler/public/application/editor/editor.tsx | 6 ++++++
4 files changed, 18 insertions(+)
diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
index fc88b31711b23..abef8afcc3985 100644
--- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
+++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
@@ -182,6 +182,9 @@ function EditorUI({ initialTextValue }: EditorProps) {
unsubscribeResizer();
clearSubscriptions();
window.removeEventListener('hashchange', onHashChange);
+ if (editorInstanceRef.current) {
+ editorInstanceRef.current.getCoreEditor().destroy();
+ }
};
}, [saveCurrentTextObject, initialTextValue, history, setInputEditor, settingsService]);
diff --git a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts
index 469ef6d79fae5..393b7eee346f5 100644
--- a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts
+++ b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts
@@ -408,4 +408,8 @@ export class LegacyCoreEditor implements CoreEditor {
},
]);
}
+
+ destroy() {
+ this.editor.destroy();
+ }
}
diff --git a/src/plugins/console/public/types/core_editor.ts b/src/plugins/console/public/types/core_editor.ts
index b71f4fff44ca5..d88d8f86b874c 100644
--- a/src/plugins/console/public/types/core_editor.ts
+++ b/src/plugins/console/public/types/core_editor.ts
@@ -268,4 +268,9 @@ export interface CoreEditor {
* detects a change
*/
registerAutocompleter(autocompleter: AutoCompleterFunction): void;
+
+ /**
+ * Release any resources in use by the editor.
+ */
+ destroy(): void;
}
diff --git a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx
index 3141f5bedc8f9..7e7d74155b2d9 100644
--- a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx
+++ b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx
@@ -56,6 +56,12 @@ export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Pro
setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null);
onEditorReady(createEditorShim(editorInstanceRef.current));
+
+ return () => {
+ if (editorInstanceRef.current) {
+ editorInstanceRef.current.destroy();
+ }
+ };
}, [initialValue, onEditorReady, licenseEnabled]);
return (
From 3f5243eefae435532b25f4dcf8bdac29f2244944 Mon Sep 17 00:00:00 2001
From: Patrick Mueller
Date: Tue, 22 Sep 2020 10:15:27 -0400
Subject: [PATCH 18/22] [Alerting] optimize calculation of unmuted alert
instances (#78021)
This PR optimizes the calculation of instances which should be executed, by optimizing the way the muted instances are removed from the collection of triggered instances.
---
x-pack/plugins/alerts/server/task_runner/task_runner.ts | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts
index 5be684eca4651..7ea3f83d747c0 100644
--- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts
+++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { pickBy, mapValues, omit, without } from 'lodash';
+import { pickBy, mapValues, without } from 'lodash';
import { Logger, KibanaRequest } from '../../../../../src/core/server';
import { TaskRunnerContext } from './task_runner_factory';
import { ConcreteTaskInstance } from '../../../task_manager/server';
@@ -228,12 +228,13 @@ export class TaskRunner {
});
if (!muteAll) {
- const enabledAlertInstances = omit(instancesWithScheduledActions, ...mutedInstanceIds);
+ const mutedInstanceIdsSet = new Set(mutedInstanceIds);
await Promise.all(
- Object.entries(enabledAlertInstances)
+ Object.entries(instancesWithScheduledActions)
.filter(
- ([, alertInstance]: [string, AlertInstance]) => !alertInstance.isThrottled(throttle)
+ ([alertInstanceName, alertInstance]: [string, AlertInstance]) =>
+ !alertInstance.isThrottled(throttle) && !mutedInstanceIdsSet.has(alertInstanceName)
)
.map(([id, alertInstance]: [string, AlertInstance]) =>
this.executeAlertInstance(id, alertInstance, executionHandler)
From a49b99011515f10e8d1e788bbd16d037a47428cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?=
Date: Tue, 22 Sep 2020 15:17:40 +0100
Subject: [PATCH 19/22] [Enterprise Search] Rename "telemetry" to "stats"
(#78124)
---
.../applications/shared/telemetry/send_telemetry.test.tsx | 8 ++++----
.../applications/shared/telemetry/send_telemetry.tsx | 2 +-
.../server/routes/enterprise_search/telemetry.test.ts | 2 +-
.../server/routes/enterprise_search/telemetry.ts | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx
index 8f7cf090e2d57..1d64b453b2c2c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx
@@ -33,7 +33,7 @@ describe('Shared Telemetry Helpers', () => {
metric: 'setup_guide',
});
- expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', {
+ expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
headers,
body: '{"product":"enterprise_search","action":"viewed","metric":"setup_guide"}',
});
@@ -54,7 +54,7 @@ describe('Shared Telemetry Helpers', () => {
http: httpMock,
});
- expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', {
+ expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
headers,
body: '{"product":"enterprise_search","action":"viewed","metric":"page"}',
});
@@ -65,7 +65,7 @@ describe('Shared Telemetry Helpers', () => {
http: httpMock,
});
- expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', {
+ expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
headers,
body: '{"product":"app_search","action":"clicked","metric":"button"}',
});
@@ -76,7 +76,7 @@ describe('Shared Telemetry Helpers', () => {
http: httpMock,
});
- expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', {
+ expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
headers,
body: '{"product":"workplace_search","action":"error","metric":"not_found"}',
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx
index 4df1428221de6..e3c9ba9b8a218 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx
@@ -27,7 +27,7 @@ interface ISendTelemetry extends ISendTelemetryProps {
export const sendTelemetry = async ({ http, product, action, metric }: ISendTelemetry) => {
try {
const body = JSON.stringify({ product, action, metric });
- await http.put('/api/enterprise_search/telemetry', { headers, body });
+ await http.put('/api/enterprise_search/stats', { headers, body });
} catch (error) {
throw new Error('Unable to send telemetry');
}
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts
index acddd3539965a..bd6f4b9da91fd 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts
@@ -35,7 +35,7 @@ describe('Enterprise Search Telemetry API', () => {
});
});
- describe('PUT /api/enterprise_search/telemetry', () => {
+ describe('PUT /api/enterprise_search/stats', () => {
it('increments the saved objects counter for App Search', async () => {
(incrementUICounter as jest.Mock).mockImplementation(jest.fn(() => successResponse));
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts
index bfc07c8b64ef5..8f6638ddc099e 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts
@@ -25,7 +25,7 @@ export function registerTelemetryRoute({
}: IRouteDependencies) {
router.put(
{
- path: '/api/enterprise_search/telemetry',
+ path: '/api/enterprise_search/stats',
validate: {
body: schema.object({
product: schema.oneOf([
From 037eac55902de5d1e40d3372e83897384f4e95b0 Mon Sep 17 00:00:00 2001
From: Nathan L Smith
Date: Tue, 22 Sep 2020 10:14:31 -0500
Subject: [PATCH 20/22] Remove service map beta badge (#78039)
Fixes #60529.
---
docs/apm/service-maps.asciidoc | 5 ---
.../components/app/ServiceMap/BetaBadge.tsx | 36 -------------------
.../components/app/ServiceMap/index.tsx | 10 +++---
.../translations/translations/ja-JP.json | 2 --
.../translations/translations/zh-CN.json | 2 --
5 files changed, 4 insertions(+), 51 deletions(-)
delete mode 100644 x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx
diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc
index db2f85c54c762..d629a95073a74 100644
--- a/docs/apm/service-maps.asciidoc
+++ b/docs/apm/service-maps.asciidoc
@@ -2,11 +2,6 @@
[[service-maps]]
=== Service maps
-beta::[]
-
-WARNING: Service map support for Internet Explorer 11 is extremely limited.
-Please use Chrome or Firefox if available.
-
A service map is a real-time visual representation of the instrumented services in your application's architecture.
It shows you how these services are connected, along with high-level metrics like average transaction duration,
requests per minute, and errors per minute.
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx
deleted file mode 100644
index b468470e3a17d..0000000000000
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { EuiBetaBadge } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import styled from 'styled-components';
-
-const BetaBadgeContainer = styled.div`
- right: ${({ theme }) => theme.eui.gutterTypes.gutterMedium};
- position: absolute;
- top: ${({ theme }) => theme.eui.gutterTypes.gutterSmall};
- z-index: 1; /* The element containing the cytoscape canvas has z-index = 0. */
-`;
-
-export function BetaBadge() {
- return (
-
-
-
- );
-}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx
index cb5a57e9ab9fb..bb450131bdfb8 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx
@@ -4,15 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { useTheme } from '../../../hooks/useTheme';
+import React from 'react';
+import { useTrackPageview } from '../../../../../observability/public';
import {
invalidLicenseMessage,
isActivePlatinumLicense,
} from '../../../../common/service_map';
import { useFetcher } from '../../../hooks/useFetcher';
import { useLicense } from '../../../hooks/useLicense';
+import { useTheme } from '../../../hooks/useTheme';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { callApmApi } from '../../../services/rest/createCallApmApi';
import { LicensePrompt } from '../../shared/LicensePrompt';
@@ -22,8 +23,6 @@ import { getCytoscapeDivStyle } from './cytoscapeOptions';
import { EmptyBanner } from './EmptyBanner';
import { Popover } from './Popover';
import { useRefDimensions } from './useRefDimensions';
-import { BetaBadge } from './BetaBadge';
-import { useTrackPageview } from '../../../../../observability/public';
interface ServiceMapProps {
serviceName?: string;
@@ -80,7 +79,6 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
style={getCytoscapeDivStyle(theme)}
>
-
{serviceName && }
@@ -96,7 +94,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
grow={false}
style={{ width: 600, textAlign: 'center' as const }}
>
-
+
);
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index b86d59762c8b8..f626835da8e11 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -4806,8 +4806,6 @@
"xpack.apm.serviceMap.avgMemoryUsagePopoverStat": "メモリー使用状況(平均)",
"xpack.apm.serviceMap.avgReqPerMinutePopoverMetric": "1分あたりのリクエスト(平均)",
"xpack.apm.serviceMap.avgTransDurationPopoverStat": "トランザクションの長さ(平均)",
- "xpack.apm.serviceMap.betaBadge": "ベータ",
- "xpack.apm.serviceMap.betaTooltipMessage": "現在、この機能はベータです。不具合を見つけた場合やご意見がある場合、サポートに問い合わせるか、またはディスカッションフォーラムにご報告ください。",
"xpack.apm.serviceMap.center": "中央",
"xpack.apm.serviceMap.download": "ダウンロード",
"xpack.apm.serviceMap.emptyBanner.docsLink": "詳細はドキュメントをご覧ください",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 28d9cfa4aaf0d..d6baa87ca9e2f 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -4809,8 +4809,6 @@
"xpack.apm.serviceMap.avgMemoryUsagePopoverStat": "内存使用率(平均值)",
"xpack.apm.serviceMap.avgReqPerMinutePopoverMetric": "每分钟请求数(平均)",
"xpack.apm.serviceMap.avgTransDurationPopoverStat": "事务持续时间(平均值)",
- "xpack.apm.serviceMap.betaBadge": "公测版",
- "xpack.apm.serviceMap.betaTooltipMessage": "此功能当前为公测版。如果遇到任何错误或有任何反馈,请报告问题或访问我们的论坛。",
"xpack.apm.serviceMap.center": "中",
"xpack.apm.serviceMap.download": "下载",
"xpack.apm.serviceMap.emptyBanner.docsLink": "在文档中了解详情",
From 99f652479a78a01e4cd29e2333415567f21fdd49 Mon Sep 17 00:00:00 2001
From: Constance
Date: Tue, 22 Sep 2020 08:33:06 -0700
Subject: [PATCH 21/22] [Enterprise Search] Fix various plugin states when app
has error connecting to Enterprise Search (#78091)
* Display error connecting prompt on Overview page instead of blank page
* Fix App Search and Workplace Search to not crash during error connecting
- due to obj type errors
---
.../applications/app_search/app_logic.test.ts | 9 +++++++++
.../applications/app_search/app_logic.ts | 4 ++--
.../error_connecting.test.tsx | 19 +++++++++++++++++++
.../error_connecting/error_connecting.tsx | 18 ++++++++++++++++++
.../components/error_connecting/index.ts | 7 +++++++
.../enterprise_search/index.test.tsx | 17 ++++++++++++++++-
.../applications/enterprise_search/index.tsx | 8 +++++++-
.../workplace_search/app_logic.test.ts | 10 ++++++++++
.../workplace_search/app_logic.ts | 11 +++++++----
9 files changed, 95 insertions(+), 8 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts
index 0f7bfe09edf7e..9410b9ef7cb03 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts
@@ -56,6 +56,15 @@ describe('AppLogic', () => {
}),
});
});
+
+ it('gracefully handles missing initial data', () => {
+ AppLogic.actions.initializeAppData({});
+
+ expect(AppLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ hasInitialized: true,
+ });
+ });
});
describe('setOnboardingComplete()', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts
index 8e5a8d75f407f..932e84af45c2b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts
@@ -39,7 +39,7 @@ export const AppLogic = kea>({
account: [
{},
{
- initializeAppData: (_, { appSearch: account }) => account,
+ initializeAppData: (_, { appSearch: account }) => account || {},
setOnboardingComplete: (account) => ({
...account,
onboardingComplete: true,
@@ -49,7 +49,7 @@ export const AppLogic = kea>({
configuredLimits: [
{},
{
- initializeAppData: (_, { configuredLimits }) => configuredLimits.appSearch,
+ initializeAppData: (_, { configuredLimits }) => configuredLimits?.appSearch || {},
},
],
ilmEnabled: [
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx
new file mode 100644
index 0000000000000..8d48875a8e1f5
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { ErrorStatePrompt } from '../../../shared/error_state';
+import { ErrorConnecting } from './';
+
+describe('ErrorConnecting', () => {
+ it('renders', () => {
+ const wrapper = shallow();
+
+ expect(wrapper.find(ErrorStatePrompt)).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx
new file mode 100644
index 0000000000000..567c77792583d
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiPage, EuiPageContent } from '@elastic/eui';
+
+import { ErrorStatePrompt } from '../../../shared/error_state';
+
+export const ErrorConnecting: React.FC = () => (
+
+
+
+
+
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts
new file mode 100644
index 0000000000000..c8b71e1a6e791
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { ErrorConnecting } from './error_connecting';
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx
index cd2a22a45bbb4..b2918dac086f6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx
@@ -6,13 +6,20 @@
import React from 'react';
import { shallow } from 'enzyme';
-
import { EuiPage } from '@elastic/eui';
+import '../__mocks__/kea.mock';
+import { useValues } from 'kea';
+
import { EnterpriseSearch } from './';
+import { ErrorConnecting } from './components/error_connecting';
import { ProductCard } from './components/product_card';
describe('EnterpriseSearch', () => {
+ beforeEach(() => {
+ (useValues as jest.Mock).mockReturnValue({ errorConnecting: false });
+ });
+
it('renders the overview page and product cards', () => {
const wrapper = shallow(
@@ -22,6 +29,14 @@ describe('EnterpriseSearch', () => {
expect(wrapper.find(ProductCard)).toHaveLength(2);
});
+ it('renders the error connecting prompt', () => {
+ (useValues as jest.Mock).mockReturnValueOnce({ errorConnecting: true });
+ const wrapper = shallow();
+
+ expect(wrapper.find(ErrorConnecting)).toHaveLength(1);
+ expect(wrapper.find(EuiPage)).toHaveLength(0);
+ });
+
describe('access checks', () => {
it('does not render the App Search card if the user does not have access to AS', () => {
const wrapper = shallow(
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx
index 373f595a6a9ea..3a3ba02e07058 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx
@@ -5,6 +5,7 @@
*/
import React from 'react';
+import { useValues } from 'kea';
import {
EuiPage,
EuiPageBody,
@@ -21,9 +22,11 @@ import { i18n } from '@kbn/i18n';
import { IInitialAppData } from '../../../common/types';
import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants';
+import { HttpLogic } from '../shared/http';
import { SetEnterpriseSearchChrome as SetPageChrome } from '../shared/kibana_chrome';
import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../shared/telemetry';
+import { ErrorConnecting } from './components/error_connecting';
import { ProductCard } from './components/product_card';
import AppSearchImage from './assets/app_search.png';
@@ -31,9 +34,12 @@ import WorkplaceSearchImage from './assets/workplace_search.png';
import './index.scss';
export const EnterpriseSearch: React.FC = ({ access = {} }) => {
+ const { errorConnecting } = useValues(HttpLogic);
const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access;
- return (
+ return errorConnecting ? (
+
+ ) : (
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts
index c52eceb2d2fdd..974e07069ddba 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts
@@ -50,5 +50,15 @@ describe('AppLogic', () => {
expect(AppLogic.values).toEqual(expectedLogicValues);
});
+
+ it('gracefully handles missing initial data', () => {
+ AppLogic.actions.initializeAppData({});
+
+ expect(AppLogic.values).toEqual({
+ ...DEFAULT_VALUES,
+ hasInitialized: true,
+ isFederatedAuth: false,
+ });
+ });
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts
index 94bd1d529b65f..629d1969a8f59 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts
@@ -21,6 +21,9 @@ export interface IAppActions {
initializeAppData(props: IInitialAppData): IInitialAppData;
}
+const emptyOrg = {} as IOrganization;
+const emptyAccount = {} as IAccount;
+
export const AppLogic = kea>({
path: ['enterprise_search', 'workplace_search', 'app_logic'],
actions: {
@@ -43,15 +46,15 @@ export const AppLogic = kea>({
},
],
organization: [
- {} as IOrganization,
+ emptyOrg,
{
- initializeAppData: (_, { workplaceSearch }) => workplaceSearch!.organization,
+ initializeAppData: (_, { workplaceSearch }) => workplaceSearch?.organization || emptyOrg,
},
],
account: [
- {} as IAccount,
+ emptyAccount,
{
- initializeAppData: (_, { workplaceSearch }) => workplaceSearch!.account,
+ initializeAppData: (_, { workplaceSearch }) => workplaceSearch?.account || emptyAccount,
},
],
},
From 7544a33901fe8ec084e937a77a44784290fb83b5 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Tue, 22 Sep 2020 18:00:19 +0200
Subject: [PATCH 22/22] [CSM] Use stacked chart for page views (#78042)
---
.../RumDashboard/Charts/PageViewsChart.tsx | 25 +-
.../lib/rum_client/get_page_view_trends.ts | 63 ++--
.../tests/csm/__snapshots__/page_views.snap | 280 ++++++++++++++++++
.../trial/tests/csm/page_views.ts | 65 ++++
.../apm_api_integration/trial/tests/index.ts | 1 +
5 files changed, 403 insertions(+), 31 deletions(-)
create mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap
create mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx
index c76be19edfe47..904144dec6de9 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx
@@ -33,7 +33,10 @@ import { ChartWrapper } from '../ChartWrapper';
import { I18LABELS } from '../translations';
interface Props {
- data?: Array>;
+ data?: {
+ topItems: string[];
+ items: Array>;
+ };
loading: boolean;
}
@@ -68,15 +71,9 @@ export function PageViewsChart({ data, loading }: Props) {
});
};
- let breakdownAccessors: Set = new Set();
- if (data && data.length > 0) {
- data.forEach((item) => {
- breakdownAccessors = new Set([
- ...Array.from(breakdownAccessors),
- ...Object.keys(item).filter((key) => key !== 'x'),
- ]);
- });
- }
+ const breakdownAccessors = data?.topItems?.length ? data?.topItems : ['y'];
+
+ const [darkMode] = useUiSetting$('theme:darkMode');
const customSeriesNaming: SeriesNameFn = ({ yAccessor }) => {
if (yAccessor === 'y') {
@@ -86,8 +83,6 @@ export function PageViewsChart({ data, loading }: Props) {
return yAccessor;
};
- const [darkMode] = useUiSetting$('theme:darkMode');
-
return (
{(!loading || data) && (
@@ -115,7 +110,8 @@ export function PageViewsChart({ data, loading }: Props) {
id="page_views"
title={I18LABELS.pageViews}
position={Position.Left}
- tickFormat={(d) => numeral(d).format('0a')}
+ tickFormat={(d) => numeral(d).format('0')}
+ labelFormat={(d) => numeral(d).format('0a')}
/>
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
index f25062c67f87a..543aa911b0b1f 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
@@ -51,6 +51,16 @@ export async function getPageViewTrends({
}
: undefined,
},
+ ...(breakdownItem
+ ? {
+ topBreakdowns: {
+ terms: {
+ field: breakdownItem.fieldName,
+ size: 9,
+ },
+ },
+ }
+ : {}),
},
},
});
@@ -59,25 +69,44 @@ export async function getPageViewTrends({
const response = await apmEventClient.search(params);
+ const { topBreakdowns } = response.aggregations ?? {};
+
+ // we are only displaying top 9
+ const topItems: string[] = (topBreakdowns?.buckets ?? []).map(
+ ({ key }) => key as string
+ );
+
const result = response.aggregations?.pageViews.buckets ?? [];
- return result.map((bucket) => {
- const { key: xVal, doc_count: bCount } = bucket;
- const res: Record = {
- x: xVal,
- y: bCount,
- };
- if ('breakdown' in bucket) {
- const categoryBuckets = bucket.breakdown.buckets;
- categoryBuckets.forEach(({ key, doc_count: docCount }) => {
- if (key === 'Other') {
- res[key + `(${breakdownItem?.name})`] = docCount;
- } else {
- res[key] = docCount;
+ return {
+ topItems,
+ items: result.map((bucket) => {
+ const { key: xVal, doc_count: bCount } = bucket;
+ const res: Record = {
+ x: xVal,
+ y: bCount,
+ };
+ if ('breakdown' in bucket) {
+ let top9Count = 0;
+ const categoryBuckets = bucket.breakdown.buckets;
+ categoryBuckets.forEach(({ key, doc_count: docCount }) => {
+ if (topItems.includes(key as string)) {
+ if (res[key]) {
+ // if term is already in object, just add it to it
+ res[key] += docCount;
+ } else {
+ res[key] = docCount;
+ }
+ top9Count += docCount;
+ }
+ });
+ // Top 9 plus others, get a diff from parent bucket total
+ if (bCount > top9Count) {
+ res.Other = bCount - top9Count;
}
- });
- }
+ }
- return res;
- });
+ return res;
+ }),
+ };
}
diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap
new file mode 100644
index 0000000000000..38b009fc73d34
--- /dev/null
+++ b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap
@@ -0,0 +1,280 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CSM page views when there is data returns page views 1`] = `
+Object {
+ "items": Array [
+ Object {
+ "x": 1600149947000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600149957000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600149967000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600149977000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600149987000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600149997000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150007000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150017000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150027000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600150037000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150047000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150057000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150067000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150077000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600150087000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150097000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150107000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150117000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150127000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150137000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150147000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150157000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150167000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150177000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600150187000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150197000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150207000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600150217000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150227000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150237000,
+ "y": 1,
+ },
+ ],
+ "topItems": Array [],
+}
+`;
+
+exports[`CSM page views when there is data returns page views with breakdown 1`] = `
+Object {
+ "items": Array [
+ Object {
+ "Chrome": 1,
+ "x": 1600149947000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600149957000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600149967000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600149977000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600149987000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600149997000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150007000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150017000,
+ "y": 0,
+ },
+ Object {
+ "Chrome": 1,
+ "x": 1600150027000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600150037000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150047000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150057000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150067000,
+ "y": 0,
+ },
+ Object {
+ "Chrome": 1,
+ "x": 1600150077000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600150087000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150097000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150107000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150117000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150127000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150137000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150147000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150157000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150167000,
+ "y": 0,
+ },
+ Object {
+ "Chrome": 1,
+ "x": 1600150177000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600150187000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150197000,
+ "y": 0,
+ },
+ Object {
+ "Chrome Mobile": 1,
+ "x": 1600150207000,
+ "y": 1,
+ },
+ Object {
+ "x": 1600150217000,
+ "y": 0,
+ },
+ Object {
+ "x": 1600150227000,
+ "y": 0,
+ },
+ Object {
+ "Chrome Mobile": 1,
+ "x": 1600150237000,
+ "y": 1,
+ },
+ ],
+ "topItems": Array [
+ "Chrome",
+ "Chrome Mobile",
+ ],
+}
+`;
+
+exports[`CSM page views when there is no data returns empty list 1`] = `
+Object {
+ "items": Array [],
+ "topItems": Array [],
+}
+`;
+
+exports[`CSM page views when there is no data returns empty list with breakdowns 1`] = `
+Object {
+ "items": Array [],
+ "topItems": Array [],
+}
+`;
diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts
new file mode 100644
index 0000000000000..ca5670d41d8ee
--- /dev/null
+++ b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import expect from '@kbn/expect';
+import { expectSnapshot } from '../../../common/match_snapshot';
+import { FtrProviderContext } from '../../../common/ftr_provider_context';
+
+export default function rumServicesApiTests({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+ const esArchiver = getService('esArchiver');
+
+ describe('CSM page views', () => {
+ describe('when there is no data', () => {
+ it('returns empty list', async () => {
+ const response = await supertest.get(
+ '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D'
+ );
+
+ expect(response.status).to.be(200);
+ expectSnapshot(response.body).toMatch();
+ });
+ it('returns empty list with breakdowns', async () => {
+ const response = await supertest.get(
+ '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D'
+ );
+
+ expect(response.status).to.be(200);
+ expectSnapshot(response.body).toMatch();
+ });
+ });
+
+ describe('when there is data', () => {
+ before(async () => {
+ await esArchiver.load('8.0.0');
+ await esArchiver.load('rum_8.0.0');
+ });
+ after(async () => {
+ await esArchiver.unload('8.0.0');
+ await esArchiver.unload('rum_8.0.0');
+ });
+
+ it('returns page views', async () => {
+ const response = await supertest.get(
+ '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D'
+ );
+
+ expect(response.status).to.be(200);
+
+ expectSnapshot(response.body).toMatch();
+ });
+ it('returns page views with breakdown', async () => {
+ const response = await supertest.get(
+ '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D'
+ );
+
+ expect(response.status).to.be(200);
+
+ expectSnapshot(response.body).toMatch();
+ });
+ });
+ });
+}
diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts
index ae62253c62d81..a026f91a02cd7 100644
--- a/x-pack/test/apm_api_integration/trial/tests/index.ts
+++ b/x-pack/test/apm_api_integration/trial/tests/index.ts
@@ -35,6 +35,7 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr
loadTestFile(require.resolve('./csm/csm_services.ts'));
loadTestFile(require.resolve('./csm/web_core_vitals.ts'));
loadTestFile(require.resolve('./csm/long_task_metrics.ts'));
+ loadTestFile(require.resolve('./csm/page_views.ts'));
});
});
}