From d570ab1eb27b1d876a8eb099bea84f234fb0199c Mon Sep 17 00:00:00 2001
From: Dmitry Lemeshko
Date: Fri, 10 Jul 2020 00:50:27 +0200
Subject: [PATCH 01/39] [services/remote/webdriver] fix eslint error (#71346)
---
test/functional/services/remote/webdriver.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts
index 867e78dfad8dc..0611c80f59b92 100644
--- a/test/functional/services/remote/webdriver.ts
+++ b/test/functional/services/remote/webdriver.ts
@@ -28,7 +28,7 @@ import { delay } from 'bluebird';
import chromeDriver from 'chromedriver';
// @ts-ignore types not available
import geckoDriver from 'geckodriver';
-import { Builder, Capabilities, logging } from 'selenium-webdriver';
+import { Builder, logging } from 'selenium-webdriver';
import chrome from 'selenium-webdriver/chrome';
import firefox from 'selenium-webdriver/firefox';
import edge from 'selenium-webdriver/edge';
From c9e8650a216635aec08399174cfe72f991e1fb3c Mon Sep 17 00:00:00 2001
From: Alex Kahan
Date: Thu, 9 Jul 2020 18:57:46 -0400
Subject: [PATCH 02/39] Deduplication of entries and items before sending to
endpoint (#71297)
* Deduplication of entries and items before sending to endpoint
* Renaming
---
.../endpoint/lib/artifacts/lists.test.ts | 139 +++++++++++++++++-
.../server/endpoint/lib/artifacts/lists.ts | 19 ++-
2 files changed, 153 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts
index acde455f77cb4..1a19306b2fd60 100644
--- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts
@@ -139,6 +139,139 @@ describe('buildEventTypeSignal', () => {
});
});
+ test('it should deduplicate exception entries', async () => {
+ const testEntries: EntriesArray = [
+ { field: 'server.domain.text', operator: 'included', type: 'match', value: 'DOMAIN' },
+ { field: 'server.domain.text', operator: 'included', type: 'match', value: 'DOMAIN' },
+ { field: 'server.domain.text', operator: 'included', type: 'match', value: 'DOMAIN' },
+ { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' },
+ {
+ field: 'host.hostname.text',
+ operator: 'included',
+ type: 'match_any',
+ value: ['estc', 'kibana'],
+ },
+ ];
+
+ const expectedEndpointExceptions = {
+ type: 'simple',
+ entries: [
+ {
+ field: 'server.domain',
+ operator: 'included',
+ type: 'exact_caseless',
+ value: 'DOMAIN',
+ },
+ {
+ field: 'server.ip',
+ operator: 'included',
+ type: 'exact_cased',
+ value: '192.168.1.1',
+ },
+ {
+ field: 'host.hostname',
+ operator: 'included',
+ type: 'exact_caseless_any',
+ value: ['estc', 'kibana'],
+ },
+ ],
+ };
+
+ const first = getFoundExceptionListItemSchemaMock();
+ first.data[0].entries = testEntries;
+ mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
+
+ const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
+ expect(resp).toEqual({
+ entries: [expectedEndpointExceptions],
+ });
+ });
+
+ test('it should not deduplicate exception entries across nested boundaries', async () => {
+ const testEntries: EntriesArray = [
+ {
+ entries: [
+ { field: 'nested.field', operator: 'included', type: 'match', value: 'some value' },
+ ],
+ field: 'some.parentField',
+ type: 'nested',
+ },
+ // Same as above but not inside the nest
+ { field: 'nested.field', operator: 'included', type: 'match', value: 'some value' },
+ ];
+
+ const expectedEndpointExceptions = {
+ type: 'simple',
+ entries: [
+ {
+ entries: [
+ {
+ field: 'nested.field',
+ operator: 'included',
+ type: 'exact_cased',
+ value: 'some value',
+ },
+ ],
+ field: 'some.parentField',
+ type: 'nested',
+ },
+ {
+ field: 'nested.field',
+ operator: 'included',
+ type: 'exact_cased',
+ value: 'some value',
+ },
+ ],
+ };
+
+ const first = getFoundExceptionListItemSchemaMock();
+ first.data[0].entries = testEntries;
+ mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
+
+ const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
+ expect(resp).toEqual({
+ entries: [expectedEndpointExceptions],
+ });
+ });
+
+ test('it should deduplicate exception items', async () => {
+ const testEntries: EntriesArray = [
+ { field: 'server.domain.text', operator: 'included', type: 'match', value: 'DOMAIN' },
+ { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' },
+ ];
+
+ const expectedEndpointExceptions = {
+ type: 'simple',
+ entries: [
+ {
+ field: 'server.domain',
+ operator: 'included',
+ type: 'exact_caseless',
+ value: 'DOMAIN',
+ },
+ {
+ field: 'server.ip',
+ operator: 'included',
+ type: 'exact_cased',
+ value: '192.168.1.1',
+ },
+ ],
+ };
+
+ const first = getFoundExceptionListItemSchemaMock();
+ first.data[0].entries = testEntries;
+
+ // Create a second exception item with the same entries
+ first.data[1] = getExceptionListItemSchemaMock();
+ first.data[1].entries = testEntries;
+ mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
+
+ const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
+ expect(resp).toEqual({
+ entries: [expectedEndpointExceptions],
+ });
+ });
+
test('it should ignore unsupported entries', async () => {
// Lists and exists are not supported by the Endpoint
const testEntries: EntriesArray = [
@@ -178,8 +311,9 @@ describe('buildEventTypeSignal', () => {
});
test('it should convert the exception lists response to the proper endpoint format while paging', async () => {
- // The first call returns one exception
+ // The first call returns two exceptions
const first = getFoundExceptionListItemSchemaMock();
+ first.data.push(getExceptionListItemSchemaMock());
// The second call returns two exceptions
const second = getFoundExceptionListItemSchemaMock();
@@ -194,7 +328,8 @@ describe('buildEventTypeSignal', () => {
.mockReturnValueOnce(second)
.mockReturnValueOnce(third);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
- expect(resp.entries.length).toEqual(3);
+ // Expect 2 exceptions, the first two calls returned the same exception list items
+ expect(resp.entries.length).toEqual(2);
});
test('it should handle no exceptions', async () => {
diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
index 556405adff62f..b756c4e3d14c3 100644
--- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
@@ -97,10 +97,18 @@ export function translateToEndpointExceptions(
exc: FoundExceptionListItemSchema,
schemaVersion: string
): TranslatedExceptionListItem[] {
+ const entrySet = new Set();
+ const entriesFiltered: TranslatedExceptionListItem[] = [];
if (schemaVersion === 'v1') {
- return exc.data.map((item) => {
- return translateItem(schemaVersion, item);
+ exc.data.forEach((entry) => {
+ const translatedItem = translateItem(schemaVersion, entry);
+ const entryHash = createHash('sha256').update(JSON.stringify(translatedItem)).digest('hex');
+ if (!entrySet.has(entryHash)) {
+ entriesFiltered.push(translatedItem);
+ entrySet.add(entryHash);
+ }
});
+ return entriesFiltered;
} else {
throw new Error('unsupported schemaVersion');
}
@@ -124,12 +132,17 @@ function translateItem(
schemaVersion: string,
item: ExceptionListItemSchema
): TranslatedExceptionListItem {
+ const itemSet = new Set();
return {
type: item.type,
entries: item.entries.reduce((translatedEntries: TranslatedEntry[], entry) => {
const translatedEntry = translateEntry(schemaVersion, entry);
if (translatedEntry !== undefined && translatedEntryType.is(translatedEntry)) {
- translatedEntries.push(translatedEntry);
+ const itemHash = createHash('sha256').update(JSON.stringify(translatedEntry)).digest('hex');
+ if (!itemSet.has(itemHash)) {
+ translatedEntries.push(translatedEntry);
+ itemSet.add(itemHash);
+ }
}
return translatedEntries;
}, []),
From 589a891bb3829c1b8b00ead2f1ce18ee74916341 Mon Sep 17 00:00:00 2001
From: spalger
Date: Thu, 9 Jul 2020 15:58:39 -0700
Subject: [PATCH 03/39] jenkins_xpack_saved_objects_field_metrics.sh expects to
be run from the KIBANA_DIR in CI
---
test/scripts/jenkins_xpack_visual_regression.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/scripts/jenkins_xpack_visual_regression.sh b/test/scripts/jenkins_xpack_visual_regression.sh
index 726af43f28e31..ac567a188a6d4 100755
--- a/test/scripts/jenkins_xpack_visual_regression.sh
+++ b/test/scripts/jenkins_xpack_visual_regression.sh
@@ -22,5 +22,5 @@ yarn percy exec -t 10000 -- -- \
# cd "$KIBANA_DIR"
# source "test/scripts/jenkins_xpack_page_load_metrics.sh"
-cd "$XPACK_DIR"
-source "$KIBANA_DIR/test/scripts/jenkins_xpack_saved_objects_field_metrics.sh"
+cd "$KIBANA_DIR"
+source "test/scripts/jenkins_xpack_saved_objects_field_metrics.sh"
From 1a81eb5c19d5e622b4f0ba962153d0812650f6d2 Mon Sep 17 00:00:00 2001
From: Christos Nasikas
Date: Fri, 10 Jul 2020 02:23:20 +0300
Subject: [PATCH 04/39] [SIEM][CASE] IBM Resilient Connector (#66385)
---
x-pack/plugins/actions/README.md | 79 ++-
.../builtin_action_types/case/utils.test.ts | 2 +-
.../server/builtin_action_types/index.ts | 2 +
.../resilient/api.test.ts | 517 +++++++++++++++++
.../builtin_action_types/resilient/api.ts | 7 +
.../builtin_action_types/resilient/config.ts | 14 +
.../builtin_action_types/resilient/index.ts | 24 +
.../builtin_action_types/resilient/mocks.ts | 124 ++++
.../builtin_action_types/resilient/schema.ts | 22 +
.../resilient/service.test.ts | 422 ++++++++++++++
.../builtin_action_types/resilient/service.ts | 197 +++++++
.../resilient/translations.ts | 11 +
.../builtin_action_types/resilient/types.ts | 46 ++
.../resilient/validators.ts | 13 +
.../case/common/api/cases/configure.ts | 8 +-
.../case/common/api/connectors/index.ts | 1 +
.../case/common/api/connectors/resilient.ts | 15 +
x-pack/plugins/case/common/constants.ts | 2 +-
.../components/configure_cases/index.test.tsx | 3 +
.../public/common/lib/connectors/config.ts | 2 +
.../public/common/lib/connectors/index.ts | 1 +
.../common/lib/connectors/resilient/config.ts | 40 ++
.../lib/connectors/resilient/flyout.tsx | 114 ++++
.../common/lib/connectors/resilient/index.tsx | 54 ++
.../common/lib/connectors/resilient/logo.svg | 3 +
.../lib/connectors/resilient/translations.ts | 72 +++
.../common/lib/connectors/resilient/types.ts | 22 +
.../security_solution/public/plugin.tsx | 3 +-
.../alerting_api_integration/common/config.ts | 1 +
.../actions_simulators/server/plugin.ts | 4 +
.../server/resilient_simulation.ts | 111 ++++
.../actions/builtin_action_types/resilient.ts | 549 ++++++++++++++++++
.../tests/actions/index.ts | 1 +
33 files changed, 2465 insertions(+), 21 deletions(-)
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/api.test.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/config.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/translations.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts
create mode 100644 x-pack/plugins/actions/server/builtin_action_types/resilient/validators.ts
create mode 100644 x-pack/plugins/case/common/api/connectors/resilient.ts
create mode 100644 x-pack/plugins/security_solution/public/common/lib/connectors/resilient/config.ts
create mode 100644 x-pack/plugins/security_solution/public/common/lib/connectors/resilient/flyout.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/lib/connectors/resilient/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/lib/connectors/resilient/logo.svg
create mode 100644 x-pack/plugins/security_solution/public/common/lib/connectors/resilient/translations.ts
create mode 100644 x-pack/plugins/security_solution/public/common/lib/connectors/resilient/types.ts
create mode 100644 x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/resilient_simulation.ts
create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts
diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md
index 494f2f38e8bff..9e07727204f88 100644
--- a/x-pack/plugins/actions/README.md
+++ b/x-pack/plugins/actions/README.md
@@ -26,15 +26,19 @@ Table of Contents
- [Executor](#executor)
- [Example](#example)
- [RESTful API](#restful-api)
- - [`POST /api/actions/action`: Create action](#post-apiaction-create-action)
- - [`DELETE /api/actions/action/{id}`: Delete action](#delete-apiactionid-delete-action)
- - [`GET /api/actions`: Get all actions](#get-apiactiongetall-get-all-actions)
- - [`GET /api/actions/action/{id}`: Get action](#get-apiactionid-get-action)
- - [`GET /api/actions/list_action_types`: List action types](#get-apiactiontypes-list-action-types)
- - [`PUT /api/actions/action/{id}`: Update action](#put-apiactionid-update-action)
- - [`POST /api/actions/action/{id}/_execute`: Execute action](#post-apiactionidexecute-execute-action)
+ - [`POST /api/actions/action`: Create action](#post-apiactionsaction-create-action)
+ - [`DELETE /api/actions/action/{id}`: Delete action](#delete-apiactionsactionid-delete-action)
+ - [`GET /api/actions`: Get all actions](#get-apiactions-get-all-actions)
+ - [`GET /api/actions/action/{id}`: Get action](#get-apiactionsactionid-get-action)
+ - [`GET /api/actions/list_action_types`: List action types](#get-apiactionslist_action_types-list-action-types)
+ - [`PUT /api/actions/action/{id}`: Update action](#put-apiactionsactionid-update-action)
+ - [`POST /api/actions/action/{id}/_execute`: Execute action](#post-apiactionsactionid_execute-execute-action)
- [Firing actions](#firing-actions)
+ - [Accessing a scoped ActionsClient](#accessing-a-scoped-actionsclient)
+ - [actionsClient.enqueueExecution(options)](#actionsclientenqueueexecutionoptions)
- [Example](#example-1)
+ - [actionsClient.execute(options)](#actionsclientexecuteoptions)
+ - [Example](#example-2)
- [Built-in Action Types](#built-in-action-types)
- [Server log](#server-log)
- [`config`](#config)
@@ -70,6 +74,11 @@ Table of Contents
- [`secrets`](#secrets-7)
- [`params`](#params-7)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1)
+ - [IBM Resilient](#ibm-resilient)
+ - [`config`](#config-8)
+ - [`secrets`](#secrets-8)
+ - [`params`](#params-8)
+ - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-2)
- [Command Line Utility](#command-line-utility)
- [Developing New Action Types](#developing-new-action-types)
@@ -99,7 +108,7 @@ Built-In-Actions are configured using the _xpack.actions_ namespoace under _kiba
| _xpack.actions._**enabled** | Feature toggle which enabled Actions in Kibana. | boolean |
| _xpack.actions._**whitelistedHosts** | Which _hostnames_ are whitelisted for the Built-In-Action? This list should contain hostnames of every external service you wish to interact with using Webhooks, Email or any other built in Action. Note that you may use the string "\*" in place of a specific hostname to enable Kibana to target any URL, but keep in mind the potential use of such a feature to execute [SSRF](https://www.owasp.org/index.php/Server_Side_Request_Forgery) attacks from your server. | Array |
| _xpack.actions._**enabledActionTypes** | A list of _actionTypes_ id's that are enabled. A "\*" may be used as an element to indicate all registered actionTypes should be enabled. The actionTypes registered for Kibana are `.server-log`, `.slack`, `.email`, `.index`, `.pagerduty`, `.webhook`. Default: `["*"]` | Array |
-| _xpack.actions._**preconfigured** | A object of action id / preconfigured actions. Default: `{}` | Array
+
+
+
+
+
@@ -1683,7 +1709,7 @@ exports[`NewVisModal should render as expected 1`] = `
-
+
+
+
+
+
+
-
+
+
+
-
+
+
+
+
+
+
+
@@ -2073,120 +2104,125 @@ exports[`NewVisModal should render as expected 1`] = `
aria-live="polite"
class="euiScreenReaderOnly"
/>
-
+
+
+
+
+
+
-
+
+
+
-
+
+
+
+
+
+
+
@@ -2307,120 +2343,125 @@ exports[`NewVisModal should render as expected 1`] = `
aria-live="polite"
class="euiScreenReaderOnly"
/>
-
+
+
+
+
+
+
-
+
+
+
-
+
+
+
+
+
+
+
@@ -2643,261 +2684,272 @@ exports[`NewVisModal should render as expected 1`] = `
className="visNewVisDialog__types"
data-test-subj="visNewDialogTypes"
>
-
-
- Vis Type 1
-
- }
- onBlur={[Function]}
- onClick={[Function]}
- onFocus={[Function]}
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
- role="menuitem"
+
-
-
-
- Vis with alias Url
-
- }
- onBlur={[Function]}
- onClick={[Function]}
- onFocus={[Function]}
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
- role="menuitem"
+
+ Vis Type 1
+
+
+
+
+
+
+
-
+
+
+
-
-
-
+
+ Vis with search
+
+
+
+
+
+
+
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
index 535aecba26770..f3e5520a14fe2 100644
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "24.1.0",
+ "@elastic/eui": "26.3.1",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json
index 612ae3806177c..b9c5b3bc5b836 100644
--- a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json
+++ b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "24.1.0",
+ "@elastic/eui": "26.3.1",
"react": "^16.12.0"
},
"scripts": {
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
index 0a6b5fb185d30..95fafdf221c64 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "24.1.0",
+ "@elastic/eui": "26.3.1",
"react": "^16.12.0"
},
"scripts": {
diff --git a/x-pack/dev-tools/jest/setup/polyfills.js b/x-pack/dev-tools/jest/setup/polyfills.js
index 822802f3dacb7..a841a3bf9cad0 100644
--- a/x-pack/dev-tools/jest/setup/polyfills.js
+++ b/x-pack/dev-tools/jest/setup/polyfills.js
@@ -21,3 +21,7 @@ require('whatwg-fetch');
if (!global.URL.hasOwnProperty('createObjectURL')) {
Object.defineProperty(global.URL, 'createObjectURL', { value: () => '' });
}
+
+// Will be replaced with a better solution in EUI
+// https://github.com/elastic/eui/issues/3713
+global._isJest = true;
diff --git a/x-pack/package.json b/x-pack/package.json
index b721cb2fc563a..29264f8920e5d 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -196,7 +196,7 @@
"@elastic/apm-rum-react": "^1.1.2",
"@elastic/datemath": "5.0.3",
"@elastic/ems-client": "7.9.3",
- "@elastic/eui": "24.1.0",
+ "@elastic/eui": "26.3.1",
"@elastic/filesaver": "1.1.2",
"@elastic/maki": "6.3.0",
"@elastic/node-crypto": "1.2.1",
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx
index a09482d663f65..a173f4068db6a 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx
@@ -11,6 +11,12 @@ import { ErrorGroupList } from '../index';
import props from './props.json';
import { MockUrlParamsContextProvider } from '../../../../../context/UrlParamsContext/MockUrlParamsContextProvider';
+jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
+ return {
+ htmlIdGenerator: () => () => `generated-id`,
+ };
+});
+
describe('ErrorGroupOverview -> List', () => {
beforeAll(() => {
mockMoment();
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap
index 6a20e3c103709..a86f7fdf41f4f 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap
@@ -133,6 +133,8 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = `
List should render with data 1`] = `
List should render with data 1`] = `
-
-
+
+
+ 1
+
+
+
+
+
-
+
diff --git a/x-pack/plugins/canvas/.storybook/storyshots.test.js b/x-pack/plugins/canvas/.storybook/storyshots.test.js
index b9fe0914b3698..a3412c3a14e79 100644
--- a/x-pack/plugins/canvas/.storybook/storyshots.test.js
+++ b/x-pack/plugins/canvas/.storybook/storyshots.test.js
@@ -63,6 +63,14 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
};
});
+// To be resolved by EUI team.
+// https://github.com/elastic/eui/issues/3712
+jest.mock('@elastic/eui/lib/components/overlay_mask/overlay_mask', () => {
+ return {
+ EuiOverlayMask: ({children}) => children,
+ };
+});
+
// Disabling this test due to https://github.com/elastic/eui/issues/2242
jest.mock(
'../public/components/workpad_header/share_menu/flyout/__examples__/share_website_flyout.stories',
diff --git a/x-pack/plugins/canvas/public/components/autocomplete/autocomplete.js b/x-pack/plugins/canvas/public/components/autocomplete/autocomplete.js
index 8afa5d16b59fd..7dc8b762359f9 100644
--- a/x-pack/plugins/canvas/public/components/autocomplete/autocomplete.js
+++ b/x-pack/plugins/canvas/public/components/autocomplete/autocomplete.js
@@ -15,7 +15,7 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { EuiFlexGroup, EuiFlexItem, EuiPanel, keyCodes } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiPanel, keys } from '@elastic/eui';
/**
* An autocomplete component. Currently this is only used for the expression editor but in theory
@@ -134,27 +134,27 @@ export class Autocomplete extends React.Component {
* the item selection, closing the menu, etc.
*/
onKeyDown = (e) => {
- const { ESCAPE, TAB, ENTER, UP, DOWN, LEFT, RIGHT } = keyCodes;
- const { keyCode } = e;
+ const { BACKSPACE, ESCAPE, TAB, ENTER, ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT } = keys;
+ const { key } = e;
const { items } = this.props;
const { isOpen, selectedIndex } = this.state;
- if ([ESCAPE, LEFT, RIGHT].includes(keyCode)) {
+ if ([ESCAPE, ARROW_LEFT, ARROW_RIGHT].includes(key)) {
this.setState({ isOpen: false });
}
- if ([TAB, ENTER].includes(keyCode) && isOpen && selectedIndex >= 0) {
+ if ([TAB, ENTER].includes(key) && isOpen && selectedIndex >= 0) {
e.preventDefault();
this.onSubmit();
- } else if (keyCode === UP && items.length > 0 && isOpen) {
+ } else if (key === ARROW_UP && items.length > 0 && isOpen) {
e.preventDefault();
this.selectPrevious();
- } else if (keyCode === DOWN && items.length > 0 && isOpen) {
+ } else if (key === ARROW_DOWN && items.length > 0 && isOpen) {
e.preventDefault();
this.selectNext();
- } else if (e.key === 'Backspace') {
+ } else if (key === BACKSPACE) {
this.setState({ isOpen: true });
- } else if (!['Shift', 'Control', 'Alt', 'Meta'].includes(e.key)) {
+ } else if (!['Shift', 'Control', 'Alt', 'Meta'].includes(key)) {
this.setState({ selectedIndex: -1 });
}
};
diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot
index ea31d1daa97ca..97d13dcd69830 100644
--- a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot
@@ -156,6 +156,8 @@ exports[`Storyshots components/WorkpadTemplates default 1`] = `
-
-
+
+
+ 1
+
+
+
+
+
-
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.ts
index 0e1bb796cbf2e..8f72875a32bd4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Breadcrumb as EuiBreadcrumb } from '@elastic/eui';
+import { EuiBreadcrumb } from '@elastic/eui';
import { History } from 'history';
import { letBrowserHandleEvent } from '../react_router_helpers';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.tsx
index ad3cd65c09516..530117e197616 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.tsx
@@ -6,7 +6,7 @@
import React, { useContext, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
-import { Breadcrumb as EuiBreadcrumb } from '@elastic/eui';
+import { EuiBreadcrumb } from '@elastic/eui';
import { KibanaContext, IKibanaContext } from '../../index';
import { appSearchBreadcrumbs, TBreadcrumbs } from './generate_breadcrumbs';
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
index dfcbb51869466..89a95135bb07a 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
@@ -127,8 +127,8 @@ describe('Data Streams tab', () => {
const { tableCellsValues } = table.getMetaData('dataStreamTable');
expect(tableCellsValues).toEqual([
- ['', 'dataStream1', '1', ''],
- ['', 'dataStream2', '1', ''],
+ ['', 'dataStream1', '1', 'Delete'],
+ ['', 'dataStream2', '1', 'Delete'],
]);
});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
index 276101486aa61..f7ebc0bcf632b 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
@@ -139,7 +139,7 @@ describe('Index Templates tab', () => {
ilmPolicyName,
composedOfString,
hasContent ? 'M S A' : 'None', // M S A -> Mappings Settings Aliases badges
- '', // Column of actions
+ 'EditDelete', // Column of actions
]);
} catch (e) {
console.error(`Error in index template at row ${i}`); // eslint-disable-line no-console
@@ -162,7 +162,7 @@ describe('Index Templates tab', () => {
indexPatterns.join(', '),
ilmPolicyName,
hasContent ? 'M S A' : 'None', // M S A -> Mappings Settings Aliases badges
- '', // Column of actions
+ 'EditDelete', // Column of actions
]);
} catch (e) {
console.error(`Error in legacy template at row ${i}`); // eslint-disable-line no-console
diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js
index 8e8c2632a2372..49902d8b09675 100644
--- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js
+++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js
@@ -198,18 +198,10 @@ describe('index table', () => {
});
test('should show system indices only when the switch is turned on', () => {
const rendered = mountWithIntl(component);
- snapshot(
- rendered
- .find('.euiPagination .euiPaginationButton .euiButtonEmpty__content > span')
- .map((span) => span.text())
- );
+ snapshot(rendered.find('.euiPagination li').map((item) => item.text()));
const switchControl = rendered.find('.euiSwitch__button');
switchControl.simulate('click');
- snapshot(
- rendered
- .find('.euiPagination .euiPaginationButton .euiButtonEmpty__content > span')
- .map((span) => span.text())
- );
+ snapshot(rendered.find('.euiPagination li').map((item) => item.text()));
});
test('should filter based on content of search input', () => {
const rendered = mountWithIntl(component);
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts
index 86eb88017b77f..6f09e51255f3b 100644
--- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts
@@ -65,7 +65,7 @@ describe('', () => {
const { name, usedBy } = componentTemplates[i];
const usedByText = usedBy.length === 0 ? 'Not in use' : usedBy.length.toString();
- expect(row).toEqual(['', name, usedByText, '', '', '', '']);
+ expect(row).toEqual(['', name, usedByText, '', '', '', 'EditDelete']);
});
});
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.scss b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.scss
index 5ad558dfafe7d..c732bc349687d 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.scss
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.scss
@@ -1,4 +1,4 @@
-@import '@elastic/eui/src/components/header/variables';
+@import '@elastic/eui/src/global_styling/variables/header';
@import '@elastic/eui/src/components/nav_drawer/variables';
/**
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts
index 3e0b78d4f2e9d..8d6a83a625651 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts
@@ -72,7 +72,7 @@ describe('', () => {
tableCellsValues.forEach((row, i) => {
const pipeline = pipelines[i];
- expect(row).toEqual(['', pipeline.name, '']);
+ expect(row).toEqual(['', pipeline.name, 'EditDelete']);
});
});
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/inline_text_input.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/inline_text_input.tsx
index 00ac8d4f6d729..ea936115f1ac9 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/inline_text_input.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/inline_text_input.tsx
@@ -5,7 +5,7 @@
*/
import classNames from 'classnames';
import React, { FunctionComponent, useState, useEffect, useCallback } from 'react';
-import { EuiFieldText, EuiText, keyCodes } from '@elastic/eui';
+import { EuiFieldText, EuiText, keys } from '@elastic/eui';
export interface Props {
placeholder: string;
@@ -40,10 +40,10 @@ export const InlineTextInput: FunctionComponent = ({
useEffect(() => {
const keyboardListener = (event: KeyboardEvent) => {
- if (event.keyCode === keyCodes.ESCAPE || event.code === 'Escape') {
+ if (event.key === keys.ESCAPE || event.code === 'Escape') {
setIsShowingTextInput(false);
}
- if (event.keyCode === keyCodes.ENTER || event.code === 'Enter') {
+ if (event.key === keys.ENTER || event.code === 'Enter') {
submitChange();
}
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx
index db71cf25faacc..4458bd66c88de 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FunctionComponent, memo, useRef, useEffect } from 'react';
-import { EuiFlexGroup, EuiFlexItem, keyCodes } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, keys } from '@elastic/eui';
import { List, WindowScroller } from 'react-virtualized';
import { ProcessorInternal, ProcessorSelector } from '../../types';
@@ -52,7 +52,7 @@ export const ProcessorsTree: FunctionComponent = memo((props) => {
useEffect(() => {
const cancelMoveKbListener = (event: KeyboardEvent) => {
// x-browser support per https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
- if (event.keyCode === keyCodes.ESCAPE || event.code === 'Escape') {
+ if (event.key === keys.ESCAPE || event.code === 'Escape') {
onAction({ type: 'cancelMove' });
}
};
diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
index cc8cbfe679eff..f0feb826f956d 100644
--- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
+++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
@@ -294,7 +294,7 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
- Please address the errors in your form.
+ Please address the highlighted errors.
- Please address the errors in your form.
+ Please address the highlighted errors.
- Please address the errors in your form.
+ Please address the highlighted errors.
- Please address the errors in your form.
+ Please address the highlighted errors.
-
+
-
+
-
+
{
if (isKeyboardEvent(e)) {
- if (e.keyCode === keyCodes.ENTER) {
+ if (e.key === keys.ENTER) {
e.preventDefault();
this._togglePopover();
- } else if (e.keyCode === keyCodes.DOWN) {
+ } else if (e.key === keys.ARROW_DOWN) {
this._openPopover();
}
}
diff --git a/x-pack/plugins/ml/public/application/components/field_title_bar/field_title_bar.test.js b/x-pack/plugins/ml/public/application/components/field_title_bar/field_title_bar.test.js
index 056fd04857cba..1b33d68042295 100644
--- a/x-pack/plugins/ml/public/application/components/field_title_bar/field_title_bar.test.js
+++ b/x-pack/plugins/ml/public/application/components/field_title_bar/field_title_bar.test.js
@@ -62,7 +62,7 @@ describe('FieldTitleBar', () => {
expect(hasClassName).toBeTruthy();
});
- test(`tooltip hovering`, () => {
+ test(`tooltip hovering`, (done) => {
const props = { card: { fieldName: 'foo', type: 'bar' } };
const wrapper = mountWithIntl();
const container = wrapper.find({ className: 'field-name' });
@@ -70,9 +70,14 @@ describe('FieldTitleBar', () => {
expect(wrapper.find('EuiToolTip').children()).toHaveLength(1);
container.simulate('mouseover');
- expect(wrapper.find('EuiToolTip').children()).toHaveLength(2);
-
- container.simulate('mouseout');
- expect(wrapper.find('EuiToolTip').children()).toHaveLength(1);
+ // EuiToolTip mounts children after a 250ms delay
+ setTimeout(() => {
+ wrapper.update();
+ expect(wrapper.find('EuiToolTip').children()).toHaveLength(2);
+ container.simulate('mouseout');
+ wrapper.update();
+ expect(wrapper.find('EuiToolTip').children()).toHaveLength(1);
+ done();
+ }, 250);
});
});
diff --git a/x-pack/plugins/ml/public/application/components/field_type_icon/field_type_icon.test.js b/x-pack/plugins/ml/public/application/components/field_type_icon/field_type_icon.test.js
index f616f7cb1b866..7e37dc10ade33 100644
--- a/x-pack/plugins/ml/public/application/components/field_type_icon/field_type_icon.test.js
+++ b/x-pack/plugins/ml/public/application/components/field_type_icon/field_type_icon.test.js
@@ -35,7 +35,8 @@ describe('FieldTypeIcon', () => {
expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(1);
container.simulate('mouseover');
- expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(2);
+ // EuiToolTip mounts children after a 250ms delay
+ setTimeout(() => expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(2), 250);
container.simulate('mouseout');
expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(1);
diff --git a/x-pack/plugins/ml/public/application/components/ml_in_memory_table/types.ts b/x-pack/plugins/ml/public/application/components/ml_in_memory_table/types.ts
index b85fb634891e5..05b941f2544b4 100644
--- a/x-pack/plugins/ml/public/application/components/ml_in_memory_table/types.ts
+++ b/x-pack/plugins/ml/public/application/components/ml_in_memory_table/types.ts
@@ -48,7 +48,7 @@ type BUTTON_ICON_COLORS = any;
type ButtonIconColorsFunc = (item: T) => BUTTON_ICON_COLORS; // (item) => oneOf(ICON_BUTTON_COLORS)
interface DefaultItemActionType {
type?: 'icon' | 'button';
- name: string;
+ name: ReactNode;
description: string;
onClick?(item: T): void;
href?: string;
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_list/group_list.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_list/group_list.js
index ef8a7dfcb7417..d989064c5057f 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_list/group_list.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_list/group_list.js
@@ -7,7 +7,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
-import { EuiIcon, keyCodes } from '@elastic/eui';
+import { EuiIcon, keys } from '@elastic/eui';
import { JobGroup } from '../../../job_group';
@@ -63,17 +63,17 @@ export class GroupList extends Component {
};
handleKeyDown = (event, group, index) => {
- switch (event.keyCode) {
- case keyCodes.ENTER:
+ switch (event.key) {
+ case keys.ENTER:
this.selectGroup(group);
break;
- case keyCodes.SPACE:
+ case keys.SPACE:
this.selectGroup(group);
break;
- case keyCodes.DOWN:
+ case keys.ARROW_DOWN:
this.moveDown(event, index);
break;
- case keyCodes.UP:
+ case keys.ARROW_UP:
this.moveUp(event, index);
break;
}
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/new_group_input/new_group_input.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/new_group_input/new_group_input.js
index 6a97d32f8cf0c..8118fc7f6df4b 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/new_group_input/new_group_input.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/new_group_input/new_group_input.js
@@ -13,7 +13,7 @@ import {
EuiFlexItem,
EuiFieldText,
EuiFormRow,
- keyCodes,
+ keys,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -61,7 +61,7 @@ export class NewGroupInput extends Component {
newGroupKeyPress = (e) => {
if (
- e.keyCode === keyCodes.ENTER &&
+ e.key === keys.ENTER &&
this.state.groupsValidationError === '' &&
this.state.tempNewGroupName !== ''
) {
diff --git a/x-pack/plugins/painless_lab/public/styles/_index.scss b/x-pack/plugins/painless_lab/public/styles/_index.scss
index e6c9574161fd8..c45b0068ded21 100644
--- a/x-pack/plugins/painless_lab/public/styles/_index.scss
+++ b/x-pack/plugins/painless_lab/public/styles/_index.scss
@@ -1,4 +1,4 @@
-@import '@elastic/eui/src/components/header/variables';
+@import '@elastic/eui/src/global_styling/variables/header';
@import '@elastic/eui/src/components/nav_drawer/variables';
/**
diff --git a/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap
index b05e74c516cd4..ddba7842f1199 100644
--- a/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap
+++ b/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap
@@ -129,11 +129,14 @@ Array [
}
/>
@@ -442,12 +443,13 @@ Array [
handler={[Function]}
/>
}
/>
diff --git a/x-pack/plugins/searchprofiler/public/styles/_index.scss b/x-pack/plugins/searchprofiler/public/styles/_index.scss
index e63042cf8fe2f..a33fcc9da53d5 100644
--- a/x-pack/plugins/searchprofiler/public/styles/_index.scss
+++ b/x-pack/plugins/searchprofiler/public/styles/_index.scss
@@ -1,4 +1,4 @@
-@import '@elastic/eui/src/components/header/variables';
+@import '@elastic/eui/src/global_styling/variables/header';
@import 'mixins';
diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap
index 050af4bd20a47..48c5680bac4e4 100644
--- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap
+++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap
@@ -173,7 +173,9 @@ exports[`APIKeysGridPage renders permission denied if user does not have require
-
+
{
wrapper.update();
});
+ wrapper.update();
const { title, confirmButtonText } = wrapper.find(EuiConfirmModal).props();
expect(title).toMatchInlineSnapshot(`"Delete role mapping 'delete-me'?"`);
expect(confirmButtonText).toMatchInlineSnapshot(`"Delete role mapping"`);
@@ -127,6 +128,7 @@ describe('DeleteProvider', () => {
wrapper.update();
});
+ wrapper.update();
const { title, confirmButtonText } = wrapper.find(EuiConfirmModal).props();
expect(title).toMatchInlineSnapshot(`"Delete 2 role mappings?"`);
expect(confirmButtonText).toMatchInlineSnapshot(`"Delete role mappings"`);
@@ -204,6 +206,7 @@ describe('DeleteProvider', () => {
});
await act(async () => {
+ wrapper.update();
findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click');
await nextTick();
wrapper.update();
@@ -268,6 +271,7 @@ describe('DeleteProvider', () => {
});
await act(async () => {
+ wrapper.update();
findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click');
await nextTick();
wrapper.update();
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx
index c457401196ba1..6c43f2f7ea734 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx
@@ -88,7 +88,7 @@ export class PrivilegeSpaceForm extends Component {
public render() {
return (
-
+
diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap
index a4d689121bcaa..16b9de4bae083 100644
--- a/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap
+++ b/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap
@@ -68,7 +68,9 @@ exports[` renders permission denied if required 1`] = `
-
+
diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx
index 7807b4a8a77d8..4371a180bc81d 100644
--- a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx
@@ -90,7 +90,7 @@ describe('CasesTableFilters ', () => {
wrapper
.find(`[data-test-subj="search-cases"]`)
.last()
- .simulate('keyup', { keyCode: 13, target: { value: 'My search' } });
+ .simulate('keyup', { key: 'Enter', target: { value: 'My search' } });
expect(onFilterChanged).toBeCalledWith({ search: 'My search' });
});
it('should call onFilterChange when status toggled', () => {
diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx
index 5e19211b47078..8d14b2357f450 100644
--- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx
@@ -95,7 +95,11 @@ describe('Configuration button', () => {
);
newWrapper.find('[data-test-subj="configure-case-button"]').first().simulate('mouseOver');
-
- expect(newWrapper.find('.euiToolTipPopover').text()).toBe(`${titleTooltip}${msgTooltip}`);
+ // EuiToolTip mounts children after a 250ms delay
+ setTimeout(
+ () =>
+ expect(newWrapper.find('.euiToolTipPopover').text()).toBe(`${titleTooltip}${msgTooltip}`),
+ 250
+ );
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
index 26775608637c0..0f93e954ab853 100644
--- a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
@@ -225,6 +225,12 @@ exports[`Paginated Table Component rendering it renders the default load more ta
"subdued": "#81858f",
"warning": "#ffce7a",
},
+ "euiFacetGutterSizes": Object {
+ "gutterLarge": "12px",
+ "gutterMedium": "8px",
+ "gutterNone": 0,
+ "gutterSmall": "4px",
+ },
"euiFilePickerTallHeight": "128px",
"euiFlyoutBorder": "1px solid #343741",
"euiFocusBackgroundColor": "#232635",
@@ -272,6 +278,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta
"euiGradientMiddle": "#282a31",
"euiGradientStartStop": "#2e3039",
"euiHeaderBackgroundColor": "#1d1e24",
+ "euiHeaderBorderColor": "#343741",
"euiHeaderBreadcrumbColor": "#d4dae5",
"euiHeaderChildSize": "48px",
"euiHeaderHeight": "48px",
@@ -589,9 +596,9 @@ exports[`Paginated Table Component rendering it renders the default load more ta
"top": "euiToolTipTop",
},
"euiTooltipBackgroundColor": "#000000",
- "euiZComboBox": 8001,
"euiZContent": 0,
"euiZContentMenu": 2000,
+ "euiZFlyout": 3000,
"euiZHeader": 1000,
"euiZLevel0": 0,
"euiZLevel1": 1000,
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx
index b5c4e6481216c..be0ba04c53233 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx
@@ -5,7 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
-import { EuiBreadcrumbs, Breadcrumb, EuiCode, EuiBetaBadge } from '@elastic/eui';
+import { EuiBreadcrumbs, EuiBreadcrumb, EuiCode, EuiBetaBadge } from '@elastic/eui';
import styled from 'styled-components';
import React, { memo } from 'react';
import { useResolverTheme } from '../assets';
@@ -58,7 +58,7 @@ export const StyledBreadcrumbs = memo(function StyledBreadcrumbs({
breadcrumbs,
truncate,
}: {
- breadcrumbs: Breadcrumb[];
+ breadcrumbs: EuiBreadcrumb[];
truncate?: boolean;
}) {
const {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap
index 22f89ffc6927e..eeb789c14a8f8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap
@@ -225,6 +225,12 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = `
"subdued": "#81858f",
"warning": "#ffce7a",
},
+ "euiFacetGutterSizes": Object {
+ "gutterLarge": "12px",
+ "gutterMedium": "8px",
+ "gutterNone": 0,
+ "gutterSmall": "4px",
+ },
"euiFilePickerTallHeight": "128px",
"euiFlyoutBorder": "1px solid #343741",
"euiFocusBackgroundColor": "#232635",
@@ -272,6 +278,7 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = `
"euiGradientMiddle": "#282a31",
"euiGradientStartStop": "#2e3039",
"euiHeaderBackgroundColor": "#1d1e24",
+ "euiHeaderBorderColor": "#343741",
"euiHeaderBreadcrumbColor": "#d4dae5",
"euiHeaderChildSize": "48px",
"euiHeaderHeight": "48px",
@@ -589,9 +596,9 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = `
"top": "euiToolTipTop",
},
"euiTooltipBackgroundColor": "#000000",
- "euiZComboBox": 8001,
"euiZContent": 0,
"euiZContentMenu": 2000,
+ "euiZFlyout": 3000,
"euiZHeader": 1000,
"euiZLevel0": 0,
"euiZLevel1": 1000,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx
index 48706c4f23906..e2def46b936be 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx
@@ -100,7 +100,7 @@ describe('StatefulOpenTimeline', () => {
);
wrapper
.find('[data-test-subj="search-bar"] input')
- .simulate('keyup', { keyCode: 13, target: { value: ' abcd ' } });
+ .simulate('keyup', { key: 'Enter', target: { value: ' abcd ' } });
expect(wrapper.find('[data-test-subj="search-row"]').first().prop('query')).toEqual('abcd');
});
@@ -122,7 +122,7 @@ describe('StatefulOpenTimeline', () => {
wrapper
.find('[data-test-subj="search-bar"] input')
- .simulate('keyup', { keyCode: 13, target: { value: ' abcd ' } });
+ .simulate('keyup', { key: 'Enter', target: { value: ' abcd ' } });
expect(wrapper.find('[data-test-subj="query-message"]').first().text()).toContain(
'Showing: 11 timelines with'
@@ -147,7 +147,7 @@ describe('StatefulOpenTimeline', () => {
wrapper
.find('[data-test-subj="search-bar"] input')
- .simulate('keyup', { keyCode: 13, target: { value: ' abcd ' } });
+ .simulate('keyup', { key: 'Enter', target: { value: ' abcd ' } });
expect(wrapper.find('[data-test-subj="selectable-query-text"]').first().text()).toEqual(
'with "abcd"'
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx
index 2e6dcb85ad769..18c2e4cff16bf 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx
@@ -138,7 +138,7 @@ describe('SearchRow', () => {
wrapper
.find('[data-test-subj="search-bar"] input')
- .simulate('keyup', { keyCode: 13, target: { value: 'abcd' } });
+ .simulate('keyup', { key: 'Enter', target: { value: 'abcd' } });
expect(onQueryChange).toHaveBeenCalled();
});
diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx
index 64ddc4428b515..b0103800d4105 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx
@@ -19,6 +19,14 @@ import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mock
import { featuresPluginMock } from '../../../../features/public/mocks';
import { Feature } from '../../../../features/public';
+// To be resolved by EUI team.
+// https://github.com/elastic/eui/issues/3712
+jest.mock('@elastic/eui/lib/components/overlay_mask', () => {
+ return {
+ EuiOverlayMask: (props: any) =>
{props.children}
,
+ };
+});
+
const space = {
id: 'my-space',
name: 'My Space',
diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap
index a5dd1abe31ca4..cf80d2de38b3f 100644
--- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap
@@ -262,6 +262,9 @@ exports[`DonutChart component passes correct props without errors for valid prop
"visible": false,
},
},
+ "background": Object {
+ "color": "rgba(255, 255, 255, 1)",
+ },
"barSeriesStyle": Object {
"displayValue": Object {
"fill": "rgba(105, 112, 125, 1)",
diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap
index 45706720a5e70..004de391a51a4 100644
--- a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap
@@ -132,13 +132,6 @@ exports[`PingListExpandedRow renders link to docs if body is not recorded but it
-
diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap
index 823346db3518a..316188eebf65b 100644
--- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap
@@ -27,6 +27,8 @@ Array [
-
+
+
diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx
index b5fe5d17312c6..7a8ba3fd32cd9 100644
--- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx
@@ -9,6 +9,12 @@ import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { AvailabilityReporting } from '../availability_reporting';
import { StatusTag } from '../location_status_tags';
+jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
+ return {
+ htmlIdGenerator: () => () => `generated-id`,
+ };
+});
+
describe('AvailabilityReporting component', () => {
let allLocations: StatusTag[];
diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/location_status_tags.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/location_status_tags.test.tsx
index bfeaa6085e998..d4aae6b022d9c 100644
--- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/location_status_tags.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/location_status_tags.test.tsx
@@ -10,6 +10,12 @@ import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { MonitorLocation } from '../../../../../../common/runtime_types/monitor';
import { LocationStatusTags } from '../index';
+jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
+ return {
+ htmlIdGenerator: () => () => `generated-id`,
+ };
+});
+
describe('LocationStatusTags component', () => {
let monitorLocations: MonitorLocation[];
diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap
index 6667da401d6a9..b711b464418a2 100644
--- a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap
@@ -288,7 +288,9 @@ exports[`EmptyState component does not render empty state with appropriate base
-
+
-
+
-
+
-
+
{
+ return {
+ htmlIdGenerator: () => () => `generated-id`,
+ };
+});
+
const testFooPings: Ping[] = [
makePing({
docId: 'foo1',
diff --git a/x-pack/test_utils/testbed/testbed.ts b/x-pack/test_utils/testbed/testbed.ts
index febd2a63cf5b8..e981d0b6918a3 100644
--- a/x-pack/test_utils/testbed/testbed.ts
+++ b/x-pack/test_utils/testbed/testbed.ts
@@ -244,8 +244,7 @@ export const registerTestBed = (
const formInput = findTestSubject(comboBox, 'comboBoxSearchInput');
setInputValue(formInput, value);
- // keyCode 13 === ENTER
- comboBox.simulate('keydown', { keyCode: 13 });
+ comboBox.simulate('keydown', { key: 'Enter' });
component.update();
};
diff --git a/yarn.lock b/yarn.lock
index 2d575634686a3..390b89ea5ce7d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2230,10 +2230,10 @@
tabbable "^1.1.0"
uuid "^3.1.0"
-"@elastic/eui@24.1.0":
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-24.1.0.tgz#40593cc474237e8c464d182faa50c748b3f66822"
- integrity sha512-Y7s327h0Z8dsO6MY7Sn1k5pOrf9ZjWH/ZE2gVtfBn2He5aFahS/+A434EBqFG0YV5W1VZtYiXtSj0AE1gjtrrw==
+"@elastic/eui@26.3.1":
+ version "26.3.1"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-26.3.1.tgz#c05cac2d0b246adb2aab7df54f34ecc6ad4f48e1"
+ integrity sha512-bNCMJIqdx7TrhXtbnYN6nxR4fjfTmdpB1ptVDThpt8+1rMSL7ZvxcVL4xg9Fjf6sKLcEQyXfDhr3u4+N3Sa4FA==
dependencies:
"@types/chroma-js" "^2.0.0"
"@types/enzyme" "^3.1.13"
@@ -2258,6 +2258,7 @@
react-virtualized "^9.21.2"
resize-observer-polyfill "^1.5.0"
tabbable "^3.0.0"
+ text-diff "^1.0.1"
uuid "^3.1.0"
"@elastic/filesaver@1.1.2":
@@ -29949,6 +29950,11 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
+text-diff@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/text-diff/-/text-diff-1.0.1.tgz#6c105905435e337857375c9d2f6ca63e453ff565"
+ integrity sha1-bBBZBUNeM3hXN1ydL2ymPkU/9WU=
+
text-hex@1.0.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
From 781220e071e92f0558a5c12c054634b5f63a36b2 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Fri, 10 Jul 2020 00:29:57 -0400
Subject: [PATCH 12/39] address index templates feedback (#71353)
---
.../template_form/steps/step_logistics.tsx | 25 ++++++++++---------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx
index 2777941175429..ad98aee5fb5f1 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx
@@ -62,20 +62,21 @@ function getFieldsMeta(esDocsBase: string) {
description: (
-
-
- {i18n.translate(
- 'xpack.idxMgmt.templateForm.stepLogistics.dataStreamDocumentionLink',
- {
- defaultMessage: 'Learn more about data streams.',
- }
- )}
-
- >
+
+ {i18n.translate(
+ 'xpack.idxMgmt.templateForm.stepLogistics.dataStreamDocumentionLink',
+ {
+ defaultMessage: 'Learn more about data streams.',
+ }
+ )}
+
),
}}
/>
From cb4020f3fcc2a78a58335abca328f35e9732080a Mon Sep 17 00:00:00 2001
From: Angela Chuang <6295984+angorayc@users.noreply.github.com>
Date: Fri, 10 Jul 2020 10:34:45 +0100
Subject: [PATCH 13/39] [SIEM] fix tooltip of notes (#71342)
* fix tooltip of notes
* fix unit test
* update notes tooltip
* fix unit test
Co-authored-by: Elastic Machine
---
.../timeline/body/actions/index.test.tsx | 69 +++++++++++++++++++
.../timeline/body/actions/index.tsx | 7 +-
.../components/timeline/body/helpers.test.ts | 2 +-
.../components/timeline/body/translations.ts | 6 +-
4 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx
index 53b018fb00adf..78ee9bdd053b2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx
@@ -9,8 +9,10 @@ import { useSelector } from 'react-redux';
import { TestProviders, mockTimelineModel } from '../../../../../common/mock';
import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants';
+import * as i18n from '../translations';
import { Actions } from '.';
+import { TimelineType } from '../../../../../../common/types/timeline';
jest.mock('react-redux', () => {
const origin = jest.requireActual('react-redux');
@@ -202,6 +204,73 @@ describe('Actions', () => {
expect(toggleShowNotes).toBeCalled();
});
+ test('it renders correct tooltip for NotesButton - timeline', () => {
+ const toggleShowNotes = jest.fn();
+
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="add-note"]').prop('toolTip')).toEqual(i18n.NOTES_TOOLTIP);
+ });
+
+ test('it renders correct tooltip for NotesButton - timeline template', () => {
+ (useSelector as jest.Mock).mockReturnValue({
+ ...mockTimelineModel,
+ timelineType: TimelineType.template,
+ });
+ const toggleShowNotes = jest.fn();
+
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="add-note"]').prop('toolTip')).toEqual(
+ i18n.NOTES_DISABLE_TOOLTIP
+ );
+ (useSelector as jest.Mock).mockReturnValue(mockTimelineModel);
+ });
+
test('it does NOT render a pin button when isEventViewer is true', () => {
const onPinClicked = jest.fn();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
index 2039307691321..2facb2b3d0ede 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
@@ -9,6 +9,7 @@ import { EuiButtonIcon, EuiCheckbox, EuiLoadingSpinner, EuiToolTip } from '@elas
import { Note } from '../../../../../common/lib/note';
import { StoreState } from '../../../../../common/store/types';
+import { TimelineType } from '../../../../../../common/types/timeline';
import { TimelineModel } from '../../../../store/timeline/model';
@@ -170,7 +171,11 @@ export const Actions = React.memo(
status={timeline.status}
timelineType={timeline.timelineType}
toggleShowNotes={toggleShowNotes}
- toolTip={timeline.timelineType ? i18n.NOTES_DISABLE_TOOLTIP : i18n.NOTES_TOOLTIP}
+ toolTip={
+ timeline.timelineType === TimelineType.template
+ ? i18n.NOTES_DISABLE_TOOLTIP
+ : i18n.NOTES_TOOLTIP
+ }
updateNote={updateNote}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts
index 7ecd7ec5ed35c..8ba1a999e2b2a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts
@@ -223,7 +223,7 @@ describe('helpers', () => {
eventHasNotes: false,
timelineType: TimelineType.template,
})
- ).toEqual('This event cannot be pinned because it is filtered by a timeline template');
+ ).toEqual('This event may not be pinned while editing a template timeline');
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts
index 5af2f3ef488b0..20467af290b19 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts
@@ -9,14 +9,14 @@ import { i18n } from '@kbn/i18n';
export const NOTES_TOOLTIP = i18n.translate(
'xpack.securitySolution.timeline.body.notes.addOrViewNotesForThisEventTooltip',
{
- defaultMessage: 'Add or view notes for this event',
+ defaultMessage: 'Add notes for this event',
}
);
export const NOTES_DISABLE_TOOLTIP = i18n.translate(
'xpack.securitySolution.timeline.body.notes.disableEventTooltip',
{
- defaultMessage: 'Add notes for event filtered by a timeline template is not allowed',
+ defaultMessage: 'Notes may not be added here while editing a template timeline',
}
);
@@ -48,7 +48,7 @@ export const PINNED_WITH_NOTES = i18n.translate(
export const DISABLE_PIN = i18n.translate(
'xpack.securitySolution.timeline.body.pinning.disablePinnnedTooltip',
{
- defaultMessage: 'This event cannot be pinned because it is filtered by a timeline template',
+ defaultMessage: 'This event may not be pinned while editing a template timeline',
}
);
From c00359ec96654e6364f5196566cdb0b3c6c80a6d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez?=
Date: Fri, 10 Jul 2020 11:55:56 +0200
Subject: [PATCH 14/39] [Logs UI] Limit `extendDatemath` to valid ranges
(#71113)
Co-authored-by: Elastic Machine
---
.../infra/public/utils/datemath.test.ts | 16 +++++++
x-pack/plugins/infra/public/utils/datemath.ts | 43 ++++++++++++++-----
2 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/x-pack/plugins/infra/public/utils/datemath.test.ts b/x-pack/plugins/infra/public/utils/datemath.test.ts
index c8fbe5583db2e..e073afb231b0b 100644
--- a/x-pack/plugins/infra/public/utils/datemath.test.ts
+++ b/x-pack/plugins/infra/public/utils/datemath.test.ts
@@ -196,6 +196,15 @@ describe('extendDatemath()', () => {
diffUnit: 'y',
});
});
+
+ it('Returns no difference if the next value would result in an epoch smaller than 0', () => {
+ // FIXME: Test will fail in ~551 years
+ expect(extendDatemath('now-500y', 'before')).toBeUndefined();
+
+ expect(
+ extendDatemath('1970-01-01T00:00:00.000Z', 'before', '1970-01-01T00:00:00.001Z')
+ ).toBeUndefined();
+ });
});
describe('with a positive operator', () => {
@@ -573,6 +582,13 @@ describe('extendDatemath()', () => {
diffUnit: 'y',
});
});
+
+ it('Returns no difference if the next value would result in an epoch bigger than the max JS date', () => {
+ expect(extendDatemath('now+275760y', 'after')).toBeUndefined();
+ expect(
+ extendDatemath('+275760-09-13T00:00:00.000Z', 'after', '+275760-09-12T23:59:59.999Z')
+ ).toBeUndefined();
+ });
});
});
});
diff --git a/x-pack/plugins/infra/public/utils/datemath.ts b/x-pack/plugins/infra/public/utils/datemath.ts
index f2bd5d94ac2c3..791fe4bdb8da7 100644
--- a/x-pack/plugins/infra/public/utils/datemath.ts
+++ b/x-pack/plugins/infra/public/utils/datemath.ts
@@ -6,6 +6,8 @@
import dateMath, { Unit } from '@elastic/datemath';
+const JS_MAX_DATE = 8640000000000000;
+
export function isValidDatemath(value: string): boolean {
const parsedValue = dateMath.parse(value);
return !!(parsedValue && parsedValue.isValid());
@@ -136,18 +138,24 @@ function extendRelativeDatemath(
// if `diffAmount` is not an integer after normalization, express the difference in the original unit
const shouldKeepDiffUnit = diffAmount % 1 !== 0;
- return {
- value: `now${operator}${normalizedAmount}${normalizedUnit}`,
- diffUnit: shouldKeepDiffUnit ? unit : newUnit,
- diffAmount: shouldKeepDiffUnit ? Math.abs(newAmount - parsedAmount) : diffAmount,
- };
+ const nextValue = `now${operator}${normalizedAmount}${normalizedUnit}`;
+
+ if (isDateInRange(nextValue)) {
+ return {
+ value: nextValue,
+ diffUnit: shouldKeepDiffUnit ? unit : newUnit,
+ diffAmount: shouldKeepDiffUnit ? Math.abs(newAmount - parsedAmount) : diffAmount,
+ };
+ } else {
+ return undefined;
+ }
}
function extendAbsoluteDatemath(
value: string,
direction: 'before' | 'after',
oppositeEdge: string
-): DatemathExtension {
+): DatemathExtension | undefined {
const valueTimestamp = datemathToEpochMillis(value)!;
const oppositeEdgeTimestamp = datemathToEpochMillis(oppositeEdge)!;
const actualTimestampDiff = Math.abs(valueTimestamp - oppositeEdgeTimestamp);
@@ -159,11 +167,15 @@ function extendAbsoluteDatemath(
? valueTimestamp - normalizedTimestampDiff
: valueTimestamp + normalizedTimestampDiff;
- return {
- value: new Date(newValue).toISOString(),
- diffUnit: normalizedDiff.unit,
- diffAmount: normalizedDiff.amount,
- };
+ if (isDateInRange(newValue)) {
+ return {
+ value: new Date(newValue).toISOString(),
+ diffUnit: normalizedDiff.unit,
+ diffAmount: normalizedDiff.amount,
+ };
+ } else {
+ return undefined;
+ }
}
const CONVERSION_RATIOS: Record> = {
@@ -265,3 +277,12 @@ export function normalizeDate(amount: number, unit: Unit): { amount: number; uni
// Cannot go one one unit above. Return as it is
return { amount, unit };
}
+
+function isDateInRange(date: string | number): boolean {
+ try {
+ const epoch = typeof date === 'string' ? datemathToEpochMillis(date) ?? -1 : date;
+ return epoch >= 0 && epoch <= JS_MAX_DATE;
+ } catch {
+ return false;
+ }
+}
From 005128c22306dea4eb36b6301f49f9bd03015602 Mon Sep 17 00:00:00 2001
From: Brandon Morelli
Date: Fri, 10 Jul 2020 03:07:49 -0700
Subject: [PATCH 15/39] [APM] UI text updates (#71333)
---
docs/apm/api.asciidoc | 2 +-
.../components/app/Settings/anomaly_detection/index.tsx | 2 +-
.../components/app/Settings/anomaly_detection/jobs_list.tsx | 4 ++--
.../observability/public/pages/overview/empty_section.ts | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc
index f6bc83d4086c2..97fdcd3e13de9 100644
--- a/docs/apm/api.asciidoc
+++ b/docs/apm/api.asciidoc
@@ -398,7 +398,7 @@ include::api.asciidoc[tag=using-the-APIs]
[%collapsible%open]
======
`version` :::
- (required, string) Name of service.
+ (required, string) Version of service.
`environment` :::
(optional, string) Environment of service.
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx
index 4ef3d78a7d303..6f985d06dba9d 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx
@@ -62,7 +62,7 @@ export const AnomalyDetection = () => {
{i18n.translate('xpack.apm.settings.anomalyDetection.descriptionText', {
defaultMessage:
- 'The Machine Learning anomaly detection integration enables application health status indicators in the Service map by identifying transaction duration anomalies.',
+ 'The Machine Learning anomaly detection integration enables application health status indicators for each configured environment in the Service map by identifying transaction duration anomalies.',
})}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx
index 674b4492c2c9c..83d19aa27ac11 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx
@@ -98,7 +98,7 @@ export const JobsList = ({
{i18n.translate(
'xpack.apm.settings.anomalyDetection.jobList.addEnvironments',
{
- defaultMessage: 'Add environments',
+ defaultMessage: 'Create ML Job',
}
)}
@@ -108,7 +108,7 @@ export const JobsList = ({
diff --git a/x-pack/plugins/observability/public/pages/overview/empty_section.ts b/x-pack/plugins/observability/public/pages/overview/empty_section.ts
index 61456bc88bd3e..e30eda9f3e056 100644
--- a/x-pack/plugins/observability/public/pages/overview/empty_section.ts
+++ b/x-pack/plugins/observability/public/pages/overview/empty_section.ts
@@ -77,7 +77,7 @@ export const getEmptySections = ({ core }: { core: AppMountContext['core'] }): I
icon: 'watchesApp',
description: i18n.translate('xpack.observability.emptySection.apps.alert.description', {
defaultMessage:
- '503 errors stacking up. Applications not responding. CPU and RAM utilization jumping. See these warnings as they happen - not as part of the post-mortem.',
+ 'Are 503 errors stacking up? Are services responding? Is CPU and RAM utilization jumping? See warnings as they happen—not as part of the post-mortem.',
}),
linkTitle: i18n.translate('xpack.observability.emptySection.apps.alert.link', {
defaultMessage: 'Create alert',
From 12ec1bef3af3f8ccc69e718a94e7202651d66c25 Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Fri, 10 Jul 2020 12:40:30 +0200
Subject: [PATCH 16/39] Fix a11y keyboard overlay (#71214)
- reinclude styling for the keyboard overlay (copied from legacy)
- refactor the exports of es_ui_shared to follow new pattern
Co-authored-by: Elastic Machine
---
.../editor/legacy/console_editor/editor.tsx | 4 +++-
.../ace/_ui_ace_keyboard_mode.scss | 24 +++++++++++++++++++
.../__packages_do_not_import__/ace/index.ts | 20 ++++++++++++++++
.../ace}/use_ui_ace_keyboard_mode.tsx | 3 +++
src/plugins/es_ui_shared/public/ace/index.ts | 20 ++++++++++++++++
src/plugins/es_ui_shared/public/index.ts | 5 ++--
.../public/application/editor/editor.tsx | 4 +++-
7 files changed, 75 insertions(+), 5 deletions(-)
create mode 100644 src/plugins/es_ui_shared/__packages_do_not_import__/ace/_ui_ace_keyboard_mode.scss
create mode 100644 src/plugins/es_ui_shared/__packages_do_not_import__/ace/index.ts
rename src/plugins/es_ui_shared/{public => __packages_do_not_import__/ace}/use_ui_ace_keyboard_mode.tsx (99%)
create mode 100644 src/plugins/es_ui_shared/public/ace/index.ts
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 6d4f532887cd9..880069d8ebc7a 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
@@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n';
import { debounce } from 'lodash';
import { parse } from 'query-string';
import React, { CSSProperties, useCallback, useEffect, useRef, useState } from 'react';
-import { useUIAceKeyboardMode } from '../../../../../../../es_ui_shared/public';
+import { ace } from '../../../../../../../es_ui_shared/public';
// @ts-ignore
import { retrieveAutoCompleteInfo, clearSubscriptions } from '../../../../../lib/mappings/mappings';
import { ConsoleMenu } from '../../../../components';
@@ -38,6 +38,8 @@ import { subscribeResizeChecker } from '../subscribe_console_resize_checker';
import { applyCurrentSettings } from './apply_editor_settings';
import { registerCommands } from './keyboard_shortcuts';
+const { useUIAceKeyboardMode } = ace;
+
export interface EditorProps {
initialTextValue: string;
}
diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/_ui_ace_keyboard_mode.scss b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/_ui_ace_keyboard_mode.scss
new file mode 100644
index 0000000000000..5b637224c1784
--- /dev/null
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/_ui_ace_keyboard_mode.scss
@@ -0,0 +1,24 @@
+.kbnUiAceKeyboardHint {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ background: transparentize($euiColorEmptyShade, 0.3);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ opacity: 0;
+
+ &:focus {
+ opacity: 1;
+ border: 2px solid $euiColorPrimary;
+ z-index: $euiZLevel1;
+ }
+
+ &.kbnUiAceKeyboardHint-isInactive {
+ display: none;
+ }
+}
diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/index.ts
new file mode 100644
index 0000000000000..72d0d6d85ee6e
--- /dev/null
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { useUIAceKeyboardMode } from './use_ui_ace_keyboard_mode';
diff --git a/src/plugins/es_ui_shared/public/use_ui_ace_keyboard_mode.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx
similarity index 99%
rename from src/plugins/es_ui_shared/public/use_ui_ace_keyboard_mode.tsx
rename to src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx
index 6048a602b2bbd..d0d1aa1d8db15 100644
--- a/src/plugins/es_ui_shared/public/use_ui_ace_keyboard_mode.tsx
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx
@@ -16,10 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
+
import React, { useEffect, useRef } from 'react';
import * as ReactDOM from 'react-dom';
import { keys, EuiText } from '@elastic/eui';
+import './_ui_ace_keyboard_mode.scss';
+
const OverlayText = () => (
// The point of this element is for accessibility purposes, so ignore eslint error
// in this case
diff --git a/src/plugins/es_ui_shared/public/ace/index.ts b/src/plugins/es_ui_shared/public/ace/index.ts
new file mode 100644
index 0000000000000..98507fa2fd6ad
--- /dev/null
+++ b/src/plugins/es_ui_shared/public/ace/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { useUIAceKeyboardMode } from '../../__packages_do_not_import__/ace';
diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts
index d472b7e462057..98a305fe68f08 100644
--- a/src/plugins/es_ui_shared/public/index.ts
+++ b/src/plugins/es_ui_shared/public/index.ts
@@ -23,6 +23,7 @@
*/
import * as Forms from './forms';
import * as Monaco from './monaco';
+import * as ace from './ace';
export { JsonEditor, OnJsonEditorUpdateHandler } from './components/json_editor';
@@ -41,8 +42,6 @@ export {
export { indices } from './indices';
-export { useUIAceKeyboardMode } from './use_ui_ace_keyboard_mode';
-
export {
installXJsonMode,
XJsonMode,
@@ -66,7 +65,7 @@ export {
useAuthorizationContext,
} from './authorization';
-export { Monaco, Forms };
+export { Monaco, Forms, ace };
export { extractQueryParams } from './url';
diff --git a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx
index 27f040f3e9eec..3141f5bedc8f9 100644
--- a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx
+++ b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx
@@ -10,7 +10,9 @@ import { EuiScreenReaderOnly } from '@elastic/eui';
import { Editor as AceEditor } from 'brace';
import { initializeEditor } from './init_editor';
-import { useUIAceKeyboardMode } from '../../../../../../src/plugins/es_ui_shared/public';
+import { ace } from '../../../../../../src/plugins/es_ui_shared/public';
+
+const { useUIAceKeyboardMode } = ace;
type EditorShim = ReturnType;
From b24632da541dddbe659e23e7d43b8d626bc002cc Mon Sep 17 00:00:00 2001
From: patrykkopycinski
Date: Fri, 10 Jul 2020 15:05:07 +0200
Subject: [PATCH 17/39] [Security Solution] Allow to configure Event Renderers
settings (#69693)
---
.../common/types/timeline/index.ts | 23 +-
.../security_solution/common/utility_types.ts | 11 +
.../cypress/screens/hosts/events.ts | 2 +-
.../alerts_viewer/default_headers.ts | 3 +-
.../drag_and_drop/draggable_wrapper.tsx | 334 ++++---
.../common/components/draggables/index.tsx | 130 +--
.../events_viewer/events_viewer.test.tsx | 2 +-
.../common/components/events_viewer/index.tsx | 19 +-
.../public/common/mock/global_state.ts | 2 +-
.../public/common/mock/mock_timeline_data.ts | 28 +-
.../public/common/mock/timeline_results.ts | 4 +-
.../components/alerts_table/actions.test.tsx | 2 +-
.../alerts_table/alerts_utility_bar/index.tsx | 8 +-
.../alerts_table/default_config.tsx | 3 +-
.../public/detections/index.ts | 4 +-
.../public/graphql/introspection.json | 99 ++
.../security_solution/public/graphql/types.ts | 24 +
.../source_destination_arrows.tsx | 4 +
.../components/fields_browser/index.test.tsx | 58 +-
.../components/fields_browser/index.tsx | 331 +++----
.../components/flyout/pane/index.tsx | 1 +
.../components/open_timeline/helpers.test.ts | 8 +-
.../components/open_timeline/types.ts | 2 +
.../row_renderers_browser/catalog/index.tsx | 199 ++++
.../catalog/translations.ts | 215 +++++
.../row_renderers_browser/constants.ts | 7 +
.../row_renderers_browser/examples/auditd.tsx | 30 +
.../examples/auditd_file.tsx | 30 +
.../row_renderers_browser/examples/index.tsx | 18 +
.../examples/netflow.tsx | 22 +
.../examples/suricata.tsx | 22 +
.../row_renderers_browser/examples/system.tsx | 30 +
.../examples/system_dns.tsx | 26 +
.../examples/system_endgame_process.tsx | 30 +
.../examples/system_file.tsx | 30 +
.../examples/system_fim.tsx | 30 +
.../examples/system_security_event.tsx | 28 +
.../examples/system_socket.tsx | 29 +
.../row_renderers_browser/examples/zeek.tsx | 22 +
.../row_renderers_browser/index.tsx | 182 ++++
.../row_renderers_browser.tsx | 179 ++++
.../row_renderers_browser/translations.ts | 43 +
.../timeline/body/actions/index.tsx | 6 +-
.../__snapshots__/index.test.tsx.snap | 906 +++++++++---------
.../body/column_headers/helpers.test.ts | 6 +-
.../timeline/body/column_headers/helpers.ts | 16 +-
.../timeline/body/column_headers/index.tsx | 39 +-
.../components/timeline/body/constants.ts | 3 +
.../body/events/event_column_view.tsx | 70 +-
.../generic_file_details.test.tsx.snap | 4 +-
.../generic_row_renderer.test.tsx.snap | 10 +-
.../auditd/generic_row_renderer.test.tsx | 8 +-
.../renderers/auditd/generic_row_renderer.tsx | 4 +
.../netflow/netflow_row_renderer.tsx | 2 +
.../body/renderers/plain_row_renderer.tsx | 11 +-
.../timeline/body/renderers/row_renderer.tsx | 2 +
.../suricata_signature.test.tsx.snap | 1 -
.../suricata/suricata_row_renderer.tsx | 3 +
.../renderers/suricata/suricata_signature.tsx | 2 -
.../renderers/system/generic_row_renderer.tsx | 9 +
.../body/renderers/zeek/zeek_row_renderer.tsx | 3 +
.../body/renderers/zeek/zeek_signature.tsx | 3 -
.../timeline/body/stateful_body.tsx | 59 +-
.../data_providers/provider_item_badge.tsx | 2 +-
.../timelines/components/timeline/styles.tsx | 24 +-
.../containers/all/index.gql_query.ts | 1 +
.../public/timelines/containers/all/index.tsx | 1 +
.../containers/one/index.gql_query.ts | 1 +
.../timelines/store/timeline/actions.ts | 9 +-
.../timelines/store/timeline/defaults.ts | 2 +-
.../timelines/store/timeline/epic.test.ts | 3 +-
.../public/timelines/store/timeline/epic.ts | 1 +
.../store/timeline/epic_local_storage.ts | 2 +
.../timelines/store/timeline/helpers.ts | 34 +-
.../public/timelines/store/timeline/model.ts | 7 +-
.../timelines/store/timeline/reducer.test.ts | 20 +-
.../timelines/store/timeline/reducer.ts | 14 +-
.../server/graphql/timeline/schema.gql.ts | 18 +
.../security_solution/server/graphql/types.ts | 31 +
.../lib/timeline/pick_saved_timeline.ts | 2 +
.../timeline/routes/utils/export_timelines.ts | 2 +-
.../lib/timeline/saved_object_mappings.ts | 3 +
82 files changed, 2544 insertions(+), 1074 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/constants.ts
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd_file.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/netflow.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/suricata.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_dns.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_endgame_process.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_file.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_fim.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_security_event.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_socket.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/zeek.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/row_renderers_browser.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/translations.ts
diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts
index 90d254b15e8b3..9e7a6f46bbcec 100644
--- a/x-pack/plugins/security_solution/common/types/timeline/index.ts
+++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts
@@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-/* eslint-disable @typescript-eslint/no-empty-interface */
+/* eslint-disable @typescript-eslint/camelcase, @typescript-eslint/no-empty-interface */
import * as runtimeTypes from 'io-ts';
import { SavedObjectsClient } from 'kibana/server';
-import { unionWithNullType } from '../../utility_types';
+import { stringEnum, unionWithNullType } from '../../utility_types';
import { NoteSavedObject, NoteSavedObjectToReturnRuntimeType } from './note';
import { PinnedEventToReturnSavedObjectRuntimeType, PinnedEventSavedObject } from './pinned_event';
@@ -164,6 +164,24 @@ export type TimelineStatusLiteralWithNull = runtimeTypes.TypeOf<
typeof TimelineStatusLiteralWithNullRt
>;
+export enum RowRendererId {
+ auditd = 'auditd',
+ auditd_file = 'auditd_file',
+ netflow = 'netflow',
+ plain = 'plain',
+ suricata = 'suricata',
+ system = 'system',
+ system_dns = 'system_dns',
+ system_endgame_process = 'system_endgame_process',
+ system_file = 'system_file',
+ system_fim = 'system_fim',
+ system_security_event = 'system_security_event',
+ system_socket = 'system_socket',
+ zeek = 'zeek',
+}
+
+export const RowRendererIdRuntimeType = stringEnum(RowRendererId, 'RowRendererId');
+
/**
* Timeline template type
*/
@@ -211,6 +229,7 @@ export const SavedTimelineRuntimeType = runtimeTypes.partial({
dataProviders: unionWithNullType(runtimeTypes.array(SavedDataProviderRuntimeType)),
description: unionWithNullType(runtimeTypes.string),
eventType: unionWithNullType(runtimeTypes.string),
+ excludedRowRendererIds: unionWithNullType(runtimeTypes.array(RowRendererIdRuntimeType)),
favorite: unionWithNullType(runtimeTypes.array(SavedFavoriteRuntimeType)),
filters: unionWithNullType(runtimeTypes.array(SavedFilterRuntimeType)),
kqlMode: unionWithNullType(runtimeTypes.string),
diff --git a/x-pack/plugins/security_solution/common/utility_types.ts b/x-pack/plugins/security_solution/common/utility_types.ts
index a12dd926a9181..43271dc40ba12 100644
--- a/x-pack/plugins/security_solution/common/utility_types.ts
+++ b/x-pack/plugins/security_solution/common/utility_types.ts
@@ -15,3 +15,14 @@ export interface DescriptionList {
export const unionWithNullType = (type: T) =>
runtimeTypes.union([type, runtimeTypes.null]);
+
+export const stringEnum = (enumObj: T, enumName = 'enum') =>
+ new runtimeTypes.Type(
+ enumName,
+ (u): u is T[keyof T] => Object.values(enumObj).includes(u),
+ (u, c) =>
+ Object.values(enumObj).includes(u)
+ ? runtimeTypes.success(u as T[keyof T])
+ : runtimeTypes.failure(u, c),
+ (a) => (a as unknown) as string
+ );
diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts
index a946fefe273e1..4b1ca19bd96fe 100644
--- a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts
@@ -7,7 +7,7 @@
export const CLOSE_MODAL = '[data-test-subj="modal-inspect-close"]';
export const EVENTS_VIEWER_FIELDS_BUTTON =
- '[data-test-subj="events-viewer-panel"] [data-test-subj="show-field-browser-gear"]';
+ '[data-test-subj="events-viewer-panel"] [data-test-subj="show-field-browser"]';
export const EVENTS_VIEWER_PANEL = '[data-test-subj="events-viewer-panel"]';
diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts
index cf5b565b99f67..ba4ecf9a33eee 100644
--- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts
+++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { RowRendererId } from '../../../../common/types/timeline';
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import {
DEFAULT_COLUMN_MIN_WIDTH,
@@ -69,5 +70,5 @@ export const alertsHeaders: ColumnHeaderOptions[] = [
export const alertsDefaultModel: SubsetTimelineModel = {
...timelineDefaults,
columns: alertsHeaders,
- showRowRenderers: false,
+ excludedRowRendererIds: Object.values(RowRendererId),
};
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx
index e7594365e8103..64f6699d21dac 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx
@@ -15,13 +15,13 @@ import {
} from 'react-beautiful-dnd';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
-import deepEqual from 'fast-deep-equal';
import { dragAndDropActions } from '../../store/drag_and_drop';
import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../../../timelines/components/row_renderers_browser/constants';
+
import { TruncatableText } from '../truncatable_text';
import { WithHoverActions } from '../with_hover_actions';
-
import { DraggableWrapperHoverContent, useGetTimelineId } from './draggable_wrapper_hover_content';
import { getDraggableId, getDroppableId } from './helpers';
import { ProviderContainer } from './provider_container';
@@ -49,13 +49,27 @@ class DragDropErrorBoundary extends React.PureComponent {
}
}
-const Wrapper = styled.div`
+interface WrapperProps {
+ disabled: boolean;
+}
+
+const Wrapper = styled.div`
display: inline-block;
max-width: 100%;
[data-rbd-placeholder-context-id] {
display: none !important;
}
+
+ ${({ disabled }) =>
+ disabled &&
+ `
+ [data-rbd-draggable-id]:hover,
+ .euiBadge:hover,
+ .euiBadge__text:hover {
+ cursor: default;
+ }
+ `}
`;
Wrapper.displayName = 'Wrapper';
@@ -74,6 +88,7 @@ type RenderFunctionProp = (
interface Props {
dataProvider: DataProvider;
+ disabled?: boolean;
inline?: boolean;
render: RenderFunctionProp;
timelineId?: string;
@@ -100,162 +115,169 @@ export const getStyle = (
};
};
-export const DraggableWrapper = React.memo(
- ({ dataProvider, onFilterAdded, render, timelineId, truncate }) => {
- const draggableRef = useRef(null);
- const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
- const [showTopN, setShowTopN] = useState(false);
- const [goGetTimelineId, setGoGetTimelineId] = useState(false);
- const timelineIdFind = useGetTimelineId(draggableRef, goGetTimelineId);
- const [providerRegistered, setProviderRegistered] = useState(false);
-
- const dispatch = useDispatch();
-
- const handleClosePopOverTrigger = useCallback(
- () => setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger),
- []
- );
-
- const toggleTopN = useCallback(() => {
- setShowTopN((prevShowTopN) => {
- const newShowTopN = !prevShowTopN;
- if (newShowTopN === false) {
- handleClosePopOverTrigger();
- }
- return newShowTopN;
- });
- }, [handleClosePopOverTrigger]);
-
- const registerProvider = useCallback(() => {
- if (!providerRegistered) {
- dispatch(dragAndDropActions.registerProvider({ provider: dataProvider }));
- setProviderRegistered(true);
+const DraggableWrapperComponent: React.FC = ({
+ dataProvider,
+ onFilterAdded,
+ render,
+ timelineId,
+ truncate,
+}) => {
+ const draggableRef = useRef(null);
+ const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
+ const [showTopN, setShowTopN] = useState(false);
+ const [goGetTimelineId, setGoGetTimelineId] = useState(false);
+ const timelineIdFind = useGetTimelineId(draggableRef, goGetTimelineId);
+ const [providerRegistered, setProviderRegistered] = useState(false);
+ const isDisabled = dataProvider.id.includes(`-${ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID}-`);
+ const dispatch = useDispatch();
+
+ const handleClosePopOverTrigger = useCallback(
+ () => setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger),
+ []
+ );
+
+ const toggleTopN = useCallback(() => {
+ setShowTopN((prevShowTopN) => {
+ const newShowTopN = !prevShowTopN;
+ if (newShowTopN === false) {
+ handleClosePopOverTrigger();
}
- }, [dispatch, providerRegistered, dataProvider]);
-
- const unRegisterProvider = useCallback(
- () => dispatch(dragAndDropActions.unRegisterProvider({ id: dataProvider.id })),
- [dispatch, dataProvider]
- );
-
- useEffect(
- () => () => {
- unRegisterProvider();
- },
- [unRegisterProvider]
- );
-
- const hoverContent = useMemo(
- () => (
-
- ),
- [
- dataProvider,
- handleClosePopOverTrigger,
- onFilterAdded,
- showTopN,
- timelineId,
- timelineIdFind,
- toggleTopN,
- ]
- );
-
- const renderContent = useCallback(
- () => (
-
-
- (
-
-
-
- {render(dataProvider, provided, snapshot)}
-
-
-
- )}
- >
- {(droppableProvided) => (
-
-
{
+ if (!isDisabled) {
+ dispatch(dragAndDropActions.registerProvider({ provider: dataProvider }));
+ setProviderRegistered(true);
+ }
+ }, [isDisabled, dispatch, dataProvider]);
+
+ const unRegisterProvider = useCallback(
+ () =>
+ providerRegistered &&
+ dispatch(dragAndDropActions.unRegisterProvider({ id: dataProvider.id })),
+ [providerRegistered, dispatch, dataProvider.id]
+ );
+
+ useEffect(
+ () => () => {
+ unRegisterProvider();
+ },
+ [unRegisterProvider]
+ );
+
+ const hoverContent = useMemo(
+ () => (
+
+ ),
+ [
+ dataProvider,
+ handleClosePopOverTrigger,
+ onFilterAdded,
+ showTopN,
+ timelineId,
+ timelineIdFind,
+ toggleTopN,
+ ]
+ );
+
+ const renderContent = useCallback(
+ () => (
+
+
+ (
+
+
+
- {(provided, snapshot) => (
- {
- provided.innerRef(e);
- draggableRef.current = e;
- }}
- data-test-subj="providerContainer"
- isDragging={snapshot.isDragging}
- registerProvider={registerProvider}
- >
- {truncate && !snapshot.isDragging ? (
-
- {render(dataProvider, provided, snapshot)}
-
- ) : (
-
- {render(dataProvider, provided, snapshot)}
-
- )}
-
- )}
-
- {droppableProvided.placeholder}
+ {render(dataProvider, provided, snapshot)}
+
- )}
-
-
-
- ),
- [dataProvider, render, registerProvider, truncate]
- );
-
- return (
-
- );
- },
- (prevProps, nextProps) =>
- deepEqual(prevProps.dataProvider, nextProps.dataProvider) &&
- prevProps.render !== nextProps.render &&
- prevProps.truncate === nextProps.truncate
-);
+
+ )}
+ >
+ {(droppableProvided) => (
+
+
+ {(provided, snapshot) => (
+ {
+ provided.innerRef(e);
+ draggableRef.current = e;
+ }}
+ data-test-subj="providerContainer"
+ isDragging={snapshot.isDragging}
+ registerProvider={registerProvider}
+ >
+ {truncate && !snapshot.isDragging ? (
+
+ {render(dataProvider, provided, snapshot)}
+
+ ) : (
+
+ {render(dataProvider, provided, snapshot)}
+
+ )}
+
+ )}
+
+ {droppableProvided.placeholder}
+
+ )}
+
+
+
+ ),
+ [dataProvider, registerProvider, render, isDisabled, truncate]
+ );
+
+ if (isDisabled) return <>{renderContent()}>;
+
+ return (
+
+ );
+};
+
+export const DraggableWrapper = React.memo(DraggableWrapperComponent);
DraggableWrapper.displayName = 'DraggableWrapper';
diff --git a/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx b/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx
index 62a07550650aa..4dc3c6fcbe440 100644
--- a/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/draggables/index.tsx
@@ -5,13 +5,16 @@
*/
import { EuiBadge, EuiToolTip, IconType } from '@elastic/eui';
-import React from 'react';
+import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';
import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper';
import { escapeDataProviderId } from '../drag_and_drop/helpers';
import { getEmptyStringTag } from '../empty_value';
-import { IS_OPERATOR } from '../../../timelines/components/timeline/data_providers/data_provider';
+import {
+ DataProvider,
+ IS_OPERATOR,
+} from '../../../timelines/components/timeline/data_providers/data_provider';
import { Provider } from '../../../timelines/components/timeline/data_providers/provider';
export interface DefaultDraggableType {
@@ -84,36 +87,48 @@ Content.displayName = 'Content';
* @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data
*/
export const DefaultDraggable = React.memo(
- ({ id, field, value, name, children, timelineId, tooltipContent, queryValue }) =>
- value != null ? (
+ ({ id, field, value, name, children, timelineId, tooltipContent, queryValue }) => {
+ const dataProviderProp: DataProvider = useMemo(
+ () => ({
+ and: [],
+ enabled: true,
+ id: escapeDataProviderId(id),
+ name: name ? name : value ?? '',
+ excluded: false,
+ kqlQuery: '',
+ queryMatch: {
+ field,
+ value: queryValue ? queryValue : value ?? '',
+ operator: IS_OPERATOR,
+ },
+ }),
+ [field, id, name, queryValue, value]
+ );
+
+ const renderCallback = useCallback(
+ (dataProvider, _, snapshot) =>
+ snapshot.isDragging ? (
+
+
+
+ ) : (
+
+ {children}
+
+ ),
+ [children, field, tooltipContent, value]
+ );
+
+ if (value == null) return null;
+
+ return (
- snapshot.isDragging ? (
-
-
-
- ) : (
-
- {children}
-
- )
- }
+ dataProvider={dataProviderProp}
+ render={renderCallback}
timelineId={timelineId}
/>
- ) : null
+ );
+ }
);
DefaultDraggable.displayName = 'DefaultDraggable';
@@ -146,33 +161,34 @@ export type BadgeDraggableType = Omit & {
* prevent a tooltip from being displayed, or pass arbitrary content
* @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data
*/
-export const DraggableBadge = React.memo(
- ({
- contextId,
- eventId,
- field,
- value,
- iconType,
- name,
- color = 'hollow',
- children,
- tooltipContent,
- queryValue,
- }) =>
- value != null ? (
-
-
- {children ? children : value !== '' ? value : getEmptyStringTag()}
-
-
- ) : null
-);
+const DraggableBadgeComponent: React.FC = ({
+ contextId,
+ eventId,
+ field,
+ value,
+ iconType,
+ name,
+ color = 'hollow',
+ children,
+ tooltipContent,
+ queryValue,
+}) =>
+ value != null ? (
+
+
+ {children ? children : value !== '' ? value : getEmptyStringTag()}
+
+
+ ) : null;
+
+DraggableBadgeComponent.displayName = 'DraggableBadgeComponent';
+export const DraggableBadge = React.memo(DraggableBadgeComponent);
DraggableBadge.displayName = 'DraggableBadge';
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
index 2a079ce015f0d..38ca1176d1700 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
@@ -77,7 +77,7 @@ describe('EventsViewer', () => {
await wait();
wrapper.update();
- expect(wrapper.find(`[data-test-subj="show-field-browser-gear"]`).first().exists()).toBe(true);
+ expect(wrapper.find(`[data-test-subj="show-field-browser"]`).first().exists()).toBe(true);
});
test('it renders the footer containing the Load More button', async () => {
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
index 02b3571421f67..b89d2b8c08625 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
@@ -45,6 +45,7 @@ const StatefulEventsViewerComponent: React.FC = ({
defaultIndices,
deleteEventQuery,
end,
+ excludedRowRendererIds,
filters,
headerFilterGroup,
id,
@@ -57,7 +58,6 @@ const StatefulEventsViewerComponent: React.FC = ({
removeColumn,
start,
showCheckboxes,
- showRowRenderers,
sort,
updateItemsPerPage,
upsertColumn,
@@ -69,7 +69,14 @@ const StatefulEventsViewerComponent: React.FC = ({
useEffect(() => {
if (createTimeline != null) {
- createTimeline({ id, columns, sort, itemsPerPage, showCheckboxes, showRowRenderers });
+ createTimeline({
+ id,
+ columns,
+ excludedRowRendererIds,
+ sort,
+ itemsPerPage,
+ showCheckboxes,
+ });
}
return () => {
deleteEventQuery({ id, inputId: 'global' });
@@ -125,7 +132,7 @@ const StatefulEventsViewerComponent: React.FC = ({
onChangeItemsPerPage={onChangeItemsPerPage}
query={query}
start={start}
- sort={sort!}
+ sort={sort}
toggleColumn={toggleColumn}
utilityBar={utilityBar}
/>
@@ -145,18 +152,19 @@ const makeMapStateToProps = () => {
columns,
dataProviders,
deletedEventIds,
+ excludedRowRendererIds,
itemsPerPage,
itemsPerPageOptions,
kqlMode,
sort,
showCheckboxes,
- showRowRenderers,
} = events;
return {
columns,
dataProviders,
deletedEventIds,
+ excludedRowRendererIds,
filters: getGlobalFiltersQuerySelector(state),
id,
isLive: input.policy.kind === 'interval',
@@ -166,7 +174,6 @@ const makeMapStateToProps = () => {
query: getGlobalQuerySelector(state),
sort,
showCheckboxes,
- showRowRenderers,
};
};
return mapStateToProps;
@@ -192,6 +199,7 @@ export const StatefulEventsViewer = connector(
deepEqual(prevProps.columns, nextProps.columns) &&
deepEqual(prevProps.defaultIndices, nextProps.defaultIndices) &&
deepEqual(prevProps.dataProviders, nextProps.dataProviders) &&
+ deepEqual(prevProps.excludedRowRendererIds, nextProps.excludedRowRendererIds) &&
prevProps.deletedEventIds === nextProps.deletedEventIds &&
prevProps.end === nextProps.end &&
deepEqual(prevProps.filters, nextProps.filters) &&
@@ -204,7 +212,6 @@ export const StatefulEventsViewer = connector(
prevProps.start === nextProps.start &&
deepEqual(prevProps.pageFilters, nextProps.pageFilters) &&
prevProps.showCheckboxes === nextProps.showCheckboxes &&
- prevProps.showRowRenderers === nextProps.showRowRenderers &&
prevProps.start === nextProps.start &&
prevProps.utilityBar === nextProps.utilityBar
)
diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
index 3d76416855e9e..89f100992e1b9 100644
--- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
@@ -195,6 +195,7 @@ export const mockGlobalState: State = {
dataProviders: [],
description: '',
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
isFavorite: false,
@@ -215,7 +216,6 @@ export const mockGlobalState: State = {
},
selectedEventIds: {},
show: false,
- showRowRenderers: true,
showCheckboxes: false,
pinnedEventIds: {},
pinnedEventsSaveObject: {},
diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts b/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts
index 7503062300d2d..9974842bff474 100644
--- a/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts
@@ -418,8 +418,8 @@ export const mockTimelineData: TimelineItem[] = [
data: [
{ field: '@timestamp', value: ['2019-03-07T05:06:51.000Z'] },
{ field: 'host.name', value: ['zeek-franfurt'] },
- { field: 'source.ip', value: ['185.176.26.101'] },
- { field: 'destination.ip', value: ['207.154.238.205'] },
+ { field: 'source.ip', value: ['192.168.26.101'] },
+ { field: 'destination.ip', value: ['192.168.238.205'] },
],
ecs: {
_id: '14',
@@ -466,8 +466,8 @@ export const mockTimelineData: TimelineItem[] = [
data: [
{ field: '@timestamp', value: ['2019-03-07T00:51:28.000Z'] },
{ field: 'host.name', value: ['suricata-zeek-singapore'] },
- { field: 'source.ip', value: ['206.189.35.240'] },
- { field: 'destination.ip', value: ['67.207.67.3'] },
+ { field: 'source.ip', value: ['192.168.35.240'] },
+ { field: 'destination.ip', value: ['192.168.67.3'] },
],
ecs: {
_id: '15',
@@ -520,8 +520,8 @@ export const mockTimelineData: TimelineItem[] = [
data: [
{ field: '@timestamp', value: ['2019-03-05T07:00:20.000Z'] },
{ field: 'host.name', value: ['suricata-zeek-singapore'] },
- { field: 'source.ip', value: ['206.189.35.240'] },
- { field: 'destination.ip', value: ['192.241.164.26'] },
+ { field: 'source.ip', value: ['192.168.35.240'] },
+ { field: 'destination.ip', value: ['192.168.164.26'] },
],
ecs: {
_id: '16',
@@ -572,7 +572,7 @@ export const mockTimelineData: TimelineItem[] = [
data: [
{ field: '@timestamp', value: ['2019-02-28T22:36:28.000Z'] },
{ field: 'host.name', value: ['zeek-franfurt'] },
- { field: 'source.ip', value: ['8.42.77.171'] },
+ { field: 'source.ip', value: ['192.168.77.171'] },
],
ecs: {
_id: '17',
@@ -621,8 +621,8 @@ export const mockTimelineData: TimelineItem[] = [
data: [
{ field: '@timestamp', value: ['2019-02-22T21:12:13.000Z'] },
{ field: 'host.name', value: ['zeek-sensor-amsterdam'] },
- { field: 'source.ip', value: ['188.166.66.184'] },
- { field: 'destination.ip', value: ['91.189.95.15'] },
+ { field: 'source.ip', value: ['192.168.66.184'] },
+ { field: 'destination.ip', value: ['192.168.95.15'] },
],
ecs: {
_id: '18',
@@ -767,7 +767,7 @@ export const mockTimelineData: TimelineItem[] = [
{ field: '@timestamp', value: ['2019-03-14T22:30:25.527Z'] },
{ field: 'event.category', value: ['user-login'] },
{ field: 'host.name', value: ['zeek-london'] },
- { field: 'source.ip', value: ['8.42.77.171'] },
+ { field: 'source.ip', value: ['192.168.77.171'] },
{ field: 'user.name', value: ['root'] },
],
ecs: {
@@ -1101,7 +1101,7 @@ export const mockTimelineData: TimelineItem[] = [
{ field: 'event.action', value: ['connected-to'] },
{ field: 'event.category', value: ['audit-rule'] },
{ field: 'host.name', value: ['zeek-london'] },
- { field: 'destination.ip', value: ['93.184.216.34'] },
+ { field: 'destination.ip', value: ['192.168.216.34'] },
{ field: 'user.name', value: ['alice'] },
],
ecs: {
@@ -1121,7 +1121,7 @@ export const mockTimelineData: TimelineItem[] = [
data: null,
summary: {
actor: { primary: ['alice'], secondary: ['alice'] },
- object: { primary: ['93.184.216.34'], secondary: ['80'], type: ['socket'] },
+ object: { primary: ['192.168.216.34'], secondary: ['80'], type: ['socket'] },
how: ['/usr/bin/wget'],
message_type: null,
sequence: null,
@@ -1133,7 +1133,7 @@ export const mockTimelineData: TimelineItem[] = [
ip: ['46.101.3.136', '10.16.0.5', 'fe80::4066:42ff:fe19:b3b9'],
},
source: null,
- destination: { ip: ['93.184.216.34'], port: [80] },
+ destination: { ip: ['192.168.216.34'], port: [80] },
geo: null,
suricata: null,
network: null,
@@ -1174,7 +1174,7 @@ export const mockTimelineData: TimelineItem[] = [
},
auditd: {
result: ['success'],
- session: ['unset'],
+ session: ['242'],
data: null,
summary: {
actor: { primary: ['unset'], secondary: ['root'] },
diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts
index 5248136437d7d..b1df41a19aebe 100644
--- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts
@@ -2098,6 +2098,7 @@ export const mockTimelineModel: TimelineModel = {
description: 'This is a sample rule description',
eventIdToNoteIds: {},
eventType: 'all',
+ excludedRowRendererIds: [],
filters: [
{
$state: {
@@ -2137,7 +2138,6 @@ export const mockTimelineModel: TimelineModel = {
selectedEventIds: {},
show: false,
showCheckboxes: false,
- showRowRenderers: true,
sort: {
columnId: '@timestamp',
sortDirection: Direction.desc,
@@ -2217,6 +2217,7 @@ export const defaultTimelineProps: CreateTimelineProps = {
description: '',
eventIdToNoteIds: {},
eventType: 'all',
+ excludedRowRendererIds: [],
filters: [],
highlightedDropAndProviderId: '',
historyIds: [],
@@ -2241,7 +2242,6 @@ export const defaultTimelineProps: CreateTimelineProps = {
selectedEventIds: {},
show: false,
showCheckboxes: false,
- showRowRenderers: true,
sort: { columnId: '@timestamp', sortDirection: Direction.desc },
status: TimelineStatus.draft,
title: '',
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
index 2fa7cfeedcd15..1213312e2a22c 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
@@ -158,6 +158,7 @@ describe('alert actions', () => {
description: 'This is a sample rule description',
eventIdToNoteIds: {},
eventType: 'all',
+ excludedRowRendererIds: [],
filters: [
{
$state: {
@@ -210,7 +211,6 @@ describe('alert actions', () => {
selectedEventIds: {},
show: true,
showCheckboxes: false,
- showRowRenderers: true,
sort: {
columnId: '@timestamp',
sortDirection: 'desc',
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx
index 0ceb2c87dd5ea..6533be1a9b09c 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx
@@ -39,6 +39,10 @@ interface AlertsUtilityBarProps {
updateAlertsStatus: UpdateAlertsStatus;
}
+const UtilityBarFlexGroup = styled(EuiFlexGroup)`
+ min-width: 175px;
+`;
+
const AlertsUtilityBarComponent: React.FC = ({
canUserCRUD,
hasIndexWrite,
@@ -69,10 +73,6 @@ const AlertsUtilityBarComponent: React.FC = ({
defaultNumberFormat
);
- const UtilityBarFlexGroup = styled(EuiFlexGroup)`
- min-width: 175px;
- `;
-
const UtilityBarPopoverContent = (closePopover: () => void) => (
{currentFilter !== FILTER_OPEN && (
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
index e95ea4531d9ad..319575c9c307f 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
@@ -11,6 +11,7 @@ import ApolloClient from 'apollo-client';
import { Dispatch } from 'redux';
import { EuiText } from '@elastic/eui';
+import { RowRendererId } from '../../../../common/types/timeline';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
import { Filter } from '../../../../../../../src/plugins/data/common/es_query';
import {
@@ -162,7 +163,7 @@ export const alertsDefaultModel: SubsetTimelineModel = {
...timelineDefaults,
columns: alertsHeaders,
showCheckboxes: true,
- showRowRenderers: false,
+ excludedRowRendererIds: Object.values(RowRendererId),
};
export const requiredFieldsForActions = [
diff --git a/x-pack/plugins/security_solution/public/detections/index.ts b/x-pack/plugins/security_solution/public/detections/index.ts
index d043127a3098b..30d1e30417583 100644
--- a/x-pack/plugins/security_solution/public/detections/index.ts
+++ b/x-pack/plugins/security_solution/public/detections/index.ts
@@ -10,7 +10,7 @@ import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline';
import { AlertsRoutes } from './routes';
import { SecuritySubPlugin } from '../app/types';
-const ALERTS_TIMELINE_IDS: TimelineIdLiteral[] = [
+const DETECTIONS_TIMELINE_IDS: TimelineIdLiteral[] = [
TimelineId.detectionsRulesDetailsPage,
TimelineId.detectionsPage,
];
@@ -22,7 +22,7 @@ export class Detections {
return {
SubPluginRoutes: AlertsRoutes,
storageTimelines: {
- timelineById: getTimelinesInStorageByIds(storage, ALERTS_TIMELINE_IDS),
+ timelineById: getTimelinesInStorageByIds(storage, DETECTIONS_TIMELINE_IDS),
},
};
}
diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json
index 2b8b07cb6a24b..20978fa3b063c 100644
--- a/x-pack/plugins/security_solution/public/graphql/introspection.json
+++ b/x-pack/plugins/security_solution/public/graphql/introspection.json
@@ -9641,6 +9641,22 @@
"isDeprecated": false,
"deprecationReason": null
},
+ {
+ "name": "excludedRowRendererIds",
+ "description": "",
+ "args": [],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": { "kind": "ENUM", "name": "RowRendererId", "ofType": null }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
{
"name": "favorite",
"description": "",
@@ -10146,6 +10162,75 @@
"enumValues": null,
"possibleTypes": null
},
+ {
+ "kind": "ENUM",
+ "name": "RowRendererId",
+ "description": "",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ { "name": "auditd", "description": "", "isDeprecated": false, "deprecationReason": null },
+ {
+ "name": "auditd_file",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "netflow",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ { "name": "plain", "description": "", "isDeprecated": false, "deprecationReason": null },
+ {
+ "name": "suricata",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ { "name": "system", "description": "", "isDeprecated": false, "deprecationReason": null },
+ {
+ "name": "system_dns",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "system_endgame_process",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "system_file",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "system_fim",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "system_security_event",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "system_socket",
+ "description": "",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ { "name": "zeek", "description": "", "isDeprecated": false, "deprecationReason": null }
+ ],
+ "possibleTypes": null
+ },
{
"kind": "OBJECT",
"name": "FavoriteTimelineResult",
@@ -11061,6 +11146,20 @@
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
},
+ {
+ "name": "excludedRowRendererIds",
+ "description": "",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": { "kind": "ENUM", "name": "RowRendererId", "ofType": null }
+ }
+ },
+ "defaultValue": null
+ },
{
"name": "filters",
"description": "",
diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts
index 2c8f2e63356e6..27aa02038097e 100644
--- a/x-pack/plugins/security_solution/public/graphql/types.ts
+++ b/x-pack/plugins/security_solution/public/graphql/types.ts
@@ -124,6 +124,8 @@ export interface TimelineInput {
eventType?: Maybe;
+ excludedRowRendererIds?: Maybe;
+
filters?: Maybe;
kqlMode?: Maybe;
@@ -349,6 +351,22 @@ export enum DataProviderType {
template = 'template',
}
+export enum RowRendererId {
+ auditd = 'auditd',
+ auditd_file = 'auditd_file',
+ netflow = 'netflow',
+ plain = 'plain',
+ suricata = 'suricata',
+ system = 'system',
+ system_dns = 'system_dns',
+ system_endgame_process = 'system_endgame_process',
+ system_file = 'system_file',
+ system_fim = 'system_fim',
+ system_security_event = 'system_security_event',
+ system_socket = 'system_socket',
+ zeek = 'zeek',
+}
+
export enum TimelineStatus {
active = 'active',
draft = 'draft',
@@ -1961,6 +1979,8 @@ export interface TimelineResult {
eventType?: Maybe;
+ excludedRowRendererIds?: Maybe;
+
favorite?: Maybe;
filters?: Maybe;
@@ -4385,6 +4405,8 @@ export namespace GetAllTimeline {
eventIdToNoteIds: Maybe;
+ excludedRowRendererIds: Maybe;
+
notes: Maybe;
noteIds: Maybe;
@@ -5454,6 +5476,8 @@ export namespace GetOneTimeline {
eventIdToNoteIds: Maybe;
+ excludedRowRendererIds: Maybe;
+
favorite: Maybe;
filters: Maybe;
diff --git a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_arrows.tsx b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_arrows.tsx
index 95cc76a349c17..73c5c1e37da0f 100644
--- a/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_arrows.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/source_destination/source_destination_arrows.tsx
@@ -35,6 +35,10 @@ Percent.displayName = 'Percent';
const SourceDestinationArrowsContainer = styled(EuiFlexGroup)`
margin: 0 2px;
+
+ .euiToolTipAnchor {
+ white-space: nowrap;
+ }
`;
SourceDestinationArrowsContainer.displayName = 'SourceDestinationArrowsContainer';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx
index 92ef5c41f3b4c..ed3f957ad11a8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx
@@ -6,11 +6,9 @@
import { mount } from 'enzyme';
import React from 'react';
-import { ActionCreator } from 'typescript-fsa';
import { mockBrowserFields } from '../../../common/containers/source/mock';
import { TestProviders } from '../../../common/mock';
-import { ColumnHeaderOptions } from '../../store/timeline/model';
import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from './helpers';
@@ -29,17 +27,6 @@ afterAll(() => {
console.warn = originalWarn;
});
-const removeColumnMock = (jest.fn() as unknown) as ActionCreator<{
- id: string;
- columnId: string;
-}>;
-
-const upsertColumnMock = (jest.fn() as unknown) as ActionCreator<{
- column: ColumnHeaderOptions;
- id: string;
- index: number;
-}>;
-
describe('StatefulFieldsBrowser', () => {
const timelineId = 'test';
@@ -54,13 +41,11 @@ describe('StatefulFieldsBrowser', () => {
timelineId={timelineId}
toggleColumn={jest.fn()}
width={FIELD_BROWSER_WIDTH}
- removeColumn={removeColumnMock}
- upsertColumn={upsertColumnMock}
/>
);
- expect(wrapper.find('[data-test-subj="show-field-browser"]').first().text()).toEqual('Columns');
+ expect(wrapper.find('[data-test-subj="show-field-browser"]').exists()).toBe(true);
});
describe('toggleShow', () => {
@@ -75,8 +60,6 @@ describe('StatefulFieldsBrowser', () => {
timelineId={timelineId}
toggleColumn={jest.fn()}
width={FIELD_BROWSER_WIDTH}
- removeColumn={removeColumnMock}
- upsertColumn={upsertColumnMock}
/>
);
@@ -95,8 +78,6 @@ describe('StatefulFieldsBrowser', () => {
timelineId={timelineId}
toggleColumn={jest.fn()}
width={FIELD_BROWSER_WIDTH}
- removeColumn={removeColumnMock}
- upsertColumn={upsertColumnMock}
/>
);
@@ -122,8 +103,6 @@ describe('StatefulFieldsBrowser', () => {
timelineId={timelineId}
toggleColumn={jest.fn()}
width={FIELD_BROWSER_WIDTH}
- removeColumn={removeColumnMock}
- upsertColumn={upsertColumnMock}
/>
);
@@ -149,8 +128,6 @@ describe('StatefulFieldsBrowser', () => {
timelineId={timelineId}
toggleColumn={jest.fn()}
width={FIELD_BROWSER_WIDTH}
- removeColumn={removeColumnMock}
- upsertColumn={upsertColumnMock}
/>
);
@@ -186,39 +163,14 @@ describe('StatefulFieldsBrowser', () => {
timelineId={timelineId}
toggleColumn={jest.fn()}
width={FIELD_BROWSER_WIDTH}
- removeColumn={removeColumnMock}
- upsertColumn={upsertColumnMock}
- />
-
- );
-
- expect(wrapper.find('[data-test-subj="show-field-browser-gear"]').first().exists()).toBe(true);
- });
-
- test('it does NOT render the Fields Browser button as a settings gear when the isEventViewer prop is false', () => {
- const isEventViewer = false;
-
- const wrapper = mount(
-
-
);
- expect(wrapper.find('[data-test-subj="show-field-browser-gear"]').first().exists()).toBe(false);
+ expect(wrapper.find('[data-test-subj="show-field-browser"]').first().exists()).toBe(true);
});
- test('it does NOT render the default Fields Browser button when the isEventViewer prop is true', () => {
+ test('it renders the Fields Browser button as a settings gear when the isEventViewer prop is false', () => {
const isEventViewer = true;
const wrapper = mount(
@@ -232,12 +184,10 @@ describe('StatefulFieldsBrowser', () => {
timelineId={timelineId}
toggleColumn={jest.fn()}
width={FIELD_BROWSER_WIDTH}
- removeColumn={removeColumnMock}
- upsertColumn={upsertColumnMock}
/>
);
- expect(wrapper.find('[data-test-subj="show-field-browser"]').first().exists()).toBe(false);
+ expect(wrapper.find('[data-test-subj="show-field-browser"]').first().exists()).toBe(true);
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.tsx
index a3937107936b6..7b843b4f69447 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.tsx
@@ -4,14 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { noop } from 'lodash/fp';
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
-import { connect, ConnectedProps } from 'react-redux';
import styled from 'styled-components';
import { BrowserFields } from '../../../common/containers/source';
-import { timelineActions } from '../../store/timeline';
import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
import { DEFAULT_CATEGORY_NAME } from '../timeline/body/column_headers/default_headers';
import { FieldsBrowser } from './field_browser';
@@ -34,181 +32,156 @@ FieldsBrowserButtonContainer.displayName = 'FieldsBrowserButtonContainer';
/**
* Manages the state of the field browser
*/
-export const StatefulFieldsBrowserComponent = React.memo(
- ({
- columnHeaders,
- browserFields,
- height,
- isEventViewer = false,
- onFieldSelected,
- onUpdateColumns,
- timelineId,
- toggleColumn,
- width,
- }) => {
- /** tracks the latest timeout id from `setTimeout`*/
- const inputTimeoutId = useRef(0);
-
- /** all field names shown in the field browser must contain this string (when specified) */
- const [filterInput, setFilterInput] = useState('');
- /** all fields in this collection have field names that match the filterInput */
- const [filteredBrowserFields, setFilteredBrowserFields] = useState(null);
- /** when true, show a spinner in the input to indicate the field browser is searching for matching field names */
- const [isSearching, setIsSearching] = useState(false);
- /** this category will be displayed in the right-hand pane of the field browser */
- const [selectedCategoryId, setSelectedCategoryId] = useState(DEFAULT_CATEGORY_NAME);
- /** show the field browser */
- const [show, setShow] = useState(false);
- useEffect(() => {
- return () => {
- if (inputTimeoutId.current !== 0) {
- // ⚠️ mutation: cancel any remaining timers and zero-out the timer id:
- clearTimeout(inputTimeoutId.current);
- inputTimeoutId.current = 0;
- }
- };
- }, []);
-
- /** Shows / hides the field browser */
- const toggleShow = useCallback(() => {
- setShow(!show);
- }, [show]);
-
- /** Invoked when the user types in the filter input */
- const updateFilter = useCallback(
- (newFilterInput: string) => {
- setFilterInput(newFilterInput);
- setIsSearching(true);
- if (inputTimeoutId.current !== 0) {
- clearTimeout(inputTimeoutId.current); // ⚠️ mutation: cancel any previous timers
- }
- // ⚠️ mutation: schedule a new timer that will apply the filter when it fires:
- inputTimeoutId.current = window.setTimeout(() => {
- const newFilteredBrowserFields = filterBrowserFieldsByFieldName({
- browserFields: mergeBrowserFieldsWithDefaultCategory(browserFields),
- substring: newFilterInput,
- });
- setFilteredBrowserFields(newFilteredBrowserFields);
- setIsSearching(false);
-
- const newSelectedCategoryId =
- newFilterInput === '' || Object.keys(newFilteredBrowserFields).length === 0
- ? DEFAULT_CATEGORY_NAME
- : Object.keys(newFilteredBrowserFields)
- .sort()
- .reduce(
- (selected, category) =>
- newFilteredBrowserFields[category].fields != null &&
- newFilteredBrowserFields[selected].fields != null &&
- Object.keys(newFilteredBrowserFields[category].fields!).length >
- Object.keys(newFilteredBrowserFields[selected].fields!).length
- ? category
- : selected,
- Object.keys(newFilteredBrowserFields)[0]
- );
- setSelectedCategoryId(newSelectedCategoryId);
- }, INPUT_TIMEOUT);
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [browserFields, filterInput, inputTimeoutId.current]
- );
-
- /**
- * Invoked when the user clicks a category name in the left-hand side of
- * the field browser
- */
- const updateSelectedCategoryId = useCallback((categoryId: string) => {
- setSelectedCategoryId(categoryId);
- }, []);
-
- /**
- * Invoked when the user clicks on the context menu to view a category's
- * columns in the timeline, this function dispatches the action that
- * causes the timeline display those columns.
- */
- const updateColumnsAndSelectCategoryId = useCallback((columns: ColumnHeaderOptions[]) => {
- onUpdateColumns(columns); // show the category columns in the timeline
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- /** Invoked when the field browser should be hidden */
- const hideFieldBrowser = useCallback(() => {
- setFilterInput('');
- setFilterInput('');
- setFilteredBrowserFields(null);
- setIsSearching(false);
- setSelectedCategoryId(DEFAULT_CATEGORY_NAME);
- setShow(false);
- }, []);
- // only merge in the default category if the field browser is visible
- const browserFieldsWithDefaultCategory = useMemo(
- () => (show ? mergeBrowserFieldsWithDefaultCategory(browserFields) : {}),
- [show, browserFields]
- );
-
- return (
- <>
-
-
- {isEventViewer ? (
-
- ) : (
-
- {i18n.FIELDS}
-
- )}
-
-
- {show && (
-
- )}
-
- >
- );
- }
-);
-
-const mapDispatchToProps = {
- removeColumn: timelineActions.removeColumn,
- upsertColumn: timelineActions.upsertColumn,
+export const StatefulFieldsBrowserComponent: React.FC = ({
+ columnHeaders,
+ browserFields,
+ height,
+ isEventViewer = false,
+ onFieldSelected,
+ onUpdateColumns,
+ timelineId,
+ toggleColumn,
+ width,
+}) => {
+ /** tracks the latest timeout id from `setTimeout`*/
+ const inputTimeoutId = useRef(0);
+
+ /** all field names shown in the field browser must contain this string (when specified) */
+ const [filterInput, setFilterInput] = useState('');
+ /** all fields in this collection have field names that match the filterInput */
+ const [filteredBrowserFields, setFilteredBrowserFields] = useState(null);
+ /** when true, show a spinner in the input to indicate the field browser is searching for matching field names */
+ const [isSearching, setIsSearching] = useState(false);
+ /** this category will be displayed in the right-hand pane of the field browser */
+ const [selectedCategoryId, setSelectedCategoryId] = useState(DEFAULT_CATEGORY_NAME);
+ /** show the field browser */
+ const [show, setShow] = useState(false);
+ useEffect(() => {
+ return () => {
+ if (inputTimeoutId.current !== 0) {
+ // ⚠️ mutation: cancel any remaining timers and zero-out the timer id:
+ clearTimeout(inputTimeoutId.current);
+ inputTimeoutId.current = 0;
+ }
+ };
+ }, []);
+
+ /** Shows / hides the field browser */
+ const toggleShow = useCallback(() => {
+ setShow(!show);
+ }, [show]);
+
+ /** Invoked when the user types in the filter input */
+ const updateFilter = useCallback(
+ (newFilterInput: string) => {
+ setFilterInput(newFilterInput);
+ setIsSearching(true);
+ if (inputTimeoutId.current !== 0) {
+ clearTimeout(inputTimeoutId.current); // ⚠️ mutation: cancel any previous timers
+ }
+ // ⚠️ mutation: schedule a new timer that will apply the filter when it fires:
+ inputTimeoutId.current = window.setTimeout(() => {
+ const newFilteredBrowserFields = filterBrowserFieldsByFieldName({
+ browserFields: mergeBrowserFieldsWithDefaultCategory(browserFields),
+ substring: newFilterInput,
+ });
+ setFilteredBrowserFields(newFilteredBrowserFields);
+ setIsSearching(false);
+
+ const newSelectedCategoryId =
+ newFilterInput === '' || Object.keys(newFilteredBrowserFields).length === 0
+ ? DEFAULT_CATEGORY_NAME
+ : Object.keys(newFilteredBrowserFields)
+ .sort()
+ .reduce(
+ (selected, category) =>
+ newFilteredBrowserFields[category].fields != null &&
+ newFilteredBrowserFields[selected].fields != null &&
+ Object.keys(newFilteredBrowserFields[category].fields!).length >
+ Object.keys(newFilteredBrowserFields[selected].fields!).length
+ ? category
+ : selected,
+ Object.keys(newFilteredBrowserFields)[0]
+ );
+ setSelectedCategoryId(newSelectedCategoryId);
+ }, INPUT_TIMEOUT);
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [browserFields, filterInput, inputTimeoutId.current]
+ );
+
+ /**
+ * Invoked when the user clicks a category name in the left-hand side of
+ * the field browser
+ */
+ const updateSelectedCategoryId = useCallback((categoryId: string) => {
+ setSelectedCategoryId(categoryId);
+ }, []);
+
+ /**
+ * Invoked when the user clicks on the context menu to view a category's
+ * columns in the timeline, this function dispatches the action that
+ * causes the timeline display those columns.
+ */
+ const updateColumnsAndSelectCategoryId = useCallback((columns: ColumnHeaderOptions[]) => {
+ onUpdateColumns(columns); // show the category columns in the timeline
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ /** Invoked when the field browser should be hidden */
+ const hideFieldBrowser = useCallback(() => {
+ setFilterInput('');
+ setFilterInput('');
+ setFilteredBrowserFields(null);
+ setIsSearching(false);
+ setSelectedCategoryId(DEFAULT_CATEGORY_NAME);
+ setShow(false);
+ }, []);
+ // only merge in the default category if the field browser is visible
+ const browserFieldsWithDefaultCategory = useMemo(
+ () => (show ? mergeBrowserFieldsWithDefaultCategory(browserFields) : {}),
+ [show, browserFields]
+ );
+
+ return (
+
+
+
+ {i18n.FIELDS}
+
+
+
+ {show && (
+
+ )}
+
+ );
};
-const connector = connect(null, mapDispatchToProps);
-
-type PropsFromRedux = ConnectedProps;
-
-export const StatefulFieldsBrowser = connector(React.memo(StatefulFieldsBrowserComponent));
+export const StatefulFieldsBrowser = React.memo(StatefulFieldsBrowserComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx
index fbe3c475c9fe6..8c03d82aafafb 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx
@@ -28,6 +28,7 @@ interface FlyoutPaneComponentProps {
const EuiFlyoutContainer = styled.div`
.timeline-flyout {
+ z-index: 4001;
min-width: 150px;
width: auto;
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts
index 31ac3240afb72..89a35fb838a96 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts
@@ -270,6 +270,7 @@ describe('helpers', () => {
deletedEventIds: [],
eventIdToNoteIds: {},
eventType: 'all',
+ excludedRowRendererIds: [],
filters: [],
highlightedDropAndProviderId: '',
historyIds: [],
@@ -294,7 +295,6 @@ describe('helpers', () => {
selectedEventIds: {},
show: false,
showCheckboxes: false,
- showRowRenderers: true,
sort: {
columnId: '@timestamp',
sortDirection: 'desc',
@@ -368,6 +368,7 @@ describe('helpers', () => {
deletedEventIds: [],
eventIdToNoteIds: {},
eventType: 'all',
+ excludedRowRendererIds: [],
filters: [],
highlightedDropAndProviderId: '',
historyIds: [],
@@ -392,7 +393,6 @@ describe('helpers', () => {
selectedEventIds: {},
show: false,
showCheckboxes: false,
- showRowRenderers: true,
sort: {
columnId: '@timestamp',
sortDirection: 'desc',
@@ -502,6 +502,7 @@ describe('helpers', () => {
deletedEventIds: [],
eventIdToNoteIds: {},
eventType: 'all',
+ excludedRowRendererIds: [],
filters: [],
highlightedDropAndProviderId: '',
historyIds: [],
@@ -532,7 +533,6 @@ describe('helpers', () => {
selectedEventIds: {},
show: false,
showCheckboxes: false,
- showRowRenderers: true,
sort: {
columnId: '@timestamp',
sortDirection: 'desc',
@@ -628,6 +628,7 @@ describe('helpers', () => {
deletedEventIds: [],
eventIdToNoteIds: {},
eventType: 'all',
+ excludedRowRendererIds: [],
filters: [
{
$state: {
@@ -701,7 +702,6 @@ describe('helpers', () => {
selectedEventIds: {},
show: false,
showCheckboxes: false,
- showRowRenderers: true,
sort: {
columnId: '@timestamp',
sortDirection: 'desc',
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts
index c21edaa916588..a8485328e8393 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts
@@ -13,6 +13,7 @@ import {
TimelineTypeLiteralWithNull,
TimelineStatus,
TemplateTimelineTypeLiteral,
+ RowRendererId,
} from '../../../../common/types/timeline';
/** The users who added a timeline to favorites */
@@ -46,6 +47,7 @@ export interface OpenTimelineResult {
created?: number | null;
description?: string | null;
eventIdToNoteIds?: Readonly> | null;
+ excludedRowRendererIds?: RowRendererId[] | null;
favorite?: FavoriteTimelineResult[] | null;
noteIds?: string[] | null;
notes?: TimelineResultNote[] | null;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx
new file mode 100644
index 0000000000000..55d1694297e2e
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx
@@ -0,0 +1,199 @@
+/*
+ * 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 { EuiLink } from '@elastic/eui';
+import React from 'react';
+import { ExternalLinkIcon } from '../../../../common/components/external_link_icon';
+
+import { RowRendererId } from '../../../../../common/types/timeline';
+import {
+ AuditdExample,
+ AuditdFileExample,
+ NetflowExample,
+ SuricataExample,
+ SystemExample,
+ SystemDnsExample,
+ SystemEndgameProcessExample,
+ SystemFileExample,
+ SystemFimExample,
+ SystemSecurityEventExample,
+ SystemSocketExample,
+ ZeekExample,
+} from '../examples';
+import * as i18n from './translations';
+
+const Link = ({ children, url }: { children: React.ReactNode; url: string }) => (
+
+ {children}
+
+
+);
+
+export interface RowRendererOption {
+ id: RowRendererId;
+ name: string;
+ description: React.ReactNode;
+ searchableDescription: string;
+ example: React.ReactNode;
+}
+
+export const renderers: RowRendererOption[] = [
+ {
+ id: RowRendererId.auditd,
+ name: i18n.AUDITD_NAME,
+ description: (
+
+
+ {i18n.AUDITD_NAME}
+ {' '}
+ {i18n.AUDITD_DESCRIPTION_PART1}
+
+ ),
+ example: AuditdExample,
+ searchableDescription: `${i18n.AUDITD_NAME} ${i18n.AUDITD_DESCRIPTION_PART1}`,
+ },
+ {
+ id: RowRendererId.auditd_file,
+ name: i18n.AUDITD_FILE_NAME,
+ description: (
+
+
+ {i18n.AUDITD_NAME}
+ {' '}
+ {i18n.AUDITD_FILE_DESCRIPTION_PART1}
+
+ ),
+ example: AuditdFileExample,
+ searchableDescription: `${i18n.AUDITD_FILE_NAME} ${i18n.AUDITD_FILE_DESCRIPTION_PART1}`,
+ },
+ {
+ id: RowRendererId.system_security_event,
+ name: i18n.AUTHENTICATION_NAME,
+ description: (
+
+
{i18n.AUTHENTICATION_DESCRIPTION_PART1}
+
+
{i18n.AUTHENTICATION_DESCRIPTION_PART2}
+
+ ),
+ example: SystemSecurityEventExample,
+ searchableDescription: `${i18n.AUTHENTICATION_DESCRIPTION_PART1} ${i18n.AUTHENTICATION_DESCRIPTION_PART2}`,
+ },
+ {
+ id: RowRendererId.system_dns,
+ name: i18n.DNS_NAME,
+ description: i18n.DNS_DESCRIPTION_PART1,
+ example: SystemDnsExample,
+ searchableDescription: i18n.DNS_DESCRIPTION_PART1,
+ },
+ {
+ id: RowRendererId.netflow,
+ name: i18n.FLOW_NAME,
+ description: (
+
+
{i18n.FLOW_DESCRIPTION_PART1}
+
+
{i18n.FLOW_DESCRIPTION_PART2}
+
+ ),
+ example: NetflowExample,
+ searchableDescription: `${i18n.FLOW_DESCRIPTION_PART1} ${i18n.FLOW_DESCRIPTION_PART2}`,
+ },
+ {
+ id: RowRendererId.system,
+ name: i18n.SYSTEM_NAME,
+ description: (
+
+
+ {i18n.SYSTEM_DESCRIPTION_PART1}{' '}
+
+ {i18n.SYSTEM_NAME}
+ {' '}
+ {i18n.SYSTEM_DESCRIPTION_PART2}
+
+
+
{i18n.SYSTEM_DESCRIPTION_PART3}
+
+ ),
+ example: SystemExample,
+ searchableDescription: `${i18n.SYSTEM_DESCRIPTION_PART1} ${i18n.SYSTEM_NAME} ${i18n.SYSTEM_DESCRIPTION_PART2} ${i18n.SYSTEM_DESCRIPTION_PART3}`,
+ },
+ {
+ id: RowRendererId.system_endgame_process,
+ name: i18n.PROCESS,
+ description: (
+
+
{i18n.PROCESS_DESCRIPTION_PART1}
+
+
{i18n.PROCESS_DESCRIPTION_PART2}
+
+ ),
+ example: SystemEndgameProcessExample,
+ searchableDescription: `${i18n.PROCESS_DESCRIPTION_PART1} ${i18n.PROCESS_DESCRIPTION_PART2}`,
+ },
+ {
+ id: RowRendererId.system_fim,
+ name: i18n.FIM_NAME,
+ description: i18n.FIM_DESCRIPTION_PART1,
+ example: SystemFimExample,
+ searchableDescription: i18n.FIM_DESCRIPTION_PART1,
+ },
+ {
+ id: RowRendererId.system_file,
+ name: i18n.FILE_NAME,
+ description: i18n.FILE_DESCRIPTION_PART1,
+ example: SystemFileExample,
+ searchableDescription: i18n.FILE_DESCRIPTION_PART1,
+ },
+ {
+ id: RowRendererId.system_socket,
+ name: i18n.SOCKET_NAME,
+ description: (
+
+
{i18n.SOCKET_DESCRIPTION_PART1}
+
+
{i18n.SOCKET_DESCRIPTION_PART2}
+
+ ),
+ example: SystemSocketExample,
+ searchableDescription: `${i18n.SOCKET_DESCRIPTION_PART1} ${i18n.SOCKET_DESCRIPTION_PART2}`,
+ },
+ {
+ id: RowRendererId.suricata,
+ name: 'Suricata',
+ description: (
+
+ {i18n.SURICATA_DESCRIPTION_PART1}{' '}
+
+ {i18n.SURICATA_NAME}
+ {' '}
+ {i18n.SURICATA_DESCRIPTION_PART2}
+
+ ),
+ example: SuricataExample,
+ searchableDescription: `${i18n.SURICATA_DESCRIPTION_PART1} ${i18n.SURICATA_NAME} ${i18n.SURICATA_DESCRIPTION_PART2}`,
+ },
+ {
+ id: RowRendererId.zeek,
+ name: i18n.ZEEK_NAME,
+ description: (
+
+ {i18n.ZEEK_DESCRIPTION_PART1}{' '}
+
+ {i18n.ZEEK_NAME}
+ {' '}
+ {i18n.ZEEK_DESCRIPTION_PART2}
+
+ ),
+ example: ZeekExample,
+ searchableDescription: `${i18n.ZEEK_DESCRIPTION_PART1} ${i18n.ZEEK_NAME} ${i18n.ZEEK_DESCRIPTION_PART2}`,
+ },
+];
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts
new file mode 100644
index 0000000000000..f4d473cdfd3d2
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts
@@ -0,0 +1,215 @@
+/*
+ * 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 AUDITD_NAME = i18n.translate('xpack.securitySolution.eventRenderers.auditdName', {
+ defaultMessage: 'Auditd',
+});
+
+export const AUDITD_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.auditdDescriptionPart1',
+ {
+ defaultMessage: 'audit events convey security-relevant logs from the Linux Audit Framework.',
+ }
+);
+
+export const AUDITD_FILE_NAME = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.auditdFileName',
+ {
+ defaultMessage: 'Auditd File',
+ }
+);
+
+export const AUDITD_FILE_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.auditdFileDescriptionPart1',
+ {
+ defaultMessage:
+ 'File events show users (and system accounts) performing CRUD operations on files via specific processes.',
+ }
+);
+
+export const AUTHENTICATION_NAME = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.authenticationName',
+ {
+ defaultMessage: 'Authentication',
+ }
+);
+
+export const AUTHENTICATION_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.authenticationDescriptionPart1',
+ {
+ defaultMessage:
+ 'Authentication events show users (and system accounts) successfully or unsuccessfully logging into hosts.',
+ }
+);
+
+export const AUTHENTICATION_DESCRIPTION_PART2 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.authenticationDescriptionPart2',
+ {
+ defaultMessage:
+ 'Some authentication events may include additional details when users authenticate on behalf of other users.',
+ }
+);
+
+export const DNS_NAME = i18n.translate('xpack.securitySolution.eventRenderers.dnsName', {
+ defaultMessage: 'Domain Name System (DNS)',
+});
+
+export const DNS_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.dnsDescriptionPart1',
+ {
+ defaultMessage:
+ 'Domain Name System (DNS) events show users (and system accounts) making requests via specific processes to translate from host names to IP addresses.',
+ }
+);
+
+export const FILE_NAME = i18n.translate('xpack.securitySolution.eventRenderers.fileName', {
+ defaultMessage: 'File',
+});
+
+export const FILE_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.fileDescriptionPart1',
+ {
+ defaultMessage:
+ 'File events show users (and system accounts) performing CRUD operations on files via specific processes.',
+ }
+);
+
+export const FIM_NAME = i18n.translate('xpack.securitySolution.eventRenderers.fimName', {
+ defaultMessage: 'File Integrity Module (FIM)',
+});
+
+export const FIM_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.fimDescriptionPart1',
+ {
+ defaultMessage:
+ 'File Integrity Module (FIM) events show users (and system accounts) performing CRUD operations on files via specific processes.',
+ }
+);
+
+export const FLOW_NAME = i18n.translate('xpack.securitySolution.eventRenderers.flowName', {
+ defaultMessage: 'Flow',
+});
+
+export const FLOW_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.flowDescriptionPart1',
+ {
+ defaultMessage:
+ "The Flow renderer visualizes the flow of data between a source and destination. It's applicable to many types of events.",
+ }
+);
+
+export const FLOW_DESCRIPTION_PART2 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.flowDescriptionPart2',
+ {
+ defaultMessage:
+ 'The hosts, ports, protocol, direction, duration, amount transferred, process, geographic location, and other details are visualized when available.',
+ }
+);
+
+export const PROCESS = i18n.translate('xpack.securitySolution.eventRenderers.processName', {
+ defaultMessage: 'Process',
+});
+
+export const PROCESS_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.processDescriptionPart1',
+ {
+ defaultMessage:
+ 'Process events show users (and system accounts) starting and stopping processes.',
+ }
+);
+
+export const PROCESS_DESCRIPTION_PART2 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.processDescriptionPart2',
+ {
+ defaultMessage:
+ 'Details including the command line arguments, parent process, and if applicable, file hashes are displayed when available.',
+ }
+);
+
+export const SOCKET_NAME = i18n.translate('xpack.securitySolution.eventRenderers.socketName', {
+ defaultMessage: 'Socket (Network)',
+});
+
+export const SOCKET_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.socketDescriptionPart1',
+ {
+ defaultMessage:
+ 'Socket (Network) events show processes listening, accepting, and closing connections.',
+ }
+);
+
+export const SOCKET_DESCRIPTION_PART2 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.socketDescriptionPart2',
+ {
+ defaultMessage:
+ 'Details including the protocol, ports, and a community ID for correlating all network events related to a single flow are displayed when available.',
+ }
+);
+
+export const SURICATA_NAME = i18n.translate('xpack.securitySolution.eventRenderers.suricataName', {
+ defaultMessage: 'Suricata',
+});
+
+export const SURICATA_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.suricataDescriptionPart1',
+ {
+ defaultMessage: 'Summarizes',
+ }
+);
+
+export const SURICATA_DESCRIPTION_PART2 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.suricataDescriptionPart2',
+ {
+ defaultMessage:
+ 'intrusion detection (IDS), inline intrusion prevention (IPS), and network security monitoring (NSM) events',
+ }
+);
+
+export const SYSTEM_NAME = i18n.translate('xpack.securitySolution.eventRenderers.systemName', {
+ defaultMessage: 'System',
+});
+
+export const SYSTEM_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.systemDescriptionPart1',
+ {
+ defaultMessage: 'The Auditbeat',
+ }
+);
+
+export const SYSTEM_DESCRIPTION_PART2 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.systemDescriptionPart2',
+ {
+ defaultMessage: 'module collects various security related information about a system.',
+ }
+);
+
+export const SYSTEM_DESCRIPTION_PART3 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.systemDescriptionPart3',
+ {
+ defaultMessage:
+ 'All datasets send both periodic state information (e.g. all currently running processes) and real-time changes (e.g. when a new process starts or stops).',
+ }
+);
+
+export const ZEEK_NAME = i18n.translate('xpack.securitySolution.eventRenderers.zeekName', {
+ defaultMessage: 'Zeek (formerly Bro)',
+});
+
+export const ZEEK_DESCRIPTION_PART1 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.zeekDescriptionPart1',
+ {
+ defaultMessage: 'Summarizes events from the',
+ }
+);
+
+export const ZEEK_DESCRIPTION_PART2 = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.zeekDescriptionPart2',
+ {
+ defaultMessage: 'Network Security Monitoring (NSM) tool',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/constants.ts b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/constants.ts
new file mode 100644
index 0000000000000..4749afda9570a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/constants.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 const ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID = 'row-renderer-example';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd.tsx
new file mode 100644
index 0000000000000..d90d0fdfa558b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { mockTimelineData } from '../../../../common/mock/mock_timeline_data';
+import { createGenericAuditRowRenderer } from '../../timeline/body/renderers/auditd/generic_row_renderer';
+import { CONNECTED_USING } from '../../timeline/body/renderers/auditd/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const AuditdExampleComponent: React.FC = () => {
+ const auditdRowRenderer = createGenericAuditRowRenderer({
+ actionName: 'connected-to',
+ text: CONNECTED_USING,
+ });
+
+ return (
+ <>
+ {auditdRowRenderer.renderRow({
+ browserFields: {},
+ data: mockTimelineData[26].ecs,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const AuditdExample = React.memo(AuditdExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd_file.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd_file.tsx
new file mode 100644
index 0000000000000..fc8e51864f50a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/auditd_file.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { mockTimelineData } from '../../../../common/mock/mock_timeline_data';
+import { createGenericFileRowRenderer } from '../../timeline/body/renderers/auditd/generic_row_renderer';
+import { OPENED_FILE, USING } from '../../timeline/body/renderers/auditd/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const AuditdFileExampleComponent: React.FC = () => {
+ const auditdFileRowRenderer = createGenericFileRowRenderer({
+ actionName: 'opened-file',
+ text: `${OPENED_FILE} ${USING}`,
+ });
+
+ return (
+ <>
+ {auditdFileRowRenderer.renderRow({
+ browserFields: {},
+ data: mockTimelineData[27].ecs,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const AuditdFileExample = React.memo(AuditdFileExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx
new file mode 100644
index 0000000000000..3cc39a3bf7050
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.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.
+ */
+
+export * from './auditd';
+export * from './auditd_file';
+export * from './netflow';
+export * from './suricata';
+export * from './system';
+export * from './system_dns';
+export * from './system_endgame_process';
+export * from './system_file';
+export * from './system_fim';
+export * from './system_security_event';
+export * from './system_socket';
+export * from './zeek';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/netflow.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/netflow.tsx
new file mode 100644
index 0000000000000..a276bafb65c60
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/netflow.tsx
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { getMockNetflowData } from '../../../../common/mock/netflow';
+import { netflowRowRenderer } from '../../timeline/body/renderers/netflow/netflow_row_renderer';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const NetflowExampleComponent: React.FC = () => (
+ <>
+ {netflowRowRenderer.renderRow({
+ browserFields: {},
+ data: getMockNetflowData(),
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+);
+export const NetflowExample = React.memo(NetflowExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/suricata.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/suricata.tsx
new file mode 100644
index 0000000000000..318f427b81f28
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/suricata.tsx
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { mockTimelineData } from '../../../../common/mock/mock_timeline_data';
+import { suricataRowRenderer } from '../../timeline/body/renderers/suricata/suricata_row_renderer';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const SuricataExampleComponent: React.FC = () => (
+ <>
+ {suricataRowRenderer.renderRow({
+ browserFields: {},
+ data: mockTimelineData[2].ecs,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+);
+export const SuricataExample = React.memo(SuricataExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system.tsx
new file mode 100644
index 0000000000000..c8c3b48ac366a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { TERMINATED_PROCESS } from '../../timeline/body/renderers/system/translations';
+import { createGenericSystemRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { mockEndgameTerminationEvent } from '../../../../common/mock/mock_endgame_ecs_data';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const SystemExampleComponent: React.FC = () => {
+ const systemRowRenderer = createGenericSystemRowRenderer({
+ actionName: 'termination_event',
+ text: TERMINATED_PROCESS,
+ });
+
+ return (
+ <>
+ {systemRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndgameTerminationEvent,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const SystemExample = React.memo(SystemExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_dns.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_dns.tsx
new file mode 100644
index 0000000000000..4937b0f05ce7c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_dns.tsx
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { createDnsRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { mockEndgameDnsRequest } from '../../../../common/mock/mock_endgame_ecs_data';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const SystemDnsExampleComponent: React.FC = () => {
+ const systemDnsRowRenderer = createDnsRowRenderer();
+
+ return (
+ <>
+ {systemDnsRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndgameDnsRequest,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const SystemDnsExample = React.memo(SystemDnsExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_endgame_process.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_endgame_process.tsx
new file mode 100644
index 0000000000000..675bc172ab6f7
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_endgame_process.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { createEndgameProcessRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { mockEndgameCreationEvent } from '../../../../common/mock/mock_endgame_ecs_data';
+import { PROCESS_STARTED } from '../../timeline/body/renderers/system/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const SystemEndgameProcessExampleComponent: React.FC = () => {
+ const systemEndgameProcessRowRenderer = createEndgameProcessRowRenderer({
+ actionName: 'creation_event',
+ text: PROCESS_STARTED,
+ });
+
+ return (
+ <>
+ {systemEndgameProcessRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndgameCreationEvent,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const SystemEndgameProcessExample = React.memo(SystemEndgameProcessExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_file.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_file.tsx
new file mode 100644
index 0000000000000..62c243a7e8502
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_file.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { mockEndgameFileDeleteEvent } from '../../../../common/mock/mock_endgame_ecs_data';
+import { createGenericFileRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { DELETED_FILE } from '../../timeline/body/renderers/system/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const SystemFileExampleComponent: React.FC = () => {
+ const systemFileRowRenderer = createGenericFileRowRenderer({
+ actionName: 'file_delete_event',
+ text: DELETED_FILE,
+ });
+
+ return (
+ <>
+ {systemFileRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndgameFileDeleteEvent,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const SystemFileExample = React.memo(SystemFileExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_fim.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_fim.tsx
new file mode 100644
index 0000000000000..ad3eeb7f797ff
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_fim.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { mockEndgameFileCreateEvent } from '../../../../common/mock/mock_endgame_ecs_data';
+import { createFimRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { CREATED_FILE } from '../../timeline/body/renderers/system/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const SystemFimExampleComponent: React.FC = () => {
+ const systemFimRowRenderer = createFimRowRenderer({
+ actionName: 'file_create_event',
+ text: CREATED_FILE,
+ });
+
+ return (
+ <>
+ {systemFimRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndgameFileCreateEvent,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const SystemFimExample = React.memo(SystemFimExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_security_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_security_event.tsx
new file mode 100644
index 0000000000000..bc577771cc90c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_security_event.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 { createSecurityEventRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { mockEndgameUserLogon } from '../../../../common/mock/mock_endgame_ecs_data';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const SystemSecurityEventExampleComponent: React.FC = () => {
+ const systemSecurityEventRowRenderer = createSecurityEventRowRenderer({
+ actionName: 'user_logon',
+ });
+
+ return (
+ <>
+ {systemSecurityEventRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndgameUserLogon,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const SystemSecurityEventExample = React.memo(SystemSecurityEventExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_socket.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_socket.tsx
new file mode 100644
index 0000000000000..dd119d1b60f39
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/system_socket.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 { ACCEPTED_A_CONNECTION_VIA } from '../../timeline/body/renderers/system/translations';
+import { createSocketRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { mockEndgameIpv4ConnectionAcceptEvent } from '../../../../common/mock/mock_endgame_ecs_data';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const SystemSocketExampleComponent: React.FC = () => {
+ const systemSocketRowRenderer = createSocketRowRenderer({
+ actionName: 'ipv4_connection_accept_event',
+ text: ACCEPTED_A_CONNECTION_VIA,
+ });
+ return (
+ <>
+ {systemSocketRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndgameIpv4ConnectionAcceptEvent,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const SystemSocketExample = React.memo(SystemSocketExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/zeek.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/zeek.tsx
new file mode 100644
index 0000000000000..56f0d207fbc6d
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/zeek.tsx
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { mockTimelineData } from '../../../../common/mock/mock_timeline_data';
+import { zeekRowRenderer } from '../../timeline/body/renderers/zeek/zeek_row_renderer';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const ZeekExampleComponent: React.FC = () => (
+ <>
+ {zeekRowRenderer.renderRow({
+ browserFields: {},
+ data: mockTimelineData[13].ecs,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+);
+export const ZeekExample = React.memo(ZeekExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx
new file mode 100644
index 0000000000000..2792b264ba7e2
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx
@@ -0,0 +1,182 @@
+/*
+ * 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 {
+ EuiButtonEmpty,
+ EuiButtonIcon,
+ EuiText,
+ EuiToolTip,
+ EuiOverlayMask,
+ EuiModal,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiModalBody,
+ EuiButton,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiInMemoryTable,
+} from '@elastic/eui';
+import React, { useState, useCallback, useRef } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import styled from 'styled-components';
+
+import { State } from '../../../common/store';
+
+import { renderers } from './catalog';
+import { setExcludedRowRendererIds as dispatchSetExcludedRowRendererIds } from '../../../timelines/store/timeline/actions';
+import { RowRenderersBrowser } from './row_renderers_browser';
+import * as i18n from './translations';
+
+const StyledEuiModal = styled(EuiModal)`
+ margin: 0 auto;
+ max-width: 95vw;
+ min-height: 95vh;
+
+ > .euiModal__flex {
+ max-height: 95vh;
+ }
+`;
+
+const StyledEuiModalBody = styled(EuiModalBody)`
+ .euiModalBody__overflow {
+ display: flex;
+ align-items: stretch;
+ overflow: hidden;
+
+ > div {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+
+ > div:first-child {
+ flex: 0;
+ }
+
+ .euiBasicTable {
+ flex: 1;
+ overflow: auto;
+ }
+ }
+ }
+`;
+
+const StyledEuiOverlayMask = styled(EuiOverlayMask)`
+ z-index: 8001;
+ padding-bottom: 0;
+
+ > div {
+ width: 100%;
+ }
+`;
+
+interface StatefulRowRenderersBrowserProps {
+ timelineId: string;
+}
+
+const StatefulRowRenderersBrowserComponent: React.FC = ({
+ timelineId,
+}) => {
+ const tableRef = useRef>();
+ const dispatch = useDispatch();
+ const excludedRowRendererIds = useSelector(
+ (state: State) => state.timeline.timelineById[timelineId]?.excludedRowRendererIds || []
+ );
+ const [show, setShow] = useState(false);
+
+ const setExcludedRowRendererIds = useCallback(
+ (payload) =>
+ dispatch(
+ dispatchSetExcludedRowRendererIds({
+ id: timelineId,
+ excludedRowRendererIds: payload,
+ })
+ ),
+ [dispatch, timelineId]
+ );
+
+ const toggleShow = useCallback(() => setShow(!show), [show]);
+
+ const hideFieldBrowser = useCallback(() => setShow(false), []);
+
+ const handleDisableAll = useCallback(() => {
+ // eslint-disable-next-line no-unused-expressions
+ tableRef?.current?.setSelection([]);
+ }, [tableRef]);
+
+ const handleEnableAll = useCallback(() => {
+ // eslint-disable-next-line no-unused-expressions
+ tableRef?.current?.setSelection(renderers);
+ }, [tableRef]);
+
+ return (
+ <>
+
+
+ {i18n.EVENT_RENDERERS_TITLE}
+
+
+
+ {show && (
+
+
+
+
+
+ {i18n.CUSTOMIZE_EVENT_RENDERERS_TITLE}
+ {i18n.CUSTOMIZE_EVENT_RENDERERS_DESCRIPTION}
+
+
+
+
+
+ {i18n.DISABLE_ALL}
+
+
+
+
+
+ {i18n.ENABLE_ALL}
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+export const StatefulRowRenderersBrowser = React.memo(StatefulRowRenderersBrowserComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/row_renderers_browser.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/row_renderers_browser.tsx
new file mode 100644
index 0000000000000..d2b0ad788fdb5
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/row_renderers_browser.tsx
@@ -0,0 +1,179 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiFlexItem, EuiInMemoryTable } from '@elastic/eui';
+import React, { useMemo, useCallback } from 'react';
+import { xor, xorBy } from 'lodash/fp';
+import styled from 'styled-components';
+
+import { RowRendererId } from '../../../../common/types/timeline';
+import { renderers, RowRendererOption } from './catalog';
+
+interface RowRenderersBrowserProps {
+ excludedRowRendererIds: RowRendererId[];
+ setExcludedRowRendererIds: (excludedRowRendererIds: RowRendererId[]) => void;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)`
+ .euiTable {
+ tr > *:last-child {
+ display: none;
+ }
+
+ .euiTableHeaderCellCheckbox > .euiTableCellContent {
+ display: none; // we don't want to display checkbox in the table
+ }
+ }
+`;
+
+const StyledEuiFlexItem = styled(EuiFlexItem)`
+ overflow: auto;
+
+ > div {
+ padding: 0;
+
+ > div {
+ margin: 0;
+ }
+ }
+`;
+
+const ExampleWrapperComponent = (Example?: React.ElementType) => {
+ if (!Example) return;
+
+ return (
+
+
+
+ );
+};
+
+const search = {
+ box: {
+ incremental: true,
+ schema: true,
+ },
+};
+
+/**
+ * Since `searchableDescription` contains raw text to power the Search bar,
+ * this "noop" function ensures it's not actually rendered
+ */
+const renderSearchableDescriptionNoop = () => <>>;
+
+const initialSorting = {
+ sort: {
+ field: 'name',
+ direction: 'asc',
+ },
+};
+
+const StyledNameButton = styled.button`
+ text-align: left;
+`;
+
+const RowRenderersBrowserComponent = React.forwardRef(
+ ({ excludedRowRendererIds = [], setExcludedRowRendererIds }: RowRenderersBrowserProps, ref) => {
+ const notExcludedRowRenderers = useMemo(() => {
+ if (excludedRowRendererIds.length === Object.keys(RowRendererId).length) return [];
+
+ return renderers.filter((renderer) => !excludedRowRendererIds.includes(renderer.id));
+ }, [excludedRowRendererIds]);
+
+ const handleNameClick = useCallback(
+ (item: RowRendererOption) => () => {
+ const newSelection = xor([item], notExcludedRowRenderers);
+ // @ts-ignore
+ ref?.current?.setSelection(newSelection); // eslint-disable-line no-unused-expressions
+ },
+ [notExcludedRowRenderers, ref]
+ );
+
+ const nameColumnRenderCallback = useCallback(
+ (value, item) => (
+
+ {value}
+
+ ),
+ [handleNameClick]
+ );
+
+ const columns = useMemo(
+ () => [
+ {
+ field: 'name',
+ name: 'Name',
+ sortable: true,
+ width: '10%',
+ render: nameColumnRenderCallback,
+ },
+ {
+ field: 'description',
+ name: 'Description',
+ width: '25%',
+ render: (description: React.ReactNode) => description,
+ },
+ {
+ field: 'example',
+ name: 'Example',
+ width: '65%',
+ render: ExampleWrapperComponent,
+ },
+ {
+ field: 'searchableDescription',
+ name: 'Searchable Description',
+ sortable: false,
+ width: '0px',
+ render: renderSearchableDescriptionNoop,
+ },
+ ],
+ [nameColumnRenderCallback]
+ );
+
+ const handleSelectable = useCallback(() => true, []);
+
+ const handleSelectionChange = useCallback(
+ (selection: RowRendererOption[]) => {
+ if (!selection || !selection.length)
+ return setExcludedRowRendererIds(Object.values(RowRendererId));
+
+ const excludedRowRenderers = xorBy('id', renderers, selection);
+
+ setExcludedRowRendererIds(excludedRowRenderers.map((rowRenderer) => rowRenderer.id));
+ },
+ [setExcludedRowRendererIds]
+ );
+
+ const selectionValue = useMemo(
+ () => ({
+ selectable: handleSelectable,
+ onSelectionChange: handleSelectionChange,
+ initialSelected: notExcludedRowRenderers,
+ }),
+ [handleSelectable, handleSelectionChange, notExcludedRowRenderers]
+ );
+
+ return (
+
+ );
+ }
+);
+
+RowRenderersBrowserComponent.displayName = 'RowRenderersBrowserComponent';
+
+export const RowRenderersBrowser = React.memo(RowRenderersBrowserComponent);
+
+RowRenderersBrowser.displayName = 'RowRenderersBrowser';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/translations.ts
new file mode 100644
index 0000000000000..93874ff3240bf
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/translations.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 EVENT_RENDERERS_TITLE = i18n.translate(
+ 'xpack.securitySolution.customizeEventRenderers.eventRenderersTitle',
+ {
+ defaultMessage: 'Event Renderers',
+ }
+);
+
+export const CUSTOMIZE_EVENT_RENDERERS_TITLE = i18n.translate(
+ 'xpack.securitySolution.customizeEventRenderers.customizeEventRenderersTitle',
+ {
+ defaultMessage: 'Customize Event Renderers',
+ }
+);
+
+export const CUSTOMIZE_EVENT_RENDERERS_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.customizeEventRenderers.customizeEventRenderersDescription',
+ {
+ defaultMessage:
+ 'Event Renderers automatically convey the most relevant details in an event to reveal its story',
+ }
+);
+
+export const ENABLE_ALL = i18n.translate(
+ 'xpack.securitySolution.customizeEventRenderers.enableAllRenderersButtonLabel',
+ {
+ defaultMessage: 'Enable all',
+ }
+);
+
+export const DISABLE_ALL = i18n.translate(
+ 'xpack.securitySolution.customizeEventRenderers.disableAllRenderersButtonLabel',
+ {
+ defaultMessage: 'Disable all',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
index 2facb2b3d0ede..125ba23a5c5a5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx
@@ -119,9 +119,9 @@ export const Actions = React.memo(
- {loading && }
-
- {!loading && (
+ {loading ? (
+
+ ) : (
-
-
-
+ }
+ columnHeaders={
+ Array [
+ Object {
+ "columnHeaderType": "not-filtered",
+ "id": "@timestamp",
+ "width": 190,
+ },
+ Object {
+ "columnHeaderType": "not-filtered",
+ "id": "message",
+ "width": 180,
+ },
+ Object {
+ "columnHeaderType": "not-filtered",
+ "id": "event.category",
+ "width": 180,
+ },
+ Object {
+ "columnHeaderType": "not-filtered",
+ "id": "event.action",
+ "width": 180,
+ },
+ Object {
+ "columnHeaderType": "not-filtered",
+ "id": "host.name",
+ "width": 180,
+ },
+ Object {
+ "columnHeaderType": "not-filtered",
+ "id": "source.ip",
+ "width": 180,
+ },
+ Object {
+ "columnHeaderType": "not-filtered",
+ "id": "destination.ip",
+ "width": 180,
+ },
+ Object {
+ "columnHeaderType": "not-filtered",
+ "id": "user.name",
+ "width": 180,
+ },
+ ]
+ }
+ data-test-subj="field-browser"
+ height={300}
+ isEventViewer={false}
+ onUpdateColumns={[MockFunction]}
+ timelineId="test"
+ toggleColumn={[MockFunction]}
+ width={900}
+ />
+
+
+
{
@@ -36,12 +36,12 @@ describe('helpers', () => {
});
test('returns the events viewer actions column width when isEventViewer is true', () => {
- expect(getActionsColumnWidth(true)).toEqual(EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH);
+ expect(getActionsColumnWidth(true)).toEqual(MINIMUM_ACTIONS_COLUMN_WIDTH);
});
test('returns the events viewer actions column width + checkbox width when isEventViewer is true and showCheckboxes is true', () => {
expect(getActionsColumnWidth(true, true)).toEqual(
- EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
+ MINIMUM_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
);
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts
index c538457431fef..903b17c4e8f15 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts
@@ -14,6 +14,7 @@ import {
SHOW_CHECK_BOXES_COLUMN_WIDTH,
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH,
DEFAULT_ACTIONS_COLUMN_WIDTH,
+ MINIMUM_ACTIONS_COLUMN_WIDTH,
} from '../constants';
/** Enriches the column headers with field details from the specified browserFields */
@@ -42,7 +43,14 @@ export const getActionsColumnWidth = (
isEventViewer: boolean,
showCheckboxes = false,
additionalActionWidth = 0
-): number =>
- (showCheckboxes ? SHOW_CHECK_BOXES_COLUMN_WIDTH : 0) +
- (isEventViewer ? EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH : DEFAULT_ACTIONS_COLUMN_WIDTH) +
- additionalActionWidth;
+): number => {
+ const checkboxesWidth = showCheckboxes ? SHOW_CHECK_BOXES_COLUMN_WIDTH : 0;
+ const actionsColumnWidth =
+ checkboxesWidth +
+ (isEventViewer ? EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH : DEFAULT_ACTIONS_COLUMN_WIDTH) +
+ additionalActionWidth;
+
+ return actionsColumnWidth > MINIMUM_ACTIONS_COLUMN_WIDTH + checkboxesWidth
+ ? actionsColumnWidth
+ : MINIMUM_ACTIONS_COLUMN_WIDTH + checkboxesWidth;
+};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx
index aa0b8d770f60c..b139aa1a7a9a6 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx
@@ -18,8 +18,6 @@ import {
DRAG_TYPE_FIELD,
droppableTimelineColumnsPrefix,
} from '../../../../../common/components/drag_and_drop/helpers';
-import { StatefulFieldsBrowser } from '../../../fields_browser';
-import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from '../../../fields_browser/helpers';
import {
OnColumnRemoved,
OnColumnResized,
@@ -29,6 +27,9 @@ import {
OnUpdateColumns,
} from '../../events';
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
+import { StatefulFieldsBrowser } from '../../../fields_browser';
+import { StatefulRowRenderersBrowser } from '../../../row_renderers_browser';
+import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from '../../../fields_browser/helpers';
import {
EventsTh,
EventsThContent,
@@ -170,6 +171,7 @@ export const ColumnHeadersComponent = ({
{showSelectAllCheckbox && (
@@ -185,22 +187,23 @@ export const ColumnHeadersComponent = ({
)}
-
-
-
+
+
+
+
{showEventsSelect && (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts
index 5f3fb4fa5113c..6b6ae3c3467b5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts
@@ -4,6 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
+/** The minimum (fixed) width of the Actions column */
+export const MINIMUM_ACTIONS_COLUMN_WIDTH = 50; // px;
+
/** The (fixed) width of the Actions column */
export const DEFAULT_ACTIONS_COLUMN_WIDTH = 76; // px;
/**
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx
index 63de117aeaf3d..23f7aad049215 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx
@@ -21,7 +21,7 @@ import { Note } from '../../../../../common/lib/note';
import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
import { AssociateNote, UpdateNote } from '../../../notes/helpers';
import { OnColumnResized, OnPinEvent, OnRowSelected, OnUnPinEvent } from '../../events';
-import { EventsTdContent, EventsTrData } from '../../styles';
+import { EventsTd, EventsTdContent, EventsTrData } from '../../styles';
import { Actions } from '../actions';
import { DataDrivenColumns } from '../data_driven_columns';
import { eventHasNotes, getPinOnClick } from '../helpers';
@@ -133,22 +133,24 @@ export const EventColumnView = React.memo(
...acc,
icon: [
...acc.icon,
-
-
- action.onClick({ eventId: id, ecsData, data })}
- />
-
- ,
+
+
+
+ action.onClick({ eventId: id, ecsData, data })}
+ />
+
+
+ ,
],
};
}
@@ -176,23 +178,25 @@ export const EventColumnView = React.memo(
return grouped.contextMenu.length > 0
? [
...grouped.icon,
-
-
+
-
-
- ,
+
+
+
+
+ ,
]
: grouped.icon;
}, [button, closePopover, id, onClickCb, data, ecsData, timelineActions, isPopoverOpen]);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_file_details.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_file_details.test.tsx.snap
index 8e806fadb7bf8..f6fbc771c954a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_file_details.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_file_details.test.tsx.snap
@@ -16,7 +16,7 @@ exports[`GenericFileDetails rendering it renders the default GenericFileDetails
processTitle="/lib/systemd/systemd-journald"
result="success"
secondary="root"
- session="unset"
+ session="242"
text="generic-text-123"
userName="root"
workingDirectory="/"
@@ -34,7 +34,7 @@ exports[`GenericFileDetails rendering it renders the default GenericFileDetails
"success",
],
"session": Array [
- "unset",
+ "242",
],
"summary": Object {
"actor": Object {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap
index b24a90589ce65..784924e896673 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/__snapshots__/generic_row_renderer.test.tsx.snap
@@ -32,7 +32,7 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly aga
"message_type": null,
"object": Object {
"primary": Array [
- "93.184.216.34",
+ "192.168.216.34",
],
"secondary": Array [
"80",
@@ -46,7 +46,7 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly aga
},
"destination": Object {
"ip": Array [
- "93.184.216.34",
+ "192.168.216.34",
],
"port": Array [
80,
@@ -113,7 +113,7 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly aga
"zeek": null,
}
}
- text="some text"
+ text="connected using"
timelineId="test"
/>
@@ -135,7 +135,7 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly agai
"success",
],
"session": Array [
- "unset",
+ "242",
],
"summary": Object {
"actor": Object {
@@ -259,7 +259,7 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly agai
}
}
fileIcon="document"
- text="some text"
+ text="opened file using"
timelineId="test"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx
index aec463f531448..1e314c0ebd281 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx
@@ -34,7 +34,7 @@ describe('GenericRowRenderer', () => {
auditd = cloneDeep(mockTimelineData[26].ecs);
connectedToRenderer = createGenericAuditRowRenderer({
actionName: 'connected-to',
- text: 'some text',
+ text: 'connected using',
});
});
test('renders correctly against snapshot', () => {
@@ -80,7 +80,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toContain(
- 'Session246alice@zeek-londonsome textwget(1490)wget www.example.comwith resultsuccessDestination93.184.216.34:80'
+ 'Session246alice@zeek-londonconnected usingwget(1490)wget www.example.comwith resultsuccessDestination192.168.216.34:80'
);
});
});
@@ -95,7 +95,7 @@ describe('GenericRowRenderer', () => {
auditdFile = cloneDeep(mockTimelineData[27].ecs);
fileToRenderer = createGenericFileRowRenderer({
actionName: 'opened-file',
- text: 'some text',
+ text: 'opened file using',
});
});
@@ -142,7 +142,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toContain(
- 'Sessionunsetroot@zeek-londonin/some text/proc/15990/attr/currentusingsystemd-journal(27244)/lib/systemd/systemd-journaldwith resultsuccess'
+ 'Session242root@zeek-londonin/opened file using/proc/15990/attr/currentusingsystemd-journal(27244)/lib/systemd/systemd-journaldwith resultsuccess'
);
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx
index e9d0bdfa3a323..3e7520f641f4a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx
@@ -10,6 +10,8 @@ import { IconType } from '@elastic/eui';
import { get } from 'lodash/fp';
import React from 'react';
+import { RowRendererId } from '../../../../../../../common/types/timeline';
+
import { RowRenderer, RowRendererContainer } from '../row_renderer';
import { AuditdGenericDetails } from './generic_details';
import { AuditdGenericFileDetails } from './generic_file_details';
@@ -22,6 +24,7 @@ export const createGenericAuditRowRenderer = ({
actionName: string;
text: string;
}): RowRenderer => ({
+ id: RowRendererId.auditd,
isInstance: (ecs) => {
const module: string | null | undefined = get('event.module[0]', ecs);
const action: string | null | undefined = get('event.action[0]', ecs);
@@ -54,6 +57,7 @@ export const createGenericFileRowRenderer = ({
text: string;
fileIcon?: IconType;
}): RowRenderer => ({
+ id: RowRendererId.auditd_file,
isInstance: (ecs) => {
const module: string | null | undefined = get('event.module[0]', ecs);
const action: string | null | undefined = get('event.action[0]', ecs);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx
index 91499fd9c30f5..795c914c3c9a0 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx
@@ -10,6 +10,7 @@ import { get } from 'lodash/fp';
import React from 'react';
import styled from 'styled-components';
+import { RowRendererId } from '../../../../../../../common/types/timeline';
import { asArrayIfExists } from '../../../../../../common/lib/helpers';
import {
TLS_CLIENT_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME,
@@ -84,6 +85,7 @@ export const eventActionMatches = (eventAction: string | object | undefined | nu
};
export const netflowRowRenderer: RowRenderer = {
+ id: RowRendererId.netflow,
isInstance: (ecs) =>
eventCategoryMatches(get(EVENT_CATEGORY_FIELD, ecs)) ||
eventActionMatches(get(EVENT_ACTION_FIELD, ecs)),
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx
index e63f60226c707..0b860491918df 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx
@@ -4,13 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
-/* eslint-disable react/display-name */
-
import React from 'react';
+import { RowRendererId } from '../../../../../../common/types/timeline';
+
import { RowRenderer } from './row_renderer';
+const PlainRowRenderer = () => <>>;
+
+PlainRowRenderer.displayName = 'PlainRowRenderer';
+
export const plainRowRenderer: RowRenderer = {
+ id: RowRendererId.plain,
isInstance: (_) => true,
- renderRow: () => <>>,
+ renderRow: PlainRowRenderer,
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/row_renderer.tsx
index 5cee0a0118dd2..609e9dba1a46e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/row_renderer.tsx
@@ -7,6 +7,7 @@
import React from 'react';
import { BrowserFields } from '../../../../../common/containers/source';
+import type { RowRendererId } from '../../../../../../common/types/timeline';
import { Ecs } from '../../../../../graphql/types';
import { EventsTrSupplement } from '../../styles';
@@ -22,6 +23,7 @@ export const RowRendererContainer = React.memo(({ chi
RowRendererContainer.displayName = 'RowRendererContainer';
export interface RowRenderer {
+ id: RowRendererId;
isInstance: (data: Ecs) => boolean;
renderRow: ({
browserFields,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_signature.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_signature.test.tsx.snap
index f766befaf47e4..e55465cfd8895 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_signature.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_signature.test.tsx.snap
@@ -34,7 +34,6 @@ exports[`SuricataSignature rendering it renders the default SuricataSignature 1`
>
Hello
-
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx
index 5012f321188d6..242f63611f2ff 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx
@@ -9,10 +9,13 @@
import { get } from 'lodash/fp';
import React from 'react';
+import { RowRendererId } from '../../../../../../../common/types/timeline';
+
import { RowRenderer, RowRendererContainer } from '../row_renderer';
import { SuricataDetails } from './suricata_details';
export const suricataRowRenderer: RowRenderer = {
+ id: RowRendererId.suricata,
isInstance: (ecs) => {
const module: string | null | undefined = get('event.module[0]', ecs);
return module != null && module.toLowerCase() === 'suricata';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx
index db0ddd857238f..1cd78178d017f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx
@@ -13,7 +13,6 @@ import {
DraggableWrapper,
} from '../../../../../../common/components/drag_and_drop/draggable_wrapper';
import { escapeDataProviderId } from '../../../../../../common/components/drag_and_drop/helpers';
-import { ExternalLinkIcon } from '../../../../../../common/components/external_link_icon';
import { GoogleLink } from '../../../../../../common/components/links';
import { Provider } from '../../../data_providers/provider';
@@ -122,7 +121,6 @@ export const SuricataSignature = React.memo<{
{signature.split(' ').splice(tokens.length).join(' ')}
-
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
index e31fc26e4ae52..67e050160805e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
@@ -9,6 +9,8 @@
import { get } from 'lodash/fp';
import React from 'react';
+import { RowRendererId } from '../../../../../../../common/types/timeline';
+
import { DnsRequestEventDetails } from '../dns/dns_request_event_details';
import { EndgameSecurityEventDetails } from '../endgame/endgame_security_event_details';
import { isFileEvent, isNillEmptyOrNotFinite } from '../helpers';
@@ -25,6 +27,7 @@ export const createGenericSystemRowRenderer = ({
actionName: string;
text: string;
}): RowRenderer => ({
+ id: RowRendererId.system,
isInstance: (ecs) => {
const module: string | null | undefined = get('event.module[0]', ecs);
const action: string | null | undefined = get('event.action[0]', ecs);
@@ -55,6 +58,7 @@ export const createEndgameProcessRowRenderer = ({
actionName: string;
text: string;
}): RowRenderer => ({
+ id: RowRendererId.system_file,
isInstance: (ecs) => {
const action: string | null | undefined = get('event.action[0]', ecs);
const category: string | null | undefined = get('event.category[0]', ecs);
@@ -86,6 +90,7 @@ export const createFimRowRenderer = ({
actionName: string;
text: string;
}): RowRenderer => ({
+ id: RowRendererId.system_fim,
isInstance: (ecs) => {
const action: string | null | undefined = get('event.action[0]', ecs);
const category: string | null | undefined = get('event.category[0]', ecs);
@@ -117,6 +122,7 @@ export const createGenericFileRowRenderer = ({
actionName: string;
text: string;
}): RowRenderer => ({
+ id: RowRendererId.system_file,
isInstance: (ecs) => {
const module: string | null | undefined = get('event.module[0]', ecs);
const action: string | null | undefined = get('event.action[0]', ecs);
@@ -147,6 +153,7 @@ export const createSocketRowRenderer = ({
actionName: string;
text: string;
}): RowRenderer => ({
+ id: RowRendererId.system_socket,
isInstance: (ecs) => {
const action: string | null | undefined = get('event.action[0]', ecs);
return action != null && action.toLowerCase() === actionName;
@@ -169,6 +176,7 @@ export const createSecurityEventRowRenderer = ({
}: {
actionName: string;
}): RowRenderer => ({
+ id: RowRendererId.system_security_event,
isInstance: (ecs) => {
const category: string | null | undefined = get('event.category[0]', ecs);
const action: string | null | undefined = get('event.action[0]', ecs);
@@ -192,6 +200,7 @@ export const createSecurityEventRowRenderer = ({
});
export const createDnsRowRenderer = (): RowRenderer => ({
+ id: RowRendererId.system_dns,
isInstance: (ecs) => {
const dnsQuestionType: string | null | undefined = get('dns.question.type[0]', ecs);
const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', ecs);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx
index 25228b04bb50b..9bbb7a4090dea 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx
@@ -9,10 +9,13 @@
import { get } from 'lodash/fp';
import React from 'react';
+import { RowRendererId } from '../../../../../../../common/types/timeline';
+
import { RowRenderer, RowRendererContainer } from '../row_renderer';
import { ZeekDetails } from './zeek_details';
export const zeekRowRenderer: RowRenderer = {
+ id: RowRendererId.zeek,
isInstance: (ecs) => {
const module: string | null | undefined = get('event.module[0]', ecs);
return module != null && module.toLowerCase() === 'zeek';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx
index cdf4a8cba68ab..74f75a0a73386 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx
@@ -15,7 +15,6 @@ import {
DraggableWrapper,
} from '../../../../../../common/components/drag_and_drop/draggable_wrapper';
import { escapeDataProviderId } from '../../../../../../common/components/drag_and_drop/helpers';
-import { ExternalLinkIcon } from '../../../../../../common/components/external_link_icon';
import { GoogleLink, ReputationLink } from '../../../../../../common/components/links';
import { Provider } from '../../../data_providers/provider';
import { IS_OPERATOR } from '../../../data_providers/data_provider';
@@ -120,7 +119,6 @@ export const Link = React.memo(({ value, link }) => {
{value}
-
);
@@ -129,7 +127,6 @@ export const Link = React.memo(({ value, link }) => {
-
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx
index c9660182a4050..141534f1dcb6f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx
@@ -10,7 +10,7 @@ import React, { useCallback, useEffect, useMemo } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import deepEqual from 'fast-deep-equal';
-import { TimelineId } from '../../../../../common/types/timeline';
+import { RowRendererId, TimelineId } from '../../../../../common/types/timeline';
import { BrowserFields } from '../../../../common/containers/source';
import { TimelineItem } from '../../../../graphql/types';
import { Note } from '../../../../common/lib/note';
@@ -60,6 +60,7 @@ const StatefulBodyComponent = React.memo(
columnHeaders,
data,
eventIdToNoteIds,
+ excludedRowRendererIds,
height,
id,
isEventViewer = false,
@@ -74,7 +75,6 @@ const StatefulBodyComponent = React.memo(
clearSelected,
show,
showCheckboxes,
- showRowRenderers,
graphEventId,
sort,
toggleColumn,
@@ -97,8 +97,7 @@ const StatefulBodyComponent = React.memo(
const onAddNoteToEvent: AddNoteToEvent = useCallback(
({ eventId, noteId }: { eventId: string; noteId: string }) =>
addNoteToEvent!({ id, eventId, noteId }),
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [id]
+ [id, addNoteToEvent]
);
const onRowSelected: OnRowSelected = useCallback(
@@ -135,35 +134,36 @@ const StatefulBodyComponent = React.memo(
(sorted) => {
updateSort!({ id, sort: sorted });
},
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [id]
+ [id, updateSort]
);
const onColumnRemoved: OnColumnRemoved = useCallback(
(columnId) => removeColumn!({ id, columnId }),
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [id]
+ [id, removeColumn]
);
const onColumnResized: OnColumnResized = useCallback(
({ columnId, delta }) => applyDeltaToColumnWidth!({ id, columnId, delta }),
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [id]
+ [applyDeltaToColumnWidth, id]
);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const onPinEvent: OnPinEvent = useCallback((eventId) => pinEvent!({ id, eventId }), [id]);
+ const onPinEvent: OnPinEvent = useCallback((eventId) => pinEvent!({ id, eventId }), [
+ id,
+ pinEvent,
+ ]);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const onUnPinEvent: OnUnPinEvent = useCallback((eventId) => unPinEvent!({ id, eventId }), [id]);
+ const onUnPinEvent: OnUnPinEvent = useCallback((eventId) => unPinEvent!({ id, eventId }), [
+ id,
+ unPinEvent,
+ ]);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const onUpdateNote: UpdateNote = useCallback((note: Note) => updateNote!({ note }), []);
+ const onUpdateNote: UpdateNote = useCallback((note: Note) => updateNote!({ note }), [
+ updateNote,
+ ]);
const onUpdateColumns: OnUpdateColumns = useCallback(
(columns) => updateColumns!({ id, columns }),
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [id]
+ [id, updateColumns]
);
// Sync to selectAll so parent components can select all events
@@ -171,8 +171,19 @@ const StatefulBodyComponent = React.memo(
if (selectAll) {
onSelectAll({ isSelected: true });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [selectAll]); // onSelectAll dependency not necessary
+ }, [onSelectAll, selectAll]);
+
+ const enabledRowRenderers = useMemo(() => {
+ if (
+ excludedRowRendererIds &&
+ excludedRowRendererIds.length === Object.keys(RowRendererId).length
+ )
+ return [plainRowRenderer];
+
+ if (!excludedRowRendererIds) return rowRenderers;
+
+ return rowRenderers.filter((rowRenderer) => !excludedRowRendererIds.includes(rowRenderer.id));
+ }, [excludedRowRendererIds]);
return (
(
onUnPinEvent={onUnPinEvent}
onUpdateColumns={onUpdateColumns}
pinnedEventIds={pinnedEventIds}
- rowRenderers={showRowRenderers ? rowRenderers : [plainRowRenderer]}
+ rowRenderers={enabledRowRenderers}
selectedEventIds={selectedEventIds}
show={id === TimelineId.active ? show : true}
showCheckboxes={showCheckboxes}
@@ -213,6 +224,7 @@ const StatefulBodyComponent = React.memo(
deepEqual(prevProps.browserFields, nextProps.browserFields) &&
deepEqual(prevProps.columnHeaders, nextProps.columnHeaders) &&
deepEqual(prevProps.data, nextProps.data) &&
+ deepEqual(prevProps.excludedRowRendererIds, nextProps.excludedRowRendererIds) &&
prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds &&
prevProps.graphEventId === nextProps.graphEventId &&
deepEqual(prevProps.notesById, nextProps.notesById) &&
@@ -225,7 +237,6 @@ const StatefulBodyComponent = React.memo(
prevProps.show === nextProps.show &&
prevProps.selectedEventIds === nextProps.selectedEventIds &&
prevProps.showCheckboxes === nextProps.showCheckboxes &&
- prevProps.showRowRenderers === nextProps.showRowRenderers &&
prevProps.sort === nextProps.sort
);
@@ -245,6 +256,7 @@ const makeMapStateToProps = () => {
columns,
eventIdToNoteIds,
eventType,
+ excludedRowRendererIds,
graphEventId,
isSelectAllChecked,
loadingEventIds,
@@ -252,13 +264,13 @@ const makeMapStateToProps = () => {
selectedEventIds,
show,
showCheckboxes,
- showRowRenderers,
} = timeline;
return {
columnHeaders: memoizedColumnHeaders(columns, browserFields),
eventIdToNoteIds,
eventType,
+ excludedRowRendererIds,
graphEventId,
isSelectAllChecked,
loadingEventIds,
@@ -268,7 +280,6 @@ const makeMapStateToProps = () => {
selectedEventIds,
show,
showCheckboxes,
- showRowRenderers,
};
};
return mapStateToProps;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx
index bc7c313553f1e..ece23d7a10886 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx
@@ -97,7 +97,7 @@ export const ProviderItemBadge = React.memo(
useEffect(() => {
// optionally register the provider if provided
- if (!providerRegistered && register != null) {
+ if (register != null) {
dispatch(dragAndDropActions.registerProvider({ provider: { ...register, and: [] } }));
setProviderRegistered(true);
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx
index 47d848021ba43..eb103d8e7e861 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx
@@ -91,10 +91,14 @@ export const EventsTrHeader = styled.div.attrs(({ className }) => ({
export const EventsThGroupActions = styled.div.attrs(({ className = '' }) => ({
className: `siemEventsTable__thGroupActions ${className}`,
-}))<{ actionsColumnWidth: number }>`
+}))<{ actionsColumnWidth: number; isEventViewer: boolean }>`
display: flex;
- flex: 0 0 ${({ actionsColumnWidth }) => `${actionsColumnWidth}px`};
+ flex: 0 0
+ ${({ actionsColumnWidth, isEventViewer }) =>
+ `${!isEventViewer ? actionsColumnWidth + 4 : actionsColumnWidth}px`};
min-width: 0;
+ padding-left: ${({ isEventViewer }) =>
+ !isEventViewer ? '4px;' : '0;'}; // match timeline event border
`;
export const EventsThGroupData = styled.div.attrs(({ className = '' }) => ({
@@ -151,6 +155,11 @@ export const EventsThContent = styled.div.attrs(({ className = '' }) => ({
width != null
? `${width}px`
: '100%'}; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */
+
+ > button.euiButtonIcon,
+ > .euiToolTipAnchor > button.euiButtonIcon {
+ margin-left: ${({ theme }) => `-${theme.eui.paddingSizes.xs}`};
+ }
`;
/* EVENTS BODY */
@@ -198,8 +207,7 @@ export const EventsTrSupplement = styled.div.attrs(({ className = '' }) => ({
}))<{ className: string }>`
font-size: ${({ theme }) => theme.eui.euiFontSizeXS};
line-height: ${({ theme }) => theme.eui.euiLineHeight};
- padding: 0 ${({ theme }) => theme.eui.paddingSizes.xs} 0
- ${({ theme }) => theme.eui.paddingSizes.xl};
+ padding: 0 ${({ theme }) => theme.eui.paddingSizes.xs} 0 52px;
`;
export const EventsTdGroupActions = styled.div.attrs(({ className = '' }) => ({
@@ -249,6 +257,11 @@ export const EventsTdContent = styled.div.attrs(({ className }) => ({
width != null
? `${width}px`
: '100%'}; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */
+
+ > button.euiButtonIcon,
+ > .euiToolTipAnchor > button.euiButtonIcon {
+ margin-left: ${({ theme }) => `-${theme.eui.paddingSizes.xs}`};
+ }
`;
/**
@@ -334,6 +347,5 @@ export const EventsHeadingHandle = styled.div.attrs(({ className = '' }) => ({
*/
export const EventsLoading = styled(EuiLoadingSpinner)`
- margin: ${({ theme }) => theme.eui.euiSizeXS};
- vertical-align: top;
+ vertical-align: middle;
`;
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/all/index.gql_query.ts b/x-pack/plugins/security_solution/public/timelines/containers/all/index.gql_query.ts
index 5cbc922f09c9a..cd03e43938b44 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/all/index.gql_query.ts
+++ b/x-pack/plugins/security_solution/public/timelines/containers/all/index.gql_query.ts
@@ -51,6 +51,7 @@ export const allTimelinesQuery = gql`
updatedBy
version
}
+ excludedRowRendererIds
notes {
eventId
note
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx
index 4ecabeef16dff..3cf33048007e3 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx
@@ -75,6 +75,7 @@ export const getAllTimeline = memoizeOne(
return acc;
}, {})
: null,
+ excludedRowRendererIds: timeline.excludedRowRendererIds,
favorite: timeline.favorite,
noteIds: timeline.noteIds,
notes:
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts b/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts
index 24beed0801aa6..0aaeb22d72afc 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts
+++ b/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts
@@ -69,6 +69,7 @@ export const oneTimelineQuery = gql`
updatedBy
version
}
+ excludedRowRendererIds
favorite {
fullName
userName
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts
index 80be2aee80b68..618de48091ce8 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts
@@ -17,7 +17,7 @@ import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/t
import { EventType, KqlMode, TimelineModel, ColumnHeaderOptions } from './model';
import { TimelineNonEcsData } from '../../../graphql/types';
-import { TimelineTypeLiteral } from '../../../../common/types/timeline';
+import { TimelineTypeLiteral, RowRendererId } from '../../../../common/types/timeline';
import { InsertTimeline } from './types';
const actionCreator = actionCreatorFactory('x-pack/security_solution/local/timeline');
@@ -59,6 +59,7 @@ export const createTimeline = actionCreator<{
start: number;
end: number;
};
+ excludedRowRendererIds?: RowRendererId[];
filters?: Filter[];
columns: ColumnHeaderOptions[];
itemsPerPage?: number;
@@ -69,7 +70,6 @@ export const createTimeline = actionCreator<{
show?: boolean;
sort?: Sort;
showCheckboxes?: boolean;
- showRowRenderers?: boolean;
timelineType?: TimelineTypeLiteral;
templateTimelineId?: string;
templateTimelineVersion?: number;
@@ -266,3 +266,8 @@ export const clearEventsDeleted = actionCreator<{
export const updateEventType = actionCreator<{ id: string; eventType: EventType }>(
'UPDATE_EVENT_TYPE'
);
+
+export const setExcludedRowRendererIds = actionCreator<{
+ id: string;
+ excludedRowRendererIds: RowRendererId[];
+}>('SET_TIMELINE_EXCLUDED_ROW_RENDERER_IDS');
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts
index 5290178092f3e..f4c4085715af9 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts
@@ -18,6 +18,7 @@ export const timelineDefaults: SubsetTimelineModel & Pick {
description: '',
eventIdToNoteIds: {},
eventType: 'all',
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
filters: [
@@ -146,7 +147,6 @@ describe('Epic Timeline', () => {
selectedEventIds: {},
show: true,
showCheckboxes: false,
- showRowRenderers: true,
sort: { columnId: '@timestamp', sortDirection: Direction.desc },
status: TimelineStatus.active,
width: 1100,
@@ -233,6 +233,7 @@ describe('Epic Timeline', () => {
},
description: '',
eventType: 'all',
+ excludedRowRendererIds: [],
filters: [
{
exists: null,
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts
index 605700cb71a2a..2f9331ec9db8e 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts
@@ -331,6 +331,7 @@ const timelineInput: TimelineInput = {
dataProviders: null,
description: null,
eventType: null,
+ excludedRowRendererIds: null,
filters: null,
kqlMode: null,
kqlQuery: null,
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts
index b3d1db23ffae8..632525750c8d8 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts
@@ -16,6 +16,7 @@ import {
removeColumn,
upsertColumn,
applyDeltaToColumnWidth,
+ setExcludedRowRendererIds,
updateColumns,
updateItemsPerPage,
updateSort,
@@ -30,6 +31,7 @@ const timelineActionTypes = [
updateColumns.type,
updateItemsPerPage.type,
updateSort.type,
+ setExcludedRowRendererIds.type,
];
export const isPageTimeline = (timelineId: string | undefined): boolean =>
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts
index a347d3e41e206..59f47297b1f65 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts
@@ -21,7 +21,11 @@ import {
} from '../../../timelines/components/timeline/data_providers/data_provider';
import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/model';
import { TimelineNonEcsData } from '../../../graphql/types';
-import { TimelineTypeLiteral, TimelineType } from '../../../../common/types/timeline';
+import {
+ TimelineTypeLiteral,
+ TimelineType,
+ RowRendererId,
+} from '../../../../common/types/timeline';
import { timelineDefaults } from './defaults';
import { ColumnHeaderOptions, KqlMode, TimelineModel, EventType } from './model';
@@ -130,6 +134,7 @@ interface AddNewTimelineParams {
start: number;
end: number;
};
+ excludedRowRendererIds?: RowRendererId[];
filters?: Filter[];
id: string;
itemsPerPage?: number;
@@ -140,7 +145,6 @@ interface AddNewTimelineParams {
show?: boolean;
sort?: Sort;
showCheckboxes?: boolean;
- showRowRenderers?: boolean;
timelineById: TimelineById;
timelineType: TimelineTypeLiteral;
}
@@ -150,6 +154,7 @@ export const addNewTimeline = ({
columns,
dataProviders = [],
dateRange = { start: 0, end: 0 },
+ excludedRowRendererIds = [],
filters = timelineDefaults.filters,
id,
itemsPerPage = timelineDefaults.itemsPerPage,
@@ -157,7 +162,6 @@ export const addNewTimeline = ({
sort = timelineDefaults.sort,
show = false,
showCheckboxes = false,
- showRowRenderers = true,
timelineById,
timelineType,
}: AddNewTimelineParams): TimelineById => {
@@ -176,6 +180,7 @@ export const addNewTimeline = ({
columns,
dataProviders,
dateRange,
+ excludedRowRendererIds,
filters,
itemsPerPage,
kqlQuery,
@@ -186,7 +191,6 @@ export const addNewTimeline = ({
isSaving: false,
isLoading: false,
showCheckboxes,
- showRowRenderers,
timelineType,
...templateTimelineInfo,
},
@@ -1436,3 +1440,25 @@ export const updateFilters = ({ id, filters, timelineById }: UpdateFiltersParams
},
};
};
+
+interface UpdateExcludedRowRenderersIds {
+ id: string;
+ excludedRowRendererIds: RowRendererId[];
+ timelineById: TimelineById;
+}
+
+export const updateExcludedRowRenderersIds = ({
+ id,
+ excludedRowRendererIds,
+ timelineById,
+}: UpdateExcludedRowRenderersIds): TimelineById => {
+ const timeline = timelineById[id];
+
+ return {
+ ...timelineById,
+ [id]: {
+ ...timeline,
+ excludedRowRendererIds,
+ },
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts
index a78fbc41ac430..95d525c7eb59f 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts
@@ -15,6 +15,7 @@ import {
TimelineStatus,
} from '../../../graphql/types';
import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/types';
+import type { RowRendererId } from '../../../../common/types/timeline';
export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages
export type KqlMode = 'filter' | 'search';
@@ -54,6 +55,8 @@ export interface TimelineModel {
eventType?: EventType;
/** A map of events in this timeline to the chronologically ordered notes (in this timeline) associated with the event */
eventIdToNoteIds: Record;
+ /** A list of Ids of excluded Row Renderers */
+ excludedRowRendererIds: RowRendererId[];
filters?: Filter[];
/** When non-empty, display a graph view for this event */
graphEventId?: string;
@@ -108,8 +111,6 @@ export interface TimelineModel {
show: boolean;
/** When true, shows checkboxes enabling selection. Selected events store in selectedEventIds **/
showCheckboxes: boolean;
- /** When true, shows additional rowRenderers below the PlainRowRenderer **/
- showRowRenderers: boolean;
/** Specifies which column the timeline is sorted on, and the direction (ascending / descending) */
sort: Sort;
/** status: active | draft */
@@ -131,6 +132,7 @@ export type SubsetTimelineModel = Readonly<
| 'description'
| 'eventType'
| 'eventIdToNoteIds'
+ | 'excludedRowRendererIds'
| 'graphEventId'
| 'highlightedDropAndProviderId'
| 'historyIds'
@@ -153,7 +155,6 @@ export type SubsetTimelineModel = Readonly<
| 'selectedEventIds'
| 'show'
| 'showCheckboxes'
- | 'showRowRenderers'
| 'sort'
| 'width'
| 'isSaving'
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts
index b8bdb4f2ad7f0..4cfc20eb81705 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts
@@ -70,6 +70,7 @@ const timelineByIdMock: TimelineById = {
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
id: 'foo',
@@ -97,7 +98,6 @@ const timelineByIdMock: TimelineById = {
selectedEventIds: {},
show: true,
showCheckboxes: false,
- showRowRenderers: true,
sort: {
columnId: '@timestamp',
sortDirection: Direction.desc,
@@ -1119,6 +1119,7 @@ describe('Timeline', () => {
deletedEventIds: [],
description: '',
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
isFavorite: false,
@@ -1139,7 +1140,6 @@ describe('Timeline', () => {
},
selectedEventIds: {},
show: true,
- showRowRenderers: true,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
@@ -1215,6 +1215,7 @@ describe('Timeline', () => {
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
isFavorite: false,
@@ -1235,7 +1236,6 @@ describe('Timeline', () => {
},
selectedEventIds: {},
show: true,
- showRowRenderers: true,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
@@ -1421,6 +1421,7 @@ describe('Timeline', () => {
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
isFavorite: false,
@@ -1441,7 +1442,6 @@ describe('Timeline', () => {
},
selectedEventIds: {},
show: true,
- showRowRenderers: true,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
@@ -1517,6 +1517,7 @@ describe('Timeline', () => {
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
isFavorite: false,
@@ -1537,7 +1538,6 @@ describe('Timeline', () => {
},
selectedEventIds: {},
show: true,
- showRowRenderers: true,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
@@ -1619,6 +1619,7 @@ describe('Timeline', () => {
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
isFavorite: false,
@@ -1639,7 +1640,6 @@ describe('Timeline', () => {
},
selectedEventIds: {},
show: true,
- showRowRenderers: true,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
@@ -1722,6 +1722,7 @@ describe('Timeline', () => {
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
isFavorite: false,
@@ -1742,7 +1743,6 @@ describe('Timeline', () => {
},
selectedEventIds: {},
show: true,
- showRowRenderers: true,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
@@ -1917,6 +1917,7 @@ describe('Timeline', () => {
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
isFavorite: false,
@@ -1937,7 +1938,6 @@ describe('Timeline', () => {
},
selectedEventIds: {},
show: true,
- showRowRenderers: true,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
@@ -1995,6 +1995,7 @@ describe('Timeline', () => {
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
isFavorite: false,
@@ -2003,7 +2004,6 @@ describe('Timeline', () => {
isLoading: false,
id: 'foo',
savedObjectId: null,
- showRowRenderers: true,
kqlMode: 'filter',
kqlQuery: { filterQuery: null, filterQueryDraft: null },
loadingEventIds: [],
@@ -2099,6 +2099,7 @@ describe('Timeline', () => {
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
+ excludedRowRendererIds: [],
highlightedDropAndProviderId: '',
historyIds: [],
id: 'foo',
@@ -2121,7 +2122,6 @@ describe('Timeline', () => {
},
selectedEventIds: {},
show: true,
- showRowRenderers: true,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts
index 6bb546c16b617..d15bce5e217fa 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts
@@ -25,6 +25,7 @@ import {
removeProvider,
setEventsDeleted,
setEventsLoading,
+ setExcludedRowRendererIds,
setFilters,
setInsertTimeline,
setKqlFilterQueryDraft,
@@ -75,6 +76,7 @@ import {
setLoadingTimelineEvents,
setSelectedTimelineEvents,
unPinTimelineEvent,
+ updateExcludedRowRenderersIds,
updateHighlightedDropAndProvider,
updateKqlFilterQueryDraft,
updateTimelineColumns,
@@ -129,13 +131,13 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
id,
dataProviders,
dateRange,
+ excludedRowRendererIds,
show,
columns,
itemsPerPage,
kqlQuery,
sort,
showCheckboxes,
- showRowRenderers,
timelineType = TimelineType.default,
filters,
}
@@ -146,6 +148,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
columns,
dataProviders,
dateRange,
+ excludedRowRendererIds,
filters,
id,
itemsPerPage,
@@ -153,7 +156,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
sort,
show,
showCheckboxes,
- showRowRenderers,
timelineById: state.timelineById,
timelineType,
}),
@@ -306,6 +308,14 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
},
},
}))
+ .case(setExcludedRowRendererIds, (state, { id, excludedRowRendererIds }) => ({
+ ...state,
+ timelineById: updateExcludedRowRenderersIds({
+ id,
+ excludedRowRendererIds,
+ timelineById: state.timelineById,
+ }),
+ }))
.case(setSelected, (state, { id, eventIds, isSelected, isSelectAllChecked }) => ({
...state,
timelineById: setSelectedTimelineEvents({
diff --git a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts
index e46d3be44dbd1..15e188e281d10 100644
--- a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts
+++ b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts
@@ -147,11 +147,28 @@ export const timelineSchema = gql`
custom
}
+ enum RowRendererId {
+ auditd
+ auditd_file
+ netflow
+ plain
+ suricata
+ system
+ system_dns
+ system_endgame_process
+ system_file
+ system_fim
+ system_security_event
+ system_socket
+ zeek
+ }
+
input TimelineInput {
columns: [ColumnHeaderInput!]
dataProviders: [DataProviderInput!]
description: String
eventType: String
+ excludedRowRendererIds: [RowRendererId!]
filters: [FilterTimelineInput!]
kqlMode: String
kqlQuery: SerializedFilterQueryInput
@@ -252,6 +269,7 @@ export const timelineSchema = gql`
description: String
eventIdToNoteIds: [NoteResult!]
eventType: String
+ excludedRowRendererIds: [RowRendererId!]
favorite: [FavoriteTimelineResult!]
filters: [FilterTimelineResult!]
kqlMode: String
diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts
index 52bb4a9862160..6553f709a7fa7 100644
--- a/x-pack/plugins/security_solution/server/graphql/types.ts
+++ b/x-pack/plugins/security_solution/server/graphql/types.ts
@@ -126,6 +126,8 @@ export interface TimelineInput {
eventType?: Maybe;
+ excludedRowRendererIds?: Maybe;
+
filters?: Maybe;
kqlMode?: Maybe;
@@ -351,6 +353,22 @@ export enum DataProviderType {
template = 'template',
}
+export enum RowRendererId {
+ auditd = 'auditd',
+ auditd_file = 'auditd_file',
+ netflow = 'netflow',
+ plain = 'plain',
+ suricata = 'suricata',
+ system = 'system',
+ system_dns = 'system_dns',
+ system_endgame_process = 'system_endgame_process',
+ system_file = 'system_file',
+ system_fim = 'system_fim',
+ system_security_event = 'system_security_event',
+ system_socket = 'system_socket',
+ zeek = 'zeek',
+}
+
export enum TimelineStatus {
active = 'active',
draft = 'draft',
@@ -1963,6 +1981,8 @@ export interface TimelineResult {
eventType?: Maybe;
+ excludedRowRendererIds?: Maybe;
+
favorite?: Maybe;
filters?: Maybe;
@@ -8101,6 +8121,12 @@ export namespace TimelineResultResolvers {
eventType?: EventTypeResolver, TypeParent, TContext>;
+ excludedRowRendererIds?: ExcludedRowRendererIdsResolver<
+ Maybe,
+ TypeParent,
+ TContext
+ >;
+
favorite?: FavoriteResolver, TypeParent, TContext>;
filters?: FiltersResolver, TypeParent, TContext>;
@@ -8184,6 +8210,11 @@ export namespace TimelineResultResolvers {
Parent = TimelineResult,
TContext = SiemContext
> = Resolver;
+ export type ExcludedRowRendererIdsResolver<
+ R = Maybe,
+ Parent = TimelineResult,
+ TContext = SiemContext
+ > = Resolver;
export type FavoriteResolver<
R = Maybe,
Parent = TimelineResult,
diff --git a/x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.ts
index eb8f6f5022985..d3d7783dc9385 100644
--- a/x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.ts
+++ b/x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.ts
@@ -44,5 +44,7 @@ export const pickSavedTimeline = (
savedTimeline.status = TimelineStatus.active;
}
+ savedTimeline.excludedRowRendererIds = savedTimeline.excludedRowRendererIds ?? [];
+
return savedTimeline;
};
diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/export_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/export_timelines.ts
index 23090bfc6f0bd..f4b97ac3510cc 100644
--- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/export_timelines.ts
+++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/export_timelines.ts
@@ -181,7 +181,7 @@ const getTimelinesFromObjects = async (
if (myTimeline != null) {
const timelineNotes = myNotes.filter((n) => n.timelineId === timelineId);
const timelinePinnedEventIds = myPinnedEventIds.filter((p) => p.timelineId === timelineId);
- const exportedTimeline = omit('status', myTimeline);
+ const exportedTimeline = omit(['status', 'excludedRowRendererIds'], myTimeline);
return [
...acc,
{
diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts
index 22b98930f3181..c5ee611dfa27f 100644
--- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts
+++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts
@@ -135,6 +135,9 @@ export const timelineSavedObjectMappings: SavedObjectsType['mappings'] = {
eventType: {
type: 'keyword',
},
+ excludedRowRendererIds: {
+ type: 'text',
+ },
favorite: {
properties: {
keySearch: {
From 3fc54e7c552998367171d76e0119eab6d4f2ff1c Mon Sep 17 00:00:00 2001
From: Madison Caldwell
Date: Fri, 10 Jul 2020 15:53:34 -0400
Subject: [PATCH 18/39] [Security Solution][Endpoint] Policy creation callback
fixes + Improved error handling in user manifest loop (#71269)
* Clean up matcher types
* Rework promise and error-handling in ManifestManager
* Write tests for ingest callback and ensure policy is returned when errors occur
* More tests for ingest callback
* Update tests
* Fix tests
Co-authored-by: Elastic Machine
---
x-pack/plugins/ingest_manager/common/mocks.ts | 43 ++++
.../endpoint/ingest_integration.test.ts | 91 ++++++++
.../server/endpoint/ingest_integration.ts | 105 +++++----
.../server/endpoint/lib/artifacts/common.ts | 7 +
.../server/endpoint/lib/artifacts/task.ts | 26 ++-
.../server/endpoint/mocks.ts | 4 +-
.../endpoint/schemas/artifacts/lists.ts | 26 +--
.../manifest_manager/manifest_manager.mock.ts | 61 ++----
.../manifest_manager/manifest_manager.test.ts | 11 +-
.../manifest_manager/manifest_manager.ts | 205 +++++++++++-------
.../apps/endpoint/policy_details.ts | 42 ++++
11 files changed, 430 insertions(+), 191 deletions(-)
create mode 100644 x-pack/plugins/ingest_manager/common/mocks.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts
diff --git a/x-pack/plugins/ingest_manager/common/mocks.ts b/x-pack/plugins/ingest_manager/common/mocks.ts
new file mode 100644
index 0000000000000..131917af44595
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/common/mocks.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 { NewPackageConfig, PackageConfig } from './types/models/package_config';
+
+export const createNewPackageConfigMock = () => {
+ return {
+ name: 'endpoint-1',
+ description: '',
+ namespace: 'default',
+ enabled: true,
+ config_id: '93c46720-c217-11ea-9906-b5b8a21b268e',
+ output_id: '',
+ package: {
+ name: 'endpoint',
+ title: 'Elastic Endpoint',
+ version: '0.9.0',
+ },
+ inputs: [],
+ } as NewPackageConfig;
+};
+
+export const createPackageConfigMock = () => {
+ const newPackageConfig = createNewPackageConfigMock();
+ return {
+ ...newPackageConfig,
+ id: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1',
+ version: 'abcd',
+ revision: 1,
+ updated_at: '2020-06-25T16:03:38.159292',
+ updated_by: 'kibana',
+ created_at: '2020-06-25T16:03:38.159292',
+ created_by: 'kibana',
+ inputs: [
+ {
+ config: {},
+ },
+ ],
+ } as PackageConfig;
+};
diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts
new file mode 100644
index 0000000000000..bb035a19f33d6
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { loggerMock } from 'src/core/server/logging/logger.mock';
+import { createNewPackageConfigMock } from '../../../ingest_manager/common/mocks';
+import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config';
+import { getManifestManagerMock } from './services/artifacts/manifest_manager/manifest_manager.mock';
+import { getPackageConfigCreateCallback } from './ingest_integration';
+
+describe('ingest_integration tests ', () => {
+ describe('ingest_integration sanity checks', () => {
+ test('policy is updated with manifest', async () => {
+ const logger = loggerMock.create();
+ const manifestManager = getManifestManagerMock();
+ const callback = getPackageConfigCreateCallback(logger, manifestManager);
+ const policyConfig = createNewPackageConfigMock();
+ const newPolicyConfig = await callback(policyConfig);
+ expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
+ expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
+ expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual({
+ artifacts: {
+ 'endpoint-exceptionlist-linux-v1': {
+ compression_algorithm: 'zlib',
+ decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
+ decoded_size: 287,
+ encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c',
+ encoded_size: 133,
+ encryption_algorithm: 'none',
+ relative_url:
+ '/api/endpoint/artifacts/download/endpoint-exceptionlist-linux-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
+ },
+ },
+ manifest_version: 'WzAsMF0=',
+ schema_version: 'v1',
+ });
+ });
+
+ test('policy is returned even if error is encountered during artifact sync', async () => {
+ const logger = loggerMock.create();
+ const manifestManager = getManifestManagerMock();
+ manifestManager.syncArtifacts = jest.fn().mockRejectedValue([new Error('error updating')]);
+ const lastDispatched = await manifestManager.getLastDispatchedManifest();
+ const callback = getPackageConfigCreateCallback(logger, manifestManager);
+ const policyConfig = createNewPackageConfigMock();
+ const newPolicyConfig = await callback(policyConfig);
+ expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
+ expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
+ expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual(
+ lastDispatched.toEndpointFormat()
+ );
+ });
+
+ test('initial policy creation succeeds if snapshot retrieval fails', async () => {
+ const logger = loggerMock.create();
+ const manifestManager = getManifestManagerMock();
+ const lastDispatched = await manifestManager.getLastDispatchedManifest();
+ manifestManager.getSnapshot = jest.fn().mockResolvedValue(null);
+ const callback = getPackageConfigCreateCallback(logger, manifestManager);
+ const policyConfig = createNewPackageConfigMock();
+ const newPolicyConfig = await callback(policyConfig);
+ expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
+ expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
+ expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual(
+ lastDispatched.toEndpointFormat()
+ );
+ });
+
+ test('subsequent policy creations succeed', async () => {
+ const logger = loggerMock.create();
+ const manifestManager = getManifestManagerMock();
+ const snapshot = await manifestManager.getSnapshot();
+ manifestManager.getLastDispatchedManifest = jest.fn().mockResolvedValue(snapshot!.manifest);
+ manifestManager.getSnapshot = jest.fn().mockResolvedValue({
+ manifest: snapshot!.manifest,
+ diffs: [],
+ });
+ const callback = getPackageConfigCreateCallback(logger, manifestManager);
+ const policyConfig = createNewPackageConfigMock();
+ const newPolicyConfig = await callback(policyConfig);
+ expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
+ expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
+ expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual(
+ snapshot!.manifest.toEndpointFormat()
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts
index 1acec1e7c53ac..e2522ac4af778 100644
--- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts
@@ -8,7 +8,9 @@ import { Logger } from '../../../../../src/core/server';
import { NewPackageConfig } from '../../../ingest_manager/common/types/models';
import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config';
import { NewPolicyData } from '../../common/endpoint/types';
-import { ManifestManager } from './services/artifacts';
+import { ManifestManager, ManifestSnapshot } from './services/artifacts';
+import { reportErrors, ManifestConstants } from './lib/artifacts/common';
+import { ManifestSchemaVersion } from '../../common/endpoint/schema/common';
/**
* Callback to handle creation of PackageConfigs in Ingest Manager
@@ -29,58 +31,83 @@ export const getPackageConfigCreateCallback = (
// follow the types/schema expected
let updatedPackageConfig = newPackageConfig as NewPolicyData;
- // get snapshot based on exception-list-agnostic SOs
- // with diffs from last dispatched manifest, if it exists
- const snapshot = await manifestManager.getSnapshot({ initialize: true });
+ // get current manifest from SO (last dispatched)
+ const manifest = (
+ await manifestManager.getLastDispatchedManifest(ManifestConstants.SCHEMA_VERSION)
+ )?.toEndpointFormat() ?? {
+ manifest_version: 'default',
+ schema_version: ManifestConstants.SCHEMA_VERSION as ManifestSchemaVersion,
+ artifacts: {},
+ };
- if (snapshot === null) {
- logger.warn('No manifest snapshot available.');
- return updatedPackageConfig;
+ // Until we get the Default Policy Configuration in the Endpoint package,
+ // we will add it here manually at creation time.
+ if (newPackageConfig.inputs.length === 0) {
+ updatedPackageConfig = {
+ ...newPackageConfig,
+ inputs: [
+ {
+ type: 'endpoint',
+ enabled: true,
+ streams: [],
+ config: {
+ artifact_manifest: {
+ value: manifest,
+ },
+ policy: {
+ value: policyConfigFactory(),
+ },
+ },
+ },
+ ],
+ };
}
- if (snapshot.diffs.length > 0) {
- // create new artifacts
- await manifestManager.syncArtifacts(snapshot, 'add');
+ let snapshot: ManifestSnapshot | null = null;
+ let success = true;
+ try {
+ // Try to get most up-to-date manifest data.
- // Until we get the Default Policy Configuration in the Endpoint package,
- // we will add it here manually at creation time.
- // @ts-ignore
- if (newPackageConfig.inputs.length === 0) {
- updatedPackageConfig = {
- ...newPackageConfig,
- inputs: [
- {
- type: 'endpoint',
- enabled: true,
- streams: [],
- config: {
- artifact_manifest: {
- value: snapshot.manifest.toEndpointFormat(),
- },
- policy: {
- value: policyConfigFactory(),
- },
- },
- },
- ],
+ // get snapshot based on exception-list-agnostic SOs
+ // with diffs from last dispatched manifest, if it exists
+ snapshot = await manifestManager.getSnapshot({ initialize: true });
+
+ if (snapshot && snapshot.diffs.length) {
+ // create new artifacts
+ const errors = await manifestManager.syncArtifacts(snapshot, 'add');
+ if (errors.length) {
+ reportErrors(logger, errors);
+ throw new Error('Error writing new artifacts.');
+ }
+ }
+
+ if (snapshot) {
+ updatedPackageConfig.inputs[0].config.artifact_manifest = {
+ value: snapshot.manifest.toEndpointFormat(),
};
}
- }
- try {
+ return updatedPackageConfig;
+ } catch (err) {
+ success = false;
+ logger.error(err);
return updatedPackageConfig;
} finally {
- if (snapshot.diffs.length > 0) {
- // TODO: let's revisit the way this callback happens... use promises?
- // only commit when we know the package config was created
+ if (success && snapshot !== null) {
try {
- await manifestManager.commit(snapshot.manifest);
+ if (snapshot.diffs.length > 0) {
+ // TODO: let's revisit the way this callback happens... use promises?
+ // only commit when we know the package config was created
+ await manifestManager.commit(snapshot.manifest);
- // clean up old artifacts
- await manifestManager.syncArtifacts(snapshot, 'delete');
+ // clean up old artifacts
+ await manifestManager.syncArtifacts(snapshot, 'delete');
+ }
} catch (err) {
logger.error(err);
}
+ } else if (snapshot === null) {
+ logger.error('No manifest snapshot available.');
}
}
};
diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts
index 9ad4554b30203..71d14eb1226d5 100644
--- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts
@@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import { Logger } from 'src/core/server';
export const ArtifactConstants = {
GLOBAL_ALLOWLIST_NAME: 'endpoint-exceptionlist',
@@ -16,3 +17,9 @@ export const ManifestConstants = {
SCHEMA_VERSION: 'v1',
INITIAL_VERSION: 'WzAsMF0=',
};
+
+export const reportErrors = (logger: Logger, errors: Error[]) => {
+ errors.forEach((err) => {
+ logger.error(err);
+ });
+};
diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts
index aa7f56e815d58..583f4499f591b 100644
--- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts
@@ -11,6 +11,7 @@ import {
TaskManagerStartContract,
} from '../../../../../task_manager/server';
import { EndpointAppContext } from '../../types';
+import { reportErrors } from './common';
export const ManifestTaskConstants = {
TIMEOUT: '1m',
@@ -88,19 +89,36 @@ export class ManifestTask {
return;
}
+ let errors: Error[] = [];
try {
// get snapshot based on exception-list-agnostic SOs
// with diffs from last dispatched manifest
const snapshot = await manifestManager.getSnapshot();
if (snapshot && snapshot.diffs.length > 0) {
// create new artifacts
- await manifestManager.syncArtifacts(snapshot, 'add');
+ errors = await manifestManager.syncArtifacts(snapshot, 'add');
+ if (errors.length) {
+ reportErrors(this.logger, errors);
+ throw new Error('Error writing new artifacts.');
+ }
// write to ingest-manager package config
- await manifestManager.dispatch(snapshot.manifest);
+ errors = await manifestManager.dispatch(snapshot.manifest);
+ if (errors.length) {
+ reportErrors(this.logger, errors);
+ throw new Error('Error dispatching manifest.');
+ }
// commit latest manifest state to user-artifact-manifest SO
- await manifestManager.commit(snapshot.manifest);
+ const error = await manifestManager.commit(snapshot.manifest);
+ if (error) {
+ reportErrors(this.logger, [error]);
+ throw new Error('Error committing manifest.');
+ }
// clean up old artifacts
- await manifestManager.syncArtifacts(snapshot, 'delete');
+ errors = await manifestManager.syncArtifacts(snapshot, 'delete');
+ if (errors.length) {
+ reportErrors(this.logger, errors);
+ throw new Error('Error cleaning up outdated artifacts.');
+ }
}
} catch (err) {
this.logger.error(err);
diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts
index 55d7baec36dc6..6a8c26e08d9dd 100644
--- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts
@@ -6,6 +6,8 @@
import { ILegacyScopedClusterClient, SavedObjectsClientContract } from 'kibana/server';
import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { loggerMock } from 'src/core/server/logging/logger.mock';
import { xpackMocks } from '../../../../mocks';
import {
AgentService,
@@ -63,8 +65,8 @@ export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked<
> => {
return {
agentService: createMockAgentService(),
+ logger: loggerMock.create(),
savedObjectsStart: savedObjectsServiceMock.createStartContract(),
- // @ts-ignore
manifestManager: getManifestManagerMock(),
registerIngestCallback: jest.fn<
ReturnType,
diff --git a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts
index b7f99fe6fe297..ed97d04eecee6 100644
--- a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts
@@ -7,38 +7,38 @@
import * as t from 'io-ts';
import { operator } from '../../../../../lists/common/schemas';
+export const translatedEntryMatchAnyMatcher = t.keyof({
+ exact_cased_any: null,
+ exact_caseless_any: null,
+});
+export type TranslatedEntryMatchAnyMatcher = t.TypeOf;
+
export const translatedEntryMatchAny = t.exact(
t.type({
field: t.string,
operator,
- type: t.keyof({
- exact_cased_any: null,
- exact_caseless_any: null,
- }),
+ type: translatedEntryMatchAnyMatcher,
value: t.array(t.string),
})
);
export type TranslatedEntryMatchAny = t.TypeOf;
-export const translatedEntryMatchAnyMatcher = translatedEntryMatchAny.type.props.type;
-export type TranslatedEntryMatchAnyMatcher = t.TypeOf;
+export const translatedEntryMatchMatcher = t.keyof({
+ exact_cased: null,
+ exact_caseless: null,
+});
+export type TranslatedEntryMatchMatcher = t.TypeOf;
export const translatedEntryMatch = t.exact(
t.type({
field: t.string,
operator,
- type: t.keyof({
- exact_cased: null,
- exact_caseless: null,
- }),
+ type: translatedEntryMatchMatcher,
value: t.string,
})
);
export type TranslatedEntryMatch = t.TypeOf;
-export const translatedEntryMatchMatcher = translatedEntryMatch.type.props.type;
-export type TranslatedEntryMatchMatcher = t.TypeOf;
-
export const translatedEntryMatcher = t.union([
translatedEntryMatchMatcher,
translatedEntryMatchAnyMatcher,
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts
index dfbe2572076d0..3bdc5dfbcbd45 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts
@@ -4,9 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// eslint-disable-next-line max-classes-per-file
import { savedObjectsClientMock, loggingSystemMock } from 'src/core/server/mocks';
import { Logger } from 'src/core/server';
+import { createPackageConfigMock } from '../../../../../../ingest_manager/common/mocks';
+import { PackageConfigServiceInterface } from '../../../../../../ingest_manager/server';
+import { createPackageConfigServiceMock } from '../../../../../../ingest_manager/server/mocks';
import { getFoundExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock';
import { listMock } from '../../../../../../lists/server/mocks';
import {
@@ -21,40 +23,6 @@ import { getArtifactClientMock } from '../artifact_client.mock';
import { getManifestClientMock } from '../manifest_client.mock';
import { ManifestManager } from './manifest_manager';
-function getMockPackageConfig() {
- return {
- id: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1',
- inputs: [
- {
- config: {},
- },
- ],
- revision: 1,
- version: 'abcd', // TODO: not yet implemented in ingest_manager (https://github.com/elastic/kibana/issues/69992)
- updated_at: '2020-06-25T16:03:38.159292',
- updated_by: 'kibana',
- created_at: '2020-06-25T16:03:38.159292',
- created_by: 'kibana',
- };
-}
-
-class PackageConfigServiceMock {
- public create = jest.fn().mockResolvedValue(getMockPackageConfig());
- public get = jest.fn().mockResolvedValue(getMockPackageConfig());
- public getByIds = jest.fn().mockResolvedValue([getMockPackageConfig()]);
- public list = jest.fn().mockResolvedValue({
- items: [getMockPackageConfig()],
- total: 1,
- page: 1,
- perPage: 20,
- });
- public update = jest.fn().mockResolvedValue(getMockPackageConfig());
-}
-
-export function getPackageConfigServiceMock() {
- return new PackageConfigServiceMock();
-}
-
async function mockBuildExceptionListArtifacts(
os: string,
schemaVersion: string
@@ -66,27 +34,23 @@ async function mockBuildExceptionListArtifacts(
return [await buildArtifact(exceptions, os, schemaVersion)];
}
-// @ts-ignore
export class ManifestManagerMock extends ManifestManager {
- // @ts-ignore
- private buildExceptionListArtifacts = async () => {
- return mockBuildExceptionListArtifacts('linux', 'v1');
- };
+ protected buildExceptionListArtifacts = jest
+ .fn()
+ .mockResolvedValue(mockBuildExceptionListArtifacts('linux', 'v1'));
- // @ts-ignore
- private getLastDispatchedManifest = jest
+ public getLastDispatchedManifest = jest
.fn()
.mockResolvedValue(new Manifest(new Date(), 'v1', ManifestConstants.INITIAL_VERSION));
- // @ts-ignore
- private getManifestClient = jest
+ protected getManifestClient = jest
.fn()
.mockReturnValue(getManifestClientMock(this.savedObjectsClient));
}
export const getManifestManagerMock = (opts?: {
cache?: ExceptionsCache;
- packageConfigService?: PackageConfigServiceMock;
+ packageConfigService?: jest.Mocked;
savedObjectsClient?: ReturnType;
}): ManifestManagerMock => {
let cache = new ExceptionsCache(5);
@@ -94,10 +58,14 @@ export const getManifestManagerMock = (opts?: {
cache = opts.cache;
}
- let packageConfigService = getPackageConfigServiceMock();
+ let packageConfigService = createPackageConfigServiceMock();
if (opts?.packageConfigService !== undefined) {
packageConfigService = opts.packageConfigService;
}
+ packageConfigService.list = jest.fn().mockResolvedValue({
+ total: 1,
+ items: [createPackageConfigMock()],
+ });
let savedObjectsClient = savedObjectsClientMock.create();
if (opts?.savedObjectsClient !== undefined) {
@@ -107,7 +75,6 @@ export const getManifestManagerMock = (opts?: {
const manifestManager = new ManifestManagerMock({
artifactClient: getArtifactClientMock(savedObjectsClient),
cache,
- // @ts-ignore
packageConfigService,
exceptionListClient: listMock.getExceptionListClient(),
logger: loggingSystemMock.create().get() as jest.Mocked,
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts
index b1cbc41459f15..d092e7060f8aa 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts
@@ -6,13 +6,14 @@
import { inflateSync } from 'zlib';
import { savedObjectsClientMock } from 'src/core/server/mocks';
+import { createPackageConfigServiceMock } from '../../../../../../ingest_manager/server/mocks';
import {
ArtifactConstants,
ManifestConstants,
Manifest,
ExceptionsCache,
} from '../../../lib/artifacts';
-import { getPackageConfigServiceMock, getManifestManagerMock } from './manifest_manager.mock';
+import { getManifestManagerMock } from './manifest_manager.mock';
describe('manifest_manager', () => {
describe('ManifestManager sanity checks', () => {
@@ -73,15 +74,15 @@ describe('manifest_manager', () => {
});
test('ManifestManager can dispatch manifest', async () => {
- const packageConfigService = getPackageConfigServiceMock();
+ const packageConfigService = createPackageConfigServiceMock();
const manifestManager = getManifestManagerMock({ packageConfigService });
const snapshot = await manifestManager.getSnapshot();
const dispatched = await manifestManager.dispatch(snapshot!.manifest);
- expect(dispatched).toEqual(true);
+ expect(dispatched).toEqual([]);
const entries = snapshot!.manifest.getEntries();
const artifact = Object.values(entries)[0].getArtifact();
expect(
- packageConfigService.update.mock.calls[0][2].inputs[0].config.artifact_manifest.value
+ packageConfigService.update.mock.calls[0][2].inputs[0].config!.artifact_manifest.value
).toEqual({
manifest_version: ManifestConstants.INITIAL_VERSION,
schema_version: 'v1',
@@ -115,7 +116,7 @@ describe('manifest_manager', () => {
snapshot!.diffs.push(diff);
const dispatched = await manifestManager.dispatch(snapshot!.manifest);
- expect(dispatched).toEqual(true);
+ expect(dispatched).toEqual([]);
await manifestManager.commit(snapshot!.manifest);
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts
index b9e289cee62af..c8cad32ab746e 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts
@@ -61,19 +61,25 @@ export class ManifestManager {
/**
* Gets a ManifestClient for the provided schemaVersion.
*
- * @param schemaVersion
+ * @param schemaVersion The schema version of the manifest.
+ * @returns {ManifestClient} A ManifestClient scoped to the provided schemaVersion.
*/
- private getManifestClient(schemaVersion: string) {
+ protected getManifestClient(schemaVersion: string): ManifestClient {
return new ManifestClient(this.savedObjectsClient, schemaVersion as ManifestSchemaVersion);
}
/**
* Builds an array of artifacts (one per supported OS) based on the current
- * state of exception-list-agnostic SO's.
+ * state of exception-list-agnostic SOs.
*
- * @param schemaVersion
+ * @param schemaVersion The schema version of the artifact
+ * @returns {Promise} An array of uncompressed artifacts built from exception-list-agnostic SOs.
+ * @throws Throws/rejects if there are errors building the list.
*/
- private async buildExceptionListArtifacts(schemaVersion: string) {
+ protected async buildExceptionListArtifacts(
+ schemaVersion: string
+ ): Promise {
+ // TODO: should wrap in try/catch?
return ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS.reduce(
async (acc: Promise, os) => {
const exceptionList = await getFullEndpointExceptionList(
@@ -90,13 +96,75 @@ export class ManifestManager {
);
}
+ /**
+ * Writes new artifact SOs based on provided snapshot.
+ *
+ * @param snapshot A ManifestSnapshot to use for writing the artifacts.
+ * @returns {Promise} Any errors encountered.
+ */
+ private async writeArtifacts(snapshot: ManifestSnapshot): Promise {
+ const errors: Error[] = [];
+ for (const diff of snapshot.diffs) {
+ const artifact = snapshot.manifest.getArtifact(diff.id);
+ if (artifact === undefined) {
+ throw new Error(
+ `Corrupted manifest detected. Diff contained artifact ${diff.id} not in manifest.`
+ );
+ }
+
+ const compressedArtifact = await compressExceptionList(Buffer.from(artifact.body, 'base64'));
+ artifact.body = compressedArtifact.toString('base64');
+ artifact.encodedSize = compressedArtifact.byteLength;
+ artifact.compressionAlgorithm = 'zlib';
+ artifact.encodedSha256 = createHash('sha256').update(compressedArtifact).digest('hex');
+
+ try {
+ // Write the artifact SO
+ await this.artifactClient.createArtifact(artifact);
+ // Cache the compressed body of the artifact
+ this.cache.set(diff.id, Buffer.from(artifact.body, 'base64'));
+ } catch (err) {
+ if (err.status === 409) {
+ this.logger.debug(`Tried to create artifact ${diff.id}, but it already exists.`);
+ } else {
+ // TODO: log error here?
+ errors.push(err);
+ }
+ }
+ }
+ return errors;
+ }
+
+ /**
+ * Deletes old artifact SOs based on provided snapshot.
+ *
+ * @param snapshot A ManifestSnapshot to use for deleting the artifacts.
+ * @returns {Promise} Any errors encountered.
+ */
+ private async deleteArtifacts(snapshot: ManifestSnapshot): Promise {
+ const errors: Error[] = [];
+ for (const diff of snapshot.diffs) {
+ try {
+ // Delete the artifact SO
+ await this.artifactClient.deleteArtifact(diff.id);
+ // TODO: should we delete the cache entry here?
+ this.logger.info(`Cleaned up artifact ${diff.id}`);
+ } catch (err) {
+ errors.push(err);
+ }
+ }
+ return errors;
+ }
+
/**
* Returns the last dispatched manifest based on the current state of the
* user-artifact-manifest SO.
*
- * @param schemaVersion
+ * @param schemaVersion The schema version of the manifest.
+ * @returns {Promise} The last dispatched manifest, or null if does not exist.
+ * @throws Throws/rejects if there is an unexpected error retrieving the manifest.
*/
- private async getLastDispatchedManifest(schemaVersion: string) {
+ public async getLastDispatchedManifest(schemaVersion: string): Promise {
try {
const manifestClient = this.getManifestClient(schemaVersion);
const manifestSo = await manifestClient.getManifest();
@@ -127,9 +195,11 @@ export class ManifestManager {
/**
* Snapshots a manifest based on current state of exception-list-agnostic SOs.
*
- * @param opts TODO
+ * @param opts Optional parameters for snapshot retrieval.
+ * @param opts.initialize Initialize a new Manifest when no manifest SO can be retrieved.
+ * @returns {Promise} A snapshot of the manifest, or null if not initialized.
*/
- public async getSnapshot(opts?: ManifestSnapshotOpts) {
+ public async getSnapshot(opts?: ManifestSnapshotOpts): Promise {
try {
let oldManifest: Manifest | null;
@@ -176,71 +246,39 @@ export class ManifestManager {
* Creates artifacts that do not yet exist and cleans up old artifacts that have been
* superceded by this snapshot.
*
- * Can be filtered to apply one or both operations.
- *
- * @param snapshot
- * @param diffType
+ * @param snapshot A ManifestSnapshot to use for sync.
+ * @returns {Promise} Any errors encountered.
*/
- public async syncArtifacts(snapshot: ManifestSnapshot, diffType?: 'add' | 'delete') {
- const filteredDiffs = snapshot.diffs.reduce((diffs: ManifestDiff[], diff) => {
- if (diff.type === diffType || diffType === undefined) {
- diffs.push(diff);
- } else if (!['add', 'delete'].includes(diff.type)) {
- // TODO: replace with io-ts schema
- throw new Error(`Unsupported diff type: ${diff.type}`);
- }
- return diffs;
- }, []);
-
- const adds = filteredDiffs.filter((diff) => {
- return diff.type === 'add';
+ public async syncArtifacts(
+ snapshot: ManifestSnapshot,
+ diffType: 'add' | 'delete'
+ ): Promise {
+ const filteredDiffs = snapshot.diffs.filter((diff) => {
+ return diff.type === diffType;
});
- const deletes = filteredDiffs.filter((diff) => {
- return diff.type === 'delete';
- });
+ const tmpSnapshot = { ...snapshot };
+ tmpSnapshot.diffs = filteredDiffs;
- for (const diff of adds) {
- const artifact = snapshot.manifest.getArtifact(diff.id);
- if (artifact === undefined) {
- throw new Error(
- `Corrupted manifest detected. Diff contained artifact ${diff.id} not in manifest.`
- );
- }
- const compressedArtifact = await compressExceptionList(Buffer.from(artifact.body, 'base64'));
- artifact.body = compressedArtifact.toString('base64');
- artifact.encodedSize = compressedArtifact.byteLength;
- artifact.compressionAlgorithm = 'zlib';
- artifact.encodedSha256 = createHash('sha256').update(compressedArtifact).digest('hex');
-
- try {
- await this.artifactClient.createArtifact(artifact);
- } catch (err) {
- if (err.status === 409) {
- this.logger.debug(`Tried to create artifact ${diff.id}, but it already exists.`);
- } else {
- throw err;
- }
- }
- // Cache the body of the artifact
- this.cache.set(diff.id, Buffer.from(artifact.body, 'base64'));
+ if (diffType === 'add') {
+ return this.writeArtifacts(tmpSnapshot);
+ } else if (diffType === 'delete') {
+ return this.deleteArtifacts(tmpSnapshot);
}
- for (const diff of deletes) {
- await this.artifactClient.deleteArtifact(diff.id);
- // TODO: should we delete the cache entry here?
- this.logger.info(`Cleaned up artifact ${diff.id}`);
- }
+ return [new Error(`Unsupported diff type: ${diffType}`)];
}
/**
* Dispatches the manifest by writing it to the endpoint package config.
*
+ * @param manifest The Manifest to dispatch.
+ * @returns {Promise} Any errors encountered.
*/
- public async dispatch(manifest: Manifest) {
+ public async dispatch(manifest: Manifest): Promise {
let paging = true;
let page = 1;
- let success = true;
+ const errors: Error[] = [];
while (paging) {
const { items, total } = await this.packageConfigService.list(this.savedObjectsClient, {
@@ -264,13 +302,10 @@ export class ManifestManager {
`Updated package config ${id} with manifest version ${manifest.getVersion()}`
);
} catch (err) {
- success = false;
- this.logger.debug(`Error updating package config ${id}`);
- this.logger.error(err);
+ errors.push(err);
}
} else {
- success = false;
- this.logger.debug(`Package config ${id} has no config.`);
+ errors.push(new Error(`Package config ${id} has no config.`));
}
}
@@ -278,32 +313,38 @@ export class ManifestManager {
page++;
}
- // TODO: revisit success logic
- return success;
+ return errors;
}
/**
* Commits a manifest to indicate that it has been dispatched.
*
- * @param manifest
+ * @param manifest The Manifest to commit.
+ * @returns {Promise} An error if encountered, or null if successful.
*/
- public async commit(manifest: Manifest) {
- const manifestClient = this.getManifestClient(manifest.getSchemaVersion());
-
- // Commit the new manifest
- if (manifest.getVersion() === ManifestConstants.INITIAL_VERSION) {
- await manifestClient.createManifest(manifest.toSavedObject());
- } else {
- const version = manifest.getVersion();
- if (version === ManifestConstants.INITIAL_VERSION) {
- throw new Error('Updating existing manifest with baseline version. Bad state.');
+ public async commit(manifest: Manifest): Promise {
+ try {
+ const manifestClient = this.getManifestClient(manifest.getSchemaVersion());
+
+ // Commit the new manifest
+ if (manifest.getVersion() === ManifestConstants.INITIAL_VERSION) {
+ await manifestClient.createManifest(manifest.toSavedObject());
+ } else {
+ const version = manifest.getVersion();
+ if (version === ManifestConstants.INITIAL_VERSION) {
+ throw new Error('Updating existing manifest with baseline version. Bad state.');
+ }
+ await manifestClient.updateManifest(manifest.toSavedObject(), {
+ version,
+ });
}
- await manifestClient.updateManifest(manifest.toSavedObject(), {
- version,
- });
+
+ this.logger.info(`Committed manifest ${manifest.getVersion()}`);
+ } catch (err) {
+ return err;
}
- this.logger.info(`Committed manifest ${manifest.getVersion()}`);
+ return null;
}
/**
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
index db33775abeb0a..7207bb3fc37b3 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
@@ -116,6 +116,48 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
version: policyInfo.packageInfo.version,
},
},
+ artifact_manifest: {
+ artifacts: {
+ 'endpoint-exceptionlist-linux-v1': {
+ compression_algorithm: 'zlib',
+ decoded_sha256:
+ 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
+ decoded_size: 14,
+ encoded_sha256:
+ 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
+ encoded_size: 22,
+ encryption_algorithm: 'none',
+ relative_url:
+ '/api/endpoint/artifacts/download/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
+ },
+ 'endpoint-exceptionlist-macos-v1': {
+ compression_algorithm: 'zlib',
+ decoded_sha256:
+ 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
+ decoded_size: 14,
+ encoded_sha256:
+ 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
+ encoded_size: 22,
+ encryption_algorithm: 'none',
+ relative_url:
+ '/api/endpoint/artifacts/download/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
+ },
+ 'endpoint-exceptionlist-windows-v1': {
+ compression_algorithm: 'zlib',
+ decoded_sha256:
+ 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
+ decoded_size: 14,
+ encoded_sha256:
+ 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
+ encoded_size: 22,
+ encryption_algorithm: 'none',
+ relative_url:
+ '/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
+ },
+ },
+ manifest_version: 'WzEwNSwxXQ==',
+ schema_version: 'v1',
+ },
policy: {
linux: {
events: { file: false, network: true, process: true },
From 66c531d90366cfd08fc671c9bf2208dc102b87e2 Mon Sep 17 00:00:00 2001
From: Zacqary Adam Xeper
Date: Fri, 10 Jul 2020 16:26:57 -0500
Subject: [PATCH 19/39] [Metrics UI] Round metric threshold time buckets to
nearest unit (#71172)
Co-authored-by: Elastic Machine
---
.../metric_threshold/lib/evaluate_alert.ts | 25 +++++++++++--------
.../metric_threshold/lib/metric_query.test.ts | 22 ++++++++++++++++
.../metric_threshold/lib/metric_query.ts | 9 +++++--
.../metric_threshold_executor.test.ts | 2 ++
.../metric_threshold_executor.ts | 4 ++-
.../alerting/metric_threshold/test_mocks.ts | 1 +
.../infra/server/utils/round_timestamp.ts | 15 +++++++++++
7 files changed, 65 insertions(+), 13 deletions(-)
create mode 100644 x-pack/plugins/infra/server/utils/round_timestamp.ts
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts
index de5eda4a1f2c3..7f6bf9551e2c1 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts
@@ -23,6 +23,7 @@ interface Aggregation {
buckets: Array<{
aggregatedValue: { value: number; values?: Array<{ key: number; value: number }> };
doc_count: number;
+ key_as_string: string;
}>;
};
}
@@ -57,17 +58,18 @@ export const evaluateAlert = (
);
const { threshold, comparator } = criterion;
const comparisonFunction = comparatorMap[comparator];
- return mapValues(currentValues, (values: number | number[] | null) => {
- if (isTooManyBucketsPreviewException(values)) throw values;
+ return mapValues(currentValues, (points: any[] | typeof NaN | null) => {
+ if (isTooManyBucketsPreviewException(points)) throw points;
return {
...criterion,
metric: criterion.metric ?? DOCUMENT_COUNT_I18N,
- currentValue: Array.isArray(values) ? last(values) : NaN,
- shouldFire: Array.isArray(values)
- ? values.map((value) => comparisonFunction(value, threshold))
+ currentValue: Array.isArray(points) ? last(points)?.value : NaN,
+ timestamp: Array.isArray(points) ? last(points)?.key : NaN,
+ shouldFire: Array.isArray(points)
+ ? points.map((point) => comparisonFunction(point.value, threshold))
: [false],
- isNoData: values === null,
- isError: isNaN(values),
+ isNoData: points === null,
+ isError: isNaN(points),
};
});
})
@@ -157,17 +159,20 @@ const getValuesFromAggregations = (
const { buckets } = aggregations.aggregatedIntervals;
if (!buckets.length) return null; // No Data state
if (aggType === Aggregators.COUNT) {
- return buckets.map((bucket) => bucket.doc_count);
+ return buckets.map((bucket) => ({ key: bucket.key_as_string, value: bucket.doc_count }));
}
if (aggType === Aggregators.P95 || aggType === Aggregators.P99) {
return buckets.map((bucket) => {
const values = bucket.aggregatedValue?.values || [];
const firstValue = first(values);
if (!firstValue) return null;
- return firstValue.value;
+ return { key: bucket.key_as_string, value: firstValue.value };
});
}
- return buckets.map((bucket) => bucket.aggregatedValue.value);
+ return buckets.map((bucket) => ({
+ key: bucket.key_as_string,
+ value: bucket.aggregatedValue.value,
+ }));
} catch (e) {
return NaN; // Error state
}
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.test.ts
index 3ad1031f574e2..b4fe8f053a44a 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.test.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.test.ts
@@ -56,4 +56,26 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => {
);
});
});
+
+ describe('handles time', () => {
+ const end = new Date('2020-07-08T22:07:27.235Z').valueOf();
+ const timerange = {
+ end,
+ start: end - 5 * 60 * 1000,
+ };
+ const searchBody = getElasticsearchMetricQuery(
+ expressionParams,
+ timefield,
+ undefined,
+ undefined,
+ timerange
+ );
+ test('by rounding timestamps to the nearest timeUnit', () => {
+ const rangeFilter = searchBody.query.bool.filter.find((filter) =>
+ filter.hasOwnProperty('range')
+ )?.range[timefield];
+ expect(rangeFilter?.lte).toBe(1594246020000);
+ expect(rangeFilter?.gte).toBe(1594245720000);
+ });
+ });
});
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts
index 15506a30529c4..078ca46d42e60 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts
@@ -3,9 +3,11 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+
import { networkTraffic } from '../../../../../common/inventory_models/shared/metrics/snapshot/network_traffic';
import { MetricExpressionParams, Aggregators } from '../types';
import { getIntervalInSeconds } from '../../../../utils/get_interval_in_seconds';
+import { roundTimestamp } from '../../../../utils/round_timestamp';
import { getDateHistogramOffset } from '../../../snapshot/query_helpers';
import { createPercentileAggregation } from './create_percentile_aggregation';
@@ -34,12 +36,15 @@ export const getElasticsearchMetricQuery = (
const interval = `${timeSize}${timeUnit}`;
const intervalAsSeconds = getIntervalInSeconds(interval);
- const to = timeframe ? timeframe.end : Date.now();
+ const to = roundTimestamp(timeframe ? timeframe.end : Date.now(), timeUnit);
// We need enough data for 5 buckets worth of data. We also need
// to convert the intervalAsSeconds to milliseconds.
const minimumFrom = to - intervalAsSeconds * 1000 * MINIMUM_BUCKETS;
- const from = timeframe && timeframe.start <= minimumFrom ? timeframe.start : minimumFrom;
+ const from = roundTimestamp(
+ timeframe && timeframe.start <= minimumFrom ? timeframe.start : minimumFrom,
+ timeUnit
+ );
const offset = getDateHistogramOffset(from, interval);
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
index 24f4bc2c678b4..003a6c3c20e98 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
@@ -94,12 +94,14 @@ describe('The metric threshold alert type', () => {
expect(getState(instanceID).alertState).toBe(AlertStates.OK);
});
test('reports expected values to the action context', async () => {
+ const now = 1577858400000;
await execute(Comparator.GT, [0.75]);
const { action } = mostRecentAction(instanceID);
expect(action.group).toBe('*');
expect(action.reason).toContain('current value is 1');
expect(action.reason).toContain('threshold of 0.75');
expect(action.reason).toContain('test.metric.1');
+ expect(action.timestamp).toBe(new Date(now).toISOString());
});
});
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
index 4c02593dd0095..bc1cc24f65eeb 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
@@ -76,11 +76,13 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs, alertId: s
}
}
if (reason) {
+ const firstResult = first(alertResults);
+ const timestamp = (firstResult && firstResult[group].timestamp) ?? moment().toISOString();
alertInstance.scheduleActions(FIRED_ACTIONS.id, {
group,
alertState: stateToAlertMessage[nextState],
reason,
- timestamp: moment().toISOString(),
+ timestamp,
value: mapToConditionsLookup(alertResults, (result) => result[group].currentValue),
threshold: mapToConditionsLookup(criteria, (c) => c.threshold),
metric: mapToConditionsLookup(criteria, (c) => c.metric),
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts
index ee2cf94a2fd62..c7e53eb2008f5 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts
@@ -12,6 +12,7 @@ const bucketsA = [
{
doc_count: 3,
aggregatedValue: { value: 1.0, values: [{ key: 95.0, value: 1.0 }] },
+ key_as_string: new Date(1577858400000).toISOString(),
},
];
diff --git a/x-pack/plugins/infra/server/utils/round_timestamp.ts b/x-pack/plugins/infra/server/utils/round_timestamp.ts
new file mode 100644
index 0000000000000..9b5ae2ac40197
--- /dev/null
+++ b/x-pack/plugins/infra/server/utils/round_timestamp.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Unit } from '@elastic/datemath';
+import moment from 'moment';
+
+export const roundTimestamp = (timestamp: number, unit: Unit) => {
+ const floor = moment(timestamp).startOf(unit).valueOf();
+ const ceil = moment(timestamp).add(1, unit).startOf(unit).valueOf();
+ if (Math.abs(timestamp - floor) <= Math.abs(timestamp - ceil)) return floor;
+ return ceil;
+};
From e1253ed0478b5c0276d5520d9f54a167a325fe0e Mon Sep 17 00:00:00 2001
From: Madison Caldwell
Date: Fri, 10 Jul 2020 18:23:51 -0400
Subject: [PATCH 20/39] [Security Solution][Ingest Manager][Endpoint] Optional
ingest manager (#71198)
---
x-pack/plugins/security_solution/kibana.json | 5 +++--
.../plugins/security_solution/public/plugin.tsx | 10 ++++++----
x-pack/plugins/security_solution/public/types.ts | 2 +-
.../endpoint_app_context_services.test.ts | 16 ++++++++--------
4 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json
index 40d3402378895..29d0ab58e8b55 100644
--- a/x-pack/plugins/security_solution/kibana.json
+++ b/x-pack/plugins/security_solution/kibana.json
@@ -11,7 +11,6 @@
"embeddable",
"features",
"home",
- "ingestManager",
"taskManager",
"inspector",
"licensing",
@@ -21,6 +20,7 @@
],
"optionalPlugins": [
"encryptedSavedObjects",
+ "ingestManager",
"ml",
"newsfeed",
"security",
@@ -33,6 +33,7 @@
"requiredBundles": [
"kibanaUtils",
"esUiShared",
- "kibanaReact"
+ "kibanaReact",
+ "ingestManager"
]
}
diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx
index 7bb4be6b50879..62328bd767748 100644
--- a/x-pack/plugins/security_solution/public/plugin.tsx
+++ b/x-pack/plugins/security_solution/public/plugin.tsx
@@ -324,10 +324,12 @@ export class Plugin implements IPlugin {
- // it('should return undefined on getAgentService if dependencies are not enabled', async () => {
- // const endpointAppContextService = new EndpointAppContextService();
- // expect(endpointAppContextService.getAgentService()).toEqual(undefined);
- // });
- // it('should return undefined on getManifestManager if dependencies are not enabled', async () => {
- // const endpointAppContextService = new EndpointAppContextService();
- // expect(endpointAppContextService.getManifestManager()).toEqual(undefined);
- // });
+ it('should return undefined on getAgentService if dependencies are not enabled', async () => {
+ const endpointAppContextService = new EndpointAppContextService();
+ expect(endpointAppContextService.getAgentService()).toEqual(undefined);
+ });
+ it('should return undefined on getManifestManager if dependencies are not enabled', async () => {
+ const endpointAppContextService = new EndpointAppContextService();
+ expect(endpointAppContextService.getManifestManager()).toEqual(undefined);
+ });
it('should throw error on getScopedSavedObjectsClient if start is not called', async () => {
const endpointAppContextService = new EndpointAppContextService();
expect(() =>
From 79df8e01ad78d0b4ecb24edad14203c116990128 Mon Sep 17 00:00:00 2001
From: Liza Katz
Date: Mon, 13 Jul 2020 09:26:34 +0300
Subject: [PATCH 21/39] improve bugfix 7198 test stability (#71250)
* improve test stability
* reenabled test
Co-authored-by: Elastic Machine
---
.../apps/discover/_doc_navigation.js | 32 ++++++++++++-------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/test/functional/apps/discover/_doc_navigation.js b/test/functional/apps/discover/_doc_navigation.js
index 9bcf7fd2d73b5..5ae799f8756c0 100644
--- a/test/functional/apps/discover/_doc_navigation.js
+++ b/test/functional/apps/discover/_doc_navigation.js
@@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) {
const retry = getService('retry');
// Flaky: https://github.com/elastic/kibana/issues/71216
- describe.skip('doc link in discover', function contextSize() {
+ describe('doc link in discover', function contextSize() {
beforeEach(async function () {
log.debug('load kibana index with default index pattern');
await esArchiver.loadIfNeeded('discover');
@@ -63,20 +63,28 @@ export default function ({ getService, getPageObjects }) {
await filterBar.addFilter('agent', 'is', 'Missing/Fields');
await PageObjects.discover.waitUntilSearchingHasFinished();
- // navigate to the doc view
- await docTable.clickRowToggle({ rowIndex: 0 });
+ await retry.try(async () => {
+ // navigate to the doc view
+ await docTable.clickRowToggle({ rowIndex: 0 });
- const details = await docTable.getDetailsRow();
- await docTable.addInclusiveFilter(details, 'referer');
- await PageObjects.discover.waitUntilSearchingHasFinished();
+ const details = await docTable.getDetailsRow();
+ await docTable.addInclusiveFilter(details, 'referer');
+ await PageObjects.discover.waitUntilSearchingHasFinished();
- const hasInclusiveFilter = await filterBar.hasFilter('referer', 'exists', true, false, true);
- expect(hasInclusiveFilter).to.be(true);
+ const hasInclusiveFilter = await filterBar.hasFilter(
+ 'referer',
+ 'exists',
+ true,
+ false,
+ true
+ );
+ expect(hasInclusiveFilter).to.be(true);
- await docTable.removeInclusiveFilter(details, 'referer');
- await PageObjects.discover.waitUntilSearchingHasFinished();
- const hasExcludeFilter = await filterBar.hasFilter('referer', 'exists', true, false, false);
- expect(hasExcludeFilter).to.be(true);
+ await docTable.removeInclusiveFilter(details, 'referer');
+ await PageObjects.discover.waitUntilSearchingHasFinished();
+ const hasExcludeFilter = await filterBar.hasFilter('referer', 'exists', true, false, false);
+ expect(hasExcludeFilter).to.be(true);
+ });
});
});
}
From 0c04d982065749e670295ec819da8796604e1a15 Mon Sep 17 00:00:00 2001
From: Vadim Dalecky
Date: Mon, 13 Jul 2020 01:53:31 -0700
Subject: [PATCH 22/39] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20use=20allow-li?=
=?UTF-8?q?st=20in=20AppArch=20codebase=20(#71400)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/plugins/data/common/field_formats/converters/url.ts | 4 ++--
.../public/state_management/url/hash_unhash_url.test.ts | 4 ++--
.../public/state_management/url/hash_unhash_url.ts | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/plugins/data/common/field_formats/converters/url.ts b/src/plugins/data/common/field_formats/converters/url.ts
index a0a498b6cab34..b797159b53486 100644
--- a/src/plugins/data/common/field_formats/converters/url.ts
+++ b/src/plugins/data/common/field_formats/converters/url.ts
@@ -30,7 +30,7 @@ import {
} from '../types';
const templateMatchRE = /{{([\s\S]+?)}}/g;
-const whitelistUrlSchemes = ['http://', 'https://'];
+const allowedUrlSchemes = ['http://', 'https://'];
const URL_TYPES = [
{
@@ -161,7 +161,7 @@ export class UrlFormat extends FieldFormat {
return this.generateImgHtml(url, imageLabel);
default:
- const inWhitelist = whitelistUrlSchemes.some((scheme) => url.indexOf(scheme) === 0);
+ const inWhitelist = allowedUrlSchemes.some((scheme) => url.indexOf(scheme) === 0);
if (!inWhitelist && !parsedUrl) {
return url;
}
diff --git a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts
index ce8cd4acb24ab..8114c2d910cb2 100644
--- a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts
+++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts
@@ -116,7 +116,7 @@ describe('hash unhash url', () => {
expect(mockStorage.length).toBe(3);
});
- it('hashes only whitelisted properties', () => {
+ it('hashes only allow-listed properties', () => {
const stateParamKey1 = '_g';
const stateParamValue1 = '(yes:!t)';
const stateParamKey2 = '_a';
@@ -227,7 +227,7 @@ describe('hash unhash url', () => {
);
});
- it('unhashes only whitelisted properties', () => {
+ it('un-hashes only allow-listed properties', () => {
const stateParamKey1 = '_g';
const stateParamValueHashed1 = 'h@4e60e02';
const state1 = { yes: true };
diff --git a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts
index ec82bdeadedd5..aaeae65f094cd 100644
--- a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts
+++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts
@@ -35,7 +35,7 @@ export const hashUrl = createQueryReplacer(hashQuery);
// naive hack, but this allows to decouple these utils from AppState, GlobalState for now
// when removing AppState, GlobalState and migrating to IState containers,
-// need to make sure that apps explicitly passing this whitelist to hash
+// need to make sure that apps explicitly passing this allow-list to hash
const __HACK_HARDCODED_LEGACY_HASHABLE_PARAMS = ['_g', '_a', '_s'];
function createQueryMapper(queryParamMapper: (q: string) => string | null) {
return (
From 60032b81ca698ac18daef5c7fcb210453e1377a2 Mon Sep 17 00:00:00 2001
From: Joe Portner <5295965+jportner@users.noreply.github.com>
Date: Mon, 13 Jul 2020 05:03:25 -0400
Subject: [PATCH 23/39] Bump lodash package version (#71392)
---
package.json | 1 +
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/package.json b/package.json
index a45f240ce13af..d58da61047d28 100644
--- a/package.json
+++ b/package.json
@@ -87,6 +87,7 @@
"**/@types/hoist-non-react-statics": "^3.3.1",
"**/@types/chai": "^4.2.11",
"**/cypress/@types/lodash": "^4.14.155",
+ "**/cypress/lodash": "^4.15.19",
"**/typescript": "3.9.5",
"**/graphql-toolkit/lodash": "^4.17.15",
"**/hoist-non-react-statics": "^3.3.2",
diff --git a/yarn.lock b/yarn.lock
index 390b89ea5ce7d..153f4e89fe969 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -20903,10 +20903,10 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5:
- version "4.17.15"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
- integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.15.19, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5:
+ version "4.17.19"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
+ integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
lodash@^3.10.1:
version "3.10.1"
From 639f8b7ca2ae74ebd4c2775e956a9c58742dcf9d Mon Sep 17 00:00:00 2001
From: Uladzislau Lasitsa
Date: Mon, 13 Jul 2020 13:28:42 +0300
Subject: [PATCH 24/39] Migrated agg table karma tests to jest (#71224)
* Migrated karma tests to jest
* Add comment
Co-authored-by: Elastic Machine
---
.../public/__tests__/vis_type_table/legacy.ts | 39 -----
.../public/agg_table/agg_table.test.js} | 153 ++++++++++--------
.../public/agg_table/agg_table_group.test.js} | 50 +++---
.../public/agg_table}/tabified_data.js | 0
.../public/paginated_table/rows.js | 5 +-
5 files changed, 125 insertions(+), 122 deletions(-)
delete mode 100644 src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts
rename src/{legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js => plugins/vis_type_table/public/agg_table/agg_table.test.js} (75%)
rename src/{legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js => plugins/vis_type_table/public/agg_table/agg_table_group.test.js} (74%)
rename src/{legacy/core_plugins/kibana/public/__tests__/vis_type_table => plugins/vis_type_table/public/agg_table}/tabified_data.js (100%)
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts
deleted file mode 100644
index 216afe5920408..0000000000000
--- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/legacy.ts
+++ /dev/null
@@ -1,39 +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 { PluginInitializerContext } from 'kibana/public';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { npStart, npSetup } from 'ui/new_platform';
-import {
- TableVisPlugin,
- TablePluginSetupDependencies,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../../plugins/vis_type_table/public/plugin';
-
-const plugins: Readonly = {
- expressions: npSetup.plugins.expressions,
- visualizations: npSetup.plugins.visualizations,
-};
-
-const pluginInstance = new TableVisPlugin({} as PluginInitializerContext);
-
-export const setup = pluginInstance.setup(npSetup.core, plugins);
-export const start = pluginInstance.start(npStart.core, {
- data: npStart.plugins.data,
- kibanaLegacy: npStart.plugins.kibanaLegacy,
-});
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js b/src/plugins/vis_type_table/public/agg_table/agg_table.test.js
similarity index 75%
rename from src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js
rename to src/plugins/vis_type_table/public/agg_table/agg_table.test.js
index 88eb299e3c3a8..0362bd55963d9 100644
--- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table.js
+++ b/src/plugins/vis_type_table/public/agg_table/agg_table.test.js
@@ -19,44 +19,71 @@
import $ from 'jquery';
import moment from 'moment';
-import ngMock from 'ng_mock';
-import expect from '@kbn/expect';
+import angular from 'angular';
+import 'angular-mocks';
import sinon from 'sinon';
-import './legacy';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { npStart } from 'ui/new_platform';
import { round } from 'lodash';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { getInnerAngular } from '../../../../../../plugins/vis_type_table/public/get_inner_angular';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { initTableVisLegacyModule } from '../../../../../../plugins/vis_type_table/public/table_vis_legacy_module';
+import { getFieldFormatsRegistry } from '../../../../test_utils/public/stub_field_formats';
+import { coreMock } from '../../../../core/public/mocks';
+import { initAngularBootstrap } from '../../../kibana_legacy/public';
+import { setUiSettings } from '../../../data/public/services';
+import { UI_SETTINGS } from '../../../data/public/';
+import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../share/public';
+
+import { setFormatService } from '../services';
+import { getInnerAngular } from '../get_inner_angular';
+import { initTableVisLegacyModule } from '../table_vis_legacy_module';
import { tabifiedData } from './tabified_data';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { configureAppAngularModule } from '../../../../../../plugins/kibana_legacy/public/angular';
+
+const uiSettings = new Map();
describe('Table Vis - AggTable Directive', function () {
+ const core = coreMock.createStart();
+
+ core.uiSettings.set = jest.fn((key, value) => {
+ uiSettings.set(key, value);
+ });
+
+ core.uiSettings.get = jest.fn((key) => {
+ const defaultValues = {
+ dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
+ 'dateFormat:tz': 'UTC',
+ [UI_SETTINGS.SHORT_DOTS_ENABLE]: true,
+ [UI_SETTINGS.FORMAT_CURRENCY_DEFAULT_PATTERN]: '($0,0.[00])',
+ [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN]: '0,0.[000]',
+ [UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN]: '0,0.[000]%',
+ [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE]: 'en',
+ [UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP]: {},
+ [CSV_SEPARATOR_SETTING]: ',',
+ [CSV_QUOTE_VALUES_SETTING]: true,
+ };
+
+ return defaultValues[key] || uiSettings.get(key);
+ });
+
let $rootScope;
let $compile;
let settings;
const initLocalAngular = () => {
- const tableVisModule = getInnerAngular('kibana/table_vis', npStart.core);
- configureAppAngularModule(tableVisModule, npStart.core, true);
+ const tableVisModule = getInnerAngular('kibana/table_vis', core);
initTableVisLegacyModule(tableVisModule);
};
- beforeEach(initLocalAngular);
-
- beforeEach(ngMock.module('kibana/table_vis'));
- beforeEach(
- ngMock.inject(function ($injector, config) {
+ beforeEach(() => {
+ setUiSettings(core.uiSettings);
+ setFormatService(getFieldFormatsRegistry(core));
+ initAngularBootstrap();
+ initLocalAngular();
+ angular.mock.module('kibana/table_vis');
+ angular.mock.inject(($injector, config) => {
settings = config;
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
- })
- );
+ });
+ });
let $scope;
beforeEach(function () {
@@ -66,7 +93,7 @@ describe('Table Vis - AggTable Directive', function () {
$scope.$destroy();
});
- it('renders a simple response properly', function () {
+ test('renders a simple response properly', function () {
$scope.dimensions = {
metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }],
buckets: [],
@@ -78,12 +105,12 @@ describe('Table Vis - AggTable Directive', function () {
);
$scope.$digest();
- expect($el.find('tbody').length).to.be(1);
- expect($el.find('td').length).to.be(1);
- expect($el.find('td').text()).to.eql('1,000');
+ expect($el.find('tbody').length).toBe(1);
+ expect($el.find('td').length).toBe(1);
+ expect($el.find('td').text()).toEqual('1,000');
});
- it('renders nothing if the table is empty', function () {
+ test('renders nothing if the table is empty', function () {
$scope.dimensions = {};
$scope.table = null;
const $el = $compile('')(
@@ -91,10 +118,10 @@ describe('Table Vis - AggTable Directive', function () {
);
$scope.$digest();
- expect($el.find('tbody').length).to.be(0);
+ expect($el.find('tbody').length).toBe(0);
});
- it('renders a complex response properly', async function () {
+ test('renders a complex response properly', async function () {
$scope.dimensions = {
buckets: [
{ accessor: 0, params: {} },
@@ -112,37 +139,37 @@ describe('Table Vis - AggTable Directive', function () {
$compile($el)($scope);
$scope.$digest();
- expect($el.find('tbody').length).to.be(1);
+ expect($el.find('tbody').length).toBe(1);
const $rows = $el.find('tbody tr');
- expect($rows.length).to.be.greaterThan(0);
+ expect($rows.length).toBeGreaterThan(0);
function validBytes(str) {
const num = str.replace(/,/g, '');
if (num !== '-') {
- expect(num).to.match(/^\d+$/);
+ expect(num).toMatch(/^\d+$/);
}
}
$rows.each(function () {
// 6 cells in every row
const $cells = $(this).find('td');
- expect($cells.length).to.be(6);
+ expect($cells.length).toBe(6);
const txts = $cells.map(function () {
return $(this).text().trim();
});
// two character country code
- expect(txts[0]).to.match(/^(png|jpg|gif|html|css)$/);
+ expect(txts[0]).toMatch(/^(png|jpg|gif|html|css)$/);
validBytes(txts[1]);
// country
- expect(txts[2]).to.match(/^\w\w$/);
+ expect(txts[2]).toMatch(/^\w\w$/);
validBytes(txts[3]);
// os
- expect(txts[4]).to.match(/^(win|mac|linux)$/);
+ expect(txts[4]).toMatch(/^(win|mac|linux)$/);
validBytes(txts[5]);
});
});
@@ -153,9 +180,9 @@ describe('Table Vis - AggTable Directive', function () {
moment.tz.setDefault(settings.get('dateFormat:tz'));
}
- const off = $scope.$on('change:config.dateFormat:tz', setDefaultTimezone);
const oldTimezoneSetting = settings.get('dateFormat:tz');
settings.set('dateFormat:tz', 'UTC');
+ setDefaultTimezone();
$scope.dimensions = {
buckets: [
@@ -181,24 +208,24 @@ describe('Table Vis - AggTable Directive', function () {
$compile($el)($scope);
$scope.$digest();
- expect($el.find('tfoot').length).to.be(1);
+ expect($el.find('tfoot').length).toBe(1);
const $rows = $el.find('tfoot tr');
- expect($rows.length).to.be(1);
+ expect($rows.length).toBe(1);
const $cells = $($rows[0]).find('th');
- expect($cells.length).to.be(6);
+ expect($cells.length).toBe(6);
for (let i = 0; i < 6; i++) {
- expect($($cells[i]).text().trim()).to.be(expected[i]);
+ expect($($cells[i]).text().trim()).toBe(expected[i]);
}
settings.set('dateFormat:tz', oldTimezoneSetting);
- off();
+ setDefaultTimezone();
}
- it('as count', async function () {
+ test('as count', async function () {
await totalsRowTest('count', ['18', '18', '18', '18', '18', '18']);
});
- it('as min', async function () {
+ test('as min', async function () {
await totalsRowTest('min', [
'',
'2014-09-28',
@@ -208,7 +235,7 @@ describe('Table Vis - AggTable Directive', function () {
'11',
]);
});
- it('as max', async function () {
+ test('as max', async function () {
await totalsRowTest('max', [
'',
'2014-10-03',
@@ -218,16 +245,16 @@ describe('Table Vis - AggTable Directive', function () {
'837',
]);
});
- it('as avg', async function () {
+ test('as avg', async function () {
await totalsRowTest('avg', ['', '', '87,221.5', '', '64.667', '206.833']);
});
- it('as sum', async function () {
+ test('as sum', async function () {
await totalsRowTest('sum', ['', '', '1,569,987', '', '1,164', '3,723']);
});
});
describe('aggTable.toCsv()', function () {
- it('escapes rows and columns properly', function () {
+ test('escapes rows and columns properly', function () {
const $el = $compile('')(
$scope
);
@@ -244,12 +271,12 @@ describe('Table Vis - AggTable Directive', function () {
rows: [{ a: 1, b: 2, c: '"foobar"' }],
};
- expect(aggTable.toCsv()).to.be(
+ expect(aggTable.toCsv()).toBe(
'one,two,"with double-quotes("")"' + '\r\n' + '1,2,"""foobar"""' + '\r\n'
);
});
- it('exports rows and columns properly', async function () {
+ test('exports rows and columns properly', async function () {
$scope.dimensions = {
buckets: [
{ accessor: 0, params: {} },
@@ -274,7 +301,7 @@ describe('Table Vis - AggTable Directive', function () {
$tableScope.table = $scope.table;
const raw = aggTable.toCsv(false);
- expect(raw).to.be(
+ expect(raw).toBe(
'"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' +
'\r\n' +
'png,412032,IT,9299,win,0' +
@@ -304,7 +331,7 @@ describe('Table Vis - AggTable Directive', function () {
);
});
- it('exports formatted rows and columns properly', async function () {
+ test('exports formatted rows and columns properly', async function () {
$scope.dimensions = {
buckets: [
{ accessor: 0, params: {} },
@@ -332,7 +359,7 @@ describe('Table Vis - AggTable Directive', function () {
$tableScope.formattedColumns[0].formatter.convert = (v) => `${v}_formatted`;
const formatted = aggTable.toCsv(true);
- expect(formatted).to.be(
+ expect(formatted).toBe(
'"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' +
'\r\n' +
'"png_formatted",412032,IT,9299,win,0' +
@@ -363,7 +390,7 @@ describe('Table Vis - AggTable Directive', function () {
});
});
- it('renders percentage columns', async function () {
+ test('renders percentage columns', async function () {
$scope.dimensions = {
buckets: [
{ accessor: 0, params: {} },
@@ -390,8 +417,8 @@ describe('Table Vis - AggTable Directive', function () {
$scope.$digest();
const $headings = $el.find('th');
- expect($headings.length).to.be(7);
- expect($headings.eq(3).text().trim()).to.be('Average bytes percentages');
+ expect($headings.length).toBe(7);
+ expect($headings.eq(3).text().trim()).toBe('Average bytes percentages');
const countColId = $scope.table.columns.find((col) => col.name === $scope.percentageCol).id;
const counts = $scope.table.rows.map((row) => row[countColId]);
@@ -400,7 +427,7 @@ describe('Table Vis - AggTable Directive', function () {
$percentageColValues.each((i, value) => {
const percentage = `${round((counts[i] / total) * 100, 3)}%`;
- expect(value).to.be(percentage);
+ expect(value).toBe(percentage);
});
});
@@ -420,7 +447,7 @@ describe('Table Vis - AggTable Directive', function () {
window.Blob = origBlob;
});
- it('calls _saveAs properly', function () {
+ test('calls _saveAs properly', function () {
const $el = $compile('')($scope);
$scope.$digest();
@@ -440,19 +467,19 @@ describe('Table Vis - AggTable Directive', function () {
aggTable.csv.filename = 'somefilename.csv';
aggTable.exportAsCsv();
- expect(saveAs.callCount).to.be(1);
+ expect(saveAs.callCount).toBe(1);
const call = saveAs.getCall(0);
- expect(call.args[0]).to.be.a(FakeBlob);
- expect(call.args[0].slices).to.eql([
+ expect(call.args[0]).toBeInstanceOf(FakeBlob);
+ expect(call.args[0].slices).toEqual([
'one,two,"with double-quotes("")"' + '\r\n' + '1,2,"""foobar"""' + '\r\n',
]);
- expect(call.args[0].opts).to.eql({
+ expect(call.args[0].opts).toEqual({
type: 'text/plain;charset=utf-8',
});
- expect(call.args[1]).to.be('somefilename.csv');
+ expect(call.args[1]).toBe('somefilename.csv');
});
- it('should use the export-title attribute', function () {
+ test('should use the export-title attribute', function () {
const expected = 'export file name';
const $el = $compile(
``
@@ -468,7 +495,7 @@ describe('Table Vis - AggTable Directive', function () {
$tableScope.exportTitle = expected;
$scope.$digest();
- expect(aggTable.csv.filename).to.equal(`${expected}.csv`);
+ expect(aggTable.csv.filename).toEqual(`${expected}.csv`);
});
});
});
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js b/src/plugins/vis_type_table/public/agg_table/agg_table_group.test.js
similarity index 74%
rename from src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js
rename to src/plugins/vis_type_table/public/agg_table/agg_table_group.test.js
index 99b397167009d..43913eed32f90 100644
--- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/agg_table_group.js
+++ b/src/plugins/vis_type_table/public/agg_table/agg_table_group.test.js
@@ -18,38 +18,50 @@
*/
import $ from 'jquery';
-import ngMock from 'ng_mock';
+import angular from 'angular';
+import 'angular-mocks';
import expect from '@kbn/expect';
-import './legacy';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { getInnerAngular } from '../../../../../../plugins/vis_type_table/public/get_inner_angular';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { initTableVisLegacyModule } from '../../../../../../plugins/vis_type_table/public/table_vis_legacy_module';
+
+import { getFieldFormatsRegistry } from '../../../../test_utils/public/stub_field_formats';
+import { coreMock } from '../../../../core/public/mocks';
+import { initAngularBootstrap } from '../../../kibana_legacy/public';
+import { setUiSettings } from '../../../data/public/services';
+import { setFormatService } from '../services';
+import { getInnerAngular } from '../get_inner_angular';
+import { initTableVisLegacyModule } from '../table_vis_legacy_module';
import { tabifiedData } from './tabified_data';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { npStart } from 'ui/new_platform';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { configureAppAngularModule } from '../../../../../../plugins/kibana_legacy/public/angular';
+
+const uiSettings = new Map();
describe('Table Vis - AggTableGroup Directive', function () {
+ const core = coreMock.createStart();
let $rootScope;
let $compile;
+ core.uiSettings.set = jest.fn((key, value) => {
+ uiSettings.set(key, value);
+ });
+
+ core.uiSettings.get = jest.fn((key) => {
+ return uiSettings.get(key);
+ });
+
const initLocalAngular = () => {
- const tableVisModule = getInnerAngular('kibana/table_vis', npStart.core);
- configureAppAngularModule(tableVisModule, npStart.core, true);
+ const tableVisModule = getInnerAngular('kibana/table_vis', core);
initTableVisLegacyModule(tableVisModule);
};
- beforeEach(initLocalAngular);
-
- beforeEach(ngMock.module('kibana/table_vis'));
- beforeEach(
- ngMock.inject(function ($injector) {
+ beforeEach(() => {
+ setUiSettings(core.uiSettings);
+ setFormatService(getFieldFormatsRegistry(core));
+ initAngularBootstrap();
+ initLocalAngular();
+ angular.mock.module('kibana/table_vis');
+ angular.mock.inject(($injector) => {
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
- })
- );
+ });
+ });
let $scope;
beforeEach(function () {
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/tabified_data.js b/src/plugins/vis_type_table/public/agg_table/tabified_data.js
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/__tests__/vis_type_table/tabified_data.js
rename to src/plugins/vis_type_table/public/agg_table/tabified_data.js
diff --git a/src/plugins/vis_type_table/public/paginated_table/rows.js b/src/plugins/vis_type_table/public/paginated_table/rows.js
index d2192a5843644..d8f01a10c63fa 100644
--- a/src/plugins/vis_type_table/public/paginated_table/rows.js
+++ b/src/plugins/vis_type_table/public/paginated_table/rows.js
@@ -19,6 +19,7 @@
import $ from 'jquery';
import _ from 'lodash';
+import angular from 'angular';
import tableCellFilterHtml from './table_cell_filter.html';
export function KbnRows($compile) {
@@ -65,7 +66,9 @@ export function KbnRows($compile) {
if (column.filterable && contentsIsDefined) {
$cell = createFilterableCell(contents);
- $cellContent = $cell.find('[data-cell-content]');
+ // in jest tests 'angular' is using jqLite. In jqLite the method find lookups only by tags.
+ // Because of this, we should change a way how we get cell content so that tests will pass.
+ $cellContent = angular.element($cell[0].querySelector('[data-cell-content]'));
} else {
$cell = $cellContent = createCell();
}
From 3fc8c7af258293e9c8045ac2fed046582aa84f9f Mon Sep 17 00:00:00 2001
From: Liza Katz
Date: Mon, 13 Jul 2020 13:47:12 +0300
Subject: [PATCH 25/39] Validate incoming url timerange (#70948)
* validate incoming url timerange
* adjust discover test
* fix tests
* stabilize tests
* oops
Co-authored-by: Elastic Machine
---
src/plugins/data/public/public.api.md | 2 +-
.../state_sync/connect_to_query_state.ts | 5 +-
.../data/public/query/timefilter/index.ts | 1 +
.../timefilter/lib/validate_timerange.test.ts | 52 +++++++++++++++++++
.../timefilter/lib/validate_timerange.ts | 28 ++++++++++
test/functional/apps/discover/_discover.js | 24 +++++----
6 files changed, 98 insertions(+), 14 deletions(-)
create mode 100644 src/plugins/data/public/query/timefilter/lib/validate_timerange.test.ts
create mode 100644 src/plugins/data/public/query/timefilter/lib/validate_timerange.ts
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 01fcefe27df3e..b532bacf5df25 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -1991,7 +1991,7 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:40:60 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:41:60 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:53:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:61:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts
index e74497a5053b4..2e62dac87f6ef 100644
--- a/src/plugins/data/public/query/state_sync/connect_to_query_state.ts
+++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts
@@ -24,6 +24,7 @@ import { BaseStateContainer } from '../../../../kibana_utils/public';
import { QuerySetup, QueryStart } from '../query_service';
import { QueryState, QueryStateChange } from './types';
import { FilterStateStore, COMPARE_ALL_OPTIONS, compareFilters } from '../../../common';
+import { validateTimeRange } from '../timefilter';
/**
* Helper to setup two-way syncing of global data and a state container
@@ -159,9 +160,9 @@ export const connectToQueryState = (
// cloneDeep is required because services are mutating passed objects
// and state in state container is frozen
if (syncConfig.time) {
- const time = state.time || timefilter.getTimeDefaults();
+ const time = validateTimeRange(state.time) ? state.time : timefilter.getTimeDefaults();
if (!_.isEqual(time, timefilter.getTime())) {
- timefilter.setTime(_.cloneDeep(time));
+ timefilter.setTime(_.cloneDeep(time!));
}
}
diff --git a/src/plugins/data/public/query/timefilter/index.ts b/src/plugins/data/public/query/timefilter/index.ts
index f71061677ceb7..19386c10ab59f 100644
--- a/src/plugins/data/public/query/timefilter/index.ts
+++ b/src/plugins/data/public/query/timefilter/index.ts
@@ -24,3 +24,4 @@ export { Timefilter, TimefilterContract } from './timefilter';
export { TimeHistory, TimeHistoryContract } from './time_history';
export { changeTimeFilter, convertRangeFilterToTimeRangeString } from './lib/change_time_filter';
export { extractTimeFilter } from './lib/extract_time_filter';
+export { validateTimeRange } from './lib/validate_timerange';
diff --git a/src/plugins/data/public/query/timefilter/lib/validate_timerange.test.ts b/src/plugins/data/public/query/timefilter/lib/validate_timerange.test.ts
new file mode 100644
index 0000000000000..e20849c21a717
--- /dev/null
+++ b/src/plugins/data/public/query/timefilter/lib/validate_timerange.test.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { validateTimeRange } from './validate_timerange';
+
+describe('Validate timerange', () => {
+ test('Validate no range', () => {
+ const ok = validateTimeRange();
+
+ expect(ok).toBe(false);
+ });
+ test('normal range', () => {
+ const ok = validateTimeRange({
+ to: 'now',
+ from: 'now-7d',
+ });
+
+ expect(ok).toBe(true);
+ });
+ test('bad from time', () => {
+ const ok = validateTimeRange({
+ to: 'nowa',
+ from: 'now-7d',
+ });
+
+ expect(ok).toBe(false);
+ });
+ test('bad to time', () => {
+ const ok = validateTimeRange({
+ to: 'now',
+ from: 'nowa-7d',
+ });
+
+ expect(ok).toBe(false);
+ });
+});
diff --git a/src/plugins/data/public/query/timefilter/lib/validate_timerange.ts b/src/plugins/data/public/query/timefilter/lib/validate_timerange.ts
new file mode 100644
index 0000000000000..f9e4aa0ae1cab
--- /dev/null
+++ b/src/plugins/data/public/query/timefilter/lib/validate_timerange.ts
@@ -0,0 +1,28 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import dateMath from '@elastic/datemath';
+import { TimeRange } from '../../../../common';
+
+export function validateTimeRange(time?: TimeRange): boolean {
+ if (!time) return false;
+ const momentDateFrom = dateMath.parse(time.from);
+ const momentDateTo = dateMath.parse(time.to);
+ return !!(momentDateFrom && momentDateFrom.isValid() && momentDateTo && momentDateTo.isValid());
+}
diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js
index 47741c1ab8a0d..94a271987ecdf 100644
--- a/test/functional/apps/discover/_discover.js
+++ b/test/functional/apps/discover/_discover.js
@@ -254,6 +254,19 @@ export default function ({ getService, getPageObjects }) {
});
});
+ describe('invalid time range in URL', function () {
+ it('should get the default timerange', async function () {
+ const prevTime = await PageObjects.timePicker.getTimeConfig();
+ await PageObjects.common.navigateToUrl('discover', '#/?_g=(time:(from:now-15m,to:null))', {
+ useActualUrl: true,
+ });
+ await PageObjects.header.awaitKibanaChrome();
+ const time = await PageObjects.timePicker.getTimeConfig();
+ expect(time.start).to.be(prevTime.start);
+ expect(time.end).to.be(prevTime.end);
+ });
+ });
+
describe('empty query', function () {
it('should update the histogram timerange when the query is resubmitted', async function () {
await kibanaServer.uiSettings.update({
@@ -268,17 +281,6 @@ export default function ({ getService, getPageObjects }) {
});
});
- describe('invalid time range in URL', function () {
- it('should display a "Invalid time range toast"', async function () {
- await PageObjects.common.navigateToUrl('discover', '#/?_g=(time:(from:now-15m,to:null))', {
- useActualUrl: true,
- });
- await PageObjects.header.awaitKibanaChrome();
- const toastMessage = await PageObjects.common.closeToast();
- expect(toastMessage).to.be('Invalid time range');
- });
- });
-
describe('managing fields', function () {
it('should add a field, sort by it, remove it and also sorting by it', async function () {
await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
From cd2d1b8053da9ca37b44fc3edc9c83bd72eecc46 Mon Sep 17 00:00:00 2001
From: MadameSheema
Date: Mon, 13 Jul 2020 13:00:44 +0200
Subject: [PATCH 26/39] [SIEM][Detections] Fixes text layout in Schedule step
(#71306)
* fixes text layout in schedule step
* Removes unused import
Co-authored-by: Garrett Spong
Co-authored-by: Elastic Machine
---
.../rules/step_schedule_rule/index.tsx | 47 ++++++++-----------
1 file changed, 19 insertions(+), 28 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx
index 60855bc5fa25f..fa0f4dbd3668c 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx
@@ -6,7 +6,6 @@
import React, { FC, memo, useCallback, useEffect, useState } from 'react';
import deepEqual from 'fast-deep-equal';
-import styled from 'styled-components';
import { setFieldValue } from '../../../pages/detection_engine/rules/helpers';
import {
@@ -25,10 +24,6 @@ interface StepScheduleRuleProps extends RuleStepProps {
defaultValues?: ScheduleStepRule | null;
}
-const RestrictedWidthContainer = styled.div`
- max-width: 300px;
-`;
-
const stepScheduleDefaultValue = {
interval: '5m',
isNew: true,
@@ -93,29 +88,25 @@ const StepScheduleRuleComponent: FC = ({
<>
From 3a17e81626c619ef1cac98215ab689c510d1fea5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Mon, 13 Jul 2020 13:59:13 +0200
Subject: [PATCH 27/39] Bump backport to 5.5.1 (#71408)
---
package.json | 2 +-
scripts/backport.js | 7 ++++-
yarn.lock | 71 ++++++++++++++++++++++++++++++++++-----------
3 files changed, 61 insertions(+), 19 deletions(-)
diff --git a/package.json b/package.json
index d58da61047d28..cf735d3663a63 100644
--- a/package.json
+++ b/package.json
@@ -408,7 +408,7 @@
"babel-eslint": "^10.0.3",
"babel-jest": "^25.5.1",
"babel-plugin-istanbul": "^6.0.0",
- "backport": "5.4.6",
+ "backport": "5.5.1",
"chai": "3.5.0",
"chance": "1.0.18",
"cheerio": "0.22.0",
diff --git a/scripts/backport.js b/scripts/backport.js
index 2094534e2c4b3..dca5912cfb133 100644
--- a/scripts/backport.js
+++ b/scripts/backport.js
@@ -18,5 +18,10 @@
*/
require('../src/setup_node_env/node_version_validator');
+var process = require('process');
+
+// forward command line args to backport
+var args = process.argv.slice(2);
+
var backport = require('backport');
-backport.run();
+backport.run({}, args);
diff --git a/yarn.lock b/yarn.lock
index 153f4e89fe969..d2de2e19f36f5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8512,16 +8512,16 @@ backo2@1.0.2:
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
-backport@5.4.6:
- version "5.4.6"
- resolved "https://registry.yarnpkg.com/backport/-/backport-5.4.6.tgz#8d8d8cb7c0df4079a40c6f4892f393daa92c1ef8"
- integrity sha512-O3fFmQXKZN5sP6R6GwXeobsEgoFzvnuTGj8/TTTjxt1xA07pfhTY67M16rr0eiDDtuSxAqWMX9Zo+5Q3DuxfpQ==
+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==
dependencies:
axios "^0.19.2"
dedent "^0.7.0"
del "^5.1.0"
find-up "^4.1.0"
- inquirer "^7.2.0"
+ inquirer "^7.3.1"
lodash.flatmap "^4.5.0"
lodash.isempty "^4.4.0"
lodash.isstring "^4.0.1"
@@ -8531,7 +8531,7 @@ backport@5.4.6:
safe-json-stringify "^1.2.0"
strip-json-comments "^3.1.0"
winston "^3.3.3"
- yargs "^15.3.1"
+ yargs "^15.4.0"
bail@^1.0.0:
version "1.0.2"
@@ -9710,6 +9710,14 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chalk@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
+ integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
chalk@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
@@ -10163,6 +10171,11 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
+cli-width@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
+ integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
+
clipboard@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d"
@@ -17870,21 +17883,21 @@ inquirer@^7.0.0:
strip-ansi "^5.1.0"
through "^2.3.6"
-inquirer@^7.2.0:
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a"
- integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==
+inquirer@^7.3.1:
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.1.tgz#ac6aba1abdfdd5ad34e7069370411edba17f6439"
+ integrity sha512-/+vOpHQHhoh90Znev8BXiuw1TDQ7IDxWsQnFafUEoK5+4uN5Eoz1p+3GqOj/NtzEi9VzWKQcV9Bm+i8moxedsA==
dependencies:
ansi-escapes "^4.2.1"
- chalk "^3.0.0"
+ chalk "^4.1.0"
cli-cursor "^3.1.0"
- cli-width "^2.0.0"
+ cli-width "^3.0.0"
external-editor "^3.0.3"
figures "^3.0.0"
- lodash "^4.17.15"
+ lodash "^4.17.16"
mute-stream "0.0.8"
run-async "^2.4.0"
- rxjs "^6.5.3"
+ rxjs "^6.6.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
through "^2.3.6"
@@ -20903,7 +20916,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.15.19, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5:
+lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.15.19, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.16, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
@@ -27539,7 +27552,7 @@ rxjs-marbles@^5.0.6:
dependencies:
fast-equals "^2.0.0"
-rxjs@6.5.5, rxjs@^6.5.3, rxjs@^6.5.5:
+rxjs@6.5.5, rxjs@^6.5.5:
version "6.5.5"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
@@ -27560,6 +27573,13 @@ rxjs@^6.1.0, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1:
dependencies:
tslib "^1.9.0"
+rxjs@^6.6.0:
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84"
+ integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==
+ dependencies:
+ tslib "^1.9.0"
+
safe-buffer@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -33416,7 +33436,7 @@ yargs-parser@^11.1.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
-yargs-parser@^18.1.1:
+yargs-parser@^18.1.1, yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
@@ -33529,6 +33549,23 @@ yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@~15.3.1:
y18n "^4.0.0"
yargs-parser "^18.1.1"
+yargs@^15.4.0:
+ version "15.4.1"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
+ integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
+ dependencies:
+ cliui "^6.0.0"
+ decamelize "^1.2.0"
+ find-up "^4.1.0"
+ get-caller-file "^2.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^2.0.0"
+ set-blocking "^2.0.0"
+ string-width "^4.2.0"
+ which-module "^2.0.0"
+ y18n "^4.0.0"
+ yargs-parser "^18.1.2"
+
yargs@^3.15.0:
version "3.32.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
From 5ccc7e9a540f965b154d1a2ad43194da78b83b7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?=
Date: Mon, 13 Jul 2020 13:14:04 +0100
Subject: [PATCH 28/39] Usage collection speed improvements (#71317)
* Usage collection speed improvements
* Remove commented code
---
.../server/collectors/find_all.ts | 2 +-
.../server/collector/collector_set.ts | 51 ++++++++++---------
2 files changed, 29 insertions(+), 24 deletions(-)
diff --git a/src/plugins/kibana_usage_collection/server/collectors/find_all.ts b/src/plugins/kibana_usage_collection/server/collectors/find_all.ts
index e6363551eba9c..5bb4f20b5c5b1 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/find_all.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/find_all.ts
@@ -28,7 +28,7 @@ export async function findAll(
savedObjectsClient: ISavedObjectsRepository,
opts: SavedObjectsFindOptions
): Promise>> {
- const { page = 1, perPage = 100, ...options } = opts;
+ const { page = 1, perPage = 10000, ...options } = opts;
const { saved_objects: savedObjects, total } = await savedObjectsClient.find({
...options,
page,
diff --git a/src/plugins/usage_collection/server/collector/collector_set.ts b/src/plugins/usage_collection/server/collector/collector_set.ts
index 2b60a45c4065d..fce17a46b7168 100644
--- a/src/plugins/usage_collection/server/collector/collector_set.ts
+++ b/src/plugins/usage_collection/server/collector/collector_set.ts
@@ -83,14 +83,16 @@ export class CollectorSet {
);
}
- const collectorTypesNotReady: string[] = [];
- let allReady = true;
- for (const collector of collectorSet.collectors.values()) {
- if (!(await collector.isReady())) {
- allReady = false;
- collectorTypesNotReady.push(collector.type);
- }
- }
+ const collectorTypesNotReady = (
+ await Promise.all(
+ [...collectorSet.collectors.values()].map(async (collector) => {
+ if (!(await collector.isReady())) {
+ return collector.type;
+ }
+ })
+ )
+ ).filter((collectorType): collectorType is string => !!collectorType);
+ const allReady = collectorTypesNotReady.length === 0;
if (!allReady && this.maximumWaitTimeForAllCollectorsInS >= 0) {
const nowTimestamp = +new Date();
@@ -119,21 +121,24 @@ export class CollectorSet {
callCluster: LegacyAPICaller,
collectors: Map> = this.collectors
) => {
- const responses = [];
- for (const collector of collectors.values()) {
- this.logger.debug(`Fetching data from ${collector.type} collector`);
- try {
- responses.push({
- type: collector.type,
- result: await collector.fetch(callCluster),
- });
- } catch (err) {
- this.logger.warn(err);
- this.logger.warn(`Unable to fetch data from ${collector.type} collector`);
- }
- }
-
- return responses;
+ const responses = await Promise.all(
+ [...collectors.values()].map(async (collector) => {
+ this.logger.debug(`Fetching data from ${collector.type} collector`);
+ try {
+ return {
+ type: collector.type,
+ result: await collector.fetch(callCluster),
+ };
+ } catch (err) {
+ this.logger.warn(err);
+ this.logger.warn(`Unable to fetch data from ${collector.type} collector`);
+ }
+ })
+ );
+
+ return responses.filter(
+ (response): response is { type: string; result: unknown } => typeof response !== 'undefined'
+ );
};
/*
From 4e9d9812c7e20e98be929d0979e4ed2467ae5ea4 Mon Sep 17 00:00:00 2001
From: Marco Liberati
Date: Mon, 13 Jul 2020 14:27:48 +0200
Subject: [PATCH 29/39] Fix TSVB table trend slope value (#71087)
Co-authored-by: Elastic Machine
---
package.json | 1 -
.../lib/vis_data/table/process_bucket.js | 15 +-
.../lib/vis_data/table/process_bucket.test.js | 159 ++++++++++++++++++
yarn.lock | 5 -
4 files changed, 170 insertions(+), 10 deletions(-)
create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.test.js
diff --git a/package.json b/package.json
index cf735d3663a63..7889909b15244 100644
--- a/package.json
+++ b/package.json
@@ -256,7 +256,6 @@
"redux-actions": "^2.6.5",
"redux-thunk": "^2.3.0",
"regenerator-runtime": "^0.13.3",
- "regression": "2.0.1",
"request": "^2.88.0",
"require-in-the-middle": "^5.0.2",
"reselect": "^4.0.0",
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.js
index 0f2a7e153bde0..909cee456c31f 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.js
@@ -20,11 +20,20 @@
import { buildProcessorFunction } from '../build_processor_function';
import { processors } from '../response_processors/table';
import { getLastValue } from '../../../../common/get_last_value';
-import regression from 'regression';
import { first, get } from 'lodash';
import { overwrite } from '../helpers';
import { getActiveSeries } from '../helpers/get_active_series';
+function trendSinceLastBucket(data) {
+ if (data.length < 2) {
+ return 0;
+ }
+ const currentBucket = data[data.length - 1];
+ const prevBucket = data[data.length - 2];
+ const trend = (currentBucket[1] - prevBucket[1]) / currentBucket[1];
+ return Number.isNaN(trend) ? 0 : trend;
+}
+
export function processBucket(panel) {
return (bucket) => {
const series = getActiveSeries(panel).map((series) => {
@@ -38,14 +47,12 @@ export function processBucket(panel) {
};
overwrite(bucket, series.id, { meta, timeseries });
}
-
const processor = buildProcessorFunction(processors, bucket, panel, series);
const result = first(processor([]));
if (!result) return null;
const data = get(result, 'data', []);
- const linearRegression = regression.linear(data);
+ result.slope = trendSinceLastBucket(data);
result.last = getLastValue(data);
- result.slope = linearRegression.equation[0];
return result;
});
return { key: bucket.key, series };
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.test.js
new file mode 100644
index 0000000000000..a4f9c71a5953d
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.test.js
@@ -0,0 +1,159 @@
+/*
+ * 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 { processBucket } from './process_bucket';
+
+function createValueObject(key, value, seriesId) {
+ return { key_as_string: `${key}`, doc_count: value, key, [seriesId]: { value } };
+}
+
+function createBucketsObjects(size, sort, seriesId) {
+ const values = Array(size)
+ .fill(1)
+ .map((_, i) => i + 1);
+ if (sort === 'flat') {
+ return values.map((_, i) => createValueObject(i, 1, seriesId));
+ }
+ if (sort === 'desc') {
+ return values.reverse().map((v, i) => createValueObject(i, v, seriesId));
+ }
+ return values.map((v, i) => createValueObject(i, v, seriesId));
+}
+
+function createPanel(series) {
+ return {
+ type: 'table',
+ time_field: '',
+ series: series.map((seriesId) => ({
+ id: seriesId,
+ metrics: [{ id: seriesId, type: 'count' }],
+ trend_arrows: 1,
+ })),
+ };
+}
+
+function createBuckets(series) {
+ return [
+ { key: 'A', trend: 'asc', size: 10 },
+ { key: 'B', trend: 'desc', size: 10 },
+ { key: 'C', trend: 'flat', size: 10 },
+ { key: 'D', trend: 'asc', size: 1, expectedTrend: 'flat' },
+ ].map(({ key, trend, size, expectedTrend }) => {
+ const baseObj = {
+ key,
+ expectedTrend: expectedTrend || trend,
+ };
+ for (const seriesId of series) {
+ baseObj[seriesId] = {
+ meta: {
+ timeField: 'timestamp',
+ seriesId: seriesId,
+ },
+ buckets: createBucketsObjects(size, trend, seriesId),
+ };
+ }
+ return baseObj;
+ });
+}
+
+function trendChecker(trend, slope) {
+ switch (trend) {
+ case 'asc':
+ return slope > 0;
+ case 'desc':
+ return slope <= 0;
+ case 'flat':
+ return slope === 0;
+ default:
+ throw Error(`Slope value ${slope} not valid for trend "${trend}"`);
+ }
+}
+
+describe('processBucket(panel)', () => {
+ describe('single metric panel', () => {
+ let panel;
+ const SERIES_ID = 'series-id';
+
+ beforeEach(() => {
+ panel = createPanel([SERIES_ID]);
+ });
+
+ test('return the correct trend direction', () => {
+ const bucketProcessor = processBucket(panel);
+ const buckets = createBuckets([SERIES_ID]);
+ for (const bucket of buckets) {
+ const result = bucketProcessor(bucket);
+ expect(result.key).toEqual(bucket.key);
+ expect(trendChecker(bucket.expectedTrend, result.series[0].slope)).toBeTruthy();
+ }
+ });
+
+ test('properly handle 0 values for trend', () => {
+ const bucketProcessor = processBucket(panel);
+ const bucketforNaNResult = {
+ key: 'NaNScenario',
+ expectedTrend: 'flat',
+ [SERIES_ID]: {
+ meta: {
+ timeField: 'timestamp',
+ seriesId: SERIES_ID,
+ },
+ buckets: [
+ // this is a flat case, but 0/0 has not a valid number result
+ createValueObject(0, 0, SERIES_ID),
+ createValueObject(1, 0, SERIES_ID),
+ ],
+ },
+ };
+ const result = bucketProcessor(bucketforNaNResult);
+ expect(result.key).toEqual(bucketforNaNResult.key);
+ expect(trendChecker(bucketforNaNResult.expectedTrend, result.series[0].slope)).toEqual(true);
+ });
+
+ test('have the side effect to create the timeseries property if missing on bucket', () => {
+ const bucketProcessor = processBucket(panel);
+ const buckets = createBuckets([SERIES_ID]);
+ for (const bucket of buckets) {
+ bucketProcessor(bucket);
+ expect(bucket[SERIES_ID].buckets).toBeUndefined();
+ expect(bucket[SERIES_ID].timeseries).toBeDefined();
+ }
+ });
+ });
+
+ describe('multiple metrics panel', () => {
+ let panel;
+ const SERIES = ['series-id-1', 'series-id-2'];
+
+ beforeEach(() => {
+ panel = createPanel(SERIES);
+ });
+
+ test('return the correct trend direction', () => {
+ const bucketProcessor = processBucket(panel);
+ const buckets = createBuckets(SERIES);
+ for (const bucket of buckets) {
+ const result = bucketProcessor(bucket);
+ expect(result.key).toEqual(bucket.key);
+ expect(trendChecker(bucket.expectedTrend, result.series[0].slope)).toBeTruthy();
+ expect(trendChecker(bucket.expectedTrend, result.series[1].slope)).toBeTruthy();
+ }
+ });
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index d2de2e19f36f5..290713d32d333 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -26736,11 +26736,6 @@ regjsparser@^0.6.4:
dependencies:
jsesc "~0.5.0"
-regression@2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/regression/-/regression-2.0.1.tgz#8d29c3e8224a10850c35e337e85a8b2fac3b0c87"
- integrity sha1-jSnD6CJKEIUMNeM36FqLL6w7DIc=
-
rehype-parse@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-6.0.0.tgz#f681555f2598165bee2c778b39f9073d17b16bca"
From a7d3b6d3442113dd7d34d2b4cde9ed4e3d532df4 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Mon, 13 Jul 2020 08:51:36 -0400
Subject: [PATCH 30/39] [Security Solution][Endpoint] Policy Details handling
saving over version changes (http 409) (#71386)
* Handle applying update on latest Package Config if 1st update generated 409
* Unit Tests to cover the handling of http 409
---
.../policy/store/policy_details/index.test.ts | 310 +++++++++++++-----
.../policy/store/policy_details/middleware.ts | 24 +-
.../policy/store/policy_details/selectors.ts | 16 +-
3 files changed, 262 insertions(+), 88 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts
index 102fd40c97672..d3ec0670d29c5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts
@@ -5,130 +5,270 @@
*/
import { PolicyDetailsState } from '../../types';
-import { createStore, Dispatch, Store } from 'redux';
-import { policyDetailsReducer, PolicyDetailsAction } from './index';
+import { applyMiddleware, createStore, Dispatch, Store } from 'redux';
+import { policyDetailsReducer, PolicyDetailsAction, policyDetailsMiddlewareFactory } from './index';
import { policyConfig } from './selectors';
import { clone } from '../../models/policy_details_config';
import { factory as policyConfigFactory } from '../../../../../../common/endpoint/models/policy_config';
+import { PolicyData } from '../../../../../../common/endpoint/types';
+import {
+ createSpyMiddleware,
+ MiddlewareActionSpyHelper,
+} from '../../../../../common/store/test_utils';
+import {
+ AppContextTestRender,
+ createAppRootMockRenderer,
+} from '../../../../../common/mock/endpoint';
+import { HttpFetchOptions } from 'kibana/public';
describe('policy details: ', () => {
- let store: Store;
+ let store: Store;
let getState: typeof store['getState'];
let dispatch: Dispatch;
+ let policyItem: PolicyData;
- beforeEach(() => {
- store = createStore(policyDetailsReducer);
- getState = store.getState;
- dispatch = store.dispatch;
-
- dispatch({
- type: 'serverReturnedPolicyDetailsData',
- payload: {
- policyItem: {
- id: '',
- name: '',
- description: '',
- created_at: '',
- created_by: '',
- updated_at: '',
- updated_by: '',
- config_id: '',
+ const generateNewPolicyItemMock = (): PolicyData => {
+ return {
+ id: '',
+ name: '',
+ description: '',
+ created_at: '',
+ created_by: '',
+ updated_at: '',
+ updated_by: '',
+ config_id: '',
+ enabled: true,
+ output_id: '',
+ inputs: [
+ {
+ type: 'endpoint',
enabled: true,
- output_id: '',
- inputs: [
- {
- type: 'endpoint',
- enabled: true,
- streams: [],
- config: {
- artifact_manifest: {
- value: {
- manifest_version: 'WzAsMF0=',
- schema_version: 'v1',
- artifacts: {},
- },
- },
- policy: {
- value: policyConfigFactory(),
- },
+ streams: [],
+ config: {
+ artifact_manifest: {
+ value: {
+ manifest_version: 'WzAsMF0=',
+ schema_version: 'v1',
+ artifacts: {},
},
},
- ],
- namespace: '',
- package: {
- name: '',
- title: '',
- version: '',
+ policy: {
+ value: policyConfigFactory(),
+ },
},
- revision: 1,
},
+ ],
+ namespace: '',
+ package: {
+ name: '',
+ title: '',
+ version: '',
},
- });
+ revision: 1,
+ };
+ };
+
+ beforeEach(() => {
+ policyItem = generateNewPolicyItemMock();
});
- describe('when the user has enabled windows process events', () => {
+ describe('When interacting with policy form', () => {
beforeEach(() => {
- const config = policyConfig(getState());
- if (!config) {
- throw new Error();
- }
-
- const newPayload1 = clone(config);
- newPayload1.windows.events.process = true;
+ store = createStore(policyDetailsReducer);
+ getState = store.getState;
+ dispatch = store.dispatch;
dispatch({
- type: 'userChangedPolicyConfig',
- payload: { policyConfig: newPayload1 },
+ type: 'serverReturnedPolicyDetailsData',
+ payload: {
+ policyItem,
+ },
});
});
- it('windows process events is enabled', () => {
- const config = policyConfig(getState());
- expect(config!.windows.events.process).toEqual(true);
+ describe('when the user has enabled windows process events', () => {
+ beforeEach(() => {
+ const config = policyConfig(getState());
+ if (!config) {
+ throw new Error();
+ }
+
+ const newPayload1 = clone(config);
+ newPayload1.windows.events.process = true;
+
+ dispatch({
+ type: 'userChangedPolicyConfig',
+ payload: { policyConfig: newPayload1 },
+ });
+ });
+
+ it('windows process events is enabled', () => {
+ const config = policyConfig(getState());
+ expect(config!.windows.events.process).toEqual(true);
+ });
});
- });
- describe('when the user has enabled mac file events', () => {
- beforeEach(() => {
- const config = policyConfig(getState());
- if (!config) {
- throw new Error();
- }
+ describe('when the user has enabled mac file events', () => {
+ beforeEach(() => {
+ const config = policyConfig(getState());
+ if (!config) {
+ throw new Error();
+ }
- const newPayload1 = clone(config);
- newPayload1.mac.events.file = true;
+ const newPayload1 = clone(config);
+ newPayload1.mac.events.file = true;
- dispatch({
- type: 'userChangedPolicyConfig',
- payload: { policyConfig: newPayload1 },
+ dispatch({
+ type: 'userChangedPolicyConfig',
+ payload: { policyConfig: newPayload1 },
+ });
+ });
+
+ it('mac file events is enabled', () => {
+ const config = policyConfig(getState());
+ expect(config!.mac.events.file).toEqual(true);
});
});
- it('mac file events is enabled', () => {
- const config = policyConfig(getState());
- expect(config!.mac.events.file).toEqual(true);
+ describe('when the user has enabled linux process events', () => {
+ beforeEach(() => {
+ const config = policyConfig(getState());
+ if (!config) {
+ throw new Error();
+ }
+
+ const newPayload1 = clone(config);
+ newPayload1.linux.events.file = true;
+
+ dispatch({
+ type: 'userChangedPolicyConfig',
+ payload: { policyConfig: newPayload1 },
+ });
+ });
+
+ it('linux file events is enabled', () => {
+ const config = policyConfig(getState());
+ expect(config!.linux.events.file).toEqual(true);
+ });
});
});
- describe('when the user has enabled linux process events', () => {
+ describe('when saving policy data', () => {
+ let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
+ let http: AppContextTestRender['coreStart']['http'];
+
beforeEach(() => {
- const config = policyConfig(getState());
- if (!config) {
- throw new Error();
- }
+ let actionSpyMiddleware: MiddlewareActionSpyHelper['actionSpyMiddleware'];
+ const { coreStart, depsStart } = createAppRootMockRenderer();
+ ({ actionSpyMiddleware, waitForAction } = createSpyMiddleware());
+ http = coreStart.http;
- const newPayload1 = clone(config);
- newPayload1.linux.events.file = true;
+ store = createStore(
+ policyDetailsReducer,
+ undefined,
+ applyMiddleware(policyDetailsMiddlewareFactory(coreStart, depsStart), actionSpyMiddleware)
+ );
+ getState = store.getState;
+ dispatch = store.dispatch;
dispatch({
- type: 'userChangedPolicyConfig',
- payload: { policyConfig: newPayload1 },
+ type: 'serverReturnedPolicyDetailsData',
+ payload: {
+ policyItem,
+ },
+ });
+ });
+
+ it('should handle HTTP 409 (version missmatch) and still save the policy', async () => {
+ policyItem.inputs[0].config.policy.value.windows.events.dns = false;
+
+ const http409Error: Error & { response?: { status: number } } = new Error('conflict');
+ http409Error.response = { status: 409 };
+
+ // The most current Policy Item. Differences to `artifact_manifest` should be preserved,
+ // while the policy data should be overwritten on next `put`.
+ const mostCurrentPolicyItem = generateNewPolicyItemMock();
+ mostCurrentPolicyItem.inputs[0].config.artifact_manifest.value.manifest_version = 'updated';
+ mostCurrentPolicyItem.inputs[0].config.policy.value.windows.events.dns = true;
+
+ http.put.mockRejectedValueOnce(http409Error);
+ http.get.mockResolvedValueOnce({
+ item: mostCurrentPolicyItem,
+ success: true,
+ });
+ http.put.mockResolvedValueOnce({
+ item: policyItem,
+ success: true,
+ });
+
+ dispatch({ type: 'userClickedPolicyDetailsSaveButton' });
+ await waitForAction('serverReturnedUpdatedPolicyDetailsData');
+
+ expect(http.put).toHaveBeenCalledTimes(2);
+
+ const lastPutCallPayload = ((http.put.mock.calls[
+ http.put.mock.calls.length - 1
+ ] as unknown) as [string, HttpFetchOptions])[1];
+
+ expect(JSON.parse(lastPutCallPayload.body as string)).toEqual({
+ name: '',
+ description: '',
+ config_id: '',
+ enabled: true,
+ output_id: '',
+ inputs: [
+ {
+ type: 'endpoint',
+ enabled: true,
+ streams: [],
+ config: {
+ artifact_manifest: {
+ value: { manifest_version: 'updated', schema_version: 'v1', artifacts: {} },
+ },
+ policy: {
+ value: {
+ windows: {
+ events: {
+ dll_and_driver_load: true,
+ dns: false,
+ file: true,
+ network: true,
+ process: true,
+ registry: true,
+ security: true,
+ },
+ malware: { mode: 'prevent' },
+ logging: { file: 'info' },
+ },
+ mac: {
+ events: { process: true, file: true, network: true },
+ malware: { mode: 'prevent' },
+ logging: { file: 'info' },
+ },
+ linux: {
+ events: { process: true, file: true, network: true },
+ logging: { file: 'info' },
+ },
+ },
+ },
+ },
+ },
+ ],
+ namespace: '',
+ package: { name: '', title: '', version: '' },
});
});
- it('linux file events is enabled', () => {
- const config = policyConfig(getState());
- expect(config!.linux.events.file).toEqual(true);
+ it('should not attempt to handle other HTTP errors', async () => {
+ const http400Error: Error & { response?: { status: number } } = new Error('not found');
+
+ http400Error.response = { status: 400 };
+ http.put.mockRejectedValueOnce(http400Error);
+ dispatch({ type: 'userClickedPolicyDetailsSaveButton' });
+
+ const failureAction = await waitForAction('serverReturnedPolicyDetailsUpdateFailure');
+ expect(failureAction.payload?.error).toBeInstanceOf(Error);
+ expect(failureAction.payload?.error?.message).toEqual('not found');
});
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts
index cfa1a478619b7..1d9e3c2198b28 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts
@@ -4,12 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { IHttpFetchError } from 'kibana/public';
import { PolicyDetailsState, UpdatePolicyResponse } from '../../types';
import {
policyIdFromParams,
isOnPolicyDetailsPage,
policyDetails,
policyDetailsForUpdate,
+ getPolicyDataForUpdate,
} from './selectors';
import {
sendGetPackageConfig,
@@ -66,7 +68,27 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory {
+ if (!error.response || error.response.status !== 409) {
+ return Promise.reject(error);
+ }
+ // Handle 409 error (version conflict) here, by using the latest document
+ // for the package config and adding the updated policy to it, ensuring that
+ // any recent updates to `manifest_artifacts` are retained.
+ return sendGetPackageConfig(http, id).then((packageConfig) => {
+ const latestUpdatedPolicyItem = packageConfig.item;
+ latestUpdatedPolicyItem.inputs[0].config.policy =
+ updatedPolicyItem.inputs[0].config.policy;
+
+ return sendPutPackageConfig(
+ http,
+ id,
+ getPolicyDataForUpdate(latestUpdatedPolicyItem) as NewPolicyData
+ );
+ });
+ }
+ );
} catch (error) {
dispatch({
type: 'serverReturnedPolicyDetailsUpdateFailure',
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts
index d2a5c1b7e14a3..cce0adf36bcce 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts
@@ -11,6 +11,7 @@ import {
Immutable,
NewPolicyData,
PolicyConfig,
+ PolicyData,
UIPolicyConfig,
} from '../../../../../../common/endpoint/types';
import { factory as policyConfigFactory } from '../../../../../../common/endpoint/models/policy_config';
@@ -20,6 +21,18 @@ import { ManagementRoutePolicyDetailsParams } from '../../../../types';
/** Returns the policy details */
export const policyDetails = (state: Immutable) => state.policyItem;
+/**
+ * Given a Policy Data (package config) object, return back a new object with only the field
+ * needed for an Update/Create API action
+ * @param policy
+ */
+export const getPolicyDataForUpdate = (
+ policy: PolicyData | Immutable
+): NewPolicyData | Immutable => {
+ const { id, revision, created_by, created_at, updated_by, updated_at, ...newPolicy } = policy;
+ return newPolicy;
+};
+
/**
* Return only the policy structure accepted for update/create
*/
@@ -27,8 +40,7 @@ export const policyDetailsForUpdate: (
state: Immutable
) => Immutable | undefined = createSelector(policyDetails, (policy) => {
if (policy) {
- const { id, revision, created_by, created_at, updated_by, updated_at, ...newPolicy } = policy;
- return newPolicy;
+ return getPolicyDataForUpdate(policy);
}
});
From f4b4dc5faa42da788b7968f30d86c45b18a5c0d9 Mon Sep 17 00:00:00 2001
From: Anton Dosov
Date: Mon, 13 Jul 2020 14:55:11 +0200
Subject: [PATCH 31/39] [uiActions] Improve context menu keyboard support
(#70705)
* Improves position resolution logic by also tracking last clicked element.
* Adds ownFocus prop, so can pick menu item with keyboard.
* Also track if target element was removed from DOM. In that case tries to use previous element. won't work all the time, but works nicely in case context menu trigger by item in other context menu.
Co-authored-by: Elastic Machine
---
.../context_menu/open_context_menu.test.ts | 84 +++++++++++++
.../public/context_menu/open_context_menu.tsx | 117 ++++++++++++------
2 files changed, 161 insertions(+), 40 deletions(-)
create mode 100644 src/plugins/ui_actions/public/context_menu/open_context_menu.test.ts
diff --git a/src/plugins/ui_actions/public/context_menu/open_context_menu.test.ts b/src/plugins/ui_actions/public/context_menu/open_context_menu.test.ts
new file mode 100644
index 0000000000000..77ce04ba24b35
--- /dev/null
+++ b/src/plugins/ui_actions/public/context_menu/open_context_menu.test.ts
@@ -0,0 +1,84 @@
+/*
+ * 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 { createInteractionPositionTracker } from './open_context_menu';
+import { fireEvent } from '@testing-library/dom';
+
+let targetEl: Element;
+const top = 100;
+const left = 100;
+const right = 200;
+const bottom = 200;
+beforeEach(() => {
+ targetEl = document.createElement('div');
+ jest.spyOn(targetEl, 'getBoundingClientRect').mockImplementation(() => ({
+ top,
+ left,
+ right,
+ bottom,
+ width: right - left,
+ height: bottom - top,
+ x: left,
+ y: top,
+ toJSON: () => {},
+ }));
+ document.body.append(targetEl);
+});
+afterEach(() => {
+ targetEl.remove();
+});
+
+test('should use last clicked element position if mouse position is outside target element', () => {
+ const { resolveLastPosition } = createInteractionPositionTracker();
+
+ fireEvent.click(targetEl, { clientX: 0, clientY: 0 });
+ const { x, y } = resolveLastPosition();
+
+ expect(y).toBe(bottom);
+ expect(x).toBe(left + (right - left) / 2);
+});
+
+test('should use mouse position if mouse inside clicked element', () => {
+ const { resolveLastPosition } = createInteractionPositionTracker();
+
+ const mouseX = 150;
+ const mouseY = 150;
+ fireEvent.click(targetEl, { clientX: mouseX, clientY: mouseY });
+
+ const { x, y } = resolveLastPosition();
+
+ expect(y).toBe(mouseX);
+ expect(x).toBe(mouseY);
+});
+
+test('should use position of previous element, if latest element is no longer in DOM', () => {
+ const { resolveLastPosition } = createInteractionPositionTracker();
+
+ const detachedElement = document.createElement('div');
+ const spy = jest.spyOn(detachedElement, 'getBoundingClientRect');
+
+ fireEvent.click(targetEl);
+ fireEvent.click(detachedElement);
+
+ const { x, y } = resolveLastPosition();
+
+ expect(y).toBe(bottom);
+ expect(x).toBe(left + (right - left) / 2);
+ expect(spy).not.toBeCalled();
+});
diff --git a/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx b/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx
index 5892c184f8a81..0d9a4c7be5670 100644
--- a/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx
+++ b/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx
@@ -26,14 +26,86 @@ import ReactDOM from 'react-dom';
let activeSession: ContextMenuSession | null = null;
const CONTAINER_ID = 'contextMenu-container';
-let initialized = false;
+/**
+ * Tries to find best position for opening context menu using mousemove and click event
+ * Returned position is relative to document
+ */
+export function createInteractionPositionTracker() {
+ let lastMouseX = 0;
+ let lastMouseY = 0;
+ const lastClicks: Array<{ el?: Element; mouseX: number; mouseY: number }> = [];
+ const MAX_LAST_CLICKS = 10;
+
+ /**
+ * Track both `mouseup` and `click`
+ * `mouseup` is for clicks and brushes with mouse
+ * `click` is a fallback for keyboard interactions
+ */
+ document.addEventListener('mouseup', onClick, true);
+ document.addEventListener('click', onClick, true);
+ document.addEventListener('mousemove', onMouseUpdate, { passive: true });
+ document.addEventListener('mouseenter', onMouseUpdate, { passive: true });
+ function onClick(event: MouseEvent) {
+ lastClicks.push({
+ el: event.target as Element,
+ mouseX: event.clientX,
+ mouseY: event.clientY,
+ });
+ if (lastClicks.length > MAX_LAST_CLICKS) {
+ lastClicks.shift();
+ }
+ }
+ function onMouseUpdate(event: MouseEvent) {
+ lastMouseX = event.clientX;
+ lastMouseY = event.clientY;
+ }
+
+ return {
+ resolveLastPosition: (): { x: number; y: number } => {
+ const lastClick = [...lastClicks]
+ .reverse()
+ .find(({ el }) => el && document.body.contains(el));
+ if (!lastClick) {
+ // fallback to last mouse position
+ return {
+ x: lastMouseX,
+ y: lastMouseY,
+ };
+ }
+
+ const { top, left, bottom, right } = lastClick.el!.getBoundingClientRect();
+
+ const mouseX = lastClick.mouseX;
+ const mouseY = lastClick.mouseY;
+
+ if (top <= mouseY && bottom >= mouseY && left <= mouseX && right >= mouseX) {
+ // click was inside target element
+ return {
+ x: mouseX,
+ y: mouseY,
+ };
+ } else {
+ // keyboard edge case. no cursor position. use target element position instead
+ return {
+ x: left + (right - left) / 2,
+ y: bottom,
+ };
+ }
+ },
+ };
+}
+
+const { resolveLastPosition } = createInteractionPositionTracker();
function getOrCreateContainerElement() {
let container = document.getElementById(CONTAINER_ID);
- const y = getMouseY() + document.body.scrollTop;
+ let { x, y } = resolveLastPosition();
+ y = y + window.scrollY;
+ x = x + window.scrollX;
+
if (!container) {
container = document.createElement('div');
- container.style.left = getMouseX() + 'px';
+ container.style.left = x + 'px';
container.style.top = y + 'px';
container.style.position = 'absolute';
@@ -44,38 +116,12 @@ function getOrCreateContainerElement() {
container.id = CONTAINER_ID;
document.body.appendChild(container);
} else {
- container.style.left = getMouseX() + 'px';
+ container.style.left = x + 'px';
container.style.top = y + 'px';
}
return container;
}
-let x: number = 0;
-let y: number = 0;
-
-function initialize() {
- if (!initialized) {
- document.addEventListener('mousemove', onMouseUpdate, false);
- document.addEventListener('mouseenter', onMouseUpdate, false);
- initialized = true;
- }
-}
-
-function onMouseUpdate(e: any) {
- x = e.pageX;
- y = e.pageY;
-}
-
-function getMouseX() {
- return x;
-}
-
-function getMouseY() {
- return y;
-}
-
-initialize();
-
/**
* A FlyoutSession describes the session of one opened flyout panel. It offers
* methods to close the flyout panel again. If you open a flyout panel you should make
@@ -87,16 +133,6 @@ initialize();
* @extends EventEmitter
*/
class ContextMenuSession extends EventEmitter {
- /**
- * Binds the current flyout session to an Angular scope, meaning this flyout
- * session will be closed as soon as the Angular scope gets destroyed.
- * @param {object} scope - An angular scope object to bind to.
- */
- public bindToAngularScope(scope: ng.IScope): void {
- const removeWatch = scope.$on('$destroy', () => this.close());
- this.on('closed', () => removeWatch());
- }
-
/**
* Closes the opened flyout as long as it's still the open one.
* If this is not the active session anymore, this method won't do anything.
@@ -151,6 +187,7 @@ export function openContextMenu(
panelPaddingSize="none"
anchorPosition="downRight"
withTitle
+ ownFocus={true}
>
Date: Mon, 13 Jul 2020 14:59:16 +0200
Subject: [PATCH 32/39] fix overflow (#70723)
---
.../lens/public/visualization_container.scss | 3 +++
.../public/visualization_container.test.tsx | 9 +++++++++
.../lens/public/visualization_container.tsx | 19 +++++++++++++++++--
3 files changed, 29 insertions(+), 2 deletions(-)
create mode 100644 x-pack/plugins/lens/public/visualization_container.scss
diff --git a/x-pack/plugins/lens/public/visualization_container.scss b/x-pack/plugins/lens/public/visualization_container.scss
new file mode 100644
index 0000000000000..e5c359112fe4b
--- /dev/null
+++ b/x-pack/plugins/lens/public/visualization_container.scss
@@ -0,0 +1,3 @@
+.lnsVisualizationContainer {
+ overflow: auto;
+}
\ No newline at end of file
diff --git a/x-pack/plugins/lens/public/visualization_container.test.tsx b/x-pack/plugins/lens/public/visualization_container.test.tsx
index b29f0a5d783f9..454399ec90121 100644
--- a/x-pack/plugins/lens/public/visualization_container.test.tsx
+++ b/x-pack/plugins/lens/public/visualization_container.test.tsx
@@ -60,4 +60,13 @@ describe('VisualizationContainer', () => {
expect(reportingEl.prop('style')).toEqual({ color: 'blue' });
});
+
+ test('combines class names with container class', () => {
+ const component = mount(
+ Hello!
+ );
+ const reportingEl = component.find('[data-shared-item]').first();
+
+ expect(reportingEl.prop('className')).toEqual('myClass lnsVisualizationContainer');
+ });
});
diff --git a/x-pack/plugins/lens/public/visualization_container.tsx b/x-pack/plugins/lens/public/visualization_container.tsx
index fb7a1268192a8..3ca8d5de932d7 100644
--- a/x-pack/plugins/lens/public/visualization_container.tsx
+++ b/x-pack/plugins/lens/public/visualization_container.tsx
@@ -5,6 +5,9 @@
*/
import React from 'react';
+import classNames from 'classnames';
+
+import './visualization_container.scss';
interface Props extends React.HTMLAttributes {
isReady?: boolean;
@@ -15,9 +18,21 @@ interface Props extends React.HTMLAttributes {
* This is a convenience component that wraps rendered Lens visualizations. It adds reporting
* attributes (data-shared-item, data-render-complete, and data-title).
*/
-export function VisualizationContainer({ isReady = true, reportTitle, children, ...rest }: Props) {
+export function VisualizationContainer({
+ isReady = true,
+ reportTitle,
+ children,
+ className,
+ ...rest
+}: Props) {
return (
-
+
{children}
);
From 84edc361f12d6620cf6f54e3c44aa5a38b0b9294 Mon Sep 17 00:00:00 2001
From: Matthias Wilhelm
Date: Mon, 13 Jul 2020 15:22:21 +0200
Subject: [PATCH 33/39] [Discover] Migrate async import of embeddable factory
to actual embeddable (#70920)
---
.../discover/public/application/embeddable/index.ts | 1 -
.../application/embeddable/search_embeddable.ts | 2 +-
.../embeddable/search_embeddable_factory.ts | 7 ++++---
.../discover/public/application/embeddable/types.ts | 11 ++++++++++-
src/plugins/discover/public/plugin.ts | 8 ++------
5 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/src/plugins/discover/public/application/embeddable/index.ts b/src/plugins/discover/public/application/embeddable/index.ts
index b86a8daa119c5..1c4e06c7c3ade 100644
--- a/src/plugins/discover/public/application/embeddable/index.ts
+++ b/src/plugins/discover/public/application/embeddable/index.ts
@@ -20,4 +20,3 @@
export { SEARCH_EMBEDDABLE_TYPE } from './constants';
export * from './types';
export * from './search_embeddable_factory';
-export * from './search_embeddable';
diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts
index e03a6b938bc4f..9a3dd0d310ff7 100644
--- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts
+++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts
@@ -38,7 +38,7 @@ import * as columnActions from '../angular/doc_table/actions/columns';
import searchTemplate from './search_template.html';
import { ISearchEmbeddable, SearchInput, SearchOutput } from './types';
import { SortOrder } from '../angular/doc_table/components/table_header/helpers';
-import { getSortForSearchSource } from '../angular/doc_table/lib/get_sort_for_search_source';
+import { getSortForSearchSource } from '../angular/doc_table';
import {
getRequestInspectorStats,
getResponseInspectorStats,
diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable_factory.ts b/src/plugins/discover/public/application/embeddable/search_embeddable_factory.ts
index 1dc5947792d5c..f61fa361f0c0e 100644
--- a/src/plugins/discover/public/application/embeddable/search_embeddable_factory.ts
+++ b/src/plugins/discover/public/application/embeddable/search_embeddable_factory.ts
@@ -28,8 +28,8 @@ import {
} from '../../../../embeddable/public';
import { TimeRange } from '../../../../data/public';
-import { SearchEmbeddable } from './search_embeddable';
-import { SearchInput, SearchOutput } from './types';
+
+import { SearchInput, SearchOutput, SearchEmbeddable } from './types';
import { SEARCH_EMBEDDABLE_TYPE } from './constants';
interface StartServices {
@@ -92,7 +92,8 @@ export class SearchEmbeddableFactory
const savedObject = await getServices().getSavedSearchById(savedObjectId);
const indexPattern = savedObject.searchSource.getField('index');
const { executeTriggerActions } = await this.getStartServices();
- return new SearchEmbeddable(
+ const { SearchEmbeddable: SearchEmbeddableClass } = await import('./search_embeddable');
+ return new SearchEmbeddableClass(
{
savedSearch: savedObject,
$rootScope,
diff --git a/src/plugins/discover/public/application/embeddable/types.ts b/src/plugins/discover/public/application/embeddable/types.ts
index 80576eb4ed7cb..d7fa9b3bc23d3 100644
--- a/src/plugins/discover/public/application/embeddable/types.ts
+++ b/src/plugins/discover/public/application/embeddable/types.ts
@@ -17,7 +17,12 @@
* under the License.
*/
-import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from 'src/plugins/embeddable/public';
+import {
+ Embeddable,
+ EmbeddableInput,
+ EmbeddableOutput,
+ IEmbeddable,
+} from 'src/plugins/embeddable/public';
import { SortOrder } from '../angular/doc_table/components/table_header/helpers';
import { Filter, IIndexPattern, TimeRange, Query } from '../../../../data/public';
import { SavedSearch } from '../..';
@@ -40,3 +45,7 @@ export interface SearchOutput extends EmbeddableOutput {
export interface ISearchEmbeddable extends IEmbeddable {
getSavedSearch(): SavedSearch;
}
+
+export interface SearchEmbeddable extends Embeddable {
+ type: string;
+}
diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts
index e97ac783c616f..20e13d204e0e9 100644
--- a/src/plugins/discover/public/plugin.ts
+++ b/src/plugins/discover/public/plugin.ts
@@ -66,6 +66,7 @@ import {
DISCOVER_APP_URL_GENERATOR,
DiscoverUrlGenerator,
} from './url_generator';
+import { SearchEmbeddableFactory } from './application/embeddable';
declare module '../../share/public' {
export interface UrlGeneratorStateMapping {
@@ -345,12 +346,7 @@ export class DiscoverPlugin
/**
* register embeddable with a slimmer embeddable version of inner angular
*/
- private async registerEmbeddable(
- core: CoreSetup,
- plugins: DiscoverSetupPlugins
- ) {
- const { SearchEmbeddableFactory } = await import('./application/embeddable');
-
+ private registerEmbeddable(core: CoreSetup, plugins: DiscoverSetupPlugins) {
if (!this.getEmbeddableInjector) {
throw Error('Discover plugin method getEmbeddableInjector is undefined');
}
From 06847519f1638030b001b2f46f9dde43cfa62b92 Mon Sep 17 00:00:00 2001
From: Stratoula Kalafateli
Date: Mon, 13 Jul 2020 16:26:33 +0300
Subject: [PATCH 34/39] [Functional test] Increase the timeout to click new vis
function (#71226)
Co-authored-by: Elastic Machine
---
test/functional/services/listing_table.ts | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts
index 9a117458c7f76..fa42eb60fa410 100644
--- a/test/functional/services/listing_table.ts
+++ b/test/functional/services/listing_table.ts
@@ -179,9 +179,12 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider
* @param promptBtnTestSubj testSubj locator for Prompt button
*/
public async clickNewButton(promptBtnTestSubj: string): Promise {
- await retry.try(async () => {
+ await retry.tryForTime(20000, async () => {
// newItemButton button is only visible when there are items in the listing table is displayed.
- if (await testSubjects.exists('newItemButton')) {
+ const isnNewItemButtonPresent = await testSubjects.exists('newItemButton', {
+ timeout: 5000,
+ });
+ if (isnNewItemButtonPresent) {
await testSubjects.click('newItemButton');
} else {
// no items exist, click createPromptButton to create new dashboard/visualization
From 0f9c80d590b7b00850e5001754b07ca389d24e19 Mon Sep 17 00:00:00 2001
From: Brent Kimmel
Date: Mon, 13 Jul 2020 09:26:43 -0400
Subject: [PATCH 35/39] [Security_Solution][Resolver] Style adjustments per UX
(#71179)
* SQUASH
* WIP 2
* add block formatting on time to related list as well
* M. Sherrier review: untranslate timestamp / remove top border on panel
* redo dep
* CI: replace missing import with type
Co-authored-by: Elastic Machine
---
.../panels/panel_content_process_detail.tsx | 49 +---
.../panels/panel_content_related_detail.tsx | 27 +-
.../panels/panel_content_related_list.tsx | 20 +-
.../view/panels/panel_content_utilities.tsx | 236 +++++++++---------
.../public/resolver/view/styles.tsx | 2 +
5 files changed, 165 insertions(+), 169 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx
index 3127c7132df3d..5d90cd11d31af 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx
@@ -31,7 +31,7 @@ import { useResolverTheme } from '../assets';
const StyledDescriptionList = styled(EuiDescriptionList)`
&.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title {
- max-width: 8em;
+ max-width: 10em;
}
`;
@@ -56,73 +56,42 @@ export const ProcessDetails = memo(function ProcessDetails({
const dateTime = eventTime ? formatDate(eventTime) : '';
const createdEntry = {
- title: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.processDescList.created',
- {
- defaultMessage: 'Created',
- }
- ),
+ title: '@timestamp',
description: dateTime,
};
const pathEntry = {
- title: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.processDescList.path', {
- defaultMessage: 'Path',
- }),
+ title: 'process.executable',
description: processPath(processEvent),
};
const pidEntry = {
- title: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.processDescList.pid', {
- defaultMessage: 'PID',
- }),
+ title: 'process.pid',
description: processPid(processEvent),
};
const userEntry = {
- title: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.processDescList.user', {
- defaultMessage: 'User',
- }),
+ title: 'user.name',
description: (userInfoForProcess(processEvent) as { name: string }).name,
};
const domainEntry = {
- title: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.processDescList.domain',
- {
- defaultMessage: 'Domain',
- }
- ),
+ title: 'user.domain',
description: (userInfoForProcess(processEvent) as { domain: string }).domain,
};
const parentPidEntry = {
- title: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.processDescList.parentPid',
- {
- defaultMessage: 'Parent PID',
- }
- ),
+ title: 'process.parent.pid',
description: processParentPid(processEvent),
};
const md5Entry = {
- title: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.processDescList.md5hash',
- {
- defaultMessage: 'MD5',
- }
- ),
+ title: 'process.hash.md5',
description: md5HashForProcess(processEvent),
};
const commandLineEntry = {
- title: i18n.translate(
- 'xpack.securitySolution.endpoint.resolver.panel.processDescList.commandLine',
- {
- defaultMessage: 'Command Line',
- }
- ),
+ title: 'process.args',
description: argsForProcess(processEvent),
};
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_detail.tsx
index f27ec56fef697..4544381d94955 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_detail.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_detail.tsx
@@ -10,7 +10,13 @@ import { EuiSpacer, EuiText, EuiDescriptionList, EuiTextColor, EuiTitle } from '
import styled from 'styled-components';
import { useSelector } from 'react-redux';
import { FormattedMessage } from 'react-intl';
-import { CrumbInfo, formatDate, StyledBreadcrumbs, BoldCode } from './panel_content_utilities';
+import {
+ CrumbInfo,
+ formatDate,
+ StyledBreadcrumbs,
+ BoldCode,
+ StyledTime,
+} from './panel_content_utilities';
import * as event from '../../../../common/endpoint/models/event';
import { ResolverEvent } from '../../../../common/endpoint/types';
import * as selectors from '../../store/selectors';
@@ -308,7 +314,7 @@ export const RelatedEventDetail = memo(function RelatedEventDetail({
return (
<>
-
+
@@ -321,11 +327,13 @@ export const RelatedEventDetail = memo(function RelatedEventDetail({
defaultMessage="{category} {eventType}"
/>
-
+
+
+
@@ -340,14 +348,15 @@ export const RelatedEventDetail = memo(function RelatedEventDetail({
return (
{index === 0 ? null : }
-
-
+
+
{sectionTitle}
+
-
+
+
+
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx
index be0ba04c53233..374c4c94c7768 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx
@@ -1,114 +1,122 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { i18n } from '@kbn/i18n';
-import { EuiBreadcrumbs, EuiBreadcrumb, EuiCode, EuiBetaBadge } from '@elastic/eui';
-import styled from 'styled-components';
-import React, { memo } from 'react';
-import { useResolverTheme } from '../assets';
-
-/**
- * A bold version of EuiCode to display certain titles with
- */
-export const BoldCode = styled(EuiCode)`
- &.euiCodeBlock code.euiCodeBlock__code {
- font-weight: 900;
- }
-`;
-
-const BetaHeader = styled(`header`)`
- margin-bottom: 1em;
-`;
-
-/**
- * The two query parameters we read/write on to control which view the table presents:
- */
-export interface CrumbInfo {
- readonly crumbId: string;
- readonly crumbEvent: string;
-}
-
-const ThemedBreadcrumbs = styled(EuiBreadcrumbs)<{ background: string; text: string }>`
- &.euiBreadcrumbs.euiBreadcrumbs--responsive {
- background-color: ${(props) => props.background};
- color: ${(props) => props.text};
- padding: 1em;
- border-radius: 5px;
- }
-
- & .euiBreadcrumbSeparator {
- background: ${(props) => props.text};
- }
-`;
-
-const betaBadgeLabel = i18n.translate(
- 'xpack.securitySolution.enpdoint.resolver.panelutils.betaBadgeLabel',
- {
- defaultMessage: 'BETA',
- }
-);
-
-/**
- * Breadcrumb menu with adjustments per direction from UX team
- */
-export const StyledBreadcrumbs = memo(function StyledBreadcrumbs({
- breadcrumbs,
- truncate,
-}: {
- breadcrumbs: EuiBreadcrumb[];
- truncate?: boolean;
-}) {
- const {
- colorMap: { resolverBreadcrumbBackground, resolverEdgeText },
- } = useResolverTheme();
- return (
- <>
-
-
-
-
- >
- );
-});
-
-/**
- * Long formatter (to second) for DateTime
- */
-export const formatter = new Intl.DateTimeFormat(i18n.getLocale(), {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
-});
-
-const invalidDateText = i18n.translate(
- 'xpack.securitySolution.enpdoint.resolver.panelutils.invaliddate',
- {
- defaultMessage: 'Invalid Date',
- }
-);
-/**
- * @returns {string} A nicely formatted string for a date
- */
-export function formatDate(
- /** To be passed through Date->Intl.DateTimeFormat */ timestamp: ConstructorParameters<
- typeof Date
- >[0]
-): string {
- const date = new Date(timestamp);
- if (isFinite(date.getTime())) {
- return formatter.format(date);
- } else {
- return invalidDateText;
- }
-}
+/*
+ * 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';
+import { EuiBreadcrumbs, EuiCode, EuiBetaBadge } from '@elastic/eui';
+import styled from 'styled-components';
+import React, { memo } from 'react';
+import { useResolverTheme } from '../assets';
+
+/**
+ * A bold version of EuiCode to display certain titles with
+ */
+export const BoldCode = styled(EuiCode)`
+ &.euiCodeBlock code.euiCodeBlock__code {
+ font-weight: 900;
+ }
+`;
+
+const BetaHeader = styled(`header`)`
+ margin-bottom: 1em;
+`;
+
+/**
+ * The two query parameters we read/write on to control which view the table presents:
+ */
+export interface CrumbInfo {
+ readonly crumbId: string;
+ readonly crumbEvent: string;
+}
+
+const ThemedBreadcrumbs = styled(EuiBreadcrumbs)<{ background: string; text: string }>`
+ &.euiBreadcrumbs.euiBreadcrumbs--responsive {
+ background-color: ${(props) => props.background};
+ color: ${(props) => props.text};
+ padding: 1em;
+ border-radius: 5px;
+ }
+
+ & .euiBreadcrumbSeparator {
+ background: ${(props) => props.text};
+ }
+`;
+
+const betaBadgeLabel = i18n.translate(
+ 'xpack.securitySolution.enpdoint.resolver.panelutils.betaBadgeLabel',
+ {
+ defaultMessage: 'BETA',
+ }
+);
+
+/**
+ * A component to keep time representations in blocks so they don't wrap
+ * and look bad.
+ */
+export const StyledTime = memo(styled('time')`
+ display: inline-block;
+ text-align: start;
+`);
+
+type Breadcrumbs = Parameters[0]['breadcrumbs'];
+/**
+ * Breadcrumb menu with adjustments per direction from UX team
+ */
+export const StyledBreadcrumbs = memo(function StyledBreadcrumbs({
+ breadcrumbs,
+}: {
+ breadcrumbs: Breadcrumbs;
+}) {
+ const {
+ colorMap: { resolverBreadcrumbBackground, resolverEdgeText },
+ } = useResolverTheme();
+ return (
+ <>
+
+
+
+
+ >
+ );
+});
+
+/**
+ * Long formatter (to second) for DateTime
+ */
+export const formatter = new Intl.DateTimeFormat(i18n.getLocale(), {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+});
+
+const invalidDateText = i18n.translate(
+ 'xpack.securitySolution.enpdoint.resolver.panelutils.invaliddate',
+ {
+ defaultMessage: 'Invalid Date',
+ }
+);
+/**
+ * @returns {string} A nicely formatted string for a date
+ */
+export function formatDate(
+ /** To be passed through Date->Intl.DateTimeFormat */ timestamp: ConstructorParameters<
+ typeof Date
+ >[0]
+): string {
+ const date = new Date(timestamp);
+ if (isFinite(date.getTime())) {
+ return formatter.format(date);
+ } else {
+ return invalidDateText;
+ }
+}
diff --git a/x-pack/plugins/security_solution/public/resolver/view/styles.tsx b/x-pack/plugins/security_solution/public/resolver/view/styles.tsx
index 2a1e67f4a9fdc..4cdb29b283f1e 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/styles.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/styles.tsx
@@ -48,6 +48,8 @@ export const StyledPanel = styled(Panel)`
overflow: auto;
width: 25em;
max-width: 50%;
+ border-radius: 0;
+ border-top: none;
`;
/**
From ba195bad36aa3289a9e22c0b9c71bbfca8704e06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 13 Jul 2020 15:37:10 +0200
Subject: [PATCH 36/39] [Logs UI] Unskip log highlight api integration test
(#71058)
This unskips one log highlighting API test, which was skipped due to changes in the Elasticsearch highlighting behavior.
---
.../api_integration/apis/metrics_ui/log_entry_highlights.ts | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts
index 823c8159a136d..4e6da9d50dc2a 100644
--- a/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts
+++ b/x-pack/test/api_integration/apis/metrics_ui/log_entry_highlights.ts
@@ -122,9 +122,7 @@ export default function ({ getService }: FtrProviderContext) {
});
});
- // Skipped since it behaves differently in master and in the 7.X branch
- // See https://github.com/elastic/kibana/issues/49959
- it.skip('highlights field columns', async () => {
+ it('highlights field columns', async () => {
const { body } = await supertest
.post(LOG_ENTRIES_HIGHLIGHTS_PATH)
.set(COMMON_HEADERS)
From 4b9902987f6f936f20db946ae9f78032d60c0eca Mon Sep 17 00:00:00 2001
From: Jorge Sanz
Date: Mon, 13 Jul 2020 15:46:07 +0200
Subject: [PATCH 37/39] [Maps] Inclusive language (#71427)
---
docs/maps/connect-to-ems.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc
index 2b88ffe2e2dda..45ced2e64aa73 100644
--- a/docs/maps/connect-to-ems.asciidoc
+++ b/docs/maps/connect-to-ems.asciidoc
@@ -19,7 +19,7 @@ Maps makes requests directly from the browser to EMS.
To connect to EMS when your Kibana server and browser are in an internal network:
. Set `map.proxyElasticMapsServiceInMaps` to `true` in your <> file to proxy EMS requests through the Kibana server.
-. Update your firewall rules to whitelist connections from your Kibana server to the EMS domains.
+. Update your firewall rules to allow connections from your Kibana server to the EMS domains.
NOTE: Coordinate map and region map visualizations do not support `map.proxyElasticMapsServiceInMaps` and will not proxy EMS requests through the Kibana server.
From 4bdd31e9c92c822fb3270cecd4465c4e7230028e Mon Sep 17 00:00:00 2001
From: Phillip Burch
Date: Mon, 13 Jul 2020 08:57:42 -0500
Subject: [PATCH 38/39] [Metrics + Logs UI] Add test for logs and metrics
telemetry (#70858)
* Add test for logs and metrics telemetry
* wait before you go
* Remove kubenetes
* Fix type check
* Add back kubernetes test
* Remove kubernetes
Co-authored-by: Elastic Machine
---
.../components/dropdown_button.tsx | 5 +-
.../waffle/waffle_inventory_switcher.tsx | 4 ++
.../test/functional/apps/infra/home_page.ts | 54 +++++++++++++++++++
.../apps/infra/logs_source_configuration.ts | 35 ++++++++++++
.../page_objects/infra_home_page.ts | 23 ++++++++
5 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/dropdown_button.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/dropdown_button.tsx
index 6e3ebee2dcb4b..62b25d5a36870 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/dropdown_button.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/dropdown_button.tsx
@@ -9,13 +9,15 @@ import React, { ReactNode } from 'react';
import { withTheme, EuiTheme } from '../../../../../../observability/public';
interface Props {
+ 'data-test-subj'?: string;
label: string;
onClick: () => void;
theme: EuiTheme | undefined;
children: ReactNode;
}
-export const DropdownButton = withTheme(({ onClick, label, theme, children }: Props) => {
+export const DropdownButton = withTheme((props: Props) => {
+ const { onClick, label, theme, children } = props;
return (
{
id: 'firstPanel',
items: [
{
+ 'data-test-subj': 'goToHost',
name: getDisplayNameForType('host'),
onClick: goToHost,
},
{
+ 'data-test-subj': 'goToPods',
name: getDisplayNameForType('pod'),
onClick: goToK8,
},
{
+ 'data-test-subj': 'goToDocker',
name: getDisplayNameForType('container'),
onClick: goToDocker,
},
@@ -117,6 +120,7 @@ export const WaffleInventorySwitcher: React.FC = () => {
const button = (
diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts
index 28279d5e5b812..04f289b69bb71 100644
--- a/x-pack/test/functional/apps/infra/home_page.ts
+++ b/x-pack/test/functional/apps/infra/home_page.ts
@@ -4,15 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import moment from 'moment';
+import expect from '@kbn/expect/expect.js';
import { FtrProviderContext } from '../../ftr_provider_context';
import { DATES } from './constants';
const DATE_WITH_DATA = DATES.metricsAndLogs.hosts.withData;
const DATE_WITHOUT_DATA = DATES.metricsAndLogs.hosts.withoutData;
+const COMMON_REQUEST_HEADERS = {
+ 'kbn-xsrf': 'some-xsrf-token',
+};
+
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const pageObjects = getPageObjects(['common', 'infraHome']);
+ const supertest = getService('supertest');
describe('Home page', function () {
this.tags('includeFirefox');
@@ -46,6 +53,53 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.infraHome.goToTime(DATE_WITHOUT_DATA);
await pageObjects.infraHome.getNoMetricsDataPrompt();
});
+
+ it('records telemetry for hosts', async () => {
+ await pageObjects.infraHome.goToTime(DATE_WITH_DATA);
+ await pageObjects.infraHome.getWaffleMap();
+
+ const resp = await supertest
+ .post(`/api/telemetry/v2/clusters/_stats`)
+ .set(COMMON_REQUEST_HEADERS)
+ .set('Accept', 'application/json')
+ .send({
+ timeRange: {
+ min: moment().subtract(1, 'hour').toISOString(),
+ max: moment().toISOString(),
+ },
+ unencrypted: true,
+ })
+ .expect(200)
+ .then((res: any) => res.body);
+
+ expect(
+ resp[0].stack_stats.kibana.plugins.infraops.last_24_hours.hits.infraops_hosts
+ ).to.be.greaterThan(0);
+ });
+
+ it('records telemetry for docker', async () => {
+ await pageObjects.infraHome.goToTime(DATE_WITH_DATA);
+ await pageObjects.infraHome.getWaffleMap();
+ await pageObjects.infraHome.goToDocker();
+
+ const resp = await supertest
+ .post(`/api/telemetry/v2/clusters/_stats`)
+ .set(COMMON_REQUEST_HEADERS)
+ .set('Accept', 'application/json')
+ .send({
+ timeRange: {
+ min: moment().subtract(1, 'hour').toISOString(),
+ max: moment().toISOString(),
+ },
+ unencrypted: true,
+ })
+ .expect(200)
+ .then((res: any) => res.body);
+
+ expect(
+ resp[0].stack_stats.kibana.plugins.infraops.last_24_hours.hits.infraops_docker
+ ).to.be.greaterThan(0);
+ });
});
});
};
diff --git a/x-pack/test/functional/apps/infra/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs_source_configuration.ts
index 7ec06e74289c9..04ffcc4847d54 100644
--- a/x-pack/test/functional/apps/infra/logs_source_configuration.ts
+++ b/x-pack/test/functional/apps/infra/logs_source_configuration.ts
@@ -5,16 +5,22 @@
*/
import expect from '@kbn/expect';
+import moment from 'moment';
import { DATES } from './constants';
import { FtrProviderContext } from '../../ftr_provider_context';
+const COMMON_REQUEST_HEADERS = {
+ 'kbn-xsrf': 'some-xsrf-token',
+};
+
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const logsUi = getService('logsUi');
const infraSourceConfigurationForm = getService('infraSourceConfigurationForm');
const pageObjects = getPageObjects(['common', 'infraLogs']);
const retry = getService('retry');
+ const supertest = getService('supertest');
describe('Logs Source Configuration', function () {
before(async () => {
@@ -97,6 +103,35 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(logStreamEntryColumns).to.have.length(3);
});
+ it('records telemetry for logs', async () => {
+ await logsUi.logStreamPage.navigateTo({
+ logPosition: {
+ start: DATES.metricsAndLogs.stream.startWithData,
+ end: DATES.metricsAndLogs.stream.endWithData,
+ },
+ });
+
+ await logsUi.logStreamPage.getStreamEntries();
+
+ const resp = await supertest
+ .post(`/api/telemetry/v2/clusters/_stats`)
+ .set(COMMON_REQUEST_HEADERS)
+ .set('Accept', 'application/json')
+ .send({
+ timeRange: {
+ min: moment().subtract(1, 'hour').toISOString(),
+ max: moment().toISOString(),
+ },
+ unencrypted: true,
+ })
+ .expect(200)
+ .then((res: any) => res.body);
+
+ expect(
+ resp[0].stack_stats.kibana.plugins.infraops.last_24_hours.hits.logs
+ ).to.be.greaterThan(0);
+ });
+
it('can change the log columns', async () => {
await pageObjects.infraLogs.navigateToTab('settings');
diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts
index 51dad594f21f5..ef6d2dc02eb80 100644
--- a/x-pack/test/functional/page_objects/infra_home_page.ts
+++ b/x-pack/test/functional/page_objects/infra_home_page.ts
@@ -33,6 +33,29 @@ export function InfraHomePageProvider({ getService }: FtrProviderContext) {
return await testSubjects.find('waffleMap');
},
+ async openInvenotrySwitcher() {
+ await testSubjects.click('openInventorySwitcher');
+ return await testSubjects.find('goToHost');
+ },
+
+ async goToHost() {
+ await testSubjects.click('openInventorySwitcher');
+ await testSubjects.find('goToHost');
+ return await testSubjects.click('goToHost');
+ },
+
+ async goToPods() {
+ await testSubjects.click('openInventorySwitcher');
+ await testSubjects.find('goToHost');
+ return await testSubjects.click('goToPods');
+ },
+
+ async goToDocker() {
+ await testSubjects.click('openInventorySwitcher');
+ await testSubjects.find('goToHost');
+ return await testSubjects.click('goToDocker');
+ },
+
async goToMetricExplorer() {
return await testSubjects.click('infrastructureNavLink_/infrastructure/metrics-explorer');
},
From ddd3a9defd35b0a932bfdd7cf6b1a4a6157ca4ae Mon Sep 17 00:00:00 2001
From: MadameSheema
Date: Mon, 13 Jul 2020 16:03:54 +0200
Subject: [PATCH 39/39] [SIEM][Detections] Fixes index patterns order (#71270)
* fixes alphabetical order for index patterns
* fixes cypress tests adding the new index pattern
* fixes jest tests
* fixes jest tests
Co-authored-by: Elastic Machine
---
x-pack/plugins/security_solution/common/constants.ts | 2 +-
.../integration/alerts_detection_rules_custom.spec.ts | 1 +
.../__snapshots__/drag_drop_context_wrapper.test.tsx.snap | 2 +-
.../__snapshots__/event_details.test.tsx.snap | 4 ++--
.../public/common/containers/source/index.test.tsx | 4 ++--
.../detection_engine/rules/fetch_index_patterns.test.tsx | 8 ++++----
.../overview/components/overview_host/index.test.tsx | 2 +-
.../overview/components/overview_network/index.test.tsx | 2 +-
.../timeline/__snapshots__/timeline.test.tsx.snap | 2 +-
.../body/column_headers/__snapshots__/index.test.tsx.snap | 2 +-
.../__snapshots__/suricata_row_renderer.test.tsx.snap | 2 +-
.../zeek/__snapshots__/zeek_details.test.tsx.snap | 2 +-
.../zeek/__snapshots__/zeek_row_renderer.test.tsx.snap | 2 +-
13 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts
index 7cd5692176ee3..4e9514feec74f 100644
--- a/x-pack/plugins/security_solution/common/constants.ts
+++ b/x-pack/plugins/security_solution/common/constants.ts
@@ -59,9 +59,9 @@ export const DEFAULT_INDEX_PATTERN = [
'auditbeat-*',
'endgame-*',
'filebeat-*',
+ 'logs-*',
'packetbeat-*',
'winlogbeat-*',
- 'logs-*',
];
/** This Kibana Advanced Setting enables the `Security news` feed widget */
diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts
index 81832b3d9edea..a51ad4388c428 100644
--- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts
@@ -131,6 +131,7 @@ describe.skip('Detection rules, custom', () => {
'auditbeat-*',
'endgame-*',
'filebeat-*',
+ 'logs-*',
'packetbeat-*',
'winlogbeat-*',
];
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap
index 0c96d0320d198..16f095e5effbb 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap
@@ -369,9 +369,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] =
"auditbeat-*",
"endgame-*",
"filebeat-*",
+ "logs-*",
"packetbeat-*",
"winlogbeat-*",
- "logs-*",
],
"name": "event.end",
"searchable": true,
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap
index 408a4c74e930f..9ca9cd6cce389 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap
@@ -377,9 +377,9 @@ exports[`EventDetails rendering should match snapshot 1`] = `
"auditbeat-*",
"endgame-*",
"filebeat-*",
+ "logs-*",
"packetbeat-*",
"winlogbeat-*",
- "logs-*",
],
"name": "event.end",
"searchable": true,
@@ -1070,9 +1070,9 @@ In other use cases the message field can be used to concatenate different values
"auditbeat-*",
"endgame-*",
"filebeat-*",
+ "logs-*",
"packetbeat-*",
"winlogbeat-*",
- "logs-*",
],
"name": "event.end",
"searchable": true,
diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx
index b9daba9a40941..bfde17723aef4 100644
--- a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx
@@ -29,7 +29,7 @@ describe('Index Fields & Browser Fields', () => {
indexPattern: {
fields: [],
title:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,packetbeat-*,winlogbeat-*,logs-*',
+ 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
},
indicesExist: true,
loading: true,
@@ -59,7 +59,7 @@ describe('Index Fields & Browser Fields', () => {
indexPattern: {
fields: mockIndexFields,
title:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,packetbeat-*,winlogbeat-*,logs-*',
+ 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
},
loading: false,
errorMessage: null,
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx
index c282a204f19a5..0204a2980b9fc 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx
@@ -352,9 +352,9 @@ describe('useFetchIndexPatterns', () => {
'auditbeat-*',
'endgame-*',
'filebeat-*',
+ 'logs-*',
'packetbeat-*',
'winlogbeat-*',
- 'logs-*',
],
name: 'event.end',
searchable: true,
@@ -369,9 +369,9 @@ describe('useFetchIndexPatterns', () => {
'auditbeat-*',
'endgame-*',
'filebeat-*',
+ 'logs-*',
'packetbeat-*',
'winlogbeat-*',
- 'logs-*',
],
indicesExists: true,
indexPatterns: {
@@ -418,7 +418,7 @@ describe('useFetchIndexPatterns', () => {
{ name: 'event.end', searchable: true, type: 'date', aggregatable: true },
],
title:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,packetbeat-*,winlogbeat-*,logs-*',
+ 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
},
},
result.current[1],
@@ -450,9 +450,9 @@ describe('useFetchIndexPatterns', () => {
'auditbeat-*',
'endgame-*',
'filebeat-*',
+ 'logs-*',
'packetbeat-*',
'winlogbeat-*',
- 'logs-*',
],
indicesExists: false,
isLoading: false,
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx
index bb9fd73d2df8e..d019a480a8045 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx
@@ -58,9 +58,9 @@ const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [
'auditbeat-*',
'endgame-*',
'filebeat-*',
+ 'logs-*',
'packetbeat-*',
'winlogbeat-*',
- 'logs-*',
],
inspect: false,
},
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx
index 0f6fce1486ee7..c7f7c4f4af254 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx
@@ -73,9 +73,9 @@ const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [
'auditbeat-*',
'endgame-*',
'filebeat-*',
+ 'logs-*',
'packetbeat-*',
'winlogbeat-*',
- 'logs-*',
],
inspect: false,
},
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/__snapshots__/timeline.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/__snapshots__/timeline.test.tsx.snap
index e38f6ad022d78..3508e12cb1be1 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/__snapshots__/timeline.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/__snapshots__/timeline.test.tsx.snap
@@ -474,9 +474,9 @@ exports[`Timeline rendering renders correctly against snapshot 1`] = `
"auditbeat-*",
"endgame-*",
"filebeat-*",
+ "logs-*",
"packetbeat-*",
"winlogbeat-*",
- "logs-*",
],
"name": "event.end",
"searchable": true,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap
index 2436e71a89b86..a5610cabc1774 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap
@@ -379,9 +379,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = `
"auditbeat-*",
"endgame-*",
"filebeat-*",
+ "logs-*",
"packetbeat-*",
"winlogbeat-*",
- "logs-*",
],
"name": "event.end",
"searchable": true,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap
index cba4b9aa72a25..8672b542eb6c6 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap
@@ -371,9 +371,9 @@ exports[`suricata_row_renderer renders correctly against snapshot 1`] = `
"auditbeat-*",
"endgame-*",
"filebeat-*",
+ "logs-*",
"packetbeat-*",
"winlogbeat-*",
- "logs-*",
],
"name": "event.end",
"searchable": true,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap
index e1000637147a8..d13c3de00c780 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap
@@ -369,9 +369,9 @@ exports[`ZeekDetails rendering it renders the default ZeekDetails 1`] = `
"auditbeat-*",
"endgame-*",
"filebeat-*",
+ "logs-*",
"packetbeat-*",
"winlogbeat-*",
- "logs-*",
],
"name": "event.end",
"searchable": true,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap
index d4c80441e6037..b8f28026dfdb5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap
@@ -371,9 +371,9 @@ exports[`zeek_row_renderer renders correctly against snapshot 1`] = `
"auditbeat-*",
"endgame-*",
"filebeat-*",
+ "logs-*",
"packetbeat-*",
"winlogbeat-*",
- "logs-*",
],
"name": "event.end",
"searchable": true,