From 832d349930be1f8d4a7a47733f413b0ab75303e8 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Mon, 12 Jul 2021 14:13:42 +0200
Subject: [PATCH 01/84] [KibanaLegacy] Remove unused stuff and make things
async if it is easy (#104638)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* remove unused stuff and make things async if it is easy
* fix problems
* load bootstrap in monitoring
* load angular bootstrap for saved searches and in unit tests
* fix vis_type_table tests
* Update x-pack/plugins/monitoring/public/plugin.ts
Co-authored-by: Ester Martí Vilaseca
* Update x-pack/plugins/monitoring/public/plugin.ts
Co-authored-by: Ester Martí Vilaseca
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Stratoula Kalafateli
Co-authored-by: Ester Martí Vilaseca
---
.../doc_table/components/row_headers.test.js | 4 +
.../angular/doc_table/doc_table.test.js | 4 +
.../application/angular/get_inner_angular.ts | 4 +-
.../application/angular/helpers/index.ts | 1 +
.../angular/helpers}/promises.d.ts | 0
.../application/angular/helpers}/promises.js | 0
src/plugins/discover/public/plugin.tsx | 2 +
.../public/angular/angular_config.tsx | 7 +-
.../kibana_legacy/public/angular/index.ts | 2 -
src/plugins/kibana_legacy/public/index.ts | 1 -
src/plugins/kibana_legacy/public/mocks.ts | 1 +
.../kibana_legacy/public/notify/index.ts | 1 -
.../notify/toasts/TOAST_NOTIFICATIONS.md | 100 ------------------
.../public/notify/toasts/index.ts | 9 --
.../notify/toasts/toast_notifications.test.ts | 76 -------------
.../notify/toasts/toast_notifications.ts | 37 -------
src/plugins/kibana_legacy/public/plugin.ts | 8 ++
.../kibana_legacy/public/utils/index.ts | 1 -
.../kibana_legacy/public/utils/system_api.ts | 40 -------
src/plugins/timelion/public/plugin.ts | 5 +-
.../public/legacy/agg_table/agg_table.test.js | 6 +-
.../legacy/agg_table/agg_table_group.test.js | 6 +-
.../public/legacy/get_inner_angular.ts | 3 -
.../paginated_table/paginated_table.test.ts | 5 +
.../legacy/table_vis_controller.test.ts | 4 +
.../public/legacy/vis_controller.ts | 1 +
x-pack/plugins/graph/public/plugin.ts | 7 +-
x-pack/plugins/monitoring/public/plugin.ts | 5 +-
28 files changed, 54 insertions(+), 286 deletions(-)
rename src/plugins/{kibana_legacy/public/angular => discover/public/application/angular/helpers}/promises.d.ts (100%)
rename src/plugins/{kibana_legacy/public/angular => discover/public/application/angular/helpers}/promises.js (100%)
delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md
delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/index.ts
delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts
delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts
delete mode 100644 src/plugins/kibana_legacy/public/utils/system_api.ts
diff --git a/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js b/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js
index a087ac8697183..1a3b34c45d05e 100644
--- a/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js
+++ b/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js
@@ -19,6 +19,7 @@ import { setScopedHistory, setServices, setDocViewsRegistry } from '../../../../
import { coreMock } from '../../../../../../../core/public/mocks';
import { dataPluginMock } from '../../../../../../data/public/mocks';
import { navigationPluginMock } from '../../../../../../navigation/public/mocks';
+import { initAngularBootstrap } from '../../../../../../kibana_legacy/public/angular_bootstrap';
import { getInnerAngularModule } from '../../get_inner_angular';
import { createBrowserHistory } from 'history';
@@ -41,6 +42,9 @@ describe('Doc Table', () => {
// Stub out a minimal mapping of 4 fields
let mapping;
+ beforeAll(async () => {
+ await initAngularBootstrap();
+ });
beforeAll(() => setScopedHistory(createBrowserHistory()));
beforeEach(() => {
angular.element.prototype.slice = jest.fn(function (index) {
diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js b/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js
index 1db35ddf18089..097f32965b141 100644
--- a/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js
+++ b/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js
@@ -17,6 +17,7 @@ import hits from '../../../__fixtures__/real_hits';
import { coreMock } from '../../../../../../core/public/mocks';
import { dataPluginMock } from '../../../../../data/public/mocks';
import { navigationPluginMock } from '../../../../../navigation/public/mocks';
+import { initAngularBootstrap } from '../../../../../kibana_legacy/public/angular_bootstrap';
import { setScopedHistory, setServices } from '../../../kibana_services';
import { getInnerAngularModule } from '../get_inner_angular';
@@ -54,6 +55,9 @@ describe('docTable', () => {
const core = coreMock.createStart();
let $elem;
+ beforeAll(async () => {
+ await initAngularBootstrap();
+ });
beforeAll(() => setScopedHistory(createBrowserHistory()));
beforeEach(() => {
angular.element.prototype.slice = jest.fn(() => {
diff --git a/src/plugins/discover/public/application/angular/get_inner_angular.ts b/src/plugins/discover/public/application/angular/get_inner_angular.ts
index 26d64d5adc8a3..992d82795302b 100644
--- a/src/plugins/discover/public/application/angular/get_inner_angular.ts
+++ b/src/plugins/discover/public/application/angular/get_inner_angular.ts
@@ -33,13 +33,12 @@ import { createDocViewerDirective } from './doc_viewer';
import { createDiscoverGridDirective } from './create_discover_grid_directive';
import { createRenderCompleteDirective } from './directives/render_complete';
import {
- initAngularBootstrap,
configureAppAngularModule,
PrivateProvider,
- PromiseServiceCreator,
registerListenEventListener,
watchMultiDecorator,
} from '../../../../kibana_legacy/public';
+import { PromiseServiceCreator } from './helpers';
import { DiscoverStartPlugins } from '../../plugin';
import { getScopedHistory } from '../../kibana_services';
import { createDiscoverDirective } from './create_discover_directive';
@@ -54,7 +53,6 @@ export function getInnerAngularModule(
deps: DiscoverStartPlugins,
context: PluginInitializerContext
) {
- initAngularBootstrap();
const module = initializeInnerAngularModule(name, core, deps.navigation, deps.data);
configureAppAngularModule(module, { core, env: context.env }, true, getScopedHistory);
return module;
diff --git a/src/plugins/discover/public/application/angular/helpers/index.ts b/src/plugins/discover/public/application/angular/helpers/index.ts
index 3d2c0b1c63b33..6a7f75b7e81a2 100644
--- a/src/plugins/discover/public/application/angular/helpers/index.ts
+++ b/src/plugins/discover/public/application/angular/helpers/index.ts
@@ -8,3 +8,4 @@
export { formatRow, formatTopLevelObject } from './row_formatter';
export { handleSourceColumnState } from './state_helpers';
+export { PromiseServiceCreator } from './promises';
diff --git a/src/plugins/kibana_legacy/public/angular/promises.d.ts b/src/plugins/discover/public/application/angular/helpers/promises.d.ts
similarity index 100%
rename from src/plugins/kibana_legacy/public/angular/promises.d.ts
rename to src/plugins/discover/public/application/angular/helpers/promises.d.ts
diff --git a/src/plugins/kibana_legacy/public/angular/promises.js b/src/plugins/discover/public/application/angular/helpers/promises.js
similarity index 100%
rename from src/plugins/kibana_legacy/public/angular/promises.js
rename to src/plugins/discover/public/application/angular/helpers/promises.js
diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx
index 3e31fe1d46d45..1e8a5cdac95ef 100644
--- a/src/plugins/discover/public/plugin.tsx
+++ b/src/plugins/discover/public/plugin.tsx
@@ -403,6 +403,7 @@ export class DiscoverPlugin
}
// this is used by application mount and tests
const { getInnerAngularModule } = await import('./application/angular/get_inner_angular');
+ await plugins.kibanaLegacy.loadAngularBootstrap();
const module = getInnerAngularModule(
innerAngularName,
core,
@@ -473,6 +474,7 @@ export class DiscoverPlugin
throw Error('Discover plugin getEmbeddableInjector: initializeServices is undefined');
}
const { core, plugins } = await this.initializeServices();
+ await getServices().kibanaLegacy.loadAngularBootstrap();
getServices().kibanaLegacy.loadFontAwesome();
const { getInnerAngularModuleEmbeddable } = await import(
'./application/angular/get_inner_angular'
diff --git a/src/plugins/kibana_legacy/public/angular/angular_config.tsx b/src/plugins/kibana_legacy/public/angular/angular_config.tsx
index daecfbc57ea99..48ee6d2db269e 100644
--- a/src/plugins/kibana_legacy/public/angular/angular_config.tsx
+++ b/src/plugins/kibana_legacy/public/angular/angular_config.tsx
@@ -13,6 +13,7 @@ import {
ILocationProvider,
IModule,
IRootScopeService,
+ IRequestConfig,
} from 'angular';
import $ from 'jquery';
import { set } from '@elastic/safer-lodash-set';
@@ -22,7 +23,6 @@ import { ChromeBreadcrumb, EnvironmentMode, PackageInfo } from 'kibana/public';
import { History } from 'history';
import { CoreStart } from 'kibana/public';
-import { isSystemApiRequest } from '../utils';
import { formatAngularHttpError, isAngularHttpError } from '../notify/lib';
export interface RouteConfiguration {
@@ -38,6 +38,11 @@ export interface RouteConfiguration {
requireUICapability?: string;
}
+function isSystemApiRequest(request: IRequestConfig) {
+ const { headers } = request;
+ return headers && !!headers['kbn-system-request'];
+}
+
/**
* Detects whether a given angular route is a dummy route that doesn't
* require any action. There are two ways this can happen:
diff --git a/src/plugins/kibana_legacy/public/angular/index.ts b/src/plugins/kibana_legacy/public/angular/index.ts
index d9d8c0c19eb7b..369495698591d 100644
--- a/src/plugins/kibana_legacy/public/angular/index.ts
+++ b/src/plugins/kibana_legacy/public/angular/index.ts
@@ -6,8 +6,6 @@
* Side Public License, v 1.
*/
-// @ts-ignore
-export { PromiseServiceCreator } from './promises';
// @ts-ignore
export { watchMultiDecorator } from './watch_multi';
export * from './angular_config';
diff --git a/src/plugins/kibana_legacy/public/index.ts b/src/plugins/kibana_legacy/public/index.ts
index 03adb768cde20..ea5172f78a68f 100644
--- a/src/plugins/kibana_legacy/public/index.ts
+++ b/src/plugins/kibana_legacy/public/index.ts
@@ -14,7 +14,6 @@ export const plugin = (initializerContext: PluginInitializerContext) =>
export * from './plugin';
-export { initAngularBootstrap } from './angular_bootstrap';
export { PaginateDirectiveProvider, PaginateControlsDirectiveProvider } from './paginate/paginate';
export * from './angular';
export * from './notify';
diff --git a/src/plugins/kibana_legacy/public/mocks.ts b/src/plugins/kibana_legacy/public/mocks.ts
index 40834635cc570..6116c0682cb3b 100644
--- a/src/plugins/kibana_legacy/public/mocks.ts
+++ b/src/plugins/kibana_legacy/public/mocks.ts
@@ -22,6 +22,7 @@ const createStartContract = (): Start => ({
getHideWriteControls: jest.fn(),
},
loadFontAwesome: jest.fn(),
+ loadAngularBootstrap: jest.fn(),
});
export const kibanaLegacyPluginMock = {
diff --git a/src/plugins/kibana_legacy/public/notify/index.ts b/src/plugins/kibana_legacy/public/notify/index.ts
index a243059cb1918..d4dcaa77cc47a 100644
--- a/src/plugins/kibana_legacy/public/notify/index.ts
+++ b/src/plugins/kibana_legacy/public/notify/index.ts
@@ -6,5 +6,4 @@
* Side Public License, v 1.
*/
-export * from './toasts';
export * from './lib';
diff --git a/src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md b/src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md
deleted file mode 100644
index de6a51f3927d1..0000000000000
--- a/src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# Toast notifications
-
-Use this service to surface toasts in the bottom-right corner of the screen. After a brief delay, they'll disappear. They're useful for notifying the user of state changes. See [the EUI docs](https://elastic.github.io/eui/) for more information on toasts and their role within the UI.
-
-## Importing the module
-
-```js
-import { toastNotifications } from 'ui/notify';
-```
-
-## Interface
-
-### Adding toasts
-
-For convenience, there are several methods which predefine the appearance of different types of toasts. Use these methods so that the same types of toasts look similar to the user.
-
-#### Default
-
-Neutral toast. Tell the user a change in state has occurred, which is not necessarily good or bad.
-
-```js
-toastNotifications.add('Copied to clipboard');
-```
-
-#### Success
-
-Let the user know that an action was successful, such as saving or deleting an object.
-
-```js
-toastNotifications.addSuccess('Your document was saved');
-```
-
-#### Warning
-
-If something OK or good happened, but perhaps wasn't perfect, show a warning toast.
-
-```js
-toastNotifications.addWarning('Your document was saved, but not its edit history');
-```
-
-#### Danger
-
-When the user initiated an action but the action failed, show them a danger toast.
-
-```js
-toastNotifications.addDanger('An error caused your document to be lost');
-```
-
-### Removing a toast
-
-Toasts will automatically be dismissed after a brief delay, but if for some reason you want to dismiss a toast, you can use the returned toast from one of the `add` methods and then pass it to `remove`.
-
-```js
-const toast = toastNotifications.add('Your document was saved');
-toastNotifications.remove(toast);
-```
-
-### Configuration options
-
-If you want to configure the toast further you can provide an object instead of a string. The properties of this object correspond to the `propTypes` accepted by the `EuiToast` component. Refer to [the EUI docs](https://elastic.github.io/eui/) for info on these `propTypes`.
-
-```js
-toastNotifications.add({
- title: 'Your document was saved',
- text: 'Only you have access to this document',
- color: 'success',
- iconType: 'check',
- 'data-test-subj': 'saveDocumentSuccess',
-});
-```
-
-Because the underlying components are React, you can use JSX to pass in React elements to the `text` prop. This gives you total flexibility over the content displayed within the toast.
-
-```js
-toastNotifications.add({
- title: 'Your document was saved',
- text: (
-
-
- Only you have access to this document. Edit permissions.
-
-
-
-
- ),
-});
-```
-
-## Use in functional tests
-
-Functional tests are commonly used to verify that a user action yielded a successful outcome. If you surface a toast to notify the user of this successful outcome, you can place a `data-test-subj` attribute on the toast and use it to check if the toast exists inside of your functional test. This acts as a proxy for verifying the successful outcome.
-
-```js
-toastNotifications.addSuccess({
- title: 'Your document was saved',
- 'data-test-subj': 'saveDocumentSuccess',
-});
-```
diff --git a/src/plugins/kibana_legacy/public/notify/toasts/index.ts b/src/plugins/kibana_legacy/public/notify/toasts/index.ts
deleted file mode 100644
index cdd7df04548fb..0000000000000
--- a/src/plugins/kibana_legacy/public/notify/toasts/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-export { ToastNotifications } from './toast_notifications';
diff --git a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts b/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts
deleted file mode 100644
index c2c5d9a4fc014..0000000000000
--- a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { notificationServiceMock } from '../../../../../core/public/mocks';
-import { ToastNotifications } from './toast_notifications';
-import { Toast } from 'kibana/public';
-import { BehaviorSubject } from 'rxjs';
-
-describe('ToastNotifications', () => {
- describe('interface', () => {
- function setup() {
- const toastsMock = notificationServiceMock.createStartContract().toasts;
- return { toastNotifications: new ToastNotifications(toastsMock), toastsMock };
- }
-
- describe('add method', () => {
- test('adds a toast', () => {
- const { toastNotifications, toastsMock } = setup();
- toastNotifications.add({});
- expect(toastsMock.add).toHaveBeenCalled();
- });
- });
-
- describe('remove method', () => {
- test('removes a toast', () => {
- const { toastNotifications, toastsMock } = setup();
- const fakeToast = {} as Toast;
- toastNotifications.remove(fakeToast);
- expect(toastsMock.remove).toHaveBeenCalledWith(fakeToast);
- });
- });
-
- describe('onChange method', () => {
- test('callback is called when observable changes', () => {
- const toastsMock = notificationServiceMock.createStartContract().toasts;
- const toasts$ = new BehaviorSubject([]);
- toastsMock.get$.mockReturnValue(toasts$);
- const toastNotifications = new ToastNotifications(toastsMock);
- const onChangeSpy = jest.fn();
- toastNotifications.onChange(onChangeSpy);
- toasts$.next([{ id: 'toast1' }]);
- toasts$.next([]);
- expect(onChangeSpy).toHaveBeenCalledTimes(2);
- });
- });
-
- describe('addSuccess method', () => {
- test('adds a success toast', () => {
- const { toastNotifications, toastsMock } = setup();
- toastNotifications.addSuccess({});
- expect(toastsMock.addSuccess).toHaveBeenCalled();
- });
- });
-
- describe('addWarning method', () => {
- test('adds a warning toast', () => {
- const { toastNotifications, toastsMock } = setup();
- toastNotifications.addWarning({});
- expect(toastsMock.addWarning).toHaveBeenCalled();
- });
- });
-
- describe('addDanger method', () => {
- test('adds a danger toast', () => {
- const { toastNotifications, toastsMock } = setup();
- toastNotifications.addWarning({});
- expect(toastsMock.addWarning).toHaveBeenCalled();
- });
- });
- });
-});
diff --git a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts b/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts
deleted file mode 100644
index e7ccbbca07b73..0000000000000
--- a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { NotificationsSetup, Toast, ToastInput, ErrorToastOptions } from 'kibana/public';
-
-export class ToastNotifications {
- public list: Toast[] = [];
-
- private onChangeCallback?: () => void;
-
- constructor(private readonly toasts: NotificationsSetup['toasts']) {
- toasts.get$().subscribe((list) => {
- this.list = list;
-
- if (this.onChangeCallback) {
- this.onChangeCallback();
- }
- });
- }
-
- public onChange = (callback: () => void) => {
- this.onChangeCallback = callback;
- };
-
- public add = (toastOrTitle: ToastInput) => this.toasts.add(toastOrTitle);
- public remove = (toast: Toast) => this.toasts.remove(toast);
- public addSuccess = (toastOrTitle: ToastInput) => this.toasts.addSuccess(toastOrTitle);
- public addWarning = (toastOrTitle: ToastInput) => this.toasts.addWarning(toastOrTitle);
- public addDanger = (toastOrTitle: ToastInput) => this.toasts.addDanger(toastOrTitle);
- public addError = (error: Error, options: ErrorToastOptions) =>
- this.toasts.addError(error, options);
-}
diff --git a/src/plugins/kibana_legacy/public/plugin.ts b/src/plugins/kibana_legacy/public/plugin.ts
index 337fdb80da7e4..f60130d367b58 100644
--- a/src/plugins/kibana_legacy/public/plugin.ts
+++ b/src/plugins/kibana_legacy/public/plugin.ts
@@ -33,6 +33,14 @@ export class KibanaLegacyPlugin {
loadFontAwesome: async () => {
await import('./font_awesome');
},
+ /**
+ * Loads angular bootstrap modules. Should be removed once the last consumer has migrated to EUI
+ * @deprecated
+ */
+ loadAngularBootstrap: async () => {
+ const { initAngularBootstrap } = await import('./angular_bootstrap');
+ initAngularBootstrap();
+ },
/**
* @deprecated
* Just exported for wiring up with dashboard mode, should not be used.
diff --git a/src/plugins/kibana_legacy/public/utils/index.ts b/src/plugins/kibana_legacy/public/utils/index.ts
index db3c0af6c8cb9..94233558b4627 100644
--- a/src/plugins/kibana_legacy/public/utils/index.ts
+++ b/src/plugins/kibana_legacy/public/utils/index.ts
@@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
-export * from './system_api';
// @ts-ignore
export { KbnAccessibleClickProvider } from './kbn_accessible_click';
// @ts-ignore
diff --git a/src/plugins/kibana_legacy/public/utils/system_api.ts b/src/plugins/kibana_legacy/public/utils/system_api.ts
deleted file mode 100644
index d0fe221935ba5..0000000000000
--- a/src/plugins/kibana_legacy/public/utils/system_api.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { IRequestConfig } from 'angular';
-
-const SYSTEM_REQUEST_HEADER_NAME = 'kbn-system-request';
-const LEGACY_SYSTEM_API_HEADER_NAME = 'kbn-system-api';
-
-/**
- * Adds a custom header designating request as system API
- * @param originalHeaders Object representing set of headers
- * @return Object representing set of headers, with system API header added in
- */
-export function addSystemApiHeader(originalHeaders: Record) {
- const systemApiHeaders = {
- [SYSTEM_REQUEST_HEADER_NAME]: true,
- };
- return {
- ...originalHeaders,
- ...systemApiHeaders,
- };
-}
-
-/**
- * Returns true if request is a system API request; false otherwise
- *
- * @param request Object Request object created by $http service
- * @return true if request is a system API request; false otherwise
- */
-export function isSystemApiRequest(request: IRequestConfig) {
- const { headers } = request;
- return (
- headers && (!!headers[SYSTEM_REQUEST_HEADER_NAME] || !!headers[LEGACY_SYSTEM_API_HEADER_NAME])
- );
-}
diff --git a/src/plugins/timelion/public/plugin.ts b/src/plugins/timelion/public/plugin.ts
index 6f8dbfdcc6704..63ea9a38e2795 100644
--- a/src/plugins/timelion/public/plugin.ts
+++ b/src/plugins/timelion/public/plugin.ts
@@ -19,7 +19,7 @@ import {
AppNavLinkStatus,
} from '../../../core/public';
import { Panel } from './panels/panel';
-import { initAngularBootstrap } from '../../kibana_legacy/public';
+import { KibanaLegacyStart } from '../../kibana_legacy/public';
import { createKbnUrlTracker } from '../../kibana_utils/public';
import { DataPublicPluginStart, esFilters, DataPublicPluginSetup } from '../../data/public';
import { NavigationPublicPluginStart } from '../../navigation/public';
@@ -41,6 +41,7 @@ export interface TimelionPluginStartDependencies {
visualizations: VisualizationsStart;
visTypeTimelion: VisTypeTimelionPluginStart;
savedObjects: SavedObjectsStart;
+ kibanaLegacy: KibanaLegacyStart;
}
/** @internal */
@@ -91,7 +92,6 @@ export class TimelionPlugin
stopUrlTracker();
};
- initAngularBootstrap();
core.application.register({
id: 'timelion',
title: 'Timelion',
@@ -103,6 +103,7 @@ export class TimelionPlugin
visTypeTimelion.isUiEnabled === false ? AppNavLinkStatus.hidden : AppNavLinkStatus.default,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
+ await pluginsStart.kibanaLegacy.loadAngularBootstrap();
this.currentHistory = params.history;
appMounted();
diff --git a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js
index 65e26ddf6e03f..cbc3db6585a7d 100644
--- a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js
+++ b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js
@@ -15,7 +15,7 @@ import { round } from 'lodash';
import { getFieldFormatsRegistry } from '../../../../data/public/test_utils';
import { coreMock } from '../../../../../core/public/mocks';
-import { initAngularBootstrap } from '../../../../kibana_legacy/public';
+import { initAngularBootstrap } from '../../../../kibana_legacy/public/angular_bootstrap';
import { setUiSettings } from '../../../../data/public/services';
import { UI_SETTINGS } from '../../../../data/public/';
import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../../share/public';
@@ -60,10 +60,12 @@ describe('Table Vis - AggTable Directive', function () {
initTableVisLegacyModule(tableVisModule);
};
+ beforeAll(async () => {
+ await initAngularBootstrap();
+ });
beforeEach(() => {
setUiSettings(core.uiSettings);
setFormatService(getFieldFormatsRegistry(core));
- initAngularBootstrap();
initLocalAngular();
angular.mock.module('kibana/table_vis');
angular.mock.inject(($injector, config) => {
diff --git a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js
index 1c6630e30e5f7..ba04b2f449f6d 100644
--- a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js
+++ b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js
@@ -13,11 +13,11 @@ import expect from '@kbn/expect';
import { getFieldFormatsRegistry } from '../../../../data/public/test_utils';
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 { initAngularBootstrap } from '../../../../kibana_legacy/public/angular_bootstrap';
import { tabifiedData } from './tabified_data';
const uiSettings = new Map();
@@ -40,10 +40,12 @@ describe('Table Vis - AggTableGroup Directive', function () {
initTableVisLegacyModule(tableVisModule);
};
+ beforeAll(async () => {
+ await initAngularBootstrap();
+ });
beforeEach(() => {
setUiSettings(core.uiSettings);
setFormatService(getFieldFormatsRegistry(core));
- initAngularBootstrap();
initLocalAngular();
angular.mock.module('kibana/table_vis');
angular.mock.inject(($injector) => {
diff --git a/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts b/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts
index 09fde318ee4df..412dd904a5e87 100644
--- a/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts
+++ b/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts
@@ -16,7 +16,6 @@ import 'angular-recursion';
import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular';
import { CoreStart, IUiSettingsClient, PluginInitializerContext } from 'kibana/public';
import {
- initAngularBootstrap,
PaginateDirectiveProvider,
PaginateControlsDirectiveProvider,
PrivateProvider,
@@ -24,8 +23,6 @@ import {
KbnAccessibleClickProvider,
} from '../../../kibana_legacy/public';
-initAngularBootstrap();
-
const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap', 'RecursionHelper'];
export function getAngularModule(name: string, core: CoreStart, context: PluginInitializerContext) {
diff --git a/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts b/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts
index 77148803e7978..3feff52f86792 100644
--- a/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts
+++ b/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts
@@ -12,6 +12,7 @@ import $ from 'jquery';
import 'angular-sanitize';
import 'angular-mocks';
+import { initAngularBootstrap } from '../../../../kibana_legacy/public/angular_bootstrap';
import { getAngularModule } from '../get_inner_angular';
import { initTableVisLegacyModule } from '../table_vis_legacy_module';
import { coreMock } from '../../../../../core/public/mocks';
@@ -56,6 +57,10 @@ describe('Table Vis - Paginated table', () => {
const defaultPerPage = 10;
let paginatedTable: any;
+ beforeAll(async () => {
+ await initAngularBootstrap();
+ });
+
const initLocalAngular = () => {
const tableVisModule = getAngularModule(
'kibana/table_vis',
diff --git a/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts b/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts
index 36a9cc9cce77f..f4a742ea16cb4 100644
--- a/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts
+++ b/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts
@@ -13,6 +13,7 @@ import $ from 'jquery';
import { getAngularModule } from './get_inner_angular';
import { initTableVisLegacyModule } from './table_vis_legacy_module';
+import { initAngularBootstrap } from '../../../kibana_legacy/public/angular_bootstrap';
import { tableVisLegacyTypeDefinition } from './table_vis_legacy_type';
import { Vis } from '../../../visualizations/public';
import { stubFields } from '../../../data/public/stubs';
@@ -76,6 +77,9 @@ describe('Table Vis - Controller', () => {
initTableVisLegacyModule(tableVisModule);
};
+ beforeAll(async () => {
+ await initAngularBootstrap();
+ });
beforeEach(initLocalAngular);
beforeEach(angular.mock.module('kibana/table_vis'));
diff --git a/src/plugins/vis_type_table/public/legacy/vis_controller.ts b/src/plugins/vis_type_table/public/legacy/vis_controller.ts
index ee446c58c0013..ec198aa96f1f9 100644
--- a/src/plugins/vis_type_table/public/legacy/vis_controller.ts
+++ b/src/plugins/vis_type_table/public/legacy/vis_controller.ts
@@ -56,6 +56,7 @@ export function getTableVisualizationControllerClass(
async initLocalAngular() {
if (!this.tableVisModule) {
const [coreStart, { kibanaLegacy }] = await core.getStartServices();
+ await kibanaLegacy.loadAngularBootstrap();
this.tableVisModule = getAngularModule(innerAngularName, coreStart, context);
initTableVisLegacyModule(this.tableVisModule);
kibanaLegacy.loadFontAwesome();
diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts
index 4525b42b3feb4..ec19e639b91c9 100644
--- a/x-pack/plugins/graph/public/plugin.ts
+++ b/x-pack/plugins/graph/public/plugin.ts
@@ -19,10 +19,7 @@ import {
} from '../../../../src/core/public';
import { Storage } from '../../../../src/plugins/kibana_utils/public';
-import {
- initAngularBootstrap,
- KibanaLegacyStart,
-} from '../../../../src/plugins/kibana_legacy/public';
+import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public';
import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public';
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
@@ -77,7 +74,6 @@ export class GraphPlugin
const config = this.initializerContext.config.get();
- initAngularBootstrap();
core.application.register({
id: 'graph',
title: 'Graph',
@@ -88,6 +84,7 @@ export class GraphPlugin
updater$: this.appUpdater$,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
+ await pluginsStart.kibanaLegacy.loadAngularBootstrap();
coreStart.chrome.docTitle.change(
i18n.translate('xpack.graph.pageTitle', { defaultMessage: 'Graph' })
);
diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts
index a5b7d4906b586..9f84165a27ba9 100644
--- a/x-pack/plugins/monitoring/public/plugin.ts
+++ b/x-pack/plugins/monitoring/public/plugin.ts
@@ -93,7 +93,10 @@ export class MonitoringPlugin
category: DEFAULT_APP_CATEGORIES.management,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
- const { AngularApp } = await import('./angular');
+ const [, { AngularApp }] = await Promise.all([
+ pluginsStart.kibanaLegacy.loadAngularBootstrap(),
+ import('./angular'),
+ ]);
const deps: MonitoringStartPluginDependencies = {
navigation: pluginsStart.navigation,
kibanaLegacy: pluginsStart.kibanaLegacy,
From 87066e06b3cfe238b021f786896afdfd74b86303 Mon Sep 17 00:00:00 2001
From: Diana Derevyankina
<54894989+DziyanaDzeraviankina@users.noreply.github.com>
Date: Mon, 12 Jul 2021 17:25:52 +0300
Subject: [PATCH 02/84] [TSVB] Top_hit supports runtime fields (#103401)
* [TSVB] Refactor top-hit aggregation to work with fields instead of _source
* Allow select date strings for top_hit aggregation in table, metric, and markdown
* Fix agg_with handling for top_hit and add some tests
* Refactor get_agg_value and fix type check for _tsvb_chart
* Refactor top_hit.js
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../application/components/aggs/top_hit.js | 37 +++++--
.../components/lib/tick_formatter.js | 2 +-
.../lib/vis_data/helpers/bucket_transform.js | 2 +-
.../lib/vis_data/helpers/get_agg_value.js | 4 +-
.../vis_data/helpers/get_agg_value.test.js | 6 +-
test/functional/apps/visualize/_tsvb_chart.ts | 103 +++++++++++++++---
.../fixtures/kbn_archiver/visualize.json | 3 +-
.../page_objects/visual_builder_page.ts | 47 ++++++++
8 files changed, 168 insertions(+), 36 deletions(-)
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js
index 546c09cdf34fd..b9ef2d8913574 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { useMemo, useEffect } from 'react';
import { AggRow } from './agg_row';
import { AggSelect } from './agg_select';
import { FieldSelect } from './field_select';
@@ -62,6 +62,7 @@ const getAggWithOptions = (field = {}, fieldTypesRestriction) => {
},
];
case KBN_FIELD_TYPES.STRING:
+ case KBN_FIELD_TYPES.DATE:
return [
{
label: i18n.translate('visTypeTimeseries.topHit.aggWithOptions.concatenate', {
@@ -91,16 +92,18 @@ const getOrderOptions = () => [
},
];
+const AGG_WITH_KEY = 'agg_with';
const ORDER_DATE_RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE];
+const getModelDefaults = () => ({
+ size: 1,
+ order: 'desc',
+ [AGG_WITH_KEY]: 'noop',
+});
+
const TopHitAggUi = (props) => {
const { fields, series, panel } = props;
- const defaults = {
- size: 1,
- agg_with: 'noop',
- order: 'desc',
- };
- const model = { ...defaults, ...props.model };
+ const model = useMemo(() => ({ ...getModelDefaults(), ...props.model }), [props.model]);
const indexPattern = series.override_index_pattern
? series.series_index_pattern
: panel.index_pattern;
@@ -110,7 +113,7 @@ const TopHitAggUi = (props) => {
PANEL_TYPES.METRIC,
PANEL_TYPES.MARKDOWN,
].includes(panel.type)
- ? [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING]
+ ? [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING, KBN_FIELD_TYPES.DATE]
: [KBN_FIELD_TYPES.NUMBER];
const handleChange = createChangeHandler(props.onChange, model);
@@ -124,13 +127,23 @@ const TopHitAggUi = (props) => {
const htmlId = htmlIdGenerator();
const selectedAggWithOption = aggWithOptions.find((option) => {
- return model.agg_with === option.value;
+ return model[AGG_WITH_KEY] === option.value;
});
const selectedOrderOption = orderOptions.find((option) => {
return model.order === option.value;
});
+ useEffect(() => {
+ const defaultFn = aggWithOptions?.[0]?.value;
+ const aggWith = model[AGG_WITH_KEY];
+ if (aggWith && defaultFn && aggWith !== defaultFn && !selectedAggWithOption) {
+ handleChange({
+ [AGG_WITH_KEY]: defaultFn,
+ });
+ }
+ }, [model, selectedAggWithOption, aggWithOptions, handleChange]);
+
return (
{
{
)}
options={aggWithOptions}
selectedOptions={selectedAggWithOption ? [selectedAggWithOption] : []}
- onChange={handleSelectChange('agg_with')}
+ onChange={handleSelectChange(AGG_WITH_KEY)}
singleSelection={{ asPlainText: true }}
+ data-test-subj="topHitAggregateWithComboBox"
/>
@@ -231,6 +245,7 @@ const TopHitAggUi = (props) => {
onChange={handleSelectChange('order_by')}
indexPattern={indexPattern}
fields={fields}
+ data-test-subj="topHitOrderByFieldSelect"
/>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js
index c1d82a182e509..9bccc13d19269 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js
@@ -16,7 +16,7 @@ export const createTickFormatter = (format = '0,0.[00]', template, getConfig = n
const fieldFormats = getFieldFormats();
if (!template) template = '{{value}}';
- const render = handlebars.compile(template, { knownHelpersOnly: true });
+ const render = handlebars.compile(template, { noEscape: true, knownHelpersOnly: true });
let formatter;
if (isDuration(format)) {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js
index 16e7b9d6072cb..13b890189325c 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js
@@ -111,7 +111,7 @@ export const bucketTransform = {
docs: {
top_hits: {
size: bucket.size,
- _source: { includes: [bucket.field] },
+ fields: [bucket.field],
},
},
},
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js
index 32d17ef6d6cb7..90df3f2675959 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js
@@ -45,10 +45,10 @@ export const getAggValue = (row, metric) => {
}
const hits = get(row, [metric.id, 'docs', 'hits', 'hits'], []);
- const values = hits.map((doc) => get(doc, `_source.${metric.field}`));
+ const values = hits.map((doc) => doc.fields[metric.field]);
const aggWith = (metric.agg_with && aggFns[metric.agg_with]) || aggFns.noop;
- return aggWith(values);
+ return aggWith(values.flat());
case METRIC_TYPES.COUNT:
return get(row, 'doc_count', null);
default:
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js
index a23c57f567563..ecbdd1563c304 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js
@@ -67,11 +67,7 @@ describe('getAggValue', () => {
doc_count: 1,
docs: {
hits: {
- hits: [
- { _source: { example: { value: 25 } } },
- { _source: { example: { value: 25 } } },
- { _source: { example: { value: 25 } } },
- ],
+ hits: [{ fields: { 'example.value': [25, 25, 25] } }],
},
},
},
diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts
index ca310493960f5..49b2ad8f9646a 100644
--- a/test/functional/apps/visualize/_tsvb_chart.ts
+++ b/test/functional/apps/visualize/_tsvb_chart.ts
@@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
'timePicker',
'visChart',
'common',
+ 'settings',
]);
describe('visual builder', function describeIndexTests() {
@@ -44,14 +45,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
describe('metric', () => {
+ const { visualBuilder } = PageObjects;
+
beforeEach(async () => {
- await PageObjects.visualBuilder.resetPage();
- await PageObjects.visualBuilder.clickMetric();
- await PageObjects.visualBuilder.checkMetricTabIsPresent();
- await PageObjects.visualBuilder.clickPanelOptions('metric');
- await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
- await PageObjects.visualBuilder.setDropLastBucket(true);
- await PageObjects.visualBuilder.clickDataTab('metric');
+ await visualBuilder.resetPage();
+ await visualBuilder.clickMetric();
+ await visualBuilder.checkMetricTabIsPresent();
+ await visualBuilder.clickPanelOptions('metric');
+ await visualBuilder.setMetricsDataTimerangeMode('Last value');
+ await visualBuilder.setDropLastBucket(true);
+ await visualBuilder.clickDataTab('metric');
});
it('should not have inspector enabled', async () => {
@@ -59,28 +62,98 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('should show correct data', async () => {
- const value = await PageObjects.visualBuilder.getMetricValue();
+ const value = await visualBuilder.getMetricValue();
expect(value).to.eql('156');
});
it('should show correct data with Math Aggregation', async () => {
- await PageObjects.visualBuilder.createNewAgg();
- await PageObjects.visualBuilder.selectAggType('math', 1);
- await PageObjects.visualBuilder.fillInVariable();
- await PageObjects.visualBuilder.fillInExpression('params.test + 1');
- const value = await PageObjects.visualBuilder.getMetricValue();
+ await visualBuilder.createNewAgg();
+ await visualBuilder.selectAggType('math', 1);
+ await visualBuilder.fillInVariable();
+ await visualBuilder.fillInExpression('params.test + 1');
+ const value = await visualBuilder.getMetricValue();
expect(value).to.eql('157');
});
it('should populate fields for basic functions', async () => {
- const { visualBuilder } = PageObjects;
-
await visualBuilder.selectAggType('Average');
await visualBuilder.setFieldForAggregation('machine.ram');
const isFieldForAggregationValid = await visualBuilder.checkFieldForAggregationValidity();
expect(isFieldForAggregationValid).to.be(true);
});
+
+ it('should show correct data for Value Count with Entire time range mode', async () => {
+ await visualBuilder.selectAggType('Value Count');
+ await visualBuilder.setFieldForAggregation('machine.ram');
+
+ await visualBuilder.clickPanelOptions('metric');
+ await visualBuilder.setMetricsDataTimerangeMode('Entire time range');
+
+ const value = await visualBuilder.getMetricValue();
+ expect(value).to.eql('13,492');
+ });
+
+ it('should show same data for kibana and string index pattern modes', async () => {
+ await visualBuilder.selectAggType('Max');
+ await visualBuilder.setFieldForAggregation('machine.ram');
+ const kibanaIndexPatternModeValue = await visualBuilder.getMetricValue();
+
+ await visualBuilder.clickPanelOptions('metric');
+ await visualBuilder.switchIndexPatternSelectionMode(false);
+ const stringIndexPatternModeValue = await visualBuilder.getMetricValue();
+
+ expect(kibanaIndexPatternModeValue).to.eql(stringIndexPatternModeValue);
+ expect(kibanaIndexPatternModeValue).to.eql('32,212,254,720');
+ });
+
+ describe('Color rules', () => {
+ beforeEach(async () => {
+ await visualBuilder.selectAggType('Min');
+ await visualBuilder.setFieldForAggregation('machine.ram');
+
+ await visualBuilder.clickPanelOptions('metric');
+ await visualBuilder.setColorRuleOperator('>= greater than or equal');
+ await visualBuilder.setColorRuleValue(0);
+ });
+
+ it('should apply color rules to visualization background', async () => {
+ await visualBuilder.setColorPickerValue('#FFCFDF');
+
+ const backGroundStyle = await visualBuilder.getBackgroundStyle();
+ expect(backGroundStyle).to.eql('background-color: rgb(255, 207, 223);');
+ });
+
+ it('should apply color rules to metric value', async () => {
+ await visualBuilder.setColorPickerValue('#AD7DE6', 1);
+
+ const backGroundStyle = await visualBuilder.getMetricValueStyle();
+ expect(backGroundStyle).to.eql('color: rgb(173, 125, 230);');
+ });
+ });
+
+ describe('Top Hit aggregation', () => {
+ beforeEach(async () => {
+ await visualBuilder.selectAggType('Top Hit');
+ await visualBuilder.setTopHitOrderByField('@timestamp');
+ });
+
+ it('should show correct data for string type field', async () => {
+ await visualBuilder.setFieldForAggregation('machine.os.raw');
+ await visualBuilder.setTopHitAggregateWithOption('Concatenate');
+
+ const value = await visualBuilder.getMetricValue();
+ expect(value).to.eql('win 7');
+ });
+
+ it('should show correct data for runtime field', async () => {
+ await visualBuilder.setFieldForAggregation('hello_world_runtime_field');
+ await visualBuilder.setTopHitAggregateWithOption('Concatenate');
+
+ const value = await visualBuilder.getMetricValue();
+ expect(value).to.eql('hello world');
+ });
+ });
});
describe('gauge', () => {
diff --git a/test/functional/fixtures/kbn_archiver/visualize.json b/test/functional/fixtures/kbn_archiver/visualize.json
index 660da856964b4..225dc0592e87d 100644
--- a/test/functional/fixtures/kbn_archiver/visualize.json
+++ b/test/functional/fixtures/kbn_archiver/visualize.json
@@ -3,6 +3,7 @@
"fieldAttrs": "{\"utc_time\":{\"customLabel\":\"UTC time\"}}",
"fieldFormatMap": "{\"bytes\":{\"id\":\"bytes\"}}",
"fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]",
+ "runtimeFieldMap":"{\"hello_world_runtime_field\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit('hello world')\"}}}",
"timeFieldName": "@timestamp",
"title": "logstash-*"
},
@@ -301,4 +302,4 @@
"references": [],
"type": "index-pattern",
"version": "WzE1LDFd"
-}
\ No newline at end of file
+}
diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts
index 8e28ffab6c9c3..fd89a88658b3a 100644
--- a/test/functional/page_objects/visual_builder_page.ts
+++ b/test/functional/page_objects/visual_builder_page.ts
@@ -575,6 +575,42 @@ export class VisualBuilderPageObject extends FtrService {
await this.testSubjects.existOrFail('euiColorPickerPopover', { timeout: 5000 });
}
+ public async setColorPickerValue(colorHex: string, nth: number = 0): Promise {
+ const picker = await this.find.allByCssSelector('.tvbColorPicker button');
+ await picker[nth].clickMouseButton();
+ await this.checkColorPickerPopUpIsPresent();
+ await this.find.setValue('.euiColorPicker input', colorHex);
+ await this.visChart.waitForVisualizationRenderingStabilized();
+ }
+
+ public async setColorRuleOperator(condition: string): Promise {
+ await this.retry.try(async () => {
+ await this.comboBox.clearInputField('colorRuleOperator');
+ await this.comboBox.set('colorRuleOperator', condition);
+ });
+ }
+
+ public async setColorRuleValue(value: number): Promise {
+ await this.retry.try(async () => {
+ const colorRuleValueInput = await this.find.byCssSelector(
+ '[data-test-subj="colorRuleValue"]'
+ );
+ await colorRuleValueInput.type(value.toString());
+ });
+ }
+
+ public async getBackgroundStyle(): Promise {
+ await this.visChart.waitForVisualizationRenderingStabilized();
+ const visualization = await this.find.byClassName('tvbVis');
+ return await visualization.getAttribute('style');
+ }
+
+ public async getMetricValueStyle(): Promise {
+ await this.visChart.waitForVisualizationRenderingStabilized();
+ const metricValue = await this.find.byCssSelector('[data-test-subj="tsvbMetricValue"]');
+ return await metricValue.getAttribute('style');
+ }
+
public async changePanelPreview(nth: number = 0): Promise {
const prevRenderingCount = await this.visChart.getVisualizationRenderingCount();
const changePreviewBtnArray = await this.testSubjects.findAll('AddActivatePanelBtn');
@@ -680,4 +716,15 @@ export class VisualBuilderPageObject extends FtrService {
const dataTimeRangeMode = await this.testSubjects.find('dataTimeRangeMode');
return await this.comboBox.isOptionSelected(dataTimeRangeMode, value);
}
+
+ public async setTopHitAggregateWithOption(option: string): Promise {
+ await this.comboBox.set('topHitAggregateWithComboBox', option);
+ }
+
+ public async setTopHitOrderByField(timeField: string) {
+ await this.retry.try(async () => {
+ await this.comboBox.clearInputField('topHitOrderByFieldSelect');
+ await this.comboBox.set('topHitOrderByFieldSelect', timeField);
+ });
+ }
}
From a80791aa4ccc2902820591299ce8a89c364d2cd6 Mon Sep 17 00:00:00 2001
From: Jason Stoltzfus
Date: Mon, 12 Jul 2021 10:26:41 -0400
Subject: [PATCH 03/84] Pass locale to calendar (#105134)
---
.../components/analytics/components/analytics_filters.tsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx
index 0c8455e986ae1..dd99d368a0105 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx
@@ -80,6 +80,7 @@ export const AnalyticsFilters: React.FC = () => {
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel',
{ defaultMessage: 'Filter by start date' }
)}
+ locale={i18n.getLocale()}
/>
}
endDateControl={
@@ -93,6 +94,7 @@ export const AnalyticsFilters: React.FC = () => {
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel',
{ defaultMessage: 'Filter by end date' }
)}
+ locale={i18n.getLocale()}
/>
}
fullWidth
From d6a36926008b000d006d012edafed5cad6a7f3f6 Mon Sep 17 00:00:00 2001
From: Kaarina Tungseth
Date: Mon, 12 Jul 2021 09:58:39 -0500
Subject: [PATCH 04/84] [DOCS] Adds how to create dashboard drilldowns for Top
N and Table TSVB panels (#104548)
---
docs/user/dashboard/tsvb.asciidoc | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc
index 89da3f7285924..11fe71b7639bb 100644
--- a/docs/user/dashboard/tsvb.asciidoc
+++ b/docs/user/dashboard/tsvb.asciidoc
@@ -148,6 +148,27 @@ The *Markdown* visualization supports Markdown with Handlebar (mustache) syntax
For answers to frequently asked *TSVB* question, review the following.
+[float]
+===== How do I create dashboard drilldowns for Top N and Table visualizations?
+
+You can create dashboard drilldowns that include the specified time range for *Top N* and *Table* visualizations.
+
+. Open the dashboard that you want to link to, then copy the URL.
+
+. Open the dashboard with the *Top N* and *Table* visualization panel, then click *Edit* in the toolbar.
+
+. Open the *Top N* or *Table* panel menu, then select *Edit visualization*.
+
+. Click *Panel options*.
+
+. In the *Item URL* field, enter the URL.
++
+For example `dashboards#/view/f193ca90-c9f4-11eb-b038-dd3270053a27`.
+
+. Click *Save and return*.
+
+. In the toolbar, cick *Save as*, then make sure *Store time with dashboard* is deselected.
+
[float]
===== Why is my TSVB visualization missing data?
From 1aa9459ba2d8c4e736fdae5c0f4f4a59f09c7973 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ece=20=C3=96zalp?=
Date: Mon, 12 Jul 2021 11:07:34 -0400
Subject: [PATCH 05/84] [CTI] converts disabled panel danger to warning
(#104989)
---
.../overview_cti_links/cti_disabled_module.tsx | 4 ++--
.../overview_cti_links/cti_inner_panel.tsx | 12 ++++--------
2 files changed, 6 insertions(+), 10 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx
index 21a4beca72f3b..1600356882c36 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx
@@ -21,11 +21,11 @@ export const CtiDisabledModuleComponent = () => {
const danger = useMemo(
() => (
+
{i18n.DANGER_BUTTON}
}
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx
index 08bf0a432f9bb..ddff78608dfb0 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx
@@ -17,13 +17,9 @@ const ButtonContainer = styled(EuiFlexGroup)`
padding: ${({ theme }) => theme.eui.paddingSizes.s};
`;
-const Title = styled(EuiText)<{ textcolor: 'primary' | 'warning' | 'danger' }>`
+const Title = styled(EuiText)<{ textcolor: 'primary' | 'warning' }>`
color: ${({ theme, textcolor }) =>
- textcolor === 'primary'
- ? theme.eui.euiColorPrimary
- : textcolor === 'warning'
- ? theme.eui.euiColorWarningText
- : theme.eui.euiColorDangerText};
+ textcolor === 'primary' ? theme.eui.euiColorPrimary : theme.eui.euiColorWarningText};
margin-bottom: ${({ theme }) => theme.eui.paddingSizes.m};
`;
@@ -40,12 +36,12 @@ export const CtiInnerPanel = ({
body,
button,
}: {
- color: 'primary' | 'warning' | 'danger';
+ color: 'primary' | 'warning';
title: string;
body: string;
button?: JSX.Element;
}) => {
- const iconType = color === 'primary' ? 'iInCircle' : color === 'warning' ? 'help' : 'alert';
+ const iconType = color === 'primary' ? 'iInCircle' : 'help';
return (
From 0c9777c6020501b6c953b4bca97f41dd858549f1 Mon Sep 17 00:00:00 2001
From: Justin Kambic
Date: Mon, 12 Jul 2021 11:23:57 -0400
Subject: [PATCH 06/84] [Uptime] Refactor page headers to avoid invalid markup
(#104215)
* Refactor monitor and waterfall page headers to avoid rendering invalid markup.
* Update tests.
* Translate page titles.
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../components/monitor/monitor_title.test.tsx | 37 ++++++++++-------
.../components/monitor/monitor_title.tsx | 41 +++++++++----------
.../step_detail/step_detail_container.tsx | 9 ++--
.../step_detail/step_page_title.tsx | 13 ++----
x-pack/plugins/uptime/public/routes.tsx | 33 +++++++++++----
.../uptime/server/lib/requests/get_certs.ts | 1 -
6 files changed, 77 insertions(+), 57 deletions(-)
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx
index 4fd6335c3d3ca..726ad235f7f49 100644
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx
@@ -11,7 +11,7 @@ import { screen } from '@testing-library/react';
import { render } from '../../lib/helper/rtl_helpers';
import * as reactRouterDom from 'react-router-dom';
import { Ping } from '../../../common/runtime_types';
-import { MonitorPageTitle } from './monitor_title';
+import { MonitorPageTitle, MonitorPageTitleContent } from './monitor_title';
jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
@@ -77,11 +77,17 @@ describe('MonitorTitle component', () => {
});
it('renders the monitor heading and EnableMonitorAlert toggle', () => {
- render(, {
- state: { monitorStatus: { status: monitorStatusWithName, loading: false } },
- });
- expect(screen.getByRole('heading', { level: 1, name: monitorName })).toBeInTheDocument();
- expect(screen.getByTestId('uptimeDisplayDefineConnector')).toBeInTheDocument();
+ render(
+ <>
+
+
+ >,
+ {
+ state: { monitorStatus: { status: monitorStatusWithName, loading: false } },
+ }
+ );
+ expect(screen.getByText(monitorName));
+ expect(screen.getByRole('switch')).toBeInTheDocument();
});
it('renders the user provided monitorId when the name is not present', () => {
@@ -89,21 +95,24 @@ describe('MonitorTitle component', () => {
render(, {
state: { monitorStatus: { status: defaultMonitorStatus, loading: false } },
});
- expect(screen.getByRole('heading', { level: 1, name: defaultMonitorId })).toBeInTheDocument();
+ expect(screen.getByText(defaultMonitorId));
});
it('renders the url when the monitorId is auto generated and the monitor name is not present', () => {
mockReactRouterDomHooks({ useParamsResponse: { monitorId: autoGeneratedMonitorIdEncoded } });
- render(, {
- state: { monitorStatus: { status: defaultMonitorStatus, loading: false } },
- });
- expect(
- screen.getByRole('heading', { level: 1, name: defaultMonitorStatus.url?.full })
- ).toBeInTheDocument();
+ render(
+
+
+
,
+ {
+ state: { monitorStatus: { status: defaultMonitorStatus, loading: false } },
+ }
+ );
+ expect(screen.getByText(defaultMonitorStatus!.url!.full!));
});
it('renders beta disclaimer for synthetics monitors', () => {
- render(, {
+ render(, {
state: { monitorStatus: { status: defaultBrowserMonitorStatus, loading: false } },
});
const betaLink = screen.getByRole('link', {
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx
index 2112af0653669..aa68e2aa7fc4b 100644
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx
@@ -5,15 +5,7 @@
* 2.0.
*/
-import {
- EuiBadge,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
- EuiTitle,
- EuiLink,
- EuiText,
-} from '@elastic/eui';
+import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiLink, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { useSelector } from 'react-redux';
@@ -40,18 +32,11 @@ const getPageTitle = (monitorId: string, selectedMonitor: Ping | null) => {
return monitorId;
};
-export const MonitorPageTitle: React.FC = () => {
+export const MonitorPageTitleContent: React.FC = () => {
const monitorId = useMonitorId();
-
const selectedMonitor = useSelector(monitorStatusSelector);
-
- const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor);
-
const type = selectedMonitor?.monitor?.type;
const isBrowser = type === 'browser';
-
- useBreadcrumbs([{ text: nameOrId }]);
-
const renderMonitorType = (monitorType: string) => {
switch (monitorType) {
case 'http':
@@ -86,12 +71,13 @@ export const MonitorPageTitle: React.FC = () => {
return '';
}
};
-
return (
<>
-
- {nameOrId}
-
+
+
+
+
+
@@ -118,7 +104,18 @@ export const MonitorPageTitle: React.FC = () => {
)}
-
>
);
};
+
+export const MonitorPageTitle: React.FC = () => {
+ const monitorId = useMonitorId();
+
+ const selectedMonitor = useSelector(monitorStatusSelector);
+
+ const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor);
+
+ useBreadcrumbs([{ text: nameOrId }]);
+
+ return {nameOrId};
+};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx
index 610107f406306..c24ecd9183865 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx
@@ -16,7 +16,7 @@ import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/
import { useMonitorBreadcrumb } from './use_monitor_breadcrumb';
import { ClientPluginsStart } from '../../../../apps/plugin';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
-import { StepPageTitle } from './step_page_title';
+import { StepPageTitleContent } from './step_page_title';
import { StepPageNavigation } from './step_page_nav';
import { WaterfallChartContainer } from './waterfall/waterfall_chart_container';
@@ -78,10 +78,11 @@ export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex })
return (
void;
handleNextStep: () => void;
}
-export const StepPageTitle = ({
- stepName,
+
+export const StepPageTitleContent = ({
stepIndex,
totalSteps,
handleNextStep,
@@ -29,11 +29,6 @@ export const StepPageTitle = ({
}: Props) => {
return (
-
-
- {stepName}
-
-
diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx
index e3c558cee2c32..2b0cc4dc5e5c2 100644
--- a/x-pack/plugins/uptime/public/routes.tsx
+++ b/x-pack/plugins/uptime/public/routes.tsx
@@ -23,7 +23,7 @@ import { UptimePage, useUptimeTelemetry } from './hooks';
import { OverviewPageComponent } from './pages/overview';
import { SyntheticsCheckSteps } from './pages/synthetics/synthetics_checks';
import { ClientPluginsStart } from './apps/plugin';
-import { MonitorPageTitle } from './components/monitor/monitor_title';
+import { MonitorPageTitle, MonitorPageTitleContent } from './components/monitor/monitor_title';
import { UptimeDatePicker } from './components/common/uptime_date_picker';
import { useKibana } from '../../../../src/plugins/kibana_react/public';
import { CertRefreshBtn } from './components/certificates/cert_refresh_btn';
@@ -36,10 +36,16 @@ interface RouteProps {
dataTestSubj: string;
title: string;
telemetryId: UptimePage;
- pageHeader?: { pageTitle: string | JSX.Element; rightSideItems?: JSX.Element[] };
+ pageHeader?: {
+ children?: JSX.Element;
+ pageTitle: string | JSX.Element;
+ rightSideItems?: JSX.Element[];
+ };
}
-const baseTitle = 'Uptime - Kibana';
+const baseTitle = i18n.translate('xpack.uptime.routes.baseTitle', {
+ defaultMessage: 'Uptime - Kibana',
+});
export const MONITORING_OVERVIEW_LABEL = i18n.translate('xpack.uptime.overview.heading', {
defaultMessage: 'Monitors',
@@ -47,18 +53,25 @@ export const MONITORING_OVERVIEW_LABEL = i18n.translate('xpack.uptime.overview.h
const Routes: RouteProps[] = [
{
- title: `Monitor | ${baseTitle}`,
+ title: i18n.translate('xpack.uptime.monitorRoute.title', {
+ defaultMessage: 'Monitor | {baseTitle}',
+ values: { baseTitle },
+ }),
path: MONITOR_ROUTE,
component: MonitorPage,
dataTestSubj: 'uptimeMonitorPage',
telemetryId: UptimePage.Monitor,
pageHeader: {
+ children: ,
pageTitle: ,
rightSideItems: [],
},
},
{
- title: `Settings | ${baseTitle}`,
+ title: i18n.translate('xpack.uptime.settingsRoute.title', {
+ defaultMessage: `Settings | {baseTitle}`,
+ values: { baseTitle },
+ }),
path: SETTINGS_ROUTE,
component: SettingsPage,
dataTestSubj: 'uptimeSettingsPage',
@@ -70,7 +83,10 @@ const Routes: RouteProps[] = [
},
},
{
- title: `Certificates | ${baseTitle}`,
+ title: i18n.translate('xpack.uptime.certificatesRoute.title', {
+ defaultMessage: `Certificates | {baseTitle}`,
+ values: { baseTitle },
+ }),
path: CERTIFICATES_ROUTE,
component: CertificatesPage,
dataTestSubj: 'uptimeCertificatesPage',
@@ -81,7 +97,10 @@ const Routes: RouteProps[] = [
},
},
{
- title: baseTitle,
+ title: i18n.translate('xpack.uptime.stepDetailRoute.title', {
+ defaultMessage: 'Synthetics detail | {baseTitle}',
+ values: { baseTitle },
+ }),
path: STEP_DETAIL_ROUTE,
component: StepDetailPage,
dataTestSubj: 'uptimeStepDetailPage',
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts
index 1b20ed9085fef..7639484f51737 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts
@@ -138,7 +138,6 @@ export const getCerts: UMElasticsearchQueryFn = asyn
searchBody.query.bool.filter.push(validityFilters);
}
- // console.log(JSON.stringify(params, null, 2));
const { body: result } = await uptimeEsClient.search({
body: searchBody,
});
From 3e5ed774700336657f25e8f0aef10e3bf17a93a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?=
Date: Mon, 12 Jul 2021 18:55:06 +0300
Subject: [PATCH 07/84] [Osquery] Fix 7.14 live query history view (#105211)
---
.../action_results/action_results_summary.tsx | 22 +------
.../osquery/public/actions/actions_table.tsx | 19 ++++++
.../public/agents/agent_id_to_name.tsx | 37 ++++++++++++
.../osquery/public/agents/agents_table.tsx | 24 ++++++--
.../public/agents/use_agent_details.ts | 36 +++++++++++
.../osquery/public/agents/use_all_agents.ts | 2 +-
.../public/live_queries/form/index.tsx | 2 +-
.../public/routes/live_queries/new/index.tsx | 10 +++-
.../routes/saved_queries/list/index.tsx | 60 ++++++++++---------
.../saved_queries/saved_queries_dropdown.tsx | 41 +++++++++----
.../scheduled_query_group_queries_table.tsx | 2 +-
.../actions/all/query.all_actions.dsl.ts | 23 +++++--
12 files changed, 207 insertions(+), 71 deletions(-)
create mode 100644 x-pack/plugins/osquery/public/agents/agent_id_to_name.tsx
create mode 100644 x-pack/plugins/osquery/public/agents/use_agent_details.ts
diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
index d3b0e38a5e033..bf4c97d63d74c 100644
--- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
+++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
@@ -8,15 +8,13 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { i18n } from '@kbn/i18n';
-import { EuiLink, EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui';
+import { EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { PLUGIN_ID } from '../../../fleet/common';
-import { pagePathGetters } from '../../../fleet/public';
+import { AgentIdToName } from '../agents/agent_id_to_name';
import { useActionResults } from './use_action_results';
import { useAllResults } from '../results/use_all_results';
import { Direction } from '../../common/search_strategy';
-import { useKibana } from '../common/lib/kibana';
interface ActionResultsSummaryProps {
actionId: string;
@@ -35,7 +33,6 @@ const ActionResultsSummaryComponent: React.FC = ({
expirationDate,
agentIds,
}) => {
- const getUrlForApp = useKibana().services.application.getUrlForApp;
// @ts-expect-error update types
const [pageIndex, setPageIndex] = useState(0);
// @ts-expect-error update types
@@ -70,20 +67,7 @@ const ActionResultsSummaryComponent: React.FC = ({
isLive,
});
- const renderAgentIdColumn = useCallback(
- (agentId) => (
-
- {agentId}
-
- ),
- [getUrlForApp]
- );
+ const renderAgentIdColumn = useCallback((agentId) => , []);
const renderRowsColumn = useCallback(
(_, item) => {
diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx
index 0ee928ad8aa14..045c1f67b070d 100644
--- a/x-pack/plugins/osquery/public/actions/actions_table.tsx
+++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx
@@ -9,6 +9,7 @@ import { isArray } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EuiBasicTable, EuiButtonIcon, EuiCodeBlock, formatDate } from '@elastic/eui';
import React, { useState, useCallback, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
import { useAllActions } from './use_all_actions';
import { Direction } from '../../common/search_strategy';
@@ -27,6 +28,7 @@ const ActionTableResultsButton = React.memo(({ ac
ActionTableResultsButton.displayName = 'ActionTableResultsButton';
const ActionsTableComponent = () => {
+ const { push } = useHistory();
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(20);
@@ -67,6 +69,16 @@ const ActionsTableComponent = () => {
[]
);
+ const handlePlayClick = useCallback(
+ (item) =>
+ push('/live_queries/new', {
+ form: {
+ query: item._source?.data?.query,
+ },
+ }),
+ [push]
+ );
+
const columns = useMemo(
() => [
{
@@ -106,6 +118,11 @@ const ActionsTableComponent = () => {
defaultMessage: 'View details',
}),
actions: [
+ {
+ type: 'icon',
+ icon: 'play',
+ onClick: handlePlayClick,
+ },
{
render: renderActionsColumn,
},
@@ -113,6 +130,7 @@ const ActionsTableComponent = () => {
},
],
[
+ handlePlayClick,
renderActionsColumn,
renderAgentsColumn,
renderCreatedByColumn,
@@ -135,6 +153,7 @@ const ActionsTableComponent = () => {
= ({ agentId }) => {
+ const getUrlForApp = useKibana().services.application.getUrlForApp;
+ const { data } = useAgentDetails({ agentId });
+
+ return (
+
+ {data?.item.local_metadata.host.name ?? agentId}
+
+ );
+};
+
+export const AgentIdToName = React.memo(AgentIdToNameComponent);
diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx
index 7e8f49c051614..53e2ce1d53420 100644
--- a/x-pack/plugins/osquery/public/agents/agents_table.tsx
+++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx
@@ -21,7 +21,12 @@ import {
generateAgentSelection,
} from './helpers';
-import { SELECT_AGENT_LABEL, generateSelectedAgentsMessage } from './translations';
+import {
+ SELECT_AGENT_LABEL,
+ generateSelectedAgentsMessage,
+ ALL_AGENTS_LABEL,
+ AGENT_POLICY_LABEL,
+} from './translations';
import {
AGENT_GROUP_KEY,
@@ -72,8 +77,17 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh
useEffect(() => {
if (agentSelection && !defaultValueInitialized.current && options.length) {
- if (agentSelection.policiesSelected) {
- const policyOptions = find(['label', 'Policy'], options);
+ if (agentSelection.allAgentsSelected) {
+ const allAgentsOptions = find(['label', ALL_AGENTS_LABEL], options);
+
+ if (allAgentsOptions?.options) {
+ setSelectedOptions(allAgentsOptions.options);
+ defaultValueInitialized.current = true;
+ }
+ }
+
+ if (agentSelection.policiesSelected.length) {
+ const policyOptions = find(['label', AGENT_POLICY_LABEL], options);
if (policyOptions) {
const defaultOptions = policyOptions.options?.filter((option) =>
@@ -82,12 +96,12 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh
if (defaultOptions?.length) {
setSelectedOptions(defaultOptions);
+ defaultValueInitialized.current = true;
}
- defaultValueInitialized.current = true;
}
}
}
- }, [agentSelection, options]);
+ }, [agentSelection, options, selectedOptions]);
useEffect(() => {
// update the groups when groups or agents have changed
diff --git a/x-pack/plugins/osquery/public/agents/use_agent_details.ts b/x-pack/plugins/osquery/public/agents/use_agent_details.ts
new file mode 100644
index 0000000000000..1a0663812dec3
--- /dev/null
+++ b/x-pack/plugins/osquery/public/agents/use_agent_details.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { useQuery } from 'react-query';
+
+import { GetOneAgentResponse, agentRouteService } from '../../../fleet/common';
+import { useErrorToast } from '../common/hooks/use_error_toast';
+import { useKibana } from '../common/lib/kibana';
+
+interface UseAgentDetails {
+ agentId: string;
+}
+
+export const useAgentDetails = ({ agentId }: UseAgentDetails) => {
+ const { http } = useKibana().services;
+ const setErrorToast = useErrorToast();
+ return useQuery(
+ ['agentDetails', agentId],
+ () => http.get(agentRouteService.getInfoPath(agentId)),
+ {
+ enabled: agentId.length > 0,
+ onSuccess: () => setErrorToast(),
+ onError: (error) =>
+ setErrorToast(error as Error, {
+ title: i18n.translate('xpack.osquery.agentDetails.fetchError', {
+ defaultMessage: 'Error while fetching agent details',
+ }),
+ }),
+ }
+ );
+};
diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts
index 30ba4d2f57907..cda15cc805437 100644
--- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts
+++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts
@@ -38,7 +38,7 @@ export const useAllAgents = (
let kuery = `last_checkin_status: online and (${policyFragment})`;
if (searchValue) {
- kuery += `and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`;
+ kuery += ` and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`;
}
return http.get(agentRouteService.getListPath(), {
diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx
index 8654a74fecfb4..658280042696e 100644
--- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx
+++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx
@@ -110,7 +110,7 @@ const LiveQueryFormComponent: React.FC = ({
{
agentSelection: {
agents: [],
- allAgentsSelected: false,
+ allAgentsSelected: true,
platformsSelected: [],
policiesSelected: [],
},
diff --git a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx
index 9967eb97cddf2..40614c1f3e1b8 100644
--- a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx
@@ -8,7 +8,7 @@
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo } from 'react';
-import { useLocation } from 'react-router-dom';
+import { useHistory, useLocation } from 'react-router-dom';
import qs from 'query-string';
import { WithHeaderLayout } from '../../../components/layouts';
@@ -19,12 +19,18 @@ import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge';
const NewLiveQueryPageComponent = () => {
useBreadcrumbs('live_query_new');
+ const { replace } = useHistory();
const location = useLocation();
const liveQueryListProps = useRouterNavigate('live_queries');
const formDefaultValue = useMemo(() => {
const queryParams = qs.parse(location.search);
+ if (location.state?.form.query) {
+ replace({ state: null });
+ return { query: location.state?.form.query };
+ }
+
if (queryParams?.agentPolicyId) {
return {
agentSelection: {
@@ -37,7 +43,7 @@ const NewLiveQueryPageComponent = () => {
}
return undefined;
- }, [location.search]);
+ }, [location.search, location.state, replace]);
const LeftColumn = useMemo(
() => (
diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx
index 7e8e8e543dfab..8738c06d06597 100644
--- a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx
@@ -16,6 +16,7 @@ import {
import React, { useCallback, useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { useHistory } from 'react-router-dom';
import { SavedObject } from 'kibana/public';
import { WithHeaderLayout } from '../../../components/layouts';
@@ -51,6 +52,7 @@ const EditButton = React.memo(EditButtonComponent);
const SavedQueriesPageComponent = () => {
useBreadcrumbs('saved_queries');
+ const { push } = useHistory();
const newQueryLinkProps = useRouterNavigate('saved_queries/new');
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
@@ -59,21 +61,15 @@ const SavedQueriesPageComponent = () => {
const { data } = useSavedQueries({ isLive: true });
- // const handlePlayClick = useCallback(
- // (item) =>
- // push({
- // search: qs.stringify({
- // tab: 'live_query',
- // }),
- // state: {
- // query: {
- // id: item.id,
- // query: item.attributes.query,
- // },
- // },
- // }),
- // [push]
- // );
+ const handlePlayClick = useCallback(
+ (item) =>
+ push('/live_queries/new', {
+ form: {
+ savedQueryId: item.id,
+ },
+ }),
+ [push]
+ );
const renderEditAction = useCallback(
(item: SavedObject<{ name: string }>) => (
@@ -96,45 +92,53 @@ const SavedQueriesPageComponent = () => {
() => [
{
field: 'attributes.id',
- name: 'Query ID',
+ name: i18n.translate('xpack.osquery.savedQueries.table.queryIdColumnTitle', {
+ defaultMessage: 'Query ID',
+ }),
sortable: true,
truncateText: true,
},
{
field: 'attributes.description',
- name: 'Description',
+ name: i18n.translate('xpack.osquery.savedQueries.table.descriptionColumnTitle', {
+ defaultMessage: 'Description',
+ }),
sortable: true,
truncateText: true,
},
{
field: 'attributes.created_by',
- name: 'Created by',
+ name: i18n.translate('xpack.osquery.savedQueries.table.createdByColumnTitle', {
+ defaultMessage: 'Created by',
+ }),
sortable: true,
truncateText: true,
},
{
field: 'attributes.updated_at',
- name: 'Last updated at',
+ name: i18n.translate('xpack.osquery.savedQueries.table.updatedAtColumnTitle', {
+ defaultMessage: 'Last updated at',
+ }),
sortable: (item: SavedObject<{ updated_at: string }>) =>
item.attributes.updated_at ? Date.parse(item.attributes.updated_at) : 0,
truncateText: true,
render: renderUpdatedAt,
},
{
- name: 'Actions',
+ name: i18n.translate('xpack.osquery.savedQueries.table.actionsColumnTitle', {
+ defaultMessage: 'Actions',
+ }),
actions: [
- // {
- // name: 'Live query',
- // description: 'Run live query',
- // type: 'icon',
- // icon: 'play',
- // onClick: handlePlayClick,
- // },
+ {
+ type: 'icon',
+ icon: 'play',
+ onClick: handlePlayClick,
+ },
{ render: renderEditAction },
],
},
],
- [renderEditAction, renderUpdatedAt]
+ [handlePlayClick, renderEditAction, renderUpdatedAt]
);
const onTableChange = useCallback(({ page = {}, sort = {} }) => {
diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
index e30954a695b2d..073b56bfd1d4c 100644
--- a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
+++ b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
@@ -7,10 +7,11 @@
import { find } from 'lodash/fp';
import { EuiCodeBlock, EuiFormRow, EuiComboBox, EuiText } from '@elastic/eui';
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { SimpleSavedObject } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { useHistory, useLocation } from 'react-router-dom';
import { useSavedQueries } from './use_saved_queries';
@@ -29,19 +30,25 @@ const SavedQueriesDropdownComponent: React.FC = ({
disabled,
onChange,
}) => {
+ const { replace } = useHistory();
+ const location = useLocation();
const [selectedOptions, setSelectedOptions] = useState([]);
const { data } = useSavedQueries({});
- const queryOptions =
- data?.savedObjects?.map((savedQuery) => ({
- label: savedQuery.attributes.id ?? '',
- value: {
- id: savedQuery.attributes.id,
- description: savedQuery.attributes.description,
- query: savedQuery.attributes.query,
- },
- })) ?? [];
+ const queryOptions = useMemo(
+ () =>
+ data?.savedObjects?.map((savedQuery) => ({
+ label: savedQuery.attributes.id ?? '',
+ value: {
+ savedObjectId: savedQuery.id,
+ id: savedQuery.attributes.id,
+ description: savedQuery.attributes.description,
+ query: savedQuery.attributes.query,
+ },
+ })) ?? [],
+ [data?.savedObjects]
+ );
const handleSavedQueryChange = useCallback(
(newSelectedOptions) => {
@@ -73,6 +80,20 @@ const SavedQueriesDropdownComponent: React.FC = ({
[]
);
+ useEffect(() => {
+ const savedQueryId = location.state?.form?.savedQueryId;
+
+ if (savedQueryId) {
+ const savedQueryOption = find(['value.savedObjectId', savedQueryId], queryOptions);
+
+ if (savedQueryOption) {
+ handleSavedQueryChange([savedQueryOption]);
+ }
+
+ replace({ state: null });
+ }
+ }, [handleSavedQueryChange, replace, location.state, queryOptions]);
+
return (
Date: Mon, 12 Jul 2021 11:56:43 -0400
Subject: [PATCH 08/84] [Fleet] Fix enrollment flyout with fleet server from
policy page (#104542)
* [Fleet] Fix enrollment flyout with fleet server from policy page
* Fix tests
* Show enrollment instructions for add agent from fleet server policy
* Fix conditions to show fleet server setup in flyout
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../agent_enrollment_flyout.test.mocks.ts | 1 +
.../agent_enrollment_flyout.test.tsx | 6 ++-
.../agent_enrollment_flyout/index.tsx | 39 ++++++++++++--
.../managed_instructions.tsx | 54 ++++++++++++-------
.../agent_enrollment_flyout/steps.tsx | 22 ++------
.../agent_enrollment_flyout/types.ts | 8 ++-
x-pack/plugins/fleet/public/types/index.ts | 1 +
7 files changed, 87 insertions(+), 44 deletions(-)
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts
index d2e7c4089e88b..5c292187982dc 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts
@@ -11,6 +11,7 @@ jest.mock('../../hooks/use_request', () => {
...module,
useGetSettings: jest.fn(),
sendGetFleetStatus: jest.fn(),
+ sendGetOneAgentPolicy: jest.fn(),
};
});
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx
index f68b1b878c51c..18296134ee1a7 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx
@@ -16,7 +16,7 @@ import { coreMock } from 'src/core/public/mocks';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import type { AgentPolicy } from '../../../common';
-import { useGetSettings, sendGetFleetStatus } from '../../hooks/use_request';
+import { useGetSettings, sendGetFleetStatus, sendGetOneAgentPolicy } from '../../hooks/use_request';
import { FleetStatusProvider, ConfigContext } from '../../hooks';
import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components';
@@ -79,6 +79,10 @@ describe('', () => {
data: { isReady: true },
});
+ (sendGetOneAgentPolicy as jest.Mock).mockResolvedValue({
+ data: { item: { package_policies: [] } },
+ });
+
(useFleetServerInstructions as jest.Mock).mockReturnValue({
serviceToken: 'test',
getServiceToken: jest.fn(),
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx
index 9b82b2a80b5e1..87911e5d6c2c7 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx
@@ -22,7 +22,9 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { useGetSettings, useUrlModal } from '../../hooks';
+import { useGetSettings, useUrlModal, sendGetOneAgentPolicy, useFleetStatus } from '../../hooks';
+import { FLEET_SERVER_PACKAGE } from '../../constants';
+import type { PackagePolicy } from '../../types';
import { ManagedInstructions } from './managed_instructions';
import { StandaloneInstructions } from './standalone_instructions';
@@ -63,6 +65,30 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({
}
}, [modal, lastModal, settings]);
+ const fleetStatus = useFleetStatus();
+ const [policyId, setSelectedPolicyId] = useState(agentPolicy?.id);
+ const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false);
+
+ useEffect(() => {
+ async function checkPolicyIsFleetServer() {
+ if (policyId && setIsFleetServerPolicySelected) {
+ const agentPolicyRequest = await sendGetOneAgentPolicy(policyId);
+ if (
+ agentPolicyRequest.data?.item &&
+ (agentPolicyRequest.data.item.package_policies as PackagePolicy[]).some(
+ (packagePolicy) => packagePolicy.package?.name === FLEET_SERVER_PACKAGE
+ )
+ ) {
+ setIsFleetServerPolicySelected(true);
+ } else {
+ setIsFleetServerPolicySelected(false);
+ }
+ }
+ }
+
+ checkPolicyIsFleetServer();
+ }, [policyId]);
+
const isLoadingInitialRequest = settings.isLoading && settings.isInitialRequest;
return (
@@ -110,16 +136,23 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({
) : undefined
}
>
- {fleetServerHosts.length === 0 && mode === 'managed' ? null : mode === 'managed' ? (
+ {mode === 'managed' ? (
) : (
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
index 61f86335cd7f9..8054c48fbbaa8 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
@@ -11,7 +11,7 @@ import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/st
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { useGetOneEnrollmentAPIKey, useGetSettings, useLink, useFleetStatus } from '../../hooks';
+import { useGetOneEnrollmentAPIKey, useLink, useFleetStatus } from '../../hooks';
import { ManualInstructions } from '../../components/enrollment_instructions';
import {
@@ -56,14 +56,19 @@ const FleetServerMissingRequirements = () => {
};
export const ManagedInstructions = React.memo(
- ({ agentPolicy, agentPolicies, viewDataStep }) => {
+ ({
+ agentPolicy,
+ agentPolicies,
+ viewDataStep,
+ setSelectedPolicyId,
+ isFleetServerPolicySelected,
+ settings,
+ }) => {
const fleetStatus = useFleetStatus();
const [selectedApiKeyId, setSelectedAPIKeyId] = useState();
- const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false);
const apiKey = useGetOneEnrollmentAPIKey(selectedApiKeyId);
- const settings = useGetSettings();
const fleetServerInstructions = useFleetServerInstructions(apiKey?.data?.item?.policy_id);
const fleetServerSteps = useMemo(() => {
@@ -88,7 +93,7 @@ export const ManagedInstructions = React.memo(
}, [fleetServerInstructions]);
const steps = useMemo(() => {
- const fleetServerHosts = settings.data?.item?.fleet_server_hosts || [];
+ const fleetServerHosts = settings?.fleet_server_hosts || [];
const baseSteps: EuiContainedStepProps[] = [
DownloadStep(),
!agentPolicy
@@ -96,7 +101,7 @@ export const ManagedInstructions = React.memo(
agentPolicies,
selectedApiKeyId,
setSelectedAPIKeyId,
- setIsFleetServerPolicySelected,
+ setSelectedPolicyId,
})
: AgentEnrollmentKeySelectionStep({ agentPolicy, selectedApiKeyId, setSelectedAPIKeyId }),
];
@@ -121,30 +126,39 @@ export const ManagedInstructions = React.memo(
}, [
agentPolicy,
selectedApiKeyId,
+ setSelectedPolicyId,
setSelectedAPIKeyId,
agentPolicies,
apiKey.data,
fleetServerSteps,
isFleetServerPolicySelected,
- settings.data?.item?.fleet_server_hosts,
+ settings?.fleet_server_hosts,
viewDataStep,
]);
+ if (fleetStatus.isReady && settings?.fleet_server_hosts.length === 0) {
+ return null;
+ }
+
+ if (fleetStatus.isReady) {
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+ }
+
return (
<>
- {fleetStatus.isReady ? (
- <>
-
-
-
-
-
- >
- ) : fleetStatus.missingRequirements?.length === 1 &&
- fleetStatus.missingRequirements[0] === 'fleet_server' ? (
+ {fleetStatus.missingRequirements?.length === 1 &&
+ fleetStatus.missingRequirements[0] === 'fleet_server' ? (
) : (
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx
index 6cffa39628d92..1cfdc45fb7dba 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx
@@ -11,9 +11,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import semver from 'semver';
-import type { AgentPolicy, PackagePolicy } from '../../types';
-import { sendGetOneAgentPolicy, useKibanaVersion } from '../../hooks';
-import { FLEET_SERVER_PACKAGE } from '../../constants';
+import type { AgentPolicy } from '../../types';
+import { useKibanaVersion } from '../../hooks';
import { EnrollmentStepAgentPolicy } from './agent_policy_selection';
import { AdvancedAgentAuthenticationSettings } from './advanced_agent_authentication_settings';
@@ -69,13 +68,11 @@ export const AgentPolicySelectionStep = ({
selectedApiKeyId,
setSelectedAPIKeyId,
excludeFleetServer,
- setIsFleetServerPolicySelected,
}: {
agentPolicies?: AgentPolicy[];
setSelectedPolicyId?: (policyId?: string) => void;
selectedApiKeyId?: string;
setSelectedAPIKeyId?: (key?: string) => void;
- setIsFleetServerPolicySelected?: (selected: boolean) => void;
excludeFleetServer?: boolean;
}) => {
const regularAgentPolicies = useMemo(() => {
@@ -92,21 +89,8 @@ export const AgentPolicySelectionStep = ({
if (setSelectedPolicyId) {
setSelectedPolicyId(policyId);
}
- if (policyId && setIsFleetServerPolicySelected) {
- const agentPolicyRequest = await sendGetOneAgentPolicy(policyId);
- if (
- agentPolicyRequest.data?.item &&
- (agentPolicyRequest.data.item.package_policies as PackagePolicy[]).some(
- (packagePolicy) => packagePolicy.package?.name === FLEET_SERVER_PACKAGE
- )
- ) {
- setIsFleetServerPolicySelected(true);
- } else {
- setIsFleetServerPolicySelected(false);
- }
- }
},
- [setIsFleetServerPolicySelected, setSelectedPolicyId]
+ [setSelectedPolicyId]
);
return {
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts
index 9ee514c634655..282a5b243caed 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts
@@ -7,7 +7,7 @@
import type { EuiStepProps } from '@elastic/eui';
-import type { AgentPolicy } from '../../types';
+import type { AgentPolicy, Settings } from '../../types';
export interface BaseProps {
/**
@@ -27,4 +27,10 @@ export interface BaseProps {
* in some way. This is an area for consumers to render a button and text explaining how data can be viewed.
*/
viewDataStep?: EuiStepProps;
+
+ settings?: Settings;
+
+ setSelectedPolicyId?: (policyId?: string) => void;
+
+ isFleetServerPolicySelected?: boolean;
}
diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts
index f21552d68e77b..c91ec42d3e527 100644
--- a/x-pack/plugins/fleet/public/types/index.ts
+++ b/x-pack/plugins/fleet/public/types/index.ts
@@ -27,6 +27,7 @@ export {
PackagePolicyPackage,
Output,
DataStream,
+ Settings,
// API schema - misc setup, status
GetFleetStatusResponse,
// API schemas - Agent policy
From 42c743be8848e08761f76903c4aa1f0b5f8e899a Mon Sep 17 00:00:00 2001
From: Caroline Horn <549577+cchaos@users.noreply.github.com>
Date: Mon, 12 Jul 2021 12:50:41 -0400
Subject: [PATCH 09/84] [Top Menu] Increase size of top menu links to `s`
(#103144)
* Increased non-emphasized header links size from `xs` to `s`
* [Observability] Updating header links to use EuiHeaderLink
* [Spaces Menu] Larger spinner
* [Help Menu] Increase size of links
* [Canvas] Increase size to `s`
---
.../chrome/ui/header/header_help_menu.tsx | 12 +--
.../data/public/ui/filter_bar/filter_bar.tsx | 2 +-
.../index_pattern_table.tsx | 2 +-
.../public/top_nav_menu/top_nav_menu_item.tsx | 2 +-
.../app/RumDashboard/ActionMenu/index.tsx | 60 ++++++--------
.../alerting_popover_flyout.tsx | 1 -
.../anomaly_detection_setup_link.tsx | 1 -
.../shared/apm_header_action_menu/index.tsx | 2 +-
.../function_reference_generator.tsx | 2 +-
.../public/components/help_menu/help_menu.tsx | 4 +-
.../__snapshots__/edit_menu.stories.storyshot | 12 +--
.../edit_menu/edit_menu.component.tsx | 2 +-
.../share_menu.stories.storyshot | 4 +-
.../share_menu/share_menu.component.tsx | 2 +-
.../__snapshots__/view_menu.stories.storyshot | 8 +-
.../view_menu/view_menu.component.tsx | 2 +-
.../file_error_callouts.tsx | 2 +-
.../components/metrics_alert_dropdown.tsx | 7 +-
.../components/alert_dropdown.tsx | 7 +-
.../infra/public/pages/logs/page_content.tsx | 37 ++++-----
.../infra/public/pages/metrics/index.tsx | 43 ++++------
.../anomaly_detection_flyout.tsx | 7 +-
.../nav_control/nav_control_popover.tsx | 2 +-
.../common/header/action_menu_content.tsx | 82 +++++++++----------
.../alerts/toggle_alert_flyout_button.tsx | 7 +-
.../overview/synthetics_callout.test.tsx | 6 --
.../overview/synthetics_callout.tsx | 10 +--
27 files changed, 134 insertions(+), 194 deletions(-)
diff --git a/src/core/public/chrome/ui/header/header_help_menu.tsx b/src/core/public/chrome/ui/header/header_help_menu.tsx
index c6a09c1177a5e..cbf89bba2ca44 100644
--- a/src/core/public/chrome/ui/header/header_help_menu.tsx
+++ b/src/core/public/chrome/ui/header/header_help_menu.tsx
@@ -211,7 +211,7 @@ export class HeaderHelpMenu extends Component {
return (
-
+
}>
-
- {ANALYZE_DATA}
-
-
-
-
-
+ {ANALYZE_MESSAGE}}>
+
- {i18n.translate('xpack.apm.addDataButtonLabel', {
- defaultMessage: 'Add data',
- })}
-
-
-
+ {ANALYZE_DATA}
+
+
+
+ {i18n.translate('xpack.apm.addDataButtonLabel', {
+ defaultMessage: 'Add data',
+ })}
+
+
);
}
diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx
index ca73f6ddd05b3..4abd36a277311 100644
--- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx
+++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx
@@ -66,7 +66,6 @@ export function AlertingPopoverAndFlyout({
const button = (
-
+
{i18n.translate('xpack.apm.settingsLinkLabel', {
defaultMessage: 'Settings',
})}
diff --git a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx
index 81532816d9c83..eb394801f549c 100644
--- a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx
+++ b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx
@@ -29,7 +29,7 @@ export const FunctionReferenceGenerator: FC = ({ functionRegistry }) => {
};
return (
-
+
Generate function reference
);
diff --git a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx
index 2877ccf41056d..af1850beb5290 100644
--- a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx
+++ b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx
@@ -46,13 +46,13 @@ export const HelpMenu: FC = ({ functionRegistry }) => {
return (
<>
-
+
{strings.getKeyboardShortcutsLinkLabel()}
{FunctionReferenceGenerator ? (
-
+
) : null}
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot
index cc33ae3526c0c..f2bc9c57cbcc6 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot
@@ -9,7 +9,7 @@ exports[`Storyshots components/WorkpadHeader/EditMenu 2 elements selected 1`] =
>
}
isOpen={popoverOpen}
closePopover={closePopover}
diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx
index f3481cab73360..302de15db9f5a 100644
--- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx
+++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx
@@ -7,7 +7,7 @@
import React, { useState, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiPopover, EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel } from '@elastic/eui';
+import { EuiPopover, EuiContextMenuItem, EuiContextMenuPanel, EuiHeaderLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { AlertFlyout } from './alert_flyout';
import { useLinkProps } from '../../../hooks/use_link_props';
@@ -83,8 +83,7 @@ export const AlertDropdown = () => {
{
id="xpack.infra.alerting.logs.alertsButton"
defaultMessage="Alerts and rules"
/>
-
+
}
isOpen={popoverOpen}
closePopover={closePopover}
diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx
index 8175a95f6a064..d8b5667e60d04 100644
--- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
+import { EuiHeaderLinks, EuiHeaderLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useContext } from 'react';
import { Route, Switch } from 'react-router-dom';
@@ -78,28 +78,19 @@ export const LogsPageContent: React.FunctionComponent = () => {
{setHeaderActionMenu && (
-
-
-
- {settingsTabTitle}
-
-
-
-
-
-
-
- {ADD_DATA_LABEL}
-
-
-
+
+
+ {settingsTabTitle}
+
+
+
+ {ADD_DATA_LABEL}
+
+
)}
diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx
index 045fcb57ae943..d4845a4dd9e44 100644
--- a/x-pack/plugins/infra/public/pages/metrics/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx
@@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
import React, { useContext } from 'react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
-import { EuiErrorBoundary, EuiFlexItem, EuiFlexGroup, EuiButtonEmpty } from '@elastic/eui';
+import { EuiErrorBoundary, EuiHeaderLinks, EuiHeaderLink } from '@elastic/eui';
import { IIndexPattern } from 'src/plugins/data/common';
import { MetricsSourceConfigurationProperties } from '../../../common/metrics_sources';
import { DocumentTitle } from '../../components/document_title';
@@ -86,31 +86,22 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
{setHeaderActionMenu && (
-
-
-
- {settingsTabTitle}
-
-
-
-
-
-
-
-
-
-
- {ADD_DATA_LABEL}
-
-
-
+
+
+ {settingsTabTitle}
+
+
+
+
+ {ADD_DATA_LABEL}
+
+
)}
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx
index d2cd4f87a5342..4e28fb4202bdc 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx
@@ -6,7 +6,7 @@
*/
import React, { useState, useCallback } from 'react';
-import { EuiButtonEmpty, EuiFlyout } from '@elastic/eui';
+import { EuiHeaderLink, EuiFlyout } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { FlyoutHome } from './flyout_home';
import { JobSetupScreen } from './job_setup_screen';
@@ -50,8 +50,7 @@ export const AnomalyDetectionFlyout = () => {
return (
<>
- {
id="xpack.infra.ml.anomalyDetectionButton"
defaultMessage="Anomaly detection"
/>
-
+
{showFlyout && (
{
}
return this.getButton(
- }>
+ }>
,
(activeSpace as Space).name
diff --git a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
index 479a512b7238a..9f00dd2e8f061 100644
--- a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
+++ b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiButtonEmpty, EuiHeaderLinks, EuiHeaderSectionItem, EuiToolTip } from '@elastic/eui';
+import { EuiHeaderLinks, EuiToolTip, EuiHeaderLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { useHistory } from 'react-router-dom';
@@ -51,53 +51,45 @@ export function ActionMenuContent(): React.ReactElement {
return (
-
-
+
+
+
+
+
+ {ANALYZE_MESSAGE}}>
+
-
-
-
-
-
-
-
- {ANALYZE_MESSAGE}}>
-
- {ANALYZE_DATA}
-
-
-
-
-
- {ADD_DATA_LABEL}
-
-
+ {ANALYZE_DATA}
+
+
+
+
+ {ADD_DATA_LABEL}
+
);
}
diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx
index 278958bd1987b..22193fe4623d6 100644
--- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx
@@ -6,7 +6,7 @@
*/
import {
- EuiButtonEmpty,
+ EuiHeaderLink,
EuiContextMenu,
EuiContextMenuPanelDescriptor,
EuiContextMenuPanelItemDescriptor,
@@ -123,8 +123,7 @@ export const ToggleAlertFlyoutButtonComponent: React.FC = ({
return (
= ({
id="xpack.uptime.alerts.toggleAlertFlyoutButtonText"
defaultMessage="Alerts and rules"
/>
-
+
}
closePopover={() => setIsOpen(false)}
isOpen={isOpen}
diff --git a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx
index 5f6f9d7a7207e..ec9e5f958ec3a 100644
--- a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx
@@ -67,9 +67,6 @@ describe('SyntheticsCallout', () => {
-
`);
});
@@ -128,9 +125,6 @@ describe('SyntheticsCallout', () => {
-
`);
wrapper.find('EuiButton').simulate('click');
diff --git a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.tsx b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.tsx
index fa28e42d7d0c1..4e9c3256f1578 100644
--- a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.tsx
@@ -5,14 +5,7 @@
* 2.0.
*/
-import {
- EuiButton,
- EuiButtonEmpty,
- EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
-} from '@elastic/eui';
+import { EuiButton, EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -74,7 +67,6 @@ export const SyntheticsCallout = () => {
-
>
);
};
From 4b7b45d69de43a56e5d74d9b4f1ec302cc48396c Mon Sep 17 00:00:00 2001
From: Frank Hassanabad
Date: Mon, 12 Jul 2021 11:18:17 -0600
Subject: [PATCH 10/84] [Security Solutions] Changes cypress tests to use
immutable object data (#105162)
## Summary
In previous pull requests I noticed that there were accidents where Cypress tests were accidentally mutating objects such as rules and mock data since the data is mutable as regular variables. This changes the data to be underneath functions and the functions all return new copies on each invocation. This makes it so that it isn't possible for Cypress tests to accidentally mutate data and mocks between tests. This should also prevent devs from accidentally writing tests that rely on previous tests accidentally mutating data.
I am hoping that this is portable back to 7.14 since it's just test changes and will make it easier to do any other bug fixes without conflicts if tests are updated. If it's not easily portable, then I will mark and update it as only 7.15 backported.
* Changes the mocks underneath `objects/` to use getter functions that return objects.
* Updates the cypress tests
### Checklist
- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
---
.../detection_alerts/alerts_details.spec.ts | 4 +-
.../integration/cases/attach_timeline.spec.ts | 14 +-
.../cases/connector_options.spec.ts | 48 ++--
.../integration/cases/connectors.spec.ts | 4 +-
.../integration/cases/creation.spec.ts | 10 +-
.../detection_alerts/alerts_details.spec.ts | 4 +-
..._detection_callouts_index_outdated.spec.ts | 8 +-
.../detection_alerts/attach_to_case.spec.ts | 4 +-
.../detection_alerts/closing.spec.ts | 4 +-
.../detection_alerts/cti_enrichments.spec.ts | 8 +-
.../detection_alerts/in_progress.spec.ts | 4 +-
.../investigate_in_timeline.spec.ts | 4 +-
.../missing_privileges_callout.spec.ts | 6 +-
.../detection_alerts/opening.spec.ts | 4 +-
.../detection_rules/custom_query_rule.spec.ts | 86 ++++----
.../event_correlation_rule.spec.ts | 24 +-
.../detection_rules/export_rule.spec.ts | 4 +-
.../indicator_match_rule.spec.ts | 136 +++++++-----
.../integration/detection_rules/links.spec.ts | 4 +-
.../machine_learning_rule.spec.ts | 38 ++--
.../detection_rules/override.spec.ts | 24 +-
.../detection_rules/sorting.spec.ts | 19 +-
.../detection_rules/threshold_rule.spec.ts | 34 +--
.../exceptions/exceptions_modal.spec.ts | 4 +-
.../exceptions/exceptions_table.spec.ts | 14 +-
.../integration/exceptions/from_alert.spec.ts | 8 +-
.../integration/exceptions/from_rule.spec.ts | 8 +-
.../integration/header/search_bar.spec.ts | 6 +-
.../integration/overview/overview.spec.ts | 6 +-
.../timeline_templates/creation.spec.ts | 29 ++-
.../timeline_templates/export.spec.ts | 4 +-
.../integration/timelines/creation.spec.ts | 21 +-
.../integration/timelines/export.spec.ts | 4 +-
.../integration/timelines/notes_tab.spec.ts | 12 +-
.../timelines/open_timeline.spec.ts | 14 +-
.../integration/timelines/query_tab.spec.ts | 12 +-
.../cypress/integration/urls/state.spec.ts | 6 +-
.../security_solution/cypress/objects/case.ts | 50 +++--
.../cypress/objects/connector.ts | 4 +-
.../cypress/objects/exception.ts | 10 +-
.../cypress/objects/filter.ts | 4 +-
.../security_solution/cypress/objects/rule.ts | 205 +++++++++---------
.../cypress/objects/timeline.ts | 30 +--
.../cypress/screens/edit_connector.ts | 10 +-
.../cypress/tasks/create_new_rule.ts | 15 +-
.../mitre/mitre_tactics_techniques.ts | 4 +-
.../detections/mitre/valid_threat_mock.ts | 4 +-
47 files changed, 516 insertions(+), 463 deletions(-)
diff --git a/x-pack/plugins/security_solution/cypress/ccs_integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/ccs_integration/detection_alerts/alerts_details.spec.ts
index f87399a666904..229bbcce87696 100644
--- a/x-pack/plugins/security_solution/cypress/ccs_integration/detection_alerts/alerts_details.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/ccs_integration/detection_alerts/alerts_details.spec.ts
@@ -18,7 +18,7 @@ import { cleanKibana } from '../../tasks/common';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { esArchiverCCSLoad, esArchiverCCSUnload } from '../../tasks/es_archiver';
-import { unmappedCCSRule } from '../../objects/rule';
+import { getUnmappedCCSRule } from '../../objects/rule';
import { ALERTS_URL } from '../../urls/navigation';
@@ -29,7 +29,7 @@ describe('Alert details with unmapped fields', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(unmappedCCSRule);
+ createCustomRuleActivated(getUnmappedCCSRule());
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
expandFirstAlert();
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts
index 29105ce1582cf..e94f7d00f0b37 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts
@@ -13,8 +13,8 @@ import {
selectCase,
} from '../../tasks/timeline';
import { DESCRIPTION_INPUT, ADD_COMMENT_INPUT } from '../../screens/create_new_case';
-import { case1 } from '../../objects/case';
-import { timeline } from '../../objects/timeline';
+import { getCase1 } from '../../objects/case';
+import { getTimeline } from '../../objects/timeline';
import { createTimeline } from '../../tasks/api_calls/timelines';
import { cleanKibana } from '../../tasks/common';
import { createCase } from '../../tasks/api_calls/cases';
@@ -23,7 +23,7 @@ describe('attach timeline to case', () => {
context('without cases created', () => {
beforeEach(() => {
cleanKibana();
- createTimeline(timeline).then((response) => {
+ createTimeline(getTimeline()).then((response) => {
cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline');
});
});
@@ -57,10 +57,10 @@ describe('attach timeline to case', () => {
context('with cases created', () => {
before(() => {
cleanKibana();
- createTimeline(timeline).then((response) =>
+ createTimeline(getTimeline()).then((response) =>
cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId')
);
- createCase(case1).then((response) => cy.wrap(response.body.id).as('caseId'));
+ createCase(getCase1()).then((response) => cy.wrap(response.body.id).as('caseId'));
});
it('attach timeline to an existing case', function () {
@@ -71,7 +71,9 @@ describe('attach timeline to case', () => {
cy.location('origin').then((origin) => {
cy.get(ADD_COMMENT_INPUT).should(
'have.text',
- `[${timeline.title}](${origin}/app/security/timelines?timeline=(id:%27${this.timelineId}%27,isOpen:!t))`
+ `[${getTimeline().title}](${origin}/app/security/timelines?timeline=(id:%27${
+ this.timelineId
+ }%27,isOpen:!t))`
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/connector_options.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/connector_options.spec.ts
index 95b555c2acae6..0959f999a4b53 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases/connector_options.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases/connector_options.spec.ts
@@ -7,13 +7,13 @@
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import {
- case1,
- connectorIds,
- mockConnectorsResponse,
- executeResponses,
- ibmResilientConnectorOptions,
- jiraConnectorOptions,
- serviceNowConnectorOpions,
+ getCase1,
+ getConnectorIds,
+ getMockConnectorsResponse,
+ getExecuteResponses,
+ getIbmResilientConnectorOptions,
+ getJiraConnectorOptions,
+ getServiceNowConnectorOptions,
} from '../../objects/case';
import {
createCase,
@@ -30,26 +30,26 @@ import { cleanKibana } from '../../tasks/common';
describe('Cases connector incident fields', () => {
beforeEach(() => {
cleanKibana();
- cy.intercept('GET', '/api/cases/configure/connectors/_find', mockConnectorsResponse);
- cy.intercept('POST', `/api/actions/action/${connectorIds.sn}/_execute`, (req) => {
+ cy.intercept('GET', '/api/cases/configure/connectors/_find', getMockConnectorsResponse());
+ cy.intercept('POST', `/api/actions/action/${getConnectorIds().sn}/_execute`, (req) => {
const response =
req.body.params.subAction === 'getChoices'
- ? executeResponses.servicenow.choices
+ ? getExecuteResponses().servicenow.choices
: { status: 'ok', data: [] };
req.reply(response);
});
- cy.intercept('POST', `/api/actions/action/${connectorIds.jira}/_execute`, (req) => {
+ cy.intercept('POST', `/api/actions/action/${getConnectorIds().jira}/_execute`, (req) => {
const response =
req.body.params.subAction === 'issueTypes'
- ? executeResponses.jira.issueTypes
- : executeResponses.jira.fieldsByIssueType;
+ ? getExecuteResponses().jira.issueTypes
+ : getExecuteResponses().jira.fieldsByIssueType;
req.reply(response);
});
- cy.intercept('POST', `/api/actions/action/${connectorIds.resilient}/_execute`, (req) => {
+ cy.intercept('POST', `/api/actions/action/${getConnectorIds().resilient}/_execute`, (req) => {
const response =
req.body.params.subAction === 'incidentTypes'
- ? executeResponses.resilient.incidentTypes
- : executeResponses.resilient.severity;
+ ? getExecuteResponses().resilient.incidentTypes
+ : getExecuteResponses().resilient.severity;
req.reply(response);
});
});
@@ -57,19 +57,19 @@ describe('Cases connector incident fields', () => {
it('Correct incident fields show when connector is changed', () => {
loginAndWaitForPageWithoutDateRange(CASES_URL);
goToCreateNewCase();
- fillCasesMandatoryfields(case1);
- fillJiraConnectorOptions(jiraConnectorOptions);
- fillServiceNowConnectorOptions(serviceNowConnectorOpions);
- fillIbmResilientConnectorOptions(ibmResilientConnectorOptions);
+ fillCasesMandatoryfields(getCase1());
+ fillJiraConnectorOptions(getJiraConnectorOptions());
+ fillServiceNowConnectorOptions(getServiceNowConnectorOptions());
+ fillIbmResilientConnectorOptions(getIbmResilientConnectorOptions());
createCase();
- cy.get(CONNECTOR_TITLE).should('have.text', ibmResilientConnectorOptions.title);
+ cy.get(CONNECTOR_TITLE).should('have.text', getIbmResilientConnectorOptions().title);
cy.get(CONNECTOR_CARD_DETAILS).should(
'have.text',
`${
- ibmResilientConnectorOptions.title
- }Incident Types: ${ibmResilientConnectorOptions.incidentTypes.join(', ')}Severity: ${
- ibmResilientConnectorOptions.severity
+ getIbmResilientConnectorOptions().title
+ }Incident Types: ${getIbmResilientConnectorOptions().incidentTypes.join(', ')}Severity: ${
+ getIbmResilientConnectorOptions().severity
}`
);
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts
index 9e55067ce4ed4..aa1bd7a5db5cc 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { serviceNowConnector } from '../../objects/case';
+import { getServiceNowConnector } from '../../objects/case';
import { SERVICE_NOW_MAPPING, TOASTER } from '../../screens/configure_cases';
@@ -77,7 +77,7 @@ describe('Cases connectors', () => {
loginAndWaitForPageWithoutDateRange(CASES_URL);
goToEditExternalConnection();
openAddNewConnectorOption();
- addServiceNowConnector(serviceNowConnector);
+ addServiceNowConnector(getServiceNowConnector());
cy.wait('@createConnector').then(({ response }) => {
cy.wrap(response!.statusCode).should('eql', 200);
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts
index c568aaae664a0..9e3b775156cab 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { case1 } from '../../objects/case';
+import { getCase1, TestCase } from '../../objects/case';
import {
ALL_CASES_CLOSED_CASES_STATS,
@@ -55,12 +55,12 @@ import { CASES_URL } from '../../urls/navigation';
describe('Cases', () => {
beforeEach(() => {
cleanKibana();
- createTimeline(case1.timeline).then((response) =>
+ createTimeline(getCase1().timeline).then((response) =>
cy
.wrap({
- ...case1,
+ ...getCase1(),
timeline: {
- ...case1.timeline,
+ ...getCase1().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
})
@@ -86,7 +86,7 @@ describe('Cases', () => {
cy.get(ALL_CASES_TAGS_COUNT).should('have.text', 'Tags2');
cy.get(ALL_CASES_NAME).should('have.text', this.mycase.name);
cy.get(ALL_CASES_REPORTER).should('have.text', this.mycase.reporter);
- (this.mycase as typeof case1).tags.forEach((tag, index) => {
+ (this.mycase as TestCase).tags.forEach((tag, index) => {
cy.get(ALL_CASES_TAGS(index)).should('have.text', tag);
});
cy.get(ALL_CASES_COMMENTS_COUNT).should('have.text', '0');
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
index d9ca43339d412..825cc7f8081e5 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
@@ -18,7 +18,7 @@ import { cleanKibana } from '../../tasks/common';
import { esArchiverLoad } from '../../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
-import { unmappedRule } from '../../objects/rule';
+import { getUnmappedRule } from '../../objects/rule';
import { ALERTS_URL } from '../../urls/navigation';
@@ -29,7 +29,7 @@ describe('Alert details with unmapped fields', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(unmappedRule);
+ createCustomRuleActivated(getUnmappedRule());
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
expandFirstAlert();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
index fb0b96c977e32..eaed80c484f60 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
@@ -7,7 +7,7 @@
import { ROLES } from '../../../common/test';
import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation';
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { PAGE_TITLE } from '../../screens/common/page';
import {
@@ -77,7 +77,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
@@ -127,7 +127,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
@@ -177,7 +177,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts
index 6cc5d2443e784..e052d1a3272ac 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { ROLES } from '../../../common/test';
import { waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded } from '../../tasks/alerts';
@@ -30,7 +30,7 @@ describe('Alerts timeline', () => {
loginAndWaitForPage(ALERTS_URL, ROLES.platform_engineer);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
refreshPage();
waitForAlertsToPopulate(500);
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts
index 6ae23733d6434..038bc30c90c1e 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import {
ALERTS,
ALERTS_COUNT,
@@ -39,7 +39,7 @@ describe('Closing alerts', () => {
loginAndWaitForPage(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule, '1', '100m', 100);
+ createCustomRuleActivated(getNewRule(), '1', '100m', 100);
refreshPage();
waitForAlertsToPopulate(100);
deleteCustomRule();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
index b03daf74ce247..522e25590994f 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newThreatIndicatorRule } from '../../objects/rule';
+import { getNewThreatIndicatorRule } from '../../objects/rule';
import { cleanKibana, reload } from '../../tasks/common';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
@@ -39,7 +39,7 @@ describe('CTI Enrichment', () => {
esArchiverLoad('suspicious_source_event');
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
- createCustomIndicatorRule(newThreatIndicatorRule);
+ createCustomIndicatorRule(getNewThreatIndicatorRule());
reload();
});
@@ -56,9 +56,9 @@ describe('CTI Enrichment', () => {
it('Displays enrichment matched.* fields on the timeline', () => {
const expectedFields = {
- 'threat.indicator.matched.atomic': newThreatIndicatorRule.atomic,
+ 'threat.indicator.matched.atomic': getNewThreatIndicatorRule().atomic,
'threat.indicator.matched.type': 'indicator_match_rule',
- 'threat.indicator.matched.field': newThreatIndicatorRule.indicatorMappingField,
+ 'threat.indicator.matched.field': getNewThreatIndicatorRule().indicatorMappingField,
};
const fields = Object.keys(expectedFields) as Array;
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts
index cb8694d5c35af..890f8a064aa9e 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import {
ALERTS,
ALERTS_COUNT,
@@ -36,7 +36,7 @@ describe('Marking alerts as in-progress', () => {
loginAndWaitForPage(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
refreshPage();
waitForAlertsToPopulate(500);
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts
index 115118b6762d9..01a06b3d59266 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { PROVIDER_BADGE } from '../../screens/timeline';
import {
@@ -27,7 +27,7 @@ describe('Alerts timeline', () => {
loginAndWaitForPage(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
refreshPage();
waitForAlertsToPopulate(500);
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts
index 20a863e742efd..0db30179284e0 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts
@@ -7,7 +7,7 @@
import { ROLES } from '../../../common/test';
import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation';
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { PAGE_TITLE } from '../../screens/common/page';
import {
@@ -95,7 +95,7 @@ describe('Detections > Callouts', () => {
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
@@ -145,7 +145,7 @@ describe('Detections > Callouts', () => {
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsPlatformEngineer(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts
index 6cbc82b93f446..4f78bdac84789 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import {
ALERTS_COUNT,
SELECTED_ALERTS,
@@ -37,7 +37,7 @@ describe('Opening alerts', () => {
loginAndWaitForPage(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
refreshPage();
waitForAlertsToPopulate(500);
selectNumberOfAlerts(5);
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts
index 218b1f7745d94..e30cde4989284 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts
@@ -7,11 +7,11 @@
import { formatMitreAttackDescription } from '../../helpers/rules';
import {
- newRule,
- existingRule,
- indexPatterns,
- editedRule,
- newOverrideRule,
+ getNewRule,
+ getExistingRule,
+ getIndexPatterns,
+ getEditedRule,
+ getNewOverrideRule,
} from '../../objects/rule';
import {
ALERT_RULE_METHOD,
@@ -120,19 +120,19 @@ import { activatesRule } from '../../tasks/rule_details';
import { ALERTS_URL } from '../../urls/navigation';
describe('Custom detection rules creation', () => {
- const expectedUrls = newRule.referenceUrls.join('');
- const expectedFalsePositives = newRule.falsePositivesExamples.join('');
- const expectedTags = newRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(newRule.mitre);
+ const expectedUrls = getNewRule().referenceUrls.join('');
+ const expectedFalsePositives = getNewRule().falsePositivesExamples.join('');
+ const expectedTags = getNewRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getNewRule().mitre);
const expectedNumberOfRules = 1;
beforeEach(() => {
cleanKibana();
- createTimeline(newRule.timeline).then((response) => {
+ createTimeline(getNewRule().timeline).then((response) => {
cy.wrap({
- ...newRule,
+ ...getNewRule(),
timeline: {
- ...newRule.timeline,
+ ...getNewRule().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@@ -201,7 +201,7 @@ describe('Custom detection rules creation', () => {
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
cy.get(DEFINITION_DETAILS).within(() => {
- getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join(''));
+ getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join(''));
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
@@ -209,11 +209,11 @@ describe('Custom detection rules creation', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
- `${newRule.runsEvery.interval}${newRule.runsEvery.type}`
+ `${getNewRule().runsEvery.interval}${getNewRule().runsEvery.type}`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
- `${newRule.lookBack.interval}${newRule.lookBack.type}`
+ `${getNewRule().lookBack.interval}${getNewRule().lookBack.type}`
);
});
@@ -236,9 +236,9 @@ describe('Custom detection rules deletion and edition', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule, 'rule1');
- createCustomRuleActivated(newOverrideRule, 'rule2');
- createCustomRuleActivated(existingRule, 'rule3');
+ createCustomRuleActivated(getNewRule(), 'rule1');
+ createCustomRuleActivated(getNewOverrideRule(), 'rule2');
+ createCustomRuleActivated(getExistingRule(), 'rule3');
reload();
});
@@ -303,16 +303,18 @@ describe('Custom detection rules deletion and edition', () => {
});
context('Edition', () => {
- const expectedEditedtags = editedRule.tags.join('');
+ const expectedEditedtags = getEditedRule().tags.join('');
const expectedEditedIndexPatterns =
- editedRule.index && editedRule.index.length ? editedRule.index : indexPatterns;
+ getEditedRule().index && getEditedRule().index.length
+ ? getEditedRule().index
+ : getIndexPatterns();
beforeEach(() => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(existingRule, 'rule1');
+ createCustomRuleActivated(getExistingRule(), 'rule1');
reload();
});
@@ -326,7 +328,7 @@ describe('Custom detection rules deletion and edition', () => {
cy.wait('@fetchRuleDetails').then(({ response }) => {
cy.wrap(response!.statusCode).should('eql', 200);
- cy.wrap(response!.body.max_signals).should('eql', existingRule.maxSignals);
+ cy.wrap(response!.body.max_signals).should('eql', getExistingRule().maxSignals);
cy.wrap(response!.body.enabled).should('eql', false);
});
});
@@ -336,25 +338,25 @@ describe('Custom detection rules deletion and edition', () => {
waitForKibana();
// expect define step to populate
- cy.get(CUSTOM_QUERY_INPUT).should('have.value', existingRule.customQuery);
- if (existingRule.index && existingRule.index.length > 0) {
- cy.get(DEFINE_INDEX_INPUT).should('have.text', existingRule.index.join(''));
+ cy.get(CUSTOM_QUERY_INPUT).should('have.value', getExistingRule().customQuery);
+ if (getExistingRule().index && getExistingRule().index.length > 0) {
+ cy.get(DEFINE_INDEX_INPUT).should('have.text', getExistingRule().index.join(''));
}
goToAboutStepTab();
// expect about step to populate
- cy.get(RULE_NAME_INPUT).invoke('val').should('eql', existingRule.name);
- cy.get(RULE_DESCRIPTION_INPUT).should('have.text', existingRule.description);
- cy.get(TAGS_FIELD).should('have.text', existingRule.tags.join(''));
- cy.get(SEVERITY_DROPDOWN).should('have.text', existingRule.severity);
- cy.get(DEFAULT_RISK_SCORE_INPUT).invoke('val').should('eql', existingRule.riskScore);
+ cy.get(RULE_NAME_INPUT).invoke('val').should('eql', getExistingRule().name);
+ cy.get(RULE_DESCRIPTION_INPUT).should('have.text', getExistingRule().description);
+ cy.get(TAGS_FIELD).should('have.text', getExistingRule().tags.join(''));
+ cy.get(SEVERITY_DROPDOWN).should('have.text', getExistingRule().severity);
+ cy.get(DEFAULT_RISK_SCORE_INPUT).invoke('val').should('eql', getExistingRule().riskScore);
goToScheduleStepTab();
// expect schedule step to populate
- const intervalParts =
- existingRule.interval && existingRule.interval.match(/[0-9]+|[a-zA-Z]+/g);
+ const interval = getExistingRule().interval;
+ const intervalParts = interval != null && interval.match(/[0-9]+|[a-zA-Z]+/g);
if (intervalParts) {
const [amount, unit] = intervalParts;
cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', amount);
@@ -380,7 +382,7 @@ describe('Custom detection rules deletion and edition', () => {
goToAboutStepTab();
cy.get(TAGS_CLEAR_BUTTON).click({ force: true });
- fillAboutRule(editedRule);
+ fillAboutRule(getEditedRule());
cy.intercept('GET', '/api/detection_engine/rules?id').as('getRule');
@@ -389,30 +391,30 @@ describe('Custom detection rules deletion and edition', () => {
cy.wait('@getRule').then(({ response }) => {
cy.wrap(response!.statusCode).should('eql', 200);
// ensure that editing rule does not modify max_signals
- cy.wrap(response!.body.max_signals).should('eql', existingRule.maxSignals);
+ cy.wrap(response!.body.max_signals).should('eql', getExistingRule().maxSignals);
});
- cy.get(RULE_NAME_HEADER).should('contain', `${editedRule.name}`);
- cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', editedRule.description);
+ cy.get(RULE_NAME_HEADER).should('contain', `${getEditedRule().name}`);
+ cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', getEditedRule().description);
cy.get(ABOUT_DETAILS).within(() => {
- getDetails(SEVERITY_DETAILS).should('have.text', editedRule.severity);
- getDetails(RISK_SCORE_DETAILS).should('have.text', editedRule.riskScore);
+ getDetails(SEVERITY_DETAILS).should('have.text', getEditedRule().severity);
+ getDetails(RISK_SCORE_DETAILS).should('have.text', getEditedRule().riskScore);
getDetails(TAGS_DETAILS).should('have.text', expectedEditedtags);
});
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
- cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', editedRule.note);
+ cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', getEditedRule().note);
cy.get(DEFINITION_DETAILS).within(() => {
getDetails(INDEX_PATTERNS_DETAILS).should(
'have.text',
expectedEditedIndexPatterns.join('')
);
- getDetails(CUSTOM_QUERY_DETAILS).should('have.text', editedRule.customQuery);
+ getDetails(CUSTOM_QUERY_DETAILS).should('have.text', getEditedRule().customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
});
- if (editedRule.interval) {
+ if (getEditedRule().interval) {
cy.get(SCHEDULE_DETAILS).within(() => {
- getDetails(RUNS_EVERY_DETAILS).should('have.text', editedRule.interval);
+ getDetails(RUNS_EVERY_DETAILS).should('have.text', getEditedRule().interval);
});
}
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts
index 337e2a8ec5033..677a9b5546494 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts
@@ -6,7 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
-import { eqlRule, eqlSequenceRule, indexPatterns } from '../../objects/rule';
+import { getEqlRule, getEqlSequenceRule, getIndexPatterns } from '../../objects/rule';
import {
ALERT_RULE_METHOD,
@@ -78,20 +78,20 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { ALERTS_URL } from '../../urls/navigation';
describe('Detection rules, EQL', () => {
- const expectedUrls = eqlRule.referenceUrls.join('');
- const expectedFalsePositives = eqlRule.falsePositivesExamples.join('');
- const expectedTags = eqlRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(eqlRule.mitre);
+ const expectedUrls = getEqlRule().referenceUrls.join('');
+ const expectedFalsePositives = getEqlRule().falsePositivesExamples.join('');
+ const expectedTags = getEqlRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getEqlRule().mitre);
const expectedNumberOfRules = 1;
const expectedNumberOfAlerts = 7;
beforeEach(() => {
cleanKibana();
- createTimeline(eqlRule.timeline).then((response) => {
+ createTimeline(getEqlRule().timeline).then((response) => {
cy.wrap({
- ...eqlRule,
+ ...getEqlRule(),
timeline: {
- ...eqlRule.timeline,
+ ...getEqlRule().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@@ -148,7 +148,7 @@ describe('Detection rules, EQL', () => {
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
cy.get(DEFINITION_DETAILS).within(() => {
- getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join(''));
+ getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join(''));
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Event Correlation');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
@@ -182,11 +182,11 @@ describe('Detection rules, sequence EQL', () => {
beforeEach(() => {
cleanKibana();
- createTimeline(eqlSequenceRule.timeline).then((response) => {
+ createTimeline(getEqlSequenceRule().timeline).then((response) => {
cy.wrap({
- ...eqlSequenceRule,
+ ...getEqlSequenceRule(),
timeline: {
- ...eqlSequenceRule.timeline,
+ ...getEqlSequenceRule().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts
index 1de636010f967..03086810a8435 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { expectedExportedRule, newRule } from '../../objects/rule';
+import { expectedExportedRule, getNewRule } from '../../objects/rule';
import {
goToManageAlertsDetectionRules,
waitForAlertsIndexToBeCreated,
@@ -28,7 +28,7 @@ describe('Export rules', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule).as('ruleResponse');
+ createCustomRule(getNewRule()).as('ruleResponse');
});
it('Exports a custom rule', function () {
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
index e1268c52f75d4..07b40df53e2d5 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
@@ -6,7 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
-import { indexPatterns, newThreatIndicatorRule } from '../../objects/rule';
+import { getIndexPatterns, getNewThreatIndicatorRule } from '../../objects/rule';
import {
ALERT_RULE_METHOD,
@@ -109,10 +109,10 @@ import { ALERTS_URL, RULE_CREATION } from '../../urls/navigation';
describe('indicator match', () => {
describe('Detection rules, Indicator Match', () => {
- const expectedUrls = newThreatIndicatorRule.referenceUrls.join('');
- const expectedFalsePositives = newThreatIndicatorRule.falsePositivesExamples.join('');
- const expectedTags = newThreatIndicatorRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(newThreatIndicatorRule.mitre);
+ const expectedUrls = getNewThreatIndicatorRule().referenceUrls.join('');
+ const expectedFalsePositives = getNewThreatIndicatorRule().falsePositivesExamples.join('');
+ const expectedTags = getNewThreatIndicatorRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getNewThreatIndicatorRule().mitre);
const expectedNumberOfRules = 1;
const expectedNumberOfAlerts = 1;
@@ -134,12 +134,12 @@ describe('indicator match', () => {
describe('Index patterns', () => {
it('Contains a predefined index pattern', () => {
- getIndicatorIndex().should('have.text', indexPatterns.join(''));
+ getIndicatorIndex().should('have.text', getIndexPatterns().join(''));
});
it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => {
getIndicatorIndicatorIndex().type(
- `${newThreatIndicatorRule.indicatorIndexPattern}{enter}`
+ `${getNewThreatIndicatorRule().indicatorIndexPattern}{enter}`
);
getDefineContinueButton().click();
getIndexPatternInvalidationText().should('not.exist');
@@ -148,7 +148,7 @@ describe('indicator match', () => {
it('Shows invalidation text when you try to continue without filling it out', () => {
getIndexPatternClearButton().click();
getIndicatorIndicatorIndex().type(
- `${newThreatIndicatorRule.indicatorIndexPattern}{enter}`
+ `${getNewThreatIndicatorRule().indicatorIndexPattern}{enter}`
);
getDefineContinueButton().click();
getIndexPatternInvalidationText().should('exist');
@@ -195,8 +195,8 @@ describe('indicator match', () => {
describe('Indicator mapping', () => {
beforeEach(() => {
fillIndexAndIndicatorIndexPattern(
- newThreatIndicatorRule.index,
- newThreatIndicatorRule.indicatorIndexPattern
+ getNewThreatIndicatorRule().index,
+ getNewThreatIndicatorRule().indicatorIndexPattern
);
});
@@ -221,8 +221,8 @@ describe('indicator match', () => {
it('Does NOT show invalidation text when there is a valid "index field" and a valid "indicator index field"', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getDefineContinueButton().click();
getIndicatorInvalidationText().should('not.exist');
@@ -231,7 +231,7 @@ describe('indicator match', () => {
it('Shows invalidation text when there is an invalid "index field" and a valid "indicator index field"', () => {
fillIndicatorMatchRow({
indexField: 'non-existent-value',
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
validColumns: 'indicatorField',
});
getDefineContinueButton().click();
@@ -240,7 +240,7 @@ describe('indicator match', () => {
it('Shows invalidation text when there is a valid "index field" and an invalid "indicator index field"', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
indicatorIndexField: 'non-existent-value',
validColumns: 'indexField',
});
@@ -250,21 +250,21 @@ describe('indicator match', () => {
it('Deletes the first row when you have two rows. Both rows valid rows of "index fields" and valid "indicator index fields". The second row should become the first row', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
indexField: 'agent.name',
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
validColumns: 'indicatorField',
});
getIndicatorDeleteButton().click();
getIndicatorIndexComboField().should('have.text', 'agent.name');
getIndicatorMappingComboField().should(
'have.text',
- newThreatIndicatorRule.indicatorIndexField
+ getNewThreatIndicatorRule().indicatorIndexField
);
getIndicatorIndexComboField(2).should('not.exist');
getIndicatorMappingComboField(2).should('not.exist');
@@ -272,14 +272,14 @@ describe('indicator match', () => {
it('Deletes the first row when you have two rows. Both rows have valid "index fields" and invalid "indicator index fields". The second row should become the first row', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
indicatorIndexField: 'non-existent-value',
validColumns: 'indexField',
});
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
- indexField: newThreatIndicatorRule.indicatorMappingField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
indicatorIndexField: 'second-non-existent-value',
validColumns: 'indexField',
});
@@ -292,14 +292,14 @@ describe('indicator match', () => {
it('Deletes the first row when you have two rows. Both rows have valid "indicator index fields" and invalid "index fields". The second row should become the first row', () => {
fillIndicatorMatchRow({
indexField: 'non-existent-value',
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
validColumns: 'indicatorField',
});
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
indexField: 'second-non-existent-value',
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
validColumns: 'indicatorField',
});
getIndicatorDeleteButton().click();
@@ -310,8 +310,8 @@ describe('indicator match', () => {
it('Deletes the first row of data but not the UI elements and the text defaults back to the placeholder of Search', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorDeleteButton().click();
getIndicatorIndexComboField().should('text', 'Search');
@@ -322,8 +322,8 @@ describe('indicator match', () => {
it('Deletes the second row when you have three rows. The first row is valid data, the second row is invalid data, and the third row is valid data. Third row should shift up correctly', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorAndButton().click();
fillIndicatorMatchRow({
@@ -335,25 +335,25 @@ describe('indicator match', () => {
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 3,
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorDeleteButton(2).click();
getIndicatorIndexComboField(1).should(
'text',
- newThreatIndicatorRule.indicatorMappingField
+ getNewThreatIndicatorRule().indicatorMappingField
);
getIndicatorMappingComboField(1).should(
'text',
- newThreatIndicatorRule.indicatorIndexField
+ getNewThreatIndicatorRule().indicatorIndexField
);
getIndicatorIndexComboField(2).should(
'text',
- newThreatIndicatorRule.indicatorMappingField
+ getNewThreatIndicatorRule().indicatorMappingField
);
getIndicatorMappingComboField(2).should(
'text',
- newThreatIndicatorRule.indicatorIndexField
+ getNewThreatIndicatorRule().indicatorIndexField
);
getIndicatorIndexComboField(3).should('not.exist');
getIndicatorMappingComboField(3).should('not.exist');
@@ -368,17 +368,17 @@ describe('indicator match', () => {
getIndicatorOrButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorDeleteButton().click();
getIndicatorIndexComboField().should(
'text',
- newThreatIndicatorRule.indicatorMappingField
+ getNewThreatIndicatorRule().indicatorMappingField
);
getIndicatorMappingComboField().should(
'text',
- newThreatIndicatorRule.indicatorIndexField
+ getNewThreatIndicatorRule().indicatorIndexField
);
getIndicatorIndexComboField(2).should('not.exist');
getIndicatorMappingComboField(2).should('not.exist');
@@ -399,9 +399,9 @@ describe('indicator match', () => {
waitForRulesTableToBeLoaded();
goToCreateNewRule();
selectIndicatorMatchType();
- fillDefineIndicatorMatchRuleAndContinue(newThreatIndicatorRule);
- fillAboutRuleAndContinue(newThreatIndicatorRule);
- fillScheduleRuleAndContinue(newThreatIndicatorRule);
+ fillDefineIndicatorMatchRuleAndContinue(getNewThreatIndicatorRule());
+ fillAboutRuleAndContinue(getNewThreatIndicatorRule());
+ fillScheduleRuleAndContinue(getNewThreatIndicatorRule());
createAndActivateRule();
cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');
@@ -417,18 +417,18 @@ describe('indicator match', () => {
cy.get(RULES_TABLE).then(($table) => {
cy.wrap($table.find(RULES_ROW).length).should('eql', 1);
});
- cy.get(RULE_NAME).should('have.text', newThreatIndicatorRule.name);
- cy.get(RISK_SCORE).should('have.text', newThreatIndicatorRule.riskScore);
- cy.get(SEVERITY).should('have.text', newThreatIndicatorRule.severity);
+ cy.get(RULE_NAME).should('have.text', getNewThreatIndicatorRule().name);
+ cy.get(RISK_SCORE).should('have.text', getNewThreatIndicatorRule().riskScore);
+ cy.get(SEVERITY).should('have.text', getNewThreatIndicatorRule().severity);
cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true');
goToRuleDetails();
- cy.get(RULE_NAME_HEADER).should('contain', `${newThreatIndicatorRule.name}`);
- cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newThreatIndicatorRule.description);
+ cy.get(RULE_NAME_HEADER).should('contain', `${getNewThreatIndicatorRule().name}`);
+ cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', getNewThreatIndicatorRule().description);
cy.get(ABOUT_DETAILS).within(() => {
- getDetails(SEVERITY_DETAILS).should('have.text', newThreatIndicatorRule.severity);
- getDetails(RISK_SCORE_DETAILS).should('have.text', newThreatIndicatorRule.riskScore);
+ getDetails(SEVERITY_DETAILS).should('have.text', getNewThreatIndicatorRule().severity);
+ getDetails(RISK_SCORE_DETAILS).should('have.text', getNewThreatIndicatorRule().riskScore);
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
});
@@ -444,18 +444,20 @@ describe('indicator match', () => {
cy.get(DEFINITION_DETAILS).within(() => {
getDetails(INDEX_PATTERNS_DETAILS).should(
'have.text',
- newThreatIndicatorRule.index.join('')
+ getNewThreatIndicatorRule().index.join('')
);
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', '*:*');
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Indicator Match');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
getDetails(INDICATOR_INDEX_PATTERNS).should(
'have.text',
- newThreatIndicatorRule.indicatorIndexPattern.join('')
+ getNewThreatIndicatorRule().indicatorIndexPattern.join('')
);
getDetails(INDICATOR_MAPPING).should(
'have.text',
- `${newThreatIndicatorRule.indicatorMappingField} MATCHES ${newThreatIndicatorRule.indicatorIndexField}`
+ `${getNewThreatIndicatorRule().indicatorMappingField} MATCHES ${
+ getNewThreatIndicatorRule().indicatorIndexField
+ }`
);
getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*');
});
@@ -463,11 +465,15 @@ describe('indicator match', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
- `${newThreatIndicatorRule.runsEvery.interval}${newThreatIndicatorRule.runsEvery.type}`
+ `${getNewThreatIndicatorRule().runsEvery.interval}${
+ getNewThreatIndicatorRule().runsEvery.type
+ }`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
- `${newThreatIndicatorRule.lookBack.interval}${newThreatIndicatorRule.lookBack.type}`
+ `${getNewThreatIndicatorRule().lookBack.interval}${
+ getNewThreatIndicatorRule().lookBack.type
+ }`
);
});
@@ -475,13 +481,15 @@ describe('indicator match', () => {
waitForAlertsToPopulate();
cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts);
- cy.get(ALERT_RULE_NAME).first().should('have.text', newThreatIndicatorRule.name);
+ cy.get(ALERT_RULE_NAME).first().should('have.text', getNewThreatIndicatorRule().name);
cy.get(ALERT_RULE_VERSION).first().should('have.text', '1');
cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threat_match');
cy.get(ALERT_RULE_SEVERITY)
.first()
- .should('have.text', newThreatIndicatorRule.severity.toLowerCase());
- cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', newThreatIndicatorRule.riskScore);
+ .should('have.text', getNewThreatIndicatorRule().severity.toLowerCase());
+ cy.get(ALERT_RULE_RISK_SCORE)
+ .first()
+ .should('have.text', getNewThreatIndicatorRule().riskScore);
});
it('Investigate alert in timeline', () => {
@@ -492,7 +500,7 @@ describe('indicator match', () => {
loadPrepackagedTimelineTemplates();
goToManageAlertsDetectionRules();
- createCustomIndicatorRule(newThreatIndicatorRule);
+ createCustomIndicatorRule(getNewThreatIndicatorRule());
reload();
goToRuleDetails();
@@ -502,13 +510,25 @@ describe('indicator match', () => {
cy.get(PROVIDER_BADGE).should('have.length', 3);
cy.get(PROVIDER_BADGE).should(
'have.text',
- `threat.indicator.matched.atomic: "${newThreatIndicatorRule.atomic}"threat.indicator.matched.type: "indicator_match_rule"threat.indicator.matched.field: "${newThreatIndicatorRule.indicatorMappingField}"`
+ `threat.indicator.matched.atomic: "${
+ getNewThreatIndicatorRule().atomic
+ }"threat.indicator.matched.type: "indicator_match_rule"threat.indicator.matched.field: "${
+ getNewThreatIndicatorRule().indicatorMappingField
+ }"`
);
cy.readFile(threatIndicatorPath).then((threatIndicator) => {
cy.get(INDICATOR_MATCH_ROW_RENDER).should(
'have.text',
- `threat.indicator.matched.field${newThreatIndicatorRule.indicatorMappingField}${accessibilityText}matched${newThreatIndicatorRule.indicatorMappingField}${newThreatIndicatorRule.atomic}${accessibilityText}threat.indicator.matched.typeindicator_match_rule${accessibilityText}fromthreat.indicator.event.dataset${threatIndicator.value.source.event.dataset}${accessibilityText}:threat.indicator.event.reference${threatIndicator.value.source.event.reference}(opens in a new tab or window)${accessibilityText}`
+ `threat.indicator.matched.field${
+ getNewThreatIndicatorRule().indicatorMappingField
+ }${accessibilityText}matched${getNewThreatIndicatorRule().indicatorMappingField}${
+ getNewThreatIndicatorRule().atomic
+ }${accessibilityText}threat.indicator.matched.typeindicator_match_rule${accessibilityText}fromthreat.indicator.event.dataset${
+ threatIndicator.value.source.event.dataset
+ }${accessibilityText}:threat.indicator.event.reference${
+ threatIndicator.value.source.event.reference
+ }(opens in a new tab or window)${accessibilityText}`
);
});
});
@@ -519,7 +539,7 @@ describe('indicator match', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
- createCustomIndicatorRule(newThreatIndicatorRule);
+ createCustomIndicatorRule(getNewThreatIndicatorRule());
reload();
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts
index fdc4bce677f74..85eb68a6cdfa9 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { RULES_MONIROTING_TABLE, RULE_NAME } from '../../screens/alerts_detection_rules';
import { goToManageAlertsDetectionRules, waitForAlertsIndexToBeCreated } from '../../tasks/alerts';
import { createCustomRuleActivated } from '../../tasks/api_calls/rules';
@@ -19,7 +19,7 @@ describe('Rules talbes links', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule, 'rule1');
+ createCustomRuleActivated(getNewRule(), 'rule1');
reload();
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts
index 2d869b314b67c..e66f8f55be986 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts
@@ -6,7 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
-import { machineLearningRule } from '../../objects/rule';
+import { getMachineLearningRule } from '../../objects/rule';
import {
CUSTOM_RULES_BTN,
@@ -65,10 +65,10 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { ALERTS_URL } from '../../urls/navigation';
describe('Detection rules, machine learning', () => {
- const expectedUrls = machineLearningRule.referenceUrls.join('');
- const expectedFalsePositives = machineLearningRule.falsePositivesExamples.join('');
- const expectedTags = machineLearningRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(machineLearningRule.mitre);
+ const expectedUrls = getMachineLearningRule().referenceUrls.join('');
+ const expectedFalsePositives = getMachineLearningRule().falsePositivesExamples.join('');
+ const expectedTags = getMachineLearningRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getMachineLearningRule().mitre);
const expectedNumberOfRules = 1;
beforeEach(() => {
@@ -83,9 +83,9 @@ describe('Detection rules, machine learning', () => {
waitForRulesTableToBeLoaded();
goToCreateNewRule();
selectMachineLearningRuleType();
- fillDefineMachineLearningRuleAndContinue(machineLearningRule);
- fillAboutRuleAndContinue(machineLearningRule);
- fillScheduleRuleAndContinue(machineLearningRule);
+ fillDefineMachineLearningRuleAndContinue(getMachineLearningRule());
+ fillAboutRuleAndContinue(getMachineLearningRule());
+ fillScheduleRuleAndContinue(getMachineLearningRule());
createAndActivateRule();
cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');
@@ -101,18 +101,18 @@ describe('Detection rules, machine learning', () => {
cy.get(RULES_TABLE).then(($table) => {
cy.wrap($table.find(RULES_ROW).length).should('eql', 1);
});
- cy.get(RULE_NAME).should('have.text', machineLearningRule.name);
- cy.get(RISK_SCORE).should('have.text', machineLearningRule.riskScore);
- cy.get(SEVERITY).should('have.text', machineLearningRule.severity);
+ cy.get(RULE_NAME).should('have.text', getMachineLearningRule().name);
+ cy.get(RISK_SCORE).should('have.text', getMachineLearningRule().riskScore);
+ cy.get(SEVERITY).should('have.text', getMachineLearningRule().severity);
cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true');
goToRuleDetails();
- cy.get(RULE_NAME_HEADER).should('contain', `${machineLearningRule.name}`);
- cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', machineLearningRule.description);
+ cy.get(RULE_NAME_HEADER).should('contain', `${getMachineLearningRule().name}`);
+ cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', getMachineLearningRule().description);
cy.get(ABOUT_DETAILS).within(() => {
- getDetails(SEVERITY_DETAILS).should('have.text', machineLearningRule.severity);
- getDetails(RISK_SCORE_DETAILS).should('have.text', machineLearningRule.riskScore);
+ getDetails(SEVERITY_DETAILS).should('have.text', getMachineLearningRule().severity);
+ getDetails(RISK_SCORE_DETAILS).should('have.text', getMachineLearningRule().riskScore);
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
});
@@ -125,11 +125,11 @@ describe('Detection rules, machine learning', () => {
cy.get(DEFINITION_DETAILS).within(() => {
getDetails(ANOMALY_SCORE_DETAILS).should(
'have.text',
- machineLearningRule.anomalyScoreThreshold
+ getMachineLearningRule().anomalyScoreThreshold
);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Machine Learning');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
- machineLearningRule.machineLearningJobs.forEach((machineLearningJob, jobIndex) => {
+ getMachineLearningRule().machineLearningJobs.forEach((machineLearningJob, jobIndex) => {
cy.get(MACHINE_LEARNING_JOB_STATUS).eq(jobIndex).should('have.text', 'Stopped');
cy.get(MACHINE_LEARNING_JOB_ID).eq(jobIndex).should('have.text', machineLearningJob);
});
@@ -137,11 +137,11 @@ describe('Detection rules, machine learning', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
- `${machineLearningRule.runsEvery.interval}${machineLearningRule.runsEvery.type}`
+ `${getMachineLearningRule().runsEvery.interval}${getMachineLearningRule().runsEvery.type}`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
- `${machineLearningRule.lookBack.interval}${machineLearningRule.lookBack.type}`
+ `${getMachineLearningRule().lookBack.interval}${getMachineLearningRule().lookBack.type}`
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts
index a791cc293c1f0..24a56dd563e17 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts
@@ -7,9 +7,9 @@
import { formatMitreAttackDescription } from '../../helpers/rules';
import {
- indexPatterns,
- newOverrideRule,
- severitiesOverride,
+ getIndexPatterns,
+ getNewOverrideRule,
+ getSeveritiesOverride,
OverrideRule,
} from '../../objects/rule';
@@ -89,18 +89,18 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { ALERTS_URL } from '../../urls/navigation';
describe('Detection rules, override', () => {
- const expectedUrls = newOverrideRule.referenceUrls.join('');
- const expectedFalsePositives = newOverrideRule.falsePositivesExamples.join('');
- const expectedTags = newOverrideRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(newOverrideRule.mitre);
+ const expectedUrls = getNewOverrideRule().referenceUrls.join('');
+ const expectedFalsePositives = getNewOverrideRule().falsePositivesExamples.join('');
+ const expectedTags = getNewOverrideRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getNewOverrideRule().mitre);
beforeEach(() => {
cleanKibana();
- createTimeline(newOverrideRule.timeline).then((response) => {
+ createTimeline(getNewOverrideRule().timeline).then((response) => {
cy.wrap({
- ...newOverrideRule,
+ ...getNewOverrideRule(),
timeline: {
- ...newOverrideRule.timeline,
+ ...getNewOverrideRule().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@@ -167,7 +167,7 @@ describe('Detection rules, override', () => {
.eq(severityOverrideIndex + i)
.should(
'have.text',
- `${severity.sourceField}:${severity.sourceValue}${severitiesOverride[i]}`
+ `${severity.sourceField}:${severity.sourceValue}${getSeveritiesOverride()[i]}`
);
});
});
@@ -175,7 +175,7 @@ describe('Detection rules, override', () => {
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
cy.get(DEFINITION_DETAILS).within(() => {
- getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join(''));
+ getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join(''));
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts
index 7d42ea533a9ae..ef3d3a82d40bd 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts
@@ -39,7 +39,12 @@ import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../../common/constants';
import { ALERTS_URL } from '../../urls/navigation';
import { createCustomRule } from '../../tasks/api_calls/rules';
import { cleanKibana } from '../../tasks/common';
-import { existingRule, newOverrideRule, newRule, newThresholdRule } from '../../objects/rule';
+import {
+ getExistingRule,
+ getNewOverrideRule,
+ getNewRule,
+ getNewThresholdRule,
+} from '../../objects/rule';
describe('Alerts detection rules', () => {
beforeEach(() => {
@@ -47,10 +52,10 @@ describe('Alerts detection rules', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule, '1');
- createCustomRule(existingRule, '2');
- createCustomRule(newOverrideRule, '3');
- createCustomRule(newThresholdRule, '4');
+ createCustomRule(getNewRule(), '1');
+ createCustomRule(getExistingRule(), '2');
+ createCustomRule(getNewOverrideRule(), '3');
+ createCustomRule(getNewThresholdRule(), '4');
});
it('Sorts by activated rules', () => {
@@ -90,8 +95,8 @@ describe('Alerts detection rules', () => {
});
it('Pagination updates page number and results', () => {
- createCustomRule({ ...newRule, name: 'Test a rule' }, '5');
- createCustomRule({ ...newRule, name: 'Not same as first rule' }, '6');
+ createCustomRule({ ...getNewRule(), name: 'Test a rule' }, '5');
+ createCustomRule({ ...getNewRule(), name: 'Not same as first rule' }, '6');
goToManageAlertsDetectionRules();
waitForRulesTableToBeLoaded();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts
index ce00c9b40aead..dba12fb4ab95c 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts
@@ -6,7 +6,12 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
-import { indexPatterns, newRule, newThresholdRule, ThresholdRule } from '../../objects/rule';
+import {
+ getIndexPatterns,
+ getNewRule,
+ getNewThresholdRule,
+ ThresholdRule,
+} from '../../objects/rule';
import {
ALERT_RULE_METHOD,
@@ -84,16 +89,16 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { ALERTS_URL } from '../../urls/navigation';
describe('Detection rules, threshold', () => {
- const expectedUrls = newThresholdRule.referenceUrls.join('');
- const expectedFalsePositives = newThresholdRule.falsePositivesExamples.join('');
- const expectedTags = newThresholdRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(newThresholdRule.mitre);
-
- const rule = { ...newThresholdRule };
+ let rule = getNewThresholdRule();
+ const expectedUrls = getNewThresholdRule().referenceUrls.join('');
+ const expectedFalsePositives = getNewThresholdRule().falsePositivesExamples.join('');
+ const expectedTags = getNewThresholdRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getNewThresholdRule().mitre);
beforeEach(() => {
+ rule = getNewThresholdRule();
cleanKibana();
- createTimeline(newThresholdRule.timeline).then((response) => {
+ createTimeline(getNewThresholdRule().timeline).then((response) => {
rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId;
});
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
@@ -149,7 +154,7 @@ describe('Detection rules, threshold', () => {
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
cy.get(DEFINITION_DETAILS).within(() => {
- getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join(''));
+ getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join(''));
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Threshold');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
@@ -181,15 +186,14 @@ describe('Detection rules, threshold', () => {
});
it('Preview results of keyword using "host.name"', () => {
- const previewRule: ThresholdRule = { ...newThresholdRule };
- previewRule.index = [...previewRule.index, '.siem-signals*'];
+ rule.index = [...rule.index, '.siem-signals*'];
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
goToManageAlertsDetectionRules();
waitForRulesTableToBeLoaded();
goToCreateNewRule();
selectThresholdRuleType();
- fillDefineThresholdRule(previewRule);
+ fillDefineThresholdRule(rule);
previewResults();
cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '3 unique hits');
@@ -197,13 +201,13 @@ describe('Detection rules, threshold', () => {
it('Preview results of "ip" using "source.ip"', () => {
const previewRule: ThresholdRule = {
- ...newThresholdRule,
+ ...rule,
thresholdField: 'source.ip',
threshold: '1',
};
previewRule.index = [...previewRule.index, '.siem-signals*'];
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
goToManageAlertsDetectionRules();
waitForRulesTableToBeLoaded();
goToCreateNewRule();
diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts
index a4b929f7d8e1d..7eedc99652f80 100644
--- a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { RULE_STATUS } from '../../screens/create_new_rule';
@@ -44,7 +44,7 @@ describe('Exceptions modal', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
goToManageAlertsDetectionRules();
goToRuleDetails();
diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts
index 83277075b35cc..051ebbb9643f6 100644
--- a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts
@@ -5,8 +5,12 @@
* 2.0.
*/
-import { exception, exceptionList, expectedExportedExceptionList } from '../../objects/exception';
-import { newRule } from '../../objects/rule';
+import {
+ getException,
+ getExceptionList,
+ expectedExportedExceptionList,
+} from '../../objects/exception';
+import { getNewRule } from '../../objects/rule';
import { RULE_STATUS } from '../../screens/create_new_rule';
@@ -46,7 +50,7 @@ describe('Exceptions Table', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
goToManageAlertsDetectionRules();
goToRuleDetails();
@@ -56,11 +60,11 @@ describe('Exceptions Table', () => {
// Add a detections exception list
goToExceptionsTab();
- addsExceptionFromRuleSettings(exception);
+ addsExceptionFromRuleSettings(getException());
waitForTheRuleToBeExecuted();
// Create exception list not used by any rules
- createExceptionList(exceptionList).as('exceptionListResponse');
+ createExceptionList(getExceptionList()).as('exceptionListResponse');
goBackToAllRulesTable();
waitForRulesTableToBeLoaded();
diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts
index 4918de7488ddd..8a683aacd5f66 100644
--- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
-import { exception } from '../../objects/exception';
-import { newRule } from '../../objects/rule';
+import { getException } from '../../objects/exception';
+import { getNewRule } from '../../objects/rule';
import { ALERTS_COUNT, NUMBER_OF_ALERTS } from '../../screens/alerts';
import { RULE_STATUS } from '../../screens/create_new_rule';
@@ -43,7 +43,7 @@ describe('From alert', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule, 'rule_testing', '10s');
+ createCustomRule(getNewRule(), 'rule_testing', '10s');
goToManageAlertsDetectionRules();
goToRuleDetails();
@@ -66,7 +66,7 @@ describe('From alert', () => {
it('Creates an exception and deletes it', () => {
addExceptionFromFirstAlert();
- addsException(exception);
+ addsException(getException());
esArchiverLoad('auditbeat_for_exceptions2');
cy.get(ALERTS_COUNT).should('exist');
diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts
index ea8988456d8b3..8fa0050a36521 100644
--- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
-import { exception } from '../../objects/exception';
-import { newRule } from '../../objects/rule';
+import { getException } from '../../objects/exception';
+import { getNewRule } from '../../objects/rule';
import { ALERTS_COUNT, NUMBER_OF_ALERTS } from '../../screens/alerts';
import { RULE_STATUS } from '../../screens/create_new_rule';
@@ -41,7 +41,7 @@ describe('From rule', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule, 'rule_testing', '10s');
+ createCustomRule(getNewRule(), 'rule_testing', '10s');
goToManageAlertsDetectionRules();
goToRuleDetails();
@@ -64,7 +64,7 @@ describe('From rule', () => {
it('Creates an exception and deletes it', () => {
goToExceptionsTab();
- addsExceptionFromRuleSettings(exception);
+ addsExceptionFromRuleSettings(getException());
esArchiverLoad('auditbeat_for_exceptions2');
waitForTheRuleToBeExecuted();
goToAlertsTab();
diff --git a/x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts
index d7bef9d67df2f..c02c2bd9ec139 100644
--- a/x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts
@@ -8,7 +8,7 @@
import { loginAndWaitForPage } from '../../tasks/login';
import { openAddFilterPopover, fillAddFilterForm } from '../../tasks/search_bar';
import { GLOBAL_SEARCH_BAR_FILTER_ITEM } from '../../screens/search_bar';
-import { hostIpFilter } from '../../objects/filter';
+import { getHostIpFilter } from '../../objects/filter';
import { HOSTS_URL } from '../../urls/navigation';
import { waitForAllHostsToBeLoaded } from '../../tasks/hosts/all_hosts';
@@ -23,11 +23,11 @@ describe('SearchBar', () => {
it('adds correctly a filter to the global search bar', () => {
openAddFilterPopover();
- fillAddFilterForm(hostIpFilter);
+ fillAddFilterForm(getHostIpFilter());
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should(
'have.text',
- `${hostIpFilter.key}: ${hostIpFilter.value}`
+ `${getHostIpFilter().key}: ${getHostIpFilter().value}`
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts
index 3ff036fa0107f..ca9f83183ab10 100644
--- a/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts
@@ -16,7 +16,7 @@ import overviewFixture from '../../fixtures/overview_search_strategy.json';
import emptyInstance from '../../fixtures/empty_instance.json';
import { cleanKibana } from '../../tasks/common';
import { createTimeline, favoriteTimeline } from '../../tasks/api_calls/timelines';
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
describe('Overview Page', () => {
before(() => {
@@ -53,7 +53,7 @@ describe('Overview Page', () => {
describe('Favorite Timelines', () => {
it('should appear on overview page', () => {
- createTimeline(timeline)
+ createTimeline(getTimeline())
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
.then((timelineId: string) => {
favoriteTimeline({ timelineId, timelineType: 'default' }).then(() => {
@@ -61,7 +61,7 @@ describe('Overview Page', () => {
loginAndWaitForPage(OVERVIEW_URL);
cy.get('[data-test-subj="overview-recent-timelines"]').should(
'contain',
- timeline.title
+ getTimeline().title
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts
index e2c1d7eef38c3..3930088f8bfdd 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts
@@ -5,14 +5,13 @@
* 2.0.
*/
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import {
FAVORITE_TIMELINE,
LOCKED_ICON,
NOTES,
NOTES_TAB_BUTTON,
- // NOTES_COUNT,
NOTES_TEXT_AREA,
PIN_EVENT,
TIMELINE_DESCRIPTION,
@@ -61,7 +60,7 @@ describe('Timeline Templates', () => {
openTimelineUsingToggle();
createNewTimelineTemplate();
populateTimeline();
- addFilter(timeline.filter);
+ addFilter(getTimeline().filter);
cy.get(PIN_EVENT).should(
'have.attr',
'aria-label',
@@ -69,21 +68,21 @@ describe('Timeline Templates', () => {
);
cy.get(LOCKED_ICON).should('be.visible');
- addNameToTimeline(timeline.title);
+ addNameToTimeline(getTimeline().title);
cy.wait('@timeline').then(({ response }) => {
const timelineId = response!.body.data.persistTimeline.timeline.savedObjectId;
- addDescriptionToTimeline(timeline.description);
- addNotesToTimeline(timeline.notes);
+ addDescriptionToTimeline(getTimeline().description);
+ addNotesToTimeline(getTimeline().notes);
markAsFavorite();
waitForTimelineChanges();
createNewTimelineTemplate();
closeTimeline();
openTimelineTemplateFromSettings(timelineId);
- cy.contains(timeline.title).should('exist');
- cy.get(TIMELINES_DESCRIPTION).first().should('have.text', timeline.description);
+ cy.contains(getTimeline().title).should('exist');
+ cy.get(TIMELINES_DESCRIPTION).first().should('have.text', getTimeline().description);
cy.get(TIMELINES_PINNED_EVENT_COUNT).first().should('have.text', '1');
cy.get(TIMELINES_NOTES_COUNT).first().should('have.text', '1');
cy.get(TIMELINES_FAVORITE).first().should('exist');
@@ -91,30 +90,30 @@ describe('Timeline Templates', () => {
openTimeline(timelineId);
cy.get(FAVORITE_TIMELINE).should('exist');
- cy.get(TIMELINE_TITLE).should('have.text', timeline.title);
- cy.get(TIMELINE_DESCRIPTION).should('have.text', timeline.description);
- cy.get(TIMELINE_QUERY).should('have.text', timeline.query);
+ cy.get(TIMELINE_TITLE).should('have.text', getTimeline().title);
+ cy.get(TIMELINE_DESCRIPTION).should('have.text', getTimeline().description);
+ cy.get(TIMELINE_QUERY).should('have.text', getTimeline().query);
// Comments this assertion until we agreed what to do with the filters.
// cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
// cy.get(NOTES_COUNT).should('have.text', '1');
cy.get(NOTES_TAB_BUTTON).click();
cy.get(NOTES_TEXT_AREA).should('exist');
- cy.get(NOTES).should('have.text', timeline.notes);
+ cy.get(NOTES).should('have.text', getTimeline().notes);
});
});
it('Create template from timeline', () => {
waitForTimelinesPanelToBeLoaded();
- createTimeline(timeline).then(() => {
+ createTimeline(getTimeline()).then(() => {
expandEventAction();
clickingOnCreateTemplateFromTimelineBtn();
cy.wait('@timeline', { timeout: 100000 }).then(({ request }) => {
expect(request.body.timeline).to.haveOwnProperty('templateTimelineId');
- expect(request.body.timeline).to.haveOwnProperty('description', timeline.description);
+ expect(request.body.timeline).to.haveOwnProperty('description', getTimeline().description);
expect(request.body.timeline.kqlQuery.filterQuery.kuery).to.haveOwnProperty(
'expression',
- timeline.query
+ getTimeline().query
);
cy.get(TIMELINE_FLYOUT_WRAPPER).should('have.css', 'visibility', 'visible');
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts
index aa0a6c9308a52..5c2d87c9b727f 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts
@@ -9,7 +9,7 @@ import { exportTimeline } from '../../tasks/timelines';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import {
expectedExportedTimelineTemplate,
- timeline as timelineTemplate,
+ getTimeline as getTimelineTemplate,
} from '../../objects/timeline';
import { TIMELINE_TEMPLATES_URL } from '../../urls/navigation';
@@ -20,7 +20,7 @@ describe('Export timelines', () => {
beforeEach(() => {
cleanKibana();
cy.intercept('POST', 'api/timeline/_export?file_name=timelines_export.ndjson').as('export');
- createTimelineTemplate(timelineTemplate).then((response) => {
+ createTimelineTemplate(getTimelineTemplate()).then((response) => {
cy.wrap(response).as('templateResponse');
cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('templateId');
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts
index 8a90b67682cb2..4203b9125d155 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import {
LOCKED_ICON,
@@ -64,7 +64,7 @@ describe('Timelines', (): void => {
before(() => {
openTimelineUsingToggle();
- addNameAndDescriptionToTimeline(timeline);
+ addNameAndDescriptionToTimeline(getTimeline());
populateTimeline();
});
@@ -73,8 +73,8 @@ describe('Timelines', (): void => {
});
it('can be added filter', () => {
- addFilter(timeline.filter);
- cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
+ addFilter(getTimeline().filter);
+ cy.get(TIMELINE_FILTER(getTimeline().filter)).should('exist');
});
it('pins an event', () => {
@@ -89,8 +89,8 @@ describe('Timelines', (): void => {
});
it('can be added notes', () => {
- addNotesToTimeline(timeline.notes);
- cy.get(NOTES_TEXT).should('have.text', timeline.notes);
+ addNotesToTimeline(getTimeline().notes);
+ cy.get(NOTES_TEXT).should('have.text', getTimeline().notes);
});
it('should update timeline after adding eql', () => {
@@ -116,17 +116,20 @@ describe('Create a timeline from a template', () => {
});
it('Should have the same query and open the timeline modal', () => {
- createTimelineTemplate(timeline).then(() => {
+ createTimelineTemplate(getTimeline()).then(() => {
expandEventAction();
cy.intercept('/api/timeline').as('timeline');
clickingOnCreateTimelineFormTemplateBtn();
cy.wait('@timeline', { timeout: 100000 }).then(({ request }) => {
if (request.body && request.body.timeline) {
- expect(request.body.timeline).to.haveOwnProperty('description', timeline.description);
+ expect(request.body.timeline).to.haveOwnProperty(
+ 'description',
+ getTimeline().description
+ );
expect(request.body.timeline.kqlQuery.filterQuery.kuery).to.haveOwnProperty(
'expression',
- timeline.query
+ getTimeline().query
);
cy.get(TIMELINE_FLYOUT_WRAPPER).should('have.css', 'visibility', 'visible');
}
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts
index c2bd31c635a70..918a554db5606 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts
@@ -10,14 +10,14 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { TIMELINES_URL } from '../../urls/navigation';
import { createTimeline } from '../../tasks/api_calls/timelines';
-import { expectedExportedTimeline, timeline } from '../../objects/timeline';
+import { expectedExportedTimeline, getTimeline } from '../../objects/timeline';
import { cleanKibana } from '../../tasks/common';
describe('Export timelines', () => {
beforeEach(() => {
cleanKibana();
cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export');
- createTimeline(timeline).then((response) => {
+ createTimeline(getTimeline()).then((response) => {
cy.wrap(response).as('timelineResponse');
cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId');
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts
index 24309b8fda084..0a784cf952ca6 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { timelineNonValidQuery } from '../../objects/timeline';
+import { getTimelineNonValidQuery } from '../../objects/timeline';
import {
NOTES_AUTHOR,
@@ -39,7 +39,7 @@ describe('Timeline notes tab', () => {
loginAndWaitForPageWithoutDateRange(TIMELINES_URL);
waitForTimelinesPanelToBeLoaded();
- createTimeline(timelineNonValidQuery)
+ createTimeline(getTimelineNonValidQuery())
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
.then((timelineId: string) =>
refreshTimelinesUntilTimeLinePresent(timelineId)
@@ -56,16 +56,16 @@ describe('Timeline notes tab', () => {
});
it('should render mockdown', () => {
cy.intercept('/api/note').as(`updateNote`);
- addNotesToTimeline(timelineNonValidQuery.notes);
+ addNotesToTimeline(getTimelineNonValidQuery().notes);
cy.wait('@updateNote').its('response.statusCode').should('eq', 200);
cy.get(NOTES_TEXT_AREA).should('exist');
});
it('should contain notes', () => {
cy.intercept('/api/note').as(`updateNote`);
- addNotesToTimeline(timelineNonValidQuery.notes);
+ addNotesToTimeline(getTimelineNonValidQuery().notes);
cy.wait('@updateNote').its('response.statusCode').should('eq', 200);
- cy.get(NOTES_TEXT).first().should('have.text', timelineNonValidQuery.notes);
+ cy.get(NOTES_TEXT).first().should('have.text', getTimelineNonValidQuery().notes);
});
it('should be able to render font in bold', () => {
@@ -91,7 +91,7 @@ describe('Timeline notes tab', () => {
it('should render the right author', () => {
cy.intercept('/api/note').as(`updateNote`);
- addNotesToTimeline(timelineNonValidQuery.notes);
+ addNotesToTimeline(getTimelineNonValidQuery().notes);
cy.wait('@updateNote').its('response.statusCode').should('eq', 200);
cy.get(NOTES_AUTHOR).first().should('have.text', text);
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts
index 814631b2af636..5c620a983b2b3 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import { TIMELINE_DESCRIPTION, TIMELINE_TITLE, OPEN_TIMELINE_MODAL } from '../../screens/timeline';
import {
@@ -39,7 +39,7 @@ describe('Open timeline', () => {
loginAndWaitForPageWithoutDateRange(TIMELINES_URL);
waitForTimelinesPanelToBeLoaded();
- createTimeline(timeline)
+ createTimeline(getTimeline())
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
.then((timelineId: string) => {
refreshTimelinesUntilTimeLinePresent(timelineId)
@@ -47,7 +47,7 @@ describe('Open timeline', () => {
// request responses and indeterminism since on clicks to activates URL's.
.then(() => cy.wait(1000))
.then(() =>
- addNoteToTimeline(timeline.notes, timelineId).should((response) =>
+ addNoteToTimeline(getTimeline().notes, timelineId).should((response) =>
expect(response.status).to.equal(200)
)
)
@@ -71,11 +71,11 @@ describe('Open timeline', () => {
});
it('should display timeline info - title', () => {
- cy.contains(timeline.title).should('exist');
+ cy.contains(getTimeline().title).should('exist');
});
it('should display timeline info - description', () => {
- cy.get(TIMELINES_DESCRIPTION).first().should('have.text', timeline.description);
+ cy.get(TIMELINES_DESCRIPTION).first().should('have.text', getTimeline().description);
});
it('should display timeline info - pinned event count', () => {
@@ -91,11 +91,11 @@ describe('Open timeline', () => {
});
it('should display timeline content - title', () => {
- cy.get(TIMELINE_TITLE).should('have.text', timeline.title);
+ cy.get(TIMELINE_TITLE).should('have.text', getTimeline().title);
});
it('should display timeline content - description', () => {
- cy.get(TIMELINE_DESCRIPTION).should('have.text', timeline.description);
+ cy.get(TIMELINE_DESCRIPTION).should('have.text', getTimeline().description);
});
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts
index f37a66ac048fb..06891121d6354 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import {
UNLOCKED_ICON,
@@ -38,7 +38,7 @@ describe('Timeline query tab', () => {
loginAndWaitForPageWithoutDateRange(TIMELINES_URL);
waitForTimelinesPanelToBeLoaded();
- createTimeline(timeline)
+ createTimeline(getTimeline())
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
.then((timelineId: string) => {
refreshTimelinesUntilTimeLinePresent(timelineId)
@@ -46,14 +46,14 @@ describe('Timeline query tab', () => {
// request responses and indeterminism since on clicks to activates URL's.
.then(() => cy.wait(1000))
.then(() =>
- addNoteToTimeline(timeline.notes, timelineId).should((response) =>
+ addNoteToTimeline(getTimeline().notes, timelineId).should((response) =>
expect(response.status).to.equal(200)
)
)
.then(() => openTimelineById(timelineId))
.then(() => pinFirstEvent())
.then(() => persistNoteToFirstEvent('event note'))
- .then(() => addFilter(timeline.filter));
+ .then(() => addFilter(getTimeline().filter));
});
});
@@ -63,7 +63,7 @@ describe('Timeline query tab', () => {
});
it('should contain the right query', () => {
- cy.get(TIMELINE_QUERY).should('have.text', `${timeline.query}`);
+ cy.get(TIMELINE_QUERY).should('have.text', `${getTimeline().query}`);
});
it('should be able to add event note', () => {
@@ -71,7 +71,7 @@ describe('Timeline query tab', () => {
});
it('should display timeline filter', () => {
- cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
+ cy.get(TIMELINE_FILTER(getTimeline().filter)).should('exist');
});
it('should display pinned events', () => {
diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts
index 842dd85b42ef8..a72657d78b70d 100644
--- a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts
@@ -37,7 +37,7 @@ import { addNameToTimeline, closeTimeline, populateTimeline } from '../../tasks/
import { HOSTS_URL } from '../../urls/navigation';
import { ABSOLUTE_DATE_RANGE } from '../../urls/state';
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import { TIMELINE } from '../../screens/create_new_case';
import { cleanKibana } from '../../tasks/common';
@@ -244,7 +244,7 @@ describe('url state', () => {
cy.intercept('PATCH', '/api/timeline').as('timeline');
- addNameToTimeline(timeline.title);
+ addNameToTimeline(getTimeline().title);
cy.wait('@timeline').then(({ response }) => {
closeTimeline();
@@ -256,7 +256,7 @@ describe('url state', () => {
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).should('not.have.text', 'Updating');
cy.get(TIMELINE).should('be.visible');
cy.get(TIMELINE_TITLE).should('be.visible');
- cy.get(TIMELINE_TITLE).should('have.text', timeline.title);
+ cy.get(TIMELINE_TITLE).should('have.text', getTimeline().title);
});
});
});
diff --git a/x-pack/plugins/security_solution/cypress/objects/case.ts b/x-pack/plugins/security_solution/cypress/objects/case.ts
index 847236688dee7..8bc90c5fa2a3b 100644
--- a/x-pack/plugins/security_solution/cypress/objects/case.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/case.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CompleteTimeline, timeline } from './timeline';
+import { CompleteTimeline, getTimeline } from './timeline';
export interface TestCase extends TestCaseWithoutTimeline {
timeline: CompleteTimeline;
@@ -43,49 +43,50 @@ export interface IbmResilientConnectorOptions {
incidentTypes: string[];
}
-export const case1: TestCase = {
+export const getCase1 = (): TestCase => ({
name: 'This is the title of the case',
tags: ['Tag1', 'Tag2'],
description: 'This is the case description',
- timeline,
+ timeline: getTimeline(),
reporter: 'elastic',
owner: 'securitySolution',
-};
+});
-export const serviceNowConnector: Connector = {
+export const getServiceNowConnector = (): Connector => ({
connectorName: 'New connector',
URL: 'https://www.test.service-now.com',
username: 'Username Name',
password: 'password',
-};
+});
-export const jiraConnectorOptions: JiraConnectorOptions = {
+export const getJiraConnectorOptions = (): JiraConnectorOptions => ({
issueType: '10006',
priority: 'High',
-};
+});
-export const serviceNowConnectorOpions: ServiceNowconnectorOptions = {
+export const getServiceNowConnectorOptions = (): ServiceNowconnectorOptions => ({
urgency: '2',
severity: '1',
impact: '3',
-};
+});
-export const ibmResilientConnectorOptions: IbmResilientConnectorOptions = {
+export const getIbmResilientConnectorOptions = (): IbmResilientConnectorOptions => ({
title: 'Resilient',
severity: 'Medium',
incidentTypes: ['Communication error (fax; email)', 'Denial of Service'],
-};
+});
export const TIMELINE_CASE_ID = '68248e00-f689-11ea-9ab2-59238b522856';
-export const connectorIds = {
+
+export const getConnectorIds = () => ({
jira: '000e5f86-08b0-4882-adfd-6df981d45c1b',
sn: '93a69ba3-3c31-4b4c-bf86-cc79a090f437',
resilient: 'a6a8dd7f-7e88-48fe-9b9f-70b668da8cbc',
-};
+});
-export const mockConnectorsResponse = [
+export const getMockConnectorsResponse = () => [
{
- id: connectorIds.jira,
+ id: getConnectorIds().jira,
actionTypeId: '.jira',
name: 'Jira',
config: {
@@ -96,7 +97,7 @@ export const mockConnectorsResponse = [
referencedByCount: 0,
},
{
- id: connectorIds.resilient,
+ id: getConnectorIds().resilient,
actionTypeId: '.resilient',
name: 'Resilient',
config: {
@@ -107,7 +108,7 @@ export const mockConnectorsResponse = [
referencedByCount: 0,
},
{
- id: connectorIds.sn,
+ id: getConnectorIds().sn,
actionTypeId: '.servicenow',
name: 'ServiceNow',
config: {
@@ -117,7 +118,8 @@ export const mockConnectorsResponse = [
referencedByCount: 0,
},
];
-export const executeResponses = {
+
+export const getExecuteResponses = () => ({
servicenow: {
choices: {
status: 'ok',
@@ -208,7 +210,7 @@ export const executeResponses = {
{ id: '10006', name: 'Task' },
{ id: '10007', name: 'Sub-task' },
],
- actionId: connectorIds.jira,
+ actionId: getConnectorIds().jira,
},
fieldsByIssueType: {
status: 'ok',
@@ -299,7 +301,7 @@ export const executeResponses = {
timetracking: { allowedValues: [], defaultValue: {} },
labels: { allowedValues: [], defaultValue: {} },
},
- actionId: connectorIds.jira,
+ actionId: getConnectorIds().jira,
},
},
resilient: {
@@ -309,7 +311,7 @@ export const executeResponses = {
{ id: 17, name: 'Communication error (fax; email)' },
{ id: 21, name: 'Denial of Service' },
],
- actionId: connectorIds.resilient,
+ actionId: getConnectorIds().resilient,
},
severity: {
status: 'ok',
@@ -318,7 +320,7 @@ export const executeResponses = {
{ id: 5, name: 'Medium' },
{ id: 6, name: 'High' },
],
- actionId: connectorIds.resilient,
+ actionId: getConnectorIds().resilient,
},
},
-};
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/connector.ts b/x-pack/plugins/security_solution/cypress/objects/connector.ts
index 2a0f1cc43eff0..a5244583bf494 100644
--- a/x-pack/plugins/security_solution/cypress/objects/connector.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/connector.ts
@@ -14,11 +14,11 @@ export interface EmailConnector {
password: string;
}
-export const emailConnector: EmailConnector = {
+export const getEmailConnector = (): EmailConnector => ({
name: 'Test connector',
from: 'test@example.com',
host: 'example.com',
port: '80',
user: 'username',
password: 'password',
-};
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/exception.ts b/x-pack/plugins/security_solution/cypress/objects/exception.ts
index 73457f10ccec6..6a934e1ec4651 100644
--- a/x-pack/plugins/security_solution/cypress/objects/exception.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/exception.ts
@@ -20,22 +20,22 @@ export interface ExceptionList {
type: 'detection' | 'endpoint';
}
-export const exceptionList: ExceptionList = {
+export const getExceptionList = (): ExceptionList => ({
description: 'Test exception list description',
list_id: 'test_exception_list',
name: 'Test exception list',
namespace_type: 'single',
tags: ['test tag'],
type: 'detection',
-};
+});
-export const exception: Exception = {
+export const getException = (): Exception => ({
field: 'host.name',
operator: 'is',
values: ['suricata-iowa'],
-};
+});
-export const expectedExportedExceptionList = (exceptionListResponse: Cypress.Response) => {
+export const expectedExportedExceptionList = (exceptionListResponse: Cypress.Response): string => {
const jsonrule = exceptionListResponse.body;
return `{"_version":"${jsonrule._version}","created_at":"${jsonrule.created_at}","created_by":"elastic","description":"${jsonrule.description}","id":"${jsonrule.id}","immutable":false,"list_id":"test_exception_list","name":"Test exception list","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"${jsonrule.tie_breaker_id}","type":"detection","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","version":1}\n"\n""\n{"exception_list_items_details":"{"exported_count":0}\n"}`;
diff --git a/x-pack/plugins/security_solution/cypress/objects/filter.ts b/x-pack/plugins/security_solution/cypress/objects/filter.ts
index b00954de17422..5a69100a4b38a 100644
--- a/x-pack/plugins/security_solution/cypress/objects/filter.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/filter.ts
@@ -10,7 +10,7 @@ export interface SearchBarFilter {
value: string;
}
-export const hostIpFilter: SearchBarFilter = {
+export const getHostIpFilter = (): SearchBarFilter => ({
key: 'host.ip',
value: '1.1.1.1',
-};
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts
index 3383ef4996ead..a10fa5b0eda78 100644
--- a/x-pack/plugins/security_solution/cypress/objects/rule.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts
@@ -7,8 +7,8 @@
/* eslint-disable @kbn/eslint/no-restricted-paths */
import { rawRules } from '../../server/lib/detection_engine/rules/prepackaged_rules/index';
-import { mockThreatData } from '../../public/detections/mitre/mitre_tactics_techniques';
-import { timeline, CompleteTimeline, indicatorMatchTimelineTemplate } from './timeline';
+import { getMockThreatData } from '../../public/detections/mitre/mitre_tactics_techniques';
+import { getTimeline, CompleteTimeline, getIndicatorMatchTimelineTemplate } from './timeline';
export const totalNumberOfPrebuiltRules = rawRules.length;
@@ -96,7 +96,7 @@ export interface MachineLearningRule {
lookBack: Interval;
}
-export const indexPatterns = [
+export const getIndexPatterns = (): string[] => [
'apm-*-transaction*',
'auditbeat-*',
'endgame-*',
@@ -106,67 +106,69 @@ export const indexPatterns = [
'winlogbeat-*',
];
-const { tactic, technique, subtechnique } = mockThreatData;
-
-const mitre1: Mitre = {
- tactic: `${tactic.name} (${tactic.id})`,
+const getMitre1 = (): Mitre => ({
+ tactic: `${getMockThreatData().tactic.name} (${getMockThreatData().tactic.id})`,
techniques: [
{
- name: `${technique.name} (${technique.id})`,
- subtechniques: [`${subtechnique.name} (${subtechnique.id})`],
+ name: `${getMockThreatData().technique.name} (${getMockThreatData().technique.id})`,
+ subtechniques: [
+ `${getMockThreatData().subtechnique.name} (${getMockThreatData().subtechnique.id})`,
+ ],
},
{
- name: `${technique.name} (${technique.id})`,
+ name: `${getMockThreatData().technique.name} (${getMockThreatData().technique.id})`,
subtechniques: [],
},
],
-};
+});
-const mitre2: Mitre = {
- tactic: `${tactic.name} (${tactic.id})`,
+const getMitre2 = (): Mitre => ({
+ tactic: `${getMockThreatData().tactic.name} (${getMockThreatData().tactic.id})`,
techniques: [
{
- name: `${technique.name} (${technique.id})`,
- subtechniques: [`${subtechnique.name} (${subtechnique.id})`],
+ name: `${getMockThreatData().technique.name} (${getMockThreatData().technique.id})`,
+ subtechniques: [
+ `${getMockThreatData().subtechnique.name} (${getMockThreatData().subtechnique.id})`,
+ ],
},
],
-};
+});
-const severityOverride1: SeverityOverride = {
+const getSeverityOverride1 = (): SeverityOverride => ({
sourceField: 'host.name',
sourceValue: 'host',
-};
+});
-const severityOverride2: SeverityOverride = {
+const getSeverityOverride2 = (): SeverityOverride => ({
sourceField: '@timestamp',
sourceValue: '10/02/2020',
-};
+});
-const severityOverride3: SeverityOverride = {
+const getSeverityOverride3 = (): SeverityOverride => ({
sourceField: 'host.geo.name',
sourceValue: 'atack',
-};
+});
-const severityOverride4: SeverityOverride = {
+const getSeverityOverride4 = (): SeverityOverride => ({
sourceField: 'agent.type',
sourceValue: 'auditbeat',
-};
+});
-const runsEvery: Interval = {
+const getRunsEvery = (): Interval => ({
interval: '1',
timeType: 'Seconds',
type: 's',
-};
+});
-const lookBack: Interval = {
+const getLookBack = (): Interval => ({
interval: '17520',
timeType: 'Hours',
type: 'h',
-};
+});
-export const newRule: CustomRule = {
+export const getNewRule = (): CustomRule => ({
customQuery: 'host.name: *',
- index: indexPatterns,
+ index: getIndexPatterns(),
name: 'New Rule Test',
description: 'The new rule description.',
severity: 'High',
@@ -174,15 +176,15 @@ export const newRule: CustomRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const unmappedRule: CustomRule = {
+export const getUnmappedRule = (): CustomRule => ({
customQuery: '*:*',
index: ['unmapped*'],
name: 'Rule with unmapped fields',
@@ -192,15 +194,15 @@ export const unmappedRule: CustomRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const unmappedCCSRule: CustomRule = {
+export const getUnmappedCCSRule = (): CustomRule => ({
customQuery: '*:*',
index: [`${ccsRemoteName}:unmapped*`],
name: 'Rule with unmapped fields',
@@ -210,15 +212,15 @@ export const unmappedCCSRule: CustomRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const existingRule: CustomRule = {
+export const getExistingRule = (): CustomRule => ({
customQuery: 'host.name: *',
name: 'Rule 1',
description: 'Description for Rule 1',
@@ -231,17 +233,17 @@ export const existingRule: CustomRule = {
falsePositivesExamples: [],
mitre: [],
note: 'This is my note',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
// Please do not change, or if you do, needs
// to be any number other than default value
maxSignals: 500,
-};
+});
-export const newOverrideRule: OverrideRule = {
+export const getNewOverrideRule = (): OverrideRule => ({
customQuery: 'host.name: *',
- index: indexPatterns,
+ index: getIndexPatterns(),
name: 'Override Rule',
description: 'The new rule description.',
severity: 'High',
@@ -249,21 +251,26 @@ export const newOverrideRule: OverrideRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- severityOverride: [severityOverride1, severityOverride2, severityOverride3, severityOverride4],
+ severityOverride: [
+ getSeverityOverride1(),
+ getSeverityOverride2(),
+ getSeverityOverride3(),
+ getSeverityOverride4(),
+ ],
riskOverride: 'destination.port',
nameOverride: 'agent.type',
timestampOverride: '@timestamp',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const newThresholdRule: ThresholdRule = {
+export const getNewThresholdRule = (): ThresholdRule => ({
customQuery: 'host.name: *',
- index: indexPatterns,
+ index: getIndexPatterns(),
name: 'Threshold Rule',
description: 'The new rule description.',
severity: 'High',
@@ -271,17 +278,17 @@ export const newThresholdRule: ThresholdRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
thresholdField: 'host.name',
threshold: '10',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const machineLearningRule: MachineLearningRule = {
+export const getMachineLearningRule = (): MachineLearningRule => ({
machineLearningJobs: ['linux_anomalous_network_service', 'linux_anomalous_network_activity_ecs'],
anomalyScoreThreshold: '20',
name: 'New ML Rule Test',
@@ -291,52 +298,52 @@ export const machineLearningRule: MachineLearningRule = {
tags: ['ML'],
referenceUrls: ['https://elastic.co/'],
falsePositivesExamples: ['False1'],
- mitre: [mitre1],
+ mitre: [getMitre1()],
note: '# test markdown',
- runsEvery,
- lookBack,
-};
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+});
-export const eqlRule: CustomRule = {
+export const getEqlRule = (): CustomRule => ({
customQuery: 'any where process.name == "which"',
name: 'New EQL Rule',
- index: indexPatterns,
+ index: getIndexPatterns(),
description: 'New EQL rule description.',
severity: 'High',
riskScore: '17',
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const eqlSequenceRule: CustomRule = {
+export const getEqlSequenceRule = (): CustomRule => ({
customQuery:
'sequence with maxspan=30s\
[any where process.name == "which"]\
[any where process.name == "xargs"]',
name: 'New EQL Sequence Rule',
- index: indexPatterns,
+ index: getIndexPatterns(),
description: 'New EQL rule description.',
severity: 'High',
riskScore: '17',
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const newThreatIndicatorRule: ThreatIndicatorRule = {
+export const getNewThreatIndicatorRule = (): ThreatIndicatorRule => ({
name: 'Threat Indicator Rule Test',
description: 'The threat indicator rule description.',
index: ['suspicious-*'],
@@ -345,31 +352,31 @@ export const newThreatIndicatorRule: ThreatIndicatorRule = {
tags: ['test', 'threat'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
indicatorIndexPattern: ['filebeat-*'],
indicatorMappingField: 'myhash.mysha256',
indicatorIndexField: 'threatintel.indicator.file.hash.sha256',
type: 'file',
atomic: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
- timeline: indicatorMatchTimelineTemplate,
+ timeline: getIndicatorMatchTimelineTemplate(),
maxSignals: 100,
-};
+});
-export const duplicatedRuleName = `${newThreatIndicatorRule.name} [Duplicate]`;
+export const duplicatedRuleName = `${getNewThreatIndicatorRule().name} [Duplicate]`;
-export const severitiesOverride = ['Low', 'Medium', 'High', 'Critical'];
+export const getSeveritiesOverride = (): string[] => ['Low', 'Medium', 'High', 'Critical'];
-export const editedRule = {
- ...existingRule,
+export const getEditedRule = (): CustomRule => ({
+ ...getExistingRule(),
severity: 'Medium',
description: 'Edited Rule description',
- tags: [...existingRule.tags, 'edited'],
-};
+ tags: [...getExistingRule().tags, 'edited'],
+});
-export const expectedExportedRule = (ruleResponse: Cypress.Response) => {
+export const expectedExportedRule = (ruleResponse: Cypress.Response): string => {
const jsonrule = ruleResponse.body;
return `{"id":"${jsonrule.id}","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","created_at":"${jsonrule.created_at}","created_by":"elastic","name":"${jsonrule.name}","tags":[],"interval":"100m","enabled":false,"description":"${jsonrule.description}","risk_score":${jsonrule.risk_score},"severity":"${jsonrule.severity}","output_index":".siem-signals-default","author":[],"false_positives":[],"from":"now-17520h","rule_id":"rule_testing","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"threat":[],"to":"now","references":[],"version":1,"exceptions_list":[],"immutable":false,"type":"query","language":"kuery","index":["exceptions-*"],"query":"${jsonrule.query}","throttle":"no_actions","actions":[]}\n{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n`;
diff --git a/x-pack/plugins/security_solution/cypress/objects/timeline.ts b/x-pack/plugins/security_solution/cypress/objects/timeline.ts
index 1b66b50605508..c13c1b01ef0ed 100644
--- a/x-pack/plugins/security_solution/cypress/objects/timeline.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/timeline.ts
@@ -24,51 +24,51 @@ export interface TimelineFilter {
value?: string;
}
-export const filter: TimelineFilter = {
+export const getFilter = (): TimelineFilter => ({
field: 'host.name',
operator: 'exists',
value: 'exists',
-};
+});
-export const timeline: CompleteTimeline = {
+export const getTimeline = (): CompleteTimeline => ({
title: 'Security Timeline',
description: 'This is the best timeline',
query: 'host.name: *',
notes: 'Yes, the best timeline',
- filter,
-};
+ filter: getFilter(),
+});
-export const indicatorMatchTimelineTemplate: CompleteTimeline = {
- ...timeline,
+export const getIndicatorMatchTimelineTemplate = (): CompleteTimeline => ({
+ ...getTimeline(),
title: 'Generic Threat Match Timeline',
templateTimelineId: '495ad7a7-316e-4544-8a0f-9c098daee76e',
-};
+});
/**
* Timeline query that finds no valid data to cut down on test failures
* or other issues for when we want to test one specific thing and not also
* test the queries happening
*/
-export const timelineNonValidQuery: CompleteTimeline = {
- ...timeline,
+export const getTimelineNonValidQuery = (): CompleteTimeline => ({
+ ...getTimeline(),
query: 'query_to_intentionally_find_nothing: *',
-};
+});
-export const caseTimeline: Timeline = {
+export const caseTimeline = (): Timeline => ({
title: 'SIEM test',
description: 'description',
query: 'host.name: *',
id: '0162c130-78be-11ea-9718-118a926974a4',
-};
+});
-export const expectedExportedTimelineTemplate = (templateResponse: Cypress.Response) => {
+export const expectedExportedTimelineTemplate = (templateResponse: Cypress.Response): string => {
const timelineTemplateBody = templateResponse.body.data.persistTimeline.timeline;
return `{"savedObjectId":"${timelineTemplateBody.savedObjectId}","version":"${timelineTemplateBody.version}","columns":[{"id":"@timestamp"},{"id":"user.name"},{"id":"event.category"},{"id":"event.action"},{"id":"host.name"}],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"expression":"${timelineTemplateBody.kqlQuery.filterQuery.kuery.expression}","kind":"kuery"}}},"dateRange":{"start":"${timelineTemplateBody.dateRange.start}","end":"${timelineTemplateBody.dateRange.end}"},"description":"${timelineTemplateBody.description}","title":"${timelineTemplateBody.title}","templateTimelineVersion":1,"timelineType":"template","created":${timelineTemplateBody.created},"createdBy":"elastic","updated":${timelineTemplateBody.updated},"updatedBy":"elastic","sort":[],"eventNotes":[],"globalNotes":[],"pinnedEventIds":[]}
`;
};
-export const expectedExportedTimeline = (timelineResponse: Cypress.Response) => {
+export const expectedExportedTimeline = (timelineResponse: Cypress.Response): string => {
const timelineBody = timelineResponse.body.data.persistTimeline.timeline;
return `{"savedObjectId":"${timelineBody.savedObjectId}","version":"${timelineBody.version}","columns":[{"id":"@timestamp"},{"id":"user.name"},{"id":"event.category"},{"id":"event.action"},{"id":"host.name"}],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"expression":"${timelineBody.kqlQuery.filterQuery.kuery.expression}","kind":"kuery"}}},"dateRange":{"start":"${timelineBody.dateRange.start}","end":"${timelineBody.dateRange.end}"},"description":"${timelineBody.description}","title":"${timelineBody.title}","created":${timelineBody.created},"createdBy":"elastic","updated":${timelineBody.updated},"updatedBy":"elastic","timelineType":"default","sort":[],"eventNotes":[],"globalNotes":[],"pinnedEventIds":[]}\n`;
diff --git a/x-pack/plugins/security_solution/cypress/screens/edit_connector.ts b/x-pack/plugins/security_solution/cypress/screens/edit_connector.ts
index 5b353983e5a92..598485b167c9f 100644
--- a/x-pack/plugins/security_solution/cypress/screens/edit_connector.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/edit_connector.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { connectorIds } from '../objects/case';
+import { getConnectorIds } from '../objects/case';
export const CONNECTOR_RESILIENT = `[data-test-subj="connector-fields-resilient"]`;
@@ -17,14 +17,16 @@ export const SELECT_INCIDENT_TYPE = `[data-test-subj="incidentTypeComboBox"] inp
export const SELECT_ISSUE_TYPE = `[data-test-subj="issueTypeSelect"]`;
-export const SELECT_JIRA = `[data-test-subj="dropdown-connector-${connectorIds.jira}"]`;
+export const SELECT_JIRA = `[data-test-subj="dropdown-connector-${getConnectorIds().jira}"]`;
export const SELECT_PRIORITY = `[data-test-subj="prioritySelect"]`;
-export const SELECT_RESILIENT = `[data-test-subj="dropdown-connector-${connectorIds.resilient}"]`;
+export const SELECT_RESILIENT = `[data-test-subj="dropdown-connector-${
+ getConnectorIds().resilient
+}"]`;
export const SELECT_SEVERITY = `[data-test-subj="severitySelect"]`;
-export const SELECT_SN = `[data-test-subj="dropdown-connector-${connectorIds.sn}"]`;
+export const SELECT_SN = `[data-test-subj="dropdown-connector-${getConnectorIds().sn}"]`;
export const SELECT_URGENCY = `[data-test-subj="urgencySelect"]`;
diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
index 1b420cd6d1520..d8d91dc9ca624 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import { emailConnector, EmailConnector } from '../objects/connector';
+import { getEmailConnector, EmailConnector } from '../objects/connector';
import {
CustomRule,
MachineLearningRule,
- machineLearningRule,
+ getMachineLearningRule,
OverrideRule,
ThreatIndicatorRule,
ThresholdRule,
@@ -397,7 +397,7 @@ export const fillIndexAndIndicatorIndexPattern = (
getIndicatorIndicatorIndex().type(`${indicatorIndex}{enter}`);
};
-export const fillEmailConnectorForm = (connector: EmailConnector = emailConnector) => {
+export const fillEmailConnectorForm = (connector: EmailConnector = getEmailConnector()) => {
cy.get(CONNECTOR_NAME_INPUT).type(connector.name);
cy.get(EMAIL_CONNECTOR_FROM_INPUT).type(connector.from);
cy.get(EMAIL_CONNECTOR_HOST_INPUT).type(connector.host);
@@ -478,9 +478,12 @@ export const fillDefineMachineLearningRuleAndContinue = (rule: MachineLearningRu
cy.get(MACHINE_LEARNING_DROPDOWN_INPUT).type(`${machineLearningJob}{enter}`);
cy.get(MACHINE_LEARNING_DROPDOWN_INPUT).type('{esc}');
});
- cy.get(ANOMALY_THRESHOLD_INPUT).type(`{selectall}${machineLearningRule.anomalyScoreThreshold}`, {
- force: true,
- });
+ cy.get(ANOMALY_THRESHOLD_INPUT).type(
+ `{selectall}${getMachineLearningRule().anomalyScoreThreshold}`,
+ {
+ force: true,
+ }
+ );
getDefineContinueButton().should('exist').click({ force: true });
cy.get(MACHINE_LEARNING_DROPDOWN_INPUT).should('not.exist');
diff --git a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts
index a5da747787ba6..f28311d9c96e7 100644
--- a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts
+++ b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts
@@ -10143,7 +10143,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [
*
* Is built alongside and sampled from the data in the file so to always be valid with the most up to date MITRE ATT&CK data
*/
-export const mockThreatData = {
+export const getMockThreatData = () => ({
tactic: {
name: 'Privilege Escalation',
id: 'TA0004',
@@ -10162,4 +10162,4 @@ export const mockThreatData = {
tactics: ['privilege-escalation', 'persistence'],
techniqueId: 'T1546',
},
-};
+});
diff --git a/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts b/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts
index a7de7494e1116..743b143213c22 100644
--- a/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts
+++ b/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts
@@ -6,9 +6,9 @@
*/
import { Threats } from '@kbn/securitysolution-io-ts-alerting-types';
-import { mockThreatData } from './mitre_tactics_techniques';
+import { getMockThreatData } from './mitre_tactics_techniques';
-const { tactic, technique, subtechnique } = mockThreatData;
+const { tactic, technique, subtechnique } = getMockThreatData();
const { tactics, ...mockTechnique } = technique;
const { tactics: subtechniqueTactics, ...mockSubtechnique } = subtechnique;
From bb1759e0653ea7af352f1b5c9d7b07a149e77c78 Mon Sep 17 00:00:00 2001
From: liza-mae
Date: Mon, 12 Jul 2021 11:29:37 -0600
Subject: [PATCH 11/84] Fix upgrade tests for 7.14 (#104993)
* Fix upgrade tests for 7.14
* Fix lint issues
* Comment out unused const
* Update uiSettings for non-default space
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../apps/dashboard/dashboard_smoke_tests.ts | 15 ++++++++++++---
.../apps/reporting/reporting_smoke_tests.ts | 2 ++
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts
index 73819b5bac695..0bc3cd7c2610e 100644
--- a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts
+++ b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts
@@ -16,6 +16,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const renderable = getService('renderable');
const dashboardExpect = getService('dashboardExpect');
const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'timePicker']);
+ const kibanaServer = getService('kibanaServer');
+ const browser = getService('browser');
describe('dashboard smoke tests', function describeIndexTests() {
const spaces = [
@@ -36,6 +38,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
basePath,
});
await PageObjects.header.waitUntilLoadingHasFinished();
+ await kibanaServer.uiSettings.update(
+ {
+ 'visualization:visualize:legacyChartsLibrary': true,
+ 'visualization:visualize:legacyPieChartsLibrary': true,
+ },
+ { space }
+ );
+ await browser.refresh();
});
dashboardTests.forEach(({ name, numPanels }) => {
it('should launch sample ' + name + ' data set dashboard', async () => {
@@ -56,9 +66,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await renderable.waitForRender();
log.debug('Checking pie charts rendered');
await pieChart.expectPieSliceCount(4);
- // https://github.com/elastic/kibana/issues/92887
- // log.debug('Checking area, bar and heatmap charts rendered');
- // await dashboardExpect.seriesElementCount(15);
+ log.debug('Checking area, bar and heatmap charts rendered');
+ await dashboardExpect.seriesElementCount(15);
log.debug('Checking saved searches rendered');
await dashboardExpect.savedSearchRowCount(49);
log.debug('Checking input controls rendered');
diff --git a/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts b/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts
index c00e761d54226..20fc34f77dbf8 100644
--- a/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts
+++ b/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts
@@ -69,6 +69,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
if (type === 'pdf_optimize') {
await testSubjects.click('usePrintLayout');
}
+ const advOpt = await find.byXPath(`//button[descendant::*[text()='Advanced options']]`);
+ await advOpt.click();
const postUrl = await find.byXPath(`//button[descendant::*[text()='Copy POST URL']]`);
await postUrl.click();
const url = await browser.getClipboardValue();
From 3638c5ffb279d9ebf1c255653f35a95a7a0ba943 Mon Sep 17 00:00:00 2001
From: Domenico Andreoli
Date: Mon, 12 Jul 2021 19:50:08 +0200
Subject: [PATCH 12/84] Drop Detection Engine FTR test POC (#104852)
---
.../apps/ccs/ccs_discover.js | 155 ------------------
1 file changed, 155 deletions(-)
diff --git a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js
index a22e4438c7dbd..588ff9a6e9f92 100644
--- a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js
+++ b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js
@@ -5,16 +5,7 @@
* 2.0.
*/
-import fs from 'fs';
-import { resolve } from 'path';
import expect from '@kbn/expect';
-import { Client as EsClient } from '@elastic/elasticsearch';
-import { KbnClient } from '@kbn/test';
-import { EsArchiver } from '@kbn/es-archiver';
-import { CA_CERT_PATH, REPO_ROOT } from '@kbn/dev-utils';
-
-const INTEGRATION_TEST_ROOT = process.env.WORKSPACE || resolve(REPO_ROOT, '../integration-test');
-const ARCHIVE = resolve(INTEGRATION_TEST_ROOT, 'test/es_archives/metricbeat');
export default ({ getService, getPageObjects }) => {
describe('Cross cluster search test in discover', async () => {
@@ -212,151 +203,5 @@ export default ({ getService, getPageObjects }) => {
expect(hitCount).to.be.lessThan(originalHitCount);
});
});
-
- describe('Detection engine', async function () {
- const supertest = getService('supertest');
- const esSupertest = getService('esSupertest');
- const config = getService('config');
-
- const esClient = new EsClient({
- ssl: {
- ca: fs.readFileSync(CA_CERT_PATH, 'utf-8'),
- },
- nodes: [process.env.TEST_ES_URLDATA],
- requestTimeout: config.get('timeouts.esRequestTimeout'),
- });
-
- const kbnClient = new KbnClient({
- log,
- url: process.env.TEST_KIBANA_URLDATA,
- certificateAuthorities: config.get('servers.kibana.certificateAuthorities'),
- uiSettingDefaults: kibanaServer.uiSettings,
- });
-
- const esArchiver = new EsArchiver({
- log,
- client: esClient,
- kbnClient,
- });
-
- let signalsId;
- let dataId;
- let ruleId;
-
- before('Prepare .siem-signal-*', async function () {
- log.info('Create index');
- // visit app/security so to create .siem-signals-* as side effect
- await PageObjects.common.navigateToApp('security', { insertTimestamp: false });
-
- log.info('Create index pattern');
- signalsId = await supertest
- .post('/api/index_patterns/index_pattern')
- .set('kbn-xsrf', 'true')
- .send({
- index_pattern: {
- title: '.siem-signals-*',
- },
- override: true,
- })
- .expect(200)
- .then((res) => JSON.parse(res.text).index_pattern.id);
- log.debug('id: ' + signalsId);
- });
-
- before('Prepare data:metricbeat-*', async function () {
- log.info('Create index');
- await esArchiver.load(ARCHIVE);
-
- log.info('Create index pattern');
- dataId = await supertest
- .post('/api/index_patterns/index_pattern')
- .set('kbn-xsrf', 'true')
- .send({
- index_pattern: {
- title: 'data:metricbeat-*',
- },
- override: true,
- })
- .expect(200)
- .then((res) => JSON.parse(res.text).index_pattern.id);
- log.debug('id: ' + dataId);
- });
-
- before('Add detection rule', async function () {
- ruleId = await supertest
- .post('/api/detection_engine/rules')
- .set('kbn-xsrf', 'true')
- .send({
- description: 'This is the description of the rule',
- risk_score: 17,
- severity: 'low',
- interval: '10s',
- name: 'CCS_Detection_test',
- type: 'query',
- from: 'now-1y',
- index: ['data:metricbeat-*'],
- query: '*:*',
- language: 'kuery',
- enabled: true,
- })
- .expect(200)
- .then((res) => JSON.parse(res.text).id);
- log.debug('id: ' + ruleId);
- });
-
- after('Clean up detection rule', async function () {
- if (ruleId !== undefined) {
- log.debug('id: ' + ruleId);
- await supertest
- .delete('/api/detection_engine/rules?id=' + ruleId)
- .set('kbn-xsrf', 'true')
- .expect(200);
- }
- });
-
- after('Clean up data:metricbeat-*', async function () {
- if (dataId !== undefined) {
- log.info('Delete index pattern');
- log.debug('id: ' + dataId);
- await supertest
- .delete('/api/index_patterns/index_pattern/' + dataId)
- .set('kbn-xsrf', 'true')
- .expect(200);
- }
-
- log.info('Delete index');
- await esArchiver.unload(ARCHIVE);
- });
-
- after('Clean up .siem-signal-*', async function () {
- if (signalsId !== undefined) {
- log.info('Delete index pattern: .siem-signals-*');
- log.debug('id: ' + signalsId);
- await supertest
- .delete('/api/index_patterns/index_pattern/' + signalsId)
- .set('kbn-xsrf', 'true')
- .expect(200);
- }
-
- log.info('Delete index alias: .siem-signals-default');
- await esSupertest
- .delete('/.siem-signals-default-000001/_alias/.siem-signals-default')
- .expect(200);
-
- log.info('Delete index: .siem-signals-default-000001');
- await esSupertest.delete('/.siem-signals-default-000001').expect(200);
- });
-
- it('Should generate alerts based on remote events', async function () {
- log.info('Check if any alert got to .siem-signals-*');
- await PageObjects.common.navigateToApp('discover', { insertTimestamp: false });
- await PageObjects.discover.selectIndexPattern('.siem-signals-*');
- await retry.tryForTime(30000, async () => {
- const hitCount = await PageObjects.discover.getHitCount();
- log.debug('### hit count = ' + hitCount);
- expect(hitCount).to.be('100');
- });
- });
- });
});
};
From afc07c376fe774e88c9096582ffb7a2cd651efc0 Mon Sep 17 00:00:00 2001
From: Constance
Date: Mon, 12 Jul 2021 10:51:38 -0700
Subject: [PATCH 13/84] [Enterprise Search] Update README/description (#105151)
* Update plugin README with beta copy & fancier product info
* Update API dev docs plugin description punctuation
* Update README dev section
- Remote reference to native auth - no longer applicable in 7.14
- Add reference to Getting Started guide (Elastic only)
* Run node scripts/build_plugin_list_docs to update https://www.elastic.co/guide/en/kibana/master/plugin-list.html
* Add link to main Kibana README
- which should link out to their contributing docs
---
docs/developer/plugin-list.asciidoc | 2 +-
x-pack/plugins/enterprise_search/README.md | 22 ++++++++++++++++----
x-pack/plugins/enterprise_search/kibana.json | 2 +-
3 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index eee92ba433721..2144fd171ff7a 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -393,7 +393,7 @@ security and spaces filtering as well as performing audit logging.
|{kib-repo}blob/{branch}/x-pack/plugins/enterprise_search/README.md[enterpriseSearch]
-|This plugin's goal is to provide a Kibana user interface to the Enterprise Search solution's products (App Search and Workplace Search). In it's current MVP state, the plugin provides the following with the goal of gathering user feedback and raising product awareness:
+|This plugin provides beta Kibana user interfaces for managing the Enterprise Search solution and its products, App Search and Workplace Search.
|{kib-repo}blob/{branch}/x-pack/plugins/event_log/README.md[eventLog]
diff --git a/x-pack/plugins/enterprise_search/README.md b/x-pack/plugins/enterprise_search/README.md
index 0b067e25e32e8..96b0391bbc8da 100644
--- a/x-pack/plugins/enterprise_search/README.md
+++ b/x-pack/plugins/enterprise_search/README.md
@@ -2,16 +2,30 @@
## Overview
-This plugin's goal is to provide a Kibana user interface to the Enterprise Search solution's products (App Search and Workplace Search). In it's current MVP state, the plugin provides the following with the goal of gathering user feedback and raising product awareness:
+This plugin provides beta Kibana user interfaces for managing the Enterprise Search solution and its products, App Search and Workplace Search.
-- **App Search:** A basic engines overview with links into the product.
-- **Workplace Search:** A simple app overview with basic statistics, links to the sources, users (if standard auth), and product settings.
+> :warning: The Kibana interface for Enterprise Search is a beta feature. It is subject to change and is not covered by the same level of support as generally available features. This interface will become the sole management panel for Enterprise Search with the 8.0 release. Until then, the standalone Enterprise Search UI remains available and supported.
+
+### App Search
+
+
+
+Add rich, relevant search to your apps and websites. https://www.elastic.co/app-search/
+
+### Workplace Search
+
+
+
+Unify all your team's content into a personalized search experience. https://www.elastic.co/workplace-search/
## Development
1. When developing locally, Enterprise Search should be running locally alongside Kibana on `localhost:3002`.
2. Update `config/kibana.dev.yml` with `enterpriseSearch.host: 'http://localhost:3002'`
-3. For faster QA/development, run Enterprise Search on [elasticsearch-native auth](https://www.elastic.co/guide/en/app-search/current/security-and-users.html#app-search-self-managed-security-and-user-management-elasticsearch-native-realm) and log in as the `elastic` superuser on Kibana.
+
+Problems? If you're an Elastic Enterprise Search engineer, please reach out to @elastic/enterprise-search-frontend for questions or our in-depth Getting Started developer guide.
+
+Don't forget to read Kibana's [contributing documentation](https://github.com/elastic/kibana/#building-and-running-kibana-andor-contributing-code) and developer guides for more general info on the Kibana ecosystem.
### Kea
diff --git a/x-pack/plugins/enterprise_search/kibana.json b/x-pack/plugins/enterprise_search/kibana.json
index f8b4261114a22..723b24f951434 100644
--- a/x-pack/plugins/enterprise_search/kibana.json
+++ b/x-pack/plugins/enterprise_search/kibana.json
@@ -12,5 +12,5 @@
"name": "Enterprise Search",
"githubTeam": "enterprise-search-frontend"
},
- "description": "Adds dashboards for discovering and managing Enterprise Search products"
+ "description": "Adds dashboards for discovering and managing Enterprise Search products."
}
From a5eadd054dda57025b53d90f17d6d7bb44e4de49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?=
Date: Mon, 12 Jul 2021 19:54:20 +0200
Subject: [PATCH 14/84] [Fleet] Prevent popovers on agent logs page from being
stuck (#105253)
---
.../components/agent_logs/filter_dataset.tsx | 9 ++++++---
.../components/agent_logs/filter_log_level.tsx | 9 ++++++---
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx
index 47174561230ba..18f6a8b565ab9 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { memo, useState, useEffect } from 'react';
+import React, { memo, useState, useEffect, useCallback } from 'react';
import { EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -22,6 +22,9 @@ export const DatasetFilter: React.FunctionComponent<{
const [isLoading, setIsLoading] = useState(false);
const [datasetValues, setDatasetValues] = useState([AGENT_DATASET]);
+ const togglePopover = useCallback(() => setIsOpen((prevIsOpen) => !prevIsOpen), [setIsOpen]);
+ const closePopover = useCallback(() => setIsOpen(false), [setIsOpen]);
+
useEffect(() => {
const fetchValues = async () => {
setIsLoading(true);
@@ -48,7 +51,7 @@ export const DatasetFilter: React.FunctionComponent<{
button={
setIsOpen(true)}
+ onClick={togglePopover}
isSelected={isOpen}
isLoading={isLoading}
numFilters={datasetValues.length}
@@ -61,7 +64,7 @@ export const DatasetFilter: React.FunctionComponent<{
}
isOpen={isOpen}
- closePopover={() => setIsOpen(false)}
+ closePopover={closePopover}
panelPaddingSize="none"
>
{datasetValues.map((dataset) => (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx
index 120f21fe68207..b423f3a8a57b3 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { memo, useState, useEffect } from 'react';
+import React, { memo, useState, useEffect, useCallback } from 'react';
import { EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -33,6 +33,9 @@ export const LogLevelFilter: React.FunctionComponent<{
const [isLoading, setIsLoading] = useState(false);
const [levelValues, setLevelValues] = useState([]);
+ const togglePopover = useCallback(() => setIsOpen((prevIsOpen) => !prevIsOpen), []);
+ const closePopover = useCallback(() => setIsOpen(false), []);
+
useEffect(() => {
const fetchValues = async () => {
setIsLoading(true);
@@ -59,7 +62,7 @@ export const LogLevelFilter: React.FunctionComponent<{
button={
setIsOpen(true)}
+ onClick={togglePopover}
isSelected={isOpen}
isLoading={isLoading}
numFilters={levelValues.length}
@@ -72,7 +75,7 @@ export const LogLevelFilter: React.FunctionComponent<{
}
isOpen={isOpen}
- closePopover={() => setIsOpen(false)}
+ closePopover={closePopover}
panelPaddingSize="none"
>
{levelValues.map((level) => (
From 76f49565c130edfb8a3f72cd807cfc3c73eae4a5 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Mon, 12 Jul 2021 14:18:35 -0400
Subject: [PATCH 15/84] Support authenticating to Elasticsearch via service
account tokens (#102121)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
config/kibana.yml | 4 +
...n-core-server.elasticsearchclientconfig.md | 2 +-
...-plugin-core-server.elasticsearchconfig.md | 3 +-
...elasticsearchconfig.serviceaccounttoken.md | 15 +++
...ore-server.elasticsearchconfig.username.md | 2 +-
...-server.legacyelasticsearchclientconfig.md | 2 +-
docs/setup/settings.asciidoc | 5 +
src/cli/serve/serve.js | 12 +-
.../client/client_config.test.ts | 48 ++++++-
.../elasticsearch/client/client_config.ts | 16 ++-
.../elasticsearch_config.test.ts | 20 +++
.../elasticsearch/elasticsearch_config.ts | 22 ++++
.../legacy/cluster_client.test.ts | 49 +++++++
.../elasticsearch/legacy/cluster_client.ts | 7 +
.../elasticsearch_client_config.test.ts | 122 ++++++++++++++++++
.../legacy/elasticsearch_client_config.ts | 11 +-
src/core/server/server.api.md | 6 +-
.../resources/base/bin/kibana-docker | 1 +
18 files changed, 327 insertions(+), 20 deletions(-)
create mode 100644 docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md
diff --git a/config/kibana.yml b/config/kibana.yml
index eefb6bb8bacda..dea9849f17b28 100644
--- a/config/kibana.yml
+++ b/config/kibana.yml
@@ -42,6 +42,10 @@
#elasticsearch.username: "kibana_system"
#elasticsearch.password: "pass"
+# Kibana can also authenticate to Elasticsearch via "service account tokens".
+# If may use this token instead of a username/password.
+# elasticsearch.serviceAccountToken: "my_token"
+
# Enables SSL and paths to the PEM-format SSL certificate and SSL key files, respectively.
# These settings enable SSL for outgoing requests from the Kibana server to the browser.
#server.ssl.enabled: false
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md
index a854e5ddad19a..208e0e0175d71 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md
@@ -9,7 +9,7 @@ Configuration options to be used to create a [cluster client](./kibana-plugin-co
Signature:
```typescript
-export declare type ElasticsearchClientConfig = Pick & {
+export declare type ElasticsearchClientConfig = Pick & {
pingTimeout?: ElasticsearchConfig['pingTimeout'] | ClientOptions['pingTimeout'];
requestTimeout?: ElasticsearchConfig['requestTimeout'] | ClientOptions['requestTimeout'];
ssl?: Partial;
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.md
index d87ea63d59b8d..a9ed614ba7552 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.md
@@ -31,10 +31,11 @@ export declare class ElasticsearchConfig
| [pingTimeout](./kibana-plugin-core-server.elasticsearchconfig.pingtimeout.md) | | Duration
| Timeout after which PING HTTP request will be aborted and retried. |
| [requestHeadersWhitelist](./kibana-plugin-core-server.elasticsearchconfig.requestheaderswhitelist.md) | | string[]
| List of Kibana client-side headers to send to Elasticsearch when request scoped cluster client is used. If this is an empty array then \*no\* client-side will be sent. |
| [requestTimeout](./kibana-plugin-core-server.elasticsearchconfig.requesttimeout.md) | | Duration
| Timeout after which HTTP request will be aborted and retried. |
+| [serviceAccountToken](./kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md) | | string
| If Elasticsearch security features are enabled, this setting provides the service account token that the Kibana server users to perform its administrative functions.This is an alternative to specifying a username and password. |
| [shardTimeout](./kibana-plugin-core-server.elasticsearchconfig.shardtimeout.md) | | Duration
| Timeout for Elasticsearch to wait for responses from shards. Set to 0 to disable. |
| [sniffInterval](./kibana-plugin-core-server.elasticsearchconfig.sniffinterval.md) | | false | Duration
| Interval to perform a sniff operation and make sure the list of nodes is complete. If false
then sniffing is disabled. |
| [sniffOnConnectionFault](./kibana-plugin-core-server.elasticsearchconfig.sniffonconnectionfault.md) | | boolean
| Specifies whether the client should immediately sniff for a more current list of nodes when a connection dies. |
| [sniffOnStart](./kibana-plugin-core-server.elasticsearchconfig.sniffonstart.md) | | boolean
| Specifies whether the client should attempt to detect the rest of the cluster when it is first instantiated. |
| [ssl](./kibana-plugin-core-server.elasticsearchconfig.ssl.md) | | Pick<SslConfigSchema, Exclude<keyof SslConfigSchema, 'certificateAuthorities' | 'keystore' | 'truststore'>> & {
certificateAuthorities?: string[];
}
| Set of settings configure SSL connection between Kibana and Elasticsearch that are required when xpack.ssl.verification_mode
in Elasticsearch is set to either certificate
or full
. |
-| [username](./kibana-plugin-core-server.elasticsearchconfig.username.md) | | string
| If Elasticsearch is protected with basic authentication, this setting provides the username that the Kibana server uses to perform its administrative functions. |
+| [username](./kibana-plugin-core-server.elasticsearchconfig.username.md) | | string
| If Elasticsearch is protected with basic authentication, this setting provides the username that the Kibana server uses to perform its administrative functions. Cannot be used in conjunction with serviceAccountToken. |
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md
new file mode 100644
index 0000000000000..5934e83de17a4
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchConfig](./kibana-plugin-core-server.elasticsearchconfig.md) > [serviceAccountToken](./kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md)
+
+## ElasticsearchConfig.serviceAccountToken property
+
+If Elasticsearch security features are enabled, this setting provides the service account token that the Kibana server users to perform its administrative functions.
+
+This is an alternative to specifying a username and password.
+
+Signature:
+
+```typescript
+readonly serviceAccountToken?: string;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.username.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.username.md
index 14db9f2e36ccf..959870ff43a0f 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.username.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.username.md
@@ -4,7 +4,7 @@
## ElasticsearchConfig.username property
-If Elasticsearch is protected with basic authentication, this setting provides the username that the Kibana server uses to perform its administrative functions.
+If Elasticsearch is protected with basic authentication, this setting provides the username that the Kibana server uses to perform its administrative functions. Cannot be used in conjunction with serviceAccountToken.
Signature:
diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md
index b028a09bee453..a80ebe2fee493 100644
--- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md
+++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md
@@ -11,7 +11,7 @@
Signature:
```typescript
-export declare type LegacyElasticsearchClientConfig = Pick & Pick & {
+export declare type LegacyElasticsearchClientConfig = Pick & Pick & {
pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout'];
requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout'];
sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval'];
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index ba333deeb1609..15abd0fa4ad96 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -284,6 +284,11 @@ the username and password that the {kib} server uses to perform maintenance
on the {kib} index at startup. {kib} users still need to authenticate with
{es}, which is proxied through the {kib} server.
+|[[elasticsearch-service-account-token]] `elasticsearch.serviceAccountToken:`
+ | beta[]. If your {es} is protected with basic authentication, this token provides the credentials
+that the {kib} server uses to perform maintenance on the {kib} index at startup. This setting
+is an alternative to `elasticsearch.username` and `elasticsearch.password`.
+
| `enterpriseSearch.host`
| The URL of your Enterprise Search instance
diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js
index ad83965efde33..be949350f7229 100644
--- a/src/cli/serve/serve.js
+++ b/src/cli/serve/serve.js
@@ -68,12 +68,14 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
delete extraCliOptions.env;
if (opts.dev) {
- if (!has('elasticsearch.username')) {
- set('elasticsearch.username', 'kibana_system');
- }
+ if (!has('elasticsearch.serviceAccountToken')) {
+ if (!has('elasticsearch.username')) {
+ set('elasticsearch.username', 'kibana_system');
+ }
- if (!has('elasticsearch.password')) {
- set('elasticsearch.password', 'changeme');
+ if (!has('elasticsearch.password')) {
+ set('elasticsearch.password', 'changeme');
+ }
}
if (opts.ssl) {
diff --git a/src/core/server/elasticsearch/client/client_config.test.ts b/src/core/server/elasticsearch/client/client_config.test.ts
index faca79b3aa6fa..7e16339b40235 100644
--- a/src/core/server/elasticsearch/client/client_config.test.ts
+++ b/src/core/server/elasticsearch/client/client_config.test.ts
@@ -204,11 +204,27 @@ describe('parseClientOptions', () => {
);
});
+ it('adds an authorization header if `serviceAccountToken` is set', () => {
+ expect(
+ parseClientOptions(
+ createConfig({
+ serviceAccountToken: 'ABC123',
+ }),
+ false
+ )
+ ).toEqual(
+ expect.objectContaining({
+ headers: expect.objectContaining({
+ authorization: `Bearer ABC123`,
+ }),
+ })
+ );
+ });
+
it('does not add auth to the nodes', () => {
const options = parseClientOptions(
createConfig({
- username: 'user',
- password: 'pass',
+ serviceAccountToken: 'ABC123',
hosts: ['http://node-A:9200'],
}),
true
@@ -252,6 +268,34 @@ describe('parseClientOptions', () => {
]
`);
});
+
+ it('does not add the authorization header even if `serviceAccountToken` is set', () => {
+ expect(
+ parseClientOptions(
+ createConfig({
+ serviceAccountToken: 'ABC123',
+ }),
+ true
+ ).headers
+ ).not.toHaveProperty('authorization');
+ });
+
+ it('does not add auth to the nodes even if `serviceAccountToken` is set', () => {
+ const options = parseClientOptions(
+ createConfig({
+ serviceAccountToken: 'ABC123',
+ hosts: ['http://node-A:9200'],
+ }),
+ true
+ );
+ expect(options.nodes).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "url": "http://node-a:9200/",
+ },
+ ]
+ `);
+ });
});
});
diff --git a/src/core/server/elasticsearch/client/client_config.ts b/src/core/server/elasticsearch/client/client_config.ts
index 3044b277db902..bbbb1ac247b3b 100644
--- a/src/core/server/elasticsearch/client/client_config.ts
+++ b/src/core/server/elasticsearch/client/client_config.ts
@@ -29,6 +29,7 @@ export type ElasticsearchClientConfig = Pick<
| 'hosts'
| 'username'
| 'password'
+ | 'serviceAccountToken'
> & {
pingTimeout?: ElasticsearchConfig['pingTimeout'] | ClientOptions['pingTimeout'];
requestTimeout?: ElasticsearchConfig['requestTimeout'] | ClientOptions['requestTimeout'];
@@ -74,11 +75,16 @@ export function parseClientOptions(
};
}
- if (config.username && config.password && !scoped) {
- clientOptions.auth = {
- username: config.username,
- password: config.password,
- };
+ if (!scoped) {
+ if (config.username && config.password) {
+ clientOptions.auth = {
+ username: config.username,
+ password: config.password,
+ };
+ } else if (config.serviceAccountToken) {
+ // TODO: change once ES client has native support for service account tokens: https://github.com/elastic/elasticsearch-js/issues/1477
+ clientOptions.headers!.authorization = `Bearer ${config.serviceAccountToken}`;
+ }
}
clientOptions.nodes = config.hosts.map((host) => convertHost(host));
diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts
index f8ef1a7a20a83..6e05baac88e34 100644
--- a/src/core/server/elasticsearch/elasticsearch_config.test.ts
+++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts
@@ -41,6 +41,7 @@ test('set correct defaults', () => {
"authorization",
],
"requestTimeout": "PT30S",
+ "serviceAccountToken": undefined,
"shardTimeout": "PT30S",
"sniffInterval": false,
"sniffOnConnectionFault": false,
@@ -377,3 +378,22 @@ test('#username throws if equal to "elastic", only while running from source', (
);
expect(() => config.schema.validate(obj, { dist: true })).not.toThrow();
});
+
+test('serviceAccountToken throws if username is also set', () => {
+ const obj = {
+ username: 'elastic',
+ serviceAccountToken: 'abc123',
+ };
+
+ expect(() => config.schema.validate(obj)).toThrowErrorMatchingInlineSnapshot(
+ `"[serviceAccountToken]: serviceAccountToken cannot be specified when \\"username\\" is also set."`
+ );
+});
+
+test('serviceAccountToken does not throw if username is not set', () => {
+ const obj = {
+ serviceAccountToken: 'abc123',
+ };
+
+ expect(() => config.schema.validate(obj)).not.toThrow();
+});
diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts
index b2b25cda3ac2a..e756d9da867b3 100644
--- a/src/core/server/elasticsearch/elasticsearch_config.ts
+++ b/src/core/server/elasticsearch/elasticsearch_config.ts
@@ -53,6 +53,18 @@ export const configSchema = schema.object({
)
),
password: schema.maybe(schema.string()),
+ serviceAccountToken: schema.maybe(
+ schema.conditional(
+ schema.siblingRef('username'),
+ schema.never(),
+ schema.string(),
+ schema.string({
+ validate: () => {
+ return `serviceAccountToken cannot be specified when "username" is also set.`;
+ },
+ })
+ )
+ ),
requestHeadersWhitelist: schema.oneOf(
[
schema.string({
@@ -272,6 +284,7 @@ export class ElasticsearchConfig {
/**
* If Elasticsearch is protected with basic authentication, this setting provides
* the username that the Kibana server uses to perform its administrative functions.
+ * Cannot be used in conjunction with serviceAccountToken.
*/
public readonly username?: string;
@@ -281,6 +294,14 @@ export class ElasticsearchConfig {
*/
public readonly password?: string;
+ /**
+ * If Elasticsearch security features are enabled, this setting provides the service account
+ * token that the Kibana server users to perform its administrative functions.
+ *
+ * This is an alternative to specifying a username and password.
+ */
+ public readonly serviceAccountToken?: string;
+
/**
* Set of settings configure SSL connection between Kibana and Elasticsearch that
* are required when `xpack.ssl.verification_mode` in Elasticsearch is set to
@@ -314,6 +335,7 @@ export class ElasticsearchConfig {
this.healthCheckDelay = rawConfig.healthCheck.delay;
this.username = rawConfig.username;
this.password = rawConfig.password;
+ this.serviceAccountToken = rawConfig.serviceAccountToken;
this.customHeaders = rawConfig.customHeaders;
const { alwaysPresentCertificate, verificationMode } = rawConfig.ssl;
diff --git a/src/core/server/elasticsearch/legacy/cluster_client.test.ts b/src/core/server/elasticsearch/legacy/cluster_client.test.ts
index 2ce19570677c5..52bc4bd45660e 100644
--- a/src/core/server/elasticsearch/legacy/cluster_client.test.ts
+++ b/src/core/server/elasticsearch/legacy/cluster_client.test.ts
@@ -101,6 +101,30 @@ describe('#callAsInternalUser', () => {
expect(mockEsClientInstance.ping).toHaveBeenLastCalledWith(mockParams);
});
+ test('sets the authorization header when a service account token is configured', async () => {
+ clusterClient = new LegacyClusterClient(
+ { apiVersion: 'es-version', serviceAccountToken: 'ABC123' } as any,
+ logger.get(),
+ 'custom-type'
+ );
+
+ const mockResponse = { data: 'ping' };
+ const mockParams = { param: 'ping' };
+ mockEsClientInstance.ping.mockImplementation(function mockCall(this: any) {
+ return Promise.resolve({
+ context: this,
+ response: mockResponse,
+ });
+ });
+
+ await clusterClient.callAsInternalUser('ping', mockParams);
+
+ expect(mockEsClientInstance.ping).toHaveBeenCalledWith({
+ headers: { authorization: 'Bearer ABC123' },
+ param: 'ping',
+ });
+ });
+
test('correctly deals with nested endpoint', async () => {
const mockResponse = { data: 'authenticate' };
const mockParams = { param: 'authenticate' };
@@ -355,6 +379,31 @@ describe('#asScoped', () => {
);
});
+ test('does not set the authorization header when a service account token is configured', async () => {
+ clusterClient = new LegacyClusterClient(
+ {
+ apiVersion: 'es-version',
+ requestHeadersWhitelist: ['zero'],
+ serviceAccountToken: 'ABC123',
+ } as any,
+ logger.get(),
+ 'custom-type'
+ );
+
+ clusterClient.asScoped(
+ httpServerMock.createRawRequest({ headers: { zero: '0', one: '1', two: '2', three: '3' } })
+ );
+
+ const expectedHeaders = { zero: '0' };
+
+ expect(MockScopedClusterClient).toHaveBeenCalledTimes(1);
+ expect(MockScopedClusterClient).toHaveBeenCalledWith(
+ expect.any(Function),
+ expect.any(Function),
+ expectedHeaders
+ );
+ });
+
test('both scoped and internal API caller fail if cluster client is closed', async () => {
clusterClient.asScoped(
httpServerMock.createRawRequest({ headers: { zero: '0', one: '1', two: '2', three: '3' } })
diff --git a/src/core/server/elasticsearch/legacy/cluster_client.ts b/src/core/server/elasticsearch/legacy/cluster_client.ts
index bdb2ca4d01b3c..6a6765b67da9f 100644
--- a/src/core/server/elasticsearch/legacy/cluster_client.ts
+++ b/src/core/server/elasticsearch/legacy/cluster_client.ts
@@ -147,6 +147,13 @@ export class LegacyClusterClient implements ILegacyClusterClient {
) => {
this.assertIsNotClosed();
+ if (this.config.serviceAccountToken) {
+ clientParams.headers = {
+ ...clientParams.headers,
+ authorization: `Bearer ${this.config.serviceAccountToken}`,
+ };
+ }
+
return await (callAPI.bind(null, this.client) as LegacyAPICaller)(
endpoint,
clientParams,
diff --git a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts
index 6239ad270d5b5..a343c0d5d2ad1 100644
--- a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts
+++ b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts
@@ -333,6 +333,128 @@ describe('#auth', () => {
});
});
+describe('#serviceAccountToken', () => {
+ it('is set when #auth is true, and a token is provided', () => {
+ expect(
+ parseElasticsearchClientConfig(
+ {
+ apiVersion: 'v7.0.0',
+ customHeaders: { xsrf: 'something' },
+ sniffOnStart: true,
+ sniffOnConnectionFault: true,
+ hosts: ['https://es.local'],
+ requestHeadersWhitelist: [],
+ serviceAccountToken: 'ABC123',
+ },
+ logger.get(),
+ 'custom-type',
+ { auth: true }
+ )
+ ).toMatchInlineSnapshot(`
+ Object {
+ "apiVersion": "v7.0.0",
+ "hosts": Array [
+ Object {
+ "headers": Object {
+ "x-elastic-product-origin": "kibana",
+ "xsrf": "something",
+ },
+ "host": "es.local",
+ "path": "/",
+ "port": "443",
+ "protocol": "https:",
+ "query": null,
+ },
+ ],
+ "keepAlive": true,
+ "log": [Function],
+ "serviceAccountToken": "ABC123",
+ "sniffOnConnectionFault": true,
+ "sniffOnStart": true,
+ }
+ `);
+ });
+
+ it('is not set when #auth is true, and a token is not provided', () => {
+ expect(
+ parseElasticsearchClientConfig(
+ {
+ apiVersion: 'v7.0.0',
+ customHeaders: { xsrf: 'something' },
+ sniffOnStart: true,
+ sniffOnConnectionFault: true,
+ hosts: ['https://es.local'],
+ requestHeadersWhitelist: [],
+ },
+ logger.get(),
+ 'custom-type',
+ { auth: true }
+ )
+ ).toMatchInlineSnapshot(`
+ Object {
+ "apiVersion": "v7.0.0",
+ "hosts": Array [
+ Object {
+ "headers": Object {
+ "x-elastic-product-origin": "kibana",
+ "xsrf": "something",
+ },
+ "host": "es.local",
+ "path": "/",
+ "port": "443",
+ "protocol": "https:",
+ "query": null,
+ },
+ ],
+ "keepAlive": true,
+ "log": [Function],
+ "sniffOnConnectionFault": true,
+ "sniffOnStart": true,
+ }
+ `);
+ });
+
+ it('is not set when #auth is false, and a token is provided', () => {
+ expect(
+ parseElasticsearchClientConfig(
+ {
+ apiVersion: 'v7.0.0',
+ customHeaders: { xsrf: 'something' },
+ sniffOnStart: true,
+ sniffOnConnectionFault: true,
+ hosts: ['https://es.local'],
+ requestHeadersWhitelist: [],
+ serviceAccountToken: 'ABC123',
+ },
+ logger.get(),
+ 'custom-type',
+ { auth: false }
+ )
+ ).toMatchInlineSnapshot(`
+ Object {
+ "apiVersion": "v7.0.0",
+ "hosts": Array [
+ Object {
+ "headers": Object {
+ "x-elastic-product-origin": "kibana",
+ "xsrf": "something",
+ },
+ "host": "es.local",
+ "path": "/",
+ "port": "443",
+ "protocol": "https:",
+ "query": null,
+ },
+ ],
+ "keepAlive": true,
+ "log": [Function],
+ "sniffOnConnectionFault": true,
+ "sniffOnStart": true,
+ }
+ `);
+ });
+});
+
describe('#customHeaders', () => {
test('override the default headers', () => {
const headerKey = Object.keys(DEFAULT_HEADERS)[0];
diff --git a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts
index d68e7635c57cb..3d81caefad457 100644
--- a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts
+++ b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts
@@ -35,6 +35,7 @@ export type LegacyElasticsearchClientConfig = Pick & {
pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout'];
requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout'];
@@ -61,6 +62,7 @@ interface LegacyElasticsearchClientConfigOverrides {
/** @internal */
type ExtendedConfigOptions = ConfigOptions &
Partial<{
+ serviceAccountToken?: string;
ssl: Partial<{
rejectUnauthorized: boolean;
checkServerIdentity: typeof checkServerIdentity;
@@ -106,9 +108,14 @@ export function parseElasticsearchClientConfig(
esClientConfig.sniffInterval = getDurationAsMs(config.sniffInterval);
}
- const needsAuth = auth !== false && config.username && config.password;
+ const needsAuth =
+ auth !== false && ((config.username && config.password) || config.serviceAccountToken);
if (needsAuth) {
- esClientConfig.httpAuth = `${config.username}:${config.password}`;
+ if (config.username) {
+ esClientConfig.httpAuth = `${config.username}:${config.password}`;
+ } else if (config.serviceAccountToken) {
+ esClientConfig.serviceAccountToken = config.serviceAccountToken;
+ }
}
if (Array.isArray(config.hosts)) {
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index ed55c6e3d09cb..65ea082c9d8a8 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -345,6 +345,7 @@ export const config: {
hosts: Type;
username: Type;
password: Type;
+ serviceAccountToken: Type;
requestHeadersWhitelist: Type;
customHeaders: Type>;
shardTimeout: Type;
@@ -948,7 +949,7 @@ export type ElasticsearchClient = Omit & {
+export type ElasticsearchClientConfig = Pick & {
pingTimeout?: ElasticsearchConfig['pingTimeout'] | ClientOptions['pingTimeout'];
requestTimeout?: ElasticsearchConfig['requestTimeout'] | ClientOptions['requestTimeout'];
ssl?: Partial;
@@ -968,6 +969,7 @@ export class ElasticsearchConfig {
readonly pingTimeout: Duration;
readonly requestHeadersWhitelist: string[];
readonly requestTimeout: Duration;
+ readonly serviceAccountToken?: string;
readonly shardTimeout: Duration;
readonly sniffInterval: false | Duration;
readonly sniffOnConnectionFault: boolean;
@@ -1675,7 +1677,7 @@ export class LegacyClusterClient implements ILegacyClusterClient {
}
// @public @deprecated (undocumented)
-export type LegacyElasticsearchClientConfig = Pick & Pick & {
+export type LegacyElasticsearchClientConfig = Pick & Pick & {
pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout'];
requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout'];
sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval'];
diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
index 39a7665f1ce5e..c7a129418765b 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
+++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
@@ -51,6 +51,7 @@ kibana_vars=(
elasticsearch.pingTimeout
elasticsearch.requestHeadersWhitelist
elasticsearch.requestTimeout
+ elasticsearch.serviceAccountToken
elasticsearch.shardTimeout
elasticsearch.sniffInterval
elasticsearch.sniffOnConnectionFault
From 0121dee9a447dc60787a8372b75a59d8e79d615a Mon Sep 17 00:00:00 2001
From: Scotty Bollinger
Date: Mon, 12 Jul 2021 13:36:59 -0500
Subject: [PATCH 16/84] Hide heading when page loading (#105263)
---
.../components/add_source/add_source_list.test.tsx | 7 +++++++
.../components/add_source/add_source_list.tsx | 4 +++-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx
index b30511f0a6d80..af94707aa3612 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx
@@ -58,6 +58,13 @@ describe('AddSourceList', () => {
expect(wrapper.find(AvailableSourcesList)).toHaveLength(1);
});
+ it('does not render header when loading', () => {
+ setMockValues({ ...mockValues, dataLoading: true });
+ const wrapper = shallow();
+
+ expect(wrapper.prop('pageHeader')).toBe(undefined);
+ });
+
describe('layout', () => {
it('renders the default workplace search layout when on an organization view', () => {
setMockValues({ ...mockValues, isOrganization: true });
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx
index a7a64194cb42f..165586dcc3903 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx
@@ -104,7 +104,9 @@ export const AddSourceList: React.FC = () => {
{!isOrganization && (
From 8f7df0347221243497e172b6ea788c11b09dc68d Mon Sep 17 00:00:00 2001
From: Lisa Cawley
Date: Mon, 12 Jul 2021 11:48:05 -0700
Subject: [PATCH 17/84] [ML] Edit stack management message (#105167)
---
.../jobs_list/components/insufficient_license_page.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx
index 6d0c3639d939c..7fcac0ad6992a 100644
--- a/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx
+++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx
@@ -30,7 +30,7 @@ export const InsufficientLicensePage: FC = ({ basePath }) => (
}
@@ -38,13 +38,13 @@ export const InsufficientLicensePage: FC = ({ basePath }) => (
),
From 8acdcff840ac1d7e0fe00319b2e84600d177e107 Mon Sep 17 00:00:00 2001
From: Candace Park <56409205+parkiino@users.noreply.github.com>
Date: Mon, 12 Jul 2021 15:37:16 -0400
Subject: [PATCH 18/84] [Security Solution][Endpoint][Host Isolation] Host
isolation cases view unit test (#104579)
---
.../user_action_tree/index.test.tsx | 87 ++++++++++++++++++-
...tion_host_isolation_comment_event.test.tsx | 42 +++++++++
...er_action_host_isolation_comment_event.tsx | 2 +-
.../plugins/cases/public/containers/mock.ts | 61 +++++++++++++
4 files changed, 190 insertions(+), 2 deletions(-)
create mode 100644 x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.test.tsx
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
index 610399c31928b..be1516843184d 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
@@ -13,7 +13,14 @@ import routeData from 'react-router';
import { getFormMock, useFormMock, useFormDataMock } from '../__mock__/form';
import { useUpdateComment } from '../../containers/use_update_comment';
-import { basicCase, basicPush, getUserAction } from '../../containers/mock';
+import {
+ basicCase,
+ basicPush,
+ getUserAction,
+ getHostIsolationUserAction,
+ hostIsolationComment,
+ hostReleaseComment,
+} from '../../containers/mock';
import { UserActionTree } from '.';
import { TestProviders } from '../../common/mock';
import { Ecs } from '../../../common';
@@ -368,4 +375,82 @@ describe(`UserActionTree`, () => {
).toEqual(true);
});
});
+ describe('Host isolation action', () => {
+ it('renders in the cases details view', async () => {
+ const isolateAction = [getHostIsolationUserAction()];
+ const props = {
+ ...defaultProps,
+ caseUserActions: isolateAction,
+ data: { ...defaultProps.data, comments: [...basicCase.comments, hostIsolationComment()] },
+ };
+
+ const wrapper = mount(
+
+
+
+ );
+ await waitFor(() => {
+ expect(wrapper.find(`[data-test-subj="endpoint-action"]`).exists()).toBe(true);
+ });
+ });
+
+ it('shows the correct username', async () => {
+ const isolateAction = [getHostIsolationUserAction()];
+ const props = {
+ ...defaultProps,
+ caseUserActions: isolateAction,
+ data: { ...defaultProps.data, comments: [hostIsolationComment()] },
+ };
+
+ const wrapper = mount(
+
+
+
+ );
+ await waitFor(() => {
+ expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().prop('name')).toEqual(
+ defaultProps.data.createdBy.fullName
+ );
+ });
+ });
+
+ it('shows a lock icon if the action is isolate', async () => {
+ const isolateAction = [getHostIsolationUserAction()];
+ const props = {
+ ...defaultProps,
+ caseUserActions: isolateAction,
+ data: { ...defaultProps.data, comments: [hostIsolationComment()] },
+ };
+
+ const wrapper = mount(
+
+
+
+ );
+ await waitFor(() => {
+ expect(
+ wrapper.find(`[data-test-subj="endpoint-action"]`).first().prop('timelineIcon')
+ ).toBe('lock');
+ });
+ });
+ it('shows a lockOpen icon if the action is unisolate/release', async () => {
+ const isolateAction = [getHostIsolationUserAction()];
+ const props = {
+ ...defaultProps,
+ caseUserActions: isolateAction,
+ data: { ...defaultProps.data, comments: [hostReleaseComment()] },
+ };
+
+ const wrapper = mount(
+
+
+
+ );
+ await waitFor(() => {
+ expect(
+ wrapper.find(`[data-test-subj="endpoint-action"]`).first().prop('timelineIcon')
+ ).toBe('lockOpen');
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.test.tsx
new file mode 100644
index 0000000000000..636cd7e40aac1
--- /dev/null
+++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.test.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+import { HostIsolationCommentEvent } from './user_action_host_isolation_comment_event';
+
+const defaultProps = () => {
+ return {
+ type: 'isolate',
+ endpoints: [{ endpointId: 'e1', hostname: 'host1' }],
+ href: jest.fn(),
+ onClick: jest.fn(),
+ };
+};
+
+describe('UserActionHostIsolationCommentEvent', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders with the correct action and hostname', async () => {
+ const wrapper = mount();
+ expect(wrapper.find(`[data-test-subj="actions-link-e1"]`).first().exists()).toBeTruthy();
+ expect(wrapper.text()).toBe('isolated host host1');
+ });
+
+ it('navigates to app on link click', async () => {
+ const onActionsLinkClick = jest.fn();
+
+ const wrapper = mount(
+
+ );
+
+ wrapper.find(`[data-test-subj="actions-link-e1"]`).first().simulate('click');
+ expect(onActionsLinkClick).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx
index d363e874a4e0d..2381d31b3ada8 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx
@@ -44,7 +44,7 @@ const HostIsolationCommentEventComponent: React.FC = ({
{endpoints[0].hostname}
diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts
index a900010235c9f..c955bb34240e2 100644
--- a/x-pack/plugins/cases/public/containers/mock.ts
+++ b/x-pack/plugins/cases/public/containers/mock.ts
@@ -76,6 +76,58 @@ export const alertComment: Comment = {
version: 'WzQ3LDFc',
};
+export const hostIsolationComment: () => Comment = () => {
+ return {
+ type: CommentType.actions,
+ comment: 'I just isolated the host!',
+ id: 'isolate-comment-id',
+ actions: {
+ targets: [
+ {
+ hostname: 'host1',
+ endpointId: '001',
+ },
+ ],
+ type: 'isolate',
+ },
+ associationType: AssociationType.case,
+ createdAt: basicCreatedAt,
+ createdBy: elasticUser,
+ owner: SECURITY_SOLUTION_OWNER,
+ pushedAt: null,
+ pushedBy: null,
+ updatedAt: null,
+ updatedBy: null,
+ version: 'WzQ3LDFc',
+ };
+};
+
+export const hostReleaseComment: () => Comment = () => {
+ return {
+ type: CommentType.actions,
+ comment: 'I just released the host!',
+ id: 'isolate-comment-id',
+ actions: {
+ targets: [
+ {
+ hostname: 'host1',
+ endpointId: '001',
+ },
+ ],
+ type: 'unisolate',
+ },
+ associationType: AssociationType.case,
+ createdAt: basicCreatedAt,
+ createdBy: elasticUser,
+ owner: SECURITY_SOLUTION_OWNER,
+ pushedAt: null,
+ pushedBy: null,
+ updatedAt: null,
+ updatedBy: null,
+ version: 'WzQ3LDFc',
+ };
+};
+
export const basicCase: Case = {
type: CaseType.individual,
owner: SECURITY_SOLUTION_OWNER,
@@ -374,6 +426,15 @@ export const getAlertUserAction = () => ({
newValue: '{"type":"alert","alertId":"alert-id-1","index":"index-id-1"}',
});
+export const getHostIsolationUserAction = () => ({
+ ...basicAction,
+ actionId: 'isolate-action-id',
+ actionField: ['comment'] as UserActionField,
+ action: 'create' as UserAction,
+ commentId: 'isolate-comment-id',
+ newValue: 'some value',
+});
+
export const caseUserActions: CaseUserActions[] = [
getUserAction(['description'], 'create'),
getUserAction(['comment'], 'create'),
From 3d724ee706b00cf7326d7f2f68af85f5016c7328 Mon Sep 17 00:00:00 2001
From: Joe Portner <5295965+jportner@users.noreply.github.com>
Date: Mon, 12 Jul 2021 16:03:32 -0400
Subject: [PATCH 19/84] [project-assigner] Add entry for Platform Security Team
(#105307)
---
.github/workflows/project-assigner.yml | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/project-assigner.yml b/.github/workflows/project-assigner.yml
index 4966a0b506317..7a09c2b35cc17 100644
--- a/.github/workflows/project-assigner.yml
+++ b/.github/workflows/project-assigner.yml
@@ -11,5 +11,13 @@ jobs:
uses: elastic/github-actions/project-assigner@v2.0.0
id: project_assigner
with:
- issue-mappings: '[{"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"}, {"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"}, {"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"}]'
+ issue-mappings: |
+ [
+ {"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"},
+ {"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"},
+ {"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"},
+ {"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"},
+ {"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"},
+ {"label": "Team:Security", "projectNumber": 320, "columnName": "Awaiting triage", "projectScope": "org"}
+ ]
ghToken: ${{ secrets.PROJECT_ASSIGNER_TOKEN }}
From e279042c56a8199b18eca4e66b056613afa8d866 Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Mon, 12 Jul 2021 13:06:11 -0700
Subject: [PATCH 20/84] Revert "[project-assigner] Add entry for Platform
Security Team (#105307)" (#105311)
This reverts commit 3d724ee706b00cf7326d7f2f68af85f5016c7328.
---
.github/workflows/project-assigner.yml | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/.github/workflows/project-assigner.yml b/.github/workflows/project-assigner.yml
index 7a09c2b35cc17..4966a0b506317 100644
--- a/.github/workflows/project-assigner.yml
+++ b/.github/workflows/project-assigner.yml
@@ -11,13 +11,5 @@ jobs:
uses: elastic/github-actions/project-assigner@v2.0.0
id: project_assigner
with:
- issue-mappings: |
- [
- {"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"},
- {"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"},
- {"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"},
- {"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"},
- {"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"},
- {"label": "Team:Security", "projectNumber": 320, "columnName": "Awaiting triage", "projectScope": "org"}
- ]
+ issue-mappings: '[{"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"}, {"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"}, {"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"}]'
ghToken: ${{ secrets.PROJECT_ASSIGNER_TOKEN }}
From fdc99681a74b810da08e84b989620c2804e363c5 Mon Sep 17 00:00:00 2001
From: Vadim Yakhin
Date: Mon, 12 Jul 2021 17:20:48 -0300
Subject: [PATCH 21/84] [Workplace Search] Port PR 4033 from ent-search to
Kibana and update typings (#105054)
* Improve typings
Custom API Source allow indexing several types of data. We didn't
account for all of them.
For example, geolocation can be array of arrays of numbers.
This commit improves typings.
The following commits mostly fix TS errors
that appear after this commit.
* Remove type castings to account for all possible variable types
* Update helper functions to accept new CustomAPIFieldValue
* Fix TS error: convert url to string before using it in EuiLink
* Update mock and tests to match updated typings
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../__mocks__/content_sources.mock.ts | 9 ++++++--
.../applications/workplace_search/types.ts | 21 +++++++++++++++++--
.../get_as_local_datetime_string.test.ts | 6 ++++++
.../utils/get_as_local_datetime_string.ts | 8 +++++--
.../workplace_search/utils/mime_types.ts | 5 ++++-
.../example_result_detail_card.tsx | 2 +-
.../example_search_result_group.tsx | 5 ++---
.../example_standout_result.tsx | 4 ++--
.../display_settings/subtitle_field.test.tsx | 8 ++++++-
.../display_settings/title_field.test.tsx | 12 +++++++++--
.../components/source_content.tsx | 2 +-
11 files changed, 65 insertions(+), 17 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
index 748dc6a7cbcf8..c599a13cc3119 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
@@ -303,6 +303,7 @@ export const exampleResult = {
titleField: 'otherTitle',
subtitleField: 'otherSubtitle',
urlField: 'myLink',
+ urlFieldIsLinkable: true,
color: '#e3e3e3',
descriptionField: 'about',
typeField: 'otherType',
@@ -314,14 +315,18 @@ export const exampleResult = {
{ fieldName: 'dogs', label: 'Canines' },
],
},
- titleFieldHover: false,
- urlFieldHover: false,
exampleDocuments: [
{
myLink: 'http://foo',
otherTitle: 'foo',
+ content_source_id: '60e85e7ea2564c265a88a4f0',
+ external_id: 'doc-60e85eb7a2564c937a88a4f3',
+ last_updated: '2021-07-09T14:35:35+00:00',
+ updated_at: '2021-07-09T14:35:35+00:00',
+ source: 'custom',
},
],
+ schemaFields: {},
};
export const mostRecentIndexJob = {
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
index edc772b369558..e50b12f781947 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
@@ -96,7 +96,7 @@ export interface ContentSource {
export interface SourceContentItem {
id: string;
last_updated: string;
- [key: string]: string;
+ [key: string]: string | CustomAPIFieldValue;
}
export interface ContentSourceDetails extends ContentSource {
@@ -186,8 +186,25 @@ export interface CustomSource {
id: string;
}
+// https://www.elastic.co/guide/en/workplace-search/current/workplace-search-custom-sources-api.html#_schema_data_types
+type CustomAPIString = string | string[];
+type CustomAPINumber = number | number[];
+type CustomAPIDate = string | string[];
+type CustomAPIGeolocation = string | string[] | number[] | number[][];
+
+export type CustomAPIFieldValue =
+ | CustomAPIString
+ | CustomAPINumber
+ | CustomAPIDate
+ | CustomAPIGeolocation;
+
export interface Result {
- [key: string]: string | string[];
+ content_source_id: string;
+ last_updated: string;
+ external_id: string;
+ updated_at: string;
+ source: string;
+ [key: string]: CustomAPIFieldValue;
}
export interface OptionValue {
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts
index 6475df7f4c399..36df182b99b85 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts
@@ -14,6 +14,12 @@ describe('getAsLocalDateTimeString', () => {
expect(getAsLocalDateTimeString(date)).toEqual(new Date(Date.parse(date)).toLocaleString());
});
+ it('returns null if passed value is not a string', () => {
+ const date = ['1', '2'];
+
+ expect(getAsLocalDateTimeString(date)).toEqual(null);
+ });
+
it('returns null if string cannot be parsed as date', () => {
const date = 'foo';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts
index d5ceb50d4c9af..6350c4e4a4099 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts
@@ -5,7 +5,11 @@
* 2.0.
*/
-export const getAsLocalDateTimeString = (str: string) => {
- const dateValue = Date.parse(str);
+import { CustomAPIFieldValue } from '../types';
+
+export const getAsLocalDateTimeString = (maybeDate: CustomAPIFieldValue) => {
+ if (typeof maybeDate !== 'string') return null;
+
+ const dateValue = Date.parse(maybeDate);
return dateValue ? new Date(dateValue).toLocaleString() : null;
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/mime_types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/mime_types.ts
index f7664c90d461c..7a5020be5986e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/mime_types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/mime_types.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { CustomAPIFieldValue } from '../types';
+
const mimeTypes = {
'application/iwork-keynote-sffkey': 'Keynote',
'application/x-iwork-keynote-sffkey': 'Keynote',
@@ -51,4 +53,5 @@ const mimeTypes = {
'video/quicktime': 'MOV',
} as { [key: string]: string };
-export const mimeType = (type: string) => mimeTypes[type.toLowerCase()] || type;
+export const mimeType = (type: CustomAPIFieldValue) =>
+ mimeTypes[type.toString().toLowerCase()] || type;
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx
index eef508b2e618f..8b0a72ac23e39 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx
@@ -62,7 +62,7 @@ export const ExampleResultDetailCard: React.FC = () => {
{detailFields.length > 0 ? (
detailFields.map(({ fieldName, label }, index) => {
- const value = result[fieldName] as string;
+ const value = result[fieldName];
const dateValue = getAsLocalDateTimeString(value);
return (
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx
index 549faf1676a54..3ca5b619c0366 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx
@@ -117,7 +117,7 @@ export const ExampleSearchResultGroup: React.FC = () => {
data-test-subj="MediaTypeField"
>
- {mimeType(result[mediaTypeField] as string)}
+ {mimeType(result[mediaTypeField])}
)}
@@ -135,8 +135,7 @@ export const ExampleSearchResultGroup: React.FC = () => {
by {result[updatedByField]}
)}
- {getAsLocalDateTimeString(result.last_updated as string) ||
- result.last_updated}
+ {getAsLocalDateTimeString(result.last_updated) || result.last_updated}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
index 46b8de6789467..b3ba4c6d50973 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
@@ -109,7 +109,7 @@ export const ExampleStandoutResult: React.FC = () => {
data-test-subj="MediaTypeField"
>
- {mimeType(result[mediaTypeField] as string)}
+ {mimeType(result[mediaTypeField])}
)}
@@ -127,7 +127,7 @@ export const ExampleStandoutResult: React.FC = () => {
by {result[updatedByField]}
)}
- {getAsLocalDateTimeString(result.last_updated as string) || result.last_updated}
+ {getAsLocalDateTimeString(result.last_updated) || result.last_updated}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.test.tsx
index 76c28ae3d4060..7506c499dff31 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.test.tsx
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { exampleResult } from '../../../../__mocks__/content_sources.mock';
+
import React from 'react';
import { shallow } from 'enzyme';
@@ -12,7 +14,11 @@ import { shallow } from 'enzyme';
import { SubtitleField } from './subtitle_field';
describe('SubtitleField', () => {
- const result = { foo: 'bar' };
+ const result = {
+ ...exampleResult.exampleDocuments[0],
+ foo: 'bar',
+ };
+
it('renders', () => {
const props = {
result,
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx
index 2ed4aa0b0fad1..e5681bc7e8619 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { exampleResult } from '../../../../__mocks__/content_sources.mock';
+
import React from 'react';
import { shallow } from 'enzyme';
@@ -12,7 +14,10 @@ import { shallow } from 'enzyme';
import { TitleField } from './title_field';
describe('TitleField', () => {
- const result = { foo: 'bar' };
+ const result = {
+ ...exampleResult.exampleDocuments[0],
+ foo: 'bar',
+ };
it('renders', () => {
const props = {
result,
@@ -26,7 +31,10 @@ describe('TitleField', () => {
it('handles title when array', () => {
const props = {
- result: { foo: ['baz', 'bar'] },
+ result: {
+ ...exampleResult.exampleDocuments[0],
+ foo: ['baz', 'bar'],
+ },
titleField: 'foo',
titleFieldHover: false,
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx
index a0e3c28f20eb0..a97cc85cb822a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx
@@ -137,7 +137,7 @@ export const SourceContent: React.FC = () => {
)}
{urlFieldIsLinkable && (
-
+
)}
From 41a93079b21b7516ce2aac40e09341056005f718 Mon Sep 17 00:00:00 2001
From: Joe Portner <5295965+jportner@users.noreply.github.com>
Date: Mon, 12 Jul 2021 16:50:00 -0400
Subject: [PATCH 22/84] [project-assigner] Add entry for Platform Security Team
again (#105327)
---
.github/workflows/project-assigner.yml | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/project-assigner.yml b/.github/workflows/project-assigner.yml
index 4966a0b506317..f4e62648a9741 100644
--- a/.github/workflows/project-assigner.yml
+++ b/.github/workflows/project-assigner.yml
@@ -8,8 +8,16 @@ jobs:
name: Assign issue or PR to project based on label
steps:
- name: Assign to project
- uses: elastic/github-actions/project-assigner@v2.0.0
+ uses: elastic/github-actions/project-assigner@v2.1.0
id: project_assigner
with:
- issue-mappings: '[{"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"}, {"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"}, {"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"}]'
+ issue-mappings: |
+ [
+ {"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"},
+ {"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"},
+ {"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"},
+ {"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"},
+ {"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"},
+ {"label": "Team:Security", "projectNumber": 320, "columnName": "Awaiting triage", "projectScope": "org"}
+ ]
ghToken: ${{ secrets.PROJECT_ASSIGNER_TOKEN }}
From c9b1a3cdef2c40190cbcfe6a06a0b495f8140956 Mon Sep 17 00:00:00 2001
From: Kevin Logan <56395104+kevinlog@users.noreply.github.com>
Date: Mon, 12 Jul 2021 17:32:37 -0400
Subject: [PATCH 23/84] [Security Solution] Push user action comments for Host
Isolation to connectors (#105265)
---
.../plugins/cases/server/client/cases/mock.ts | 100 ++++++++++++++++++
.../cases/server/client/cases/utils.test.ts | 85 +++++++++++++++
.../cases/server/client/cases/utils.ts | 27 ++++-
3 files changed, 210 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/cases/server/client/cases/mock.ts b/x-pack/plugins/cases/server/client/cases/mock.ts
index 23db57c6d3097..313d6cd12a6db 100644
--- a/x-pack/plugins/cases/server/client/cases/mock.ts
+++ b/x-pack/plugins/cases/server/client/cases/mock.ts
@@ -52,6 +52,106 @@ export const comment: CommentResponse = {
version: 'WzEsMV0=',
};
+export const isolateCommentActions: CommentResponse = {
+ associationType: AssociationType.case,
+ id: 'mock-action-comment-1',
+ comment: 'Isolating this for investigation',
+ type: CommentType.actions as const,
+ created_at: '2019-11-25T21:55:00.177Z',
+ actions: {
+ targets: [
+ {
+ endpointId: '123',
+ hostname: 'windows-host-1',
+ },
+ ],
+ type: 'isolate',
+ },
+ created_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ owner: SECURITY_SOLUTION_OWNER,
+ pushed_at: null,
+ pushed_by: null,
+ updated_at: '2019-11-25T21:55:00.177Z',
+ updated_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ version: 'WzEsMV0=',
+};
+
+export const releaseCommentActions: CommentResponse = {
+ associationType: AssociationType.case,
+ id: 'mock-action-comment-1',
+ comment: 'Releasing this for investigation',
+ type: CommentType.actions as const,
+ created_at: '2019-11-25T21:55:00.177Z',
+ actions: {
+ targets: [
+ {
+ endpointId: '123',
+ hostname: 'windows-host-1',
+ },
+ ],
+ type: 'unisolate',
+ },
+ created_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ owner: SECURITY_SOLUTION_OWNER,
+ pushed_at: null,
+ pushed_by: null,
+ updated_at: '2019-11-25T21:55:00.177Z',
+ updated_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ version: 'WzEsMV0=',
+};
+
+export const isolateCommentActionsMultipleTargets: CommentResponse = {
+ associationType: AssociationType.case,
+ id: 'mock-action-comment-1',
+ comment: 'Isolating this for investigation',
+ type: CommentType.actions as const,
+ created_at: '2019-11-25T21:55:00.177Z',
+ actions: {
+ targets: [
+ {
+ endpointId: '123',
+ hostname: 'windows-host-1',
+ },
+ {
+ endpointId: '456',
+ hostname: 'windows-host-2',
+ },
+ ],
+ type: 'isolate',
+ },
+ created_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ owner: SECURITY_SOLUTION_OWNER,
+ pushed_at: null,
+ pushed_by: null,
+ updated_at: '2019-11-25T21:55:00.177Z',
+ updated_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ version: 'WzEsMV0=',
+};
+
export const commentAlert: CommentResponse = {
associationType: AssociationType.case,
id: 'mock-comment-1',
diff --git a/x-pack/plugins/cases/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts
index bfd5d1279420b..dc8af8785056d 100644
--- a/x-pack/plugins/cases/server/client/cases/utils.test.ts
+++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts
@@ -18,6 +18,9 @@ import {
commentAlert,
commentAlertMultipleIds,
commentGeneratedAlert,
+ isolateCommentActions,
+ releaseCommentActions,
+ isolateCommentActionsMultipleTargets,
} from './mock';
import {
@@ -37,6 +40,52 @@ const formatComment = {
comment: 'Wow, good luck catching that bad meanie!',
};
+const formatIsolateActionComment = {
+ commentId: isolateCommentActions.id,
+ comment: 'Isolating this for investigation',
+ actions: {
+ targets: [
+ {
+ hostname: 'windows-host-1',
+ endpointId: '123',
+ },
+ ],
+ type: 'isolate',
+ },
+};
+
+const formatReleaseActionComment = {
+ commentId: releaseCommentActions.id,
+ comment: 'Releasing this for investigation',
+ actions: {
+ targets: [
+ {
+ hostname: 'windows-host-1',
+ endpointId: '123',
+ },
+ ],
+ type: 'unisolate',
+ },
+};
+
+const formatIsolateCommentActionsMultipleTargets = {
+ commentId: isolateCommentActionsMultipleTargets.id,
+ comment: 'Isolating this for investigation',
+ actions: {
+ targets: [
+ {
+ hostname: 'windows-host-1',
+ endpointId: '123',
+ },
+ {
+ hostname: 'windows-host-2',
+ endpointId: '456',
+ },
+ ],
+ type: 'isolate',
+ },
+};
+
const params = { ...basicParams };
describe('utils', () => {
@@ -289,6 +338,42 @@ describe('utils', () => {
},
]);
});
+
+ test('transform isolate action comment', () => {
+ const comments = [isolateCommentActions];
+ const res = transformComments(comments, ['informationCreated']);
+ const actionText = `Isolated host ${formatIsolateActionComment.actions.targets[0].hostname} with comment: ${formatIsolateActionComment.comment}`;
+ expect(res).toEqual([
+ {
+ commentId: formatIsolateActionComment.commentId,
+ comment: `${actionText} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`,
+ },
+ ]);
+ });
+
+ test('transform release action comment', () => {
+ const comments = [releaseCommentActions];
+ const res = transformComments(comments, ['informationCreated']);
+ const actionText = `Released host ${formatReleaseActionComment.actions.targets[0].hostname} with comment: ${formatReleaseActionComment.comment}`;
+ expect(res).toEqual([
+ {
+ commentId: formatReleaseActionComment.commentId,
+ comment: `${actionText} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`,
+ },
+ ]);
+ });
+
+ test('transform isolate action comment with multiple hosts', () => {
+ const comments = [isolateCommentActionsMultipleTargets];
+ const res = transformComments(comments, ['informationCreated']);
+ const actionText = `Isolated host ${formatIsolateCommentActionsMultipleTargets.actions.targets[0].hostname} and 1 more with comment: ${formatIsolateCommentActionsMultipleTargets.comment}`;
+ expect(res).toEqual([
+ {
+ commentId: formatIsolateCommentActionsMultipleTargets.commentId,
+ comment: `${actionText} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`,
+ },
+ ]);
+ });
});
describe('transformers', () => {
diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts
index f5a10d705e095..f44adbea2c8b2 100644
--- a/x-pack/plugins/cases/server/client/cases/utils.ts
+++ b/x-pack/plugins/cases/server/client/cases/utils.ts
@@ -19,6 +19,7 @@ import {
CommentAttributes,
CommentRequestUserType,
CommentRequestAlertType,
+ CommentRequestActionsType,
} from '../../../common';
import { ActionsClient } from '../../../../actions/server';
import { CasesClientGetAlertsResponse } from '../../client/alerts/types';
@@ -76,6 +77,17 @@ const getCommentContent = (comment: CommentResponse): string => {
} else if (comment.type === CommentType.alert || comment.type === CommentType.generatedAlert) {
const ids = getAlertIds(comment);
return `Alert with ids ${ids.join(', ')} added to case`;
+ } else if (
+ comment.type === CommentType.actions &&
+ (comment.actions.type === 'isolate' || comment.actions.type === 'unisolate')
+ ) {
+ const firstHostname =
+ comment.actions.targets?.length > 0 ? comment.actions.targets[0].hostname : 'unknown';
+ const totalHosts = comment.actions.targets.length;
+ const actionText = comment.actions.type === 'isolate' ? 'Isolated' : 'Released';
+ const additionalHostsText = totalHosts - 1 > 0 ? `and ${totalHosts - 1} more ` : ``;
+
+ return `${actionText} host ${firstHostname} ${additionalHostsText}with comment: ${comment.comment}`;
}
return '';
@@ -161,7 +173,8 @@ export const createIncident = async ({
const commentsToBeUpdated = caseComments?.filter(
(comment) =>
// We push only user's comments
- comment.type === CommentType.user && commentsIdsToBeUpdated.has(comment.id)
+ (comment.type === CommentType.user || comment.type === CommentType.actions) &&
+ commentsIdsToBeUpdated.has(comment.id)
);
const totalAlerts = countAlerts(caseComments);
@@ -322,7 +335,7 @@ export const isCommentAlertType = (
export const getCommentContextFromAttributes = (
attributes: CommentAttributes
-): CommentRequestUserType | CommentRequestAlertType => {
+): CommentRequestUserType | CommentRequestAlertType | CommentRequestActionsType => {
const owner = attributes.owner;
switch (attributes.type) {
case CommentType.user:
@@ -340,6 +353,16 @@ export const getCommentContextFromAttributes = (
rule: attributes.rule,
owner,
};
+ case CommentType.actions:
+ return {
+ type: attributes.type,
+ comment: attributes.comment,
+ actions: {
+ targets: attributes.actions.targets,
+ type: attributes.actions.type,
+ },
+ owner,
+ };
default:
return {
type: CommentType.user,
From 0e83e992f0b049c4513fe6925bd0493eaae8e14b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ece=20=C3=96zalp?=
Date: Mon, 12 Jul 2021 17:32:54 -0400
Subject: [PATCH 24/84] [CTI] adds Recorded Future link (#105301)
---
x-pack/plugins/security_solution/common/cti/constants.ts | 2 +-
.../overview_cti_links/threat_intel_panel_view.tsx | 4 ++--
.../overview/containers/overview_cti_links/index.tsx | 9 ++++-----
3 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/cti/constants.ts b/x-pack/plugins/security_solution/common/cti/constants.ts
index 631a13df1ecb1..7b22e9036f566 100644
--- a/x-pack/plugins/security_solution/common/cti/constants.ts
+++ b/x-pack/plugins/security_solution/common/cti/constants.ts
@@ -65,9 +65,9 @@ export const CTI_DEFAULT_SOURCES = [
'Abuse Malware',
'AlienVault OTX',
'Anomali',
- 'Anomali ThreatStream',
'Malware Bazaar',
'MISP',
+ 'Recorded Future',
];
export const DEFAULT_CTI_SOURCE_INDEX = ['filebeat-*'];
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx
index b34f6e657d39a..51ce06762ddf9 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx
@@ -159,10 +159,10 @@ export const ThreatIntelPanelView: React.FC = ({
alignItems="center"
justifyContent="flexEnd"
>
-
+
{count}
-
+
{path ? (
{linkCopy}
diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/index.tsx
index b7f919dc97013..8839aff7dc33d 100644
--- a/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/index.tsx
@@ -74,8 +74,8 @@ export const useCtiDashboardLinks = (
})
)
);
- const items = DashboardsSO.savedObjects?.reduce(
- (acc: CtiListItem[], dashboardSO, i) => {
+ const items = DashboardsSO.savedObjects
+ ?.reduce((acc: CtiListItem[], dashboardSO, i) => {
const item = createLinkFromDashboardSO(
dashboardSO,
eventCountsByDataset,
@@ -87,9 +87,8 @@ export const useCtiDashboardLinks = (
acc.push(item);
}
return acc;
- },
- []
- );
+ }, [])
+ .sort((a, b) => (a.title > b.title ? 1 : -1));
setListItems(items);
} else {
handleDisabledPlugin();
From a11e9ed235df1a7d6eac4d461fd0c27f341d316b Mon Sep 17 00:00:00 2001
From: Lee Drengenberg
Date: Mon, 12 Jul 2021 17:05:31 -0500
Subject: [PATCH 25/84] new dialog for built-in alerts needs to be clicked
(#105284)
---
.../apps/monitoring/_monitoring_metricbeat.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/x-pack/test/stack_functional_integration/apps/monitoring/_monitoring_metricbeat.js b/x-pack/test/stack_functional_integration/apps/monitoring/_monitoring_metricbeat.js
index d8341c56aa25c..9dcc18b3c3f20 100644
--- a/x-pack/test/stack_functional_integration/apps/monitoring/_monitoring_metricbeat.js
+++ b/x-pack/test/stack_functional_integration/apps/monitoring/_monitoring_metricbeat.js
@@ -12,6 +12,7 @@ export default ({ getService, getPageObjects }) => {
const log = getService('log');
const testSubjects = getService('testSubjects');
const isSaml = !!process.env.VM.includes('saml') || !!process.env.VM.includes('oidc');
+ const clusterOverview = getService('monitoringClusterOverview');
before(async () => {
await browser.setWindowSize(1200, 800);
@@ -25,6 +26,7 @@ export default ({ getService, getPageObjects }) => {
}
// navigateToApp without a username and password will default to the superuser
await PageObjects.common.navigateToApp('monitoring', { insertTimestamp: false });
+ await clusterOverview.closeAlertsModal();
});
it('should have Monitoring already enabled', async () => {
From dcc468c2f4b2663e1c7d59857ce849c55500f023 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?=
Date: Tue, 13 Jul 2021 01:08:47 +0300
Subject: [PATCH 26/84] [Osquery] Fix live query form saved queries picker bug
(#105308)
---
.../public/live_queries/form/index.tsx | 2 +-
.../form/live_query_query_field.tsx | 17 +++++--
.../saved_queries/saved_queries_dropdown.tsx | 51 +++++++++++++++----
.../queries/query_flyout.tsx | 8 ++-
4 files changed, 60 insertions(+), 18 deletions(-)
diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx
index 658280042696e..8654a74fecfb4 100644
--- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx
+++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx
@@ -110,7 +110,7 @@ const LiveQueryFormComponent: React.FC = ({
{
agentSelection: {
agents: [],
- allAgentsSelected: true,
+ allAgentsSelected: false,
platformsSelected: [],
policiesSelected: [],
},
diff --git a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx
index 9f0b5acd8994a..070339bb58af2 100644
--- a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx
+++ b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx
@@ -6,12 +6,15 @@
*/
import { EuiFormRow, EuiSpacer } from '@elastic/eui';
-import React, { useCallback } from 'react';
+import React, { useCallback, useRef } from 'react';
import { OsquerySchemaLink } from '../../components/osquery_schema_link';
import { FieldHook } from '../../shared_imports';
import { OsqueryEditor } from '../../editor';
-import { SavedQueriesDropdown } from '../../saved_queries/saved_queries_dropdown';
+import {
+ SavedQueriesDropdown,
+ SavedQueriesDropdownRef,
+} from '../../saved_queries/saved_queries_dropdown';
interface LiveQueryQueryFieldProps {
disabled?: boolean;
@@ -21,16 +24,18 @@ interface LiveQueryQueryFieldProps {
const LiveQueryQueryFieldComponent: React.FC = ({ disabled, field }) => {
const { value, setValue, errors } = field;
const error = errors[0]?.message;
+ const savedQueriesDropdownRef = useRef(null);
const handleSavedQueryChange = useCallback(
(savedQuery) => {
- setValue(savedQuery.query);
+ setValue(savedQuery?.query ?? '');
},
[setValue]
);
const handleEditorChange = useCallback(
(newValue) => {
+ savedQueriesDropdownRef.current?.clearSelection();
setValue(newValue);
},
[setValue]
@@ -39,7 +44,11 @@ const LiveQueryQueryFieldComponent: React.FC = ({ disa
return (
<>
-
+
}>
diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
index 073b56bfd1d4c..30df2267fbfa1 100644
--- a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
+++ b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
@@ -7,7 +7,14 @@
import { find } from 'lodash/fp';
import { EuiCodeBlock, EuiFormRow, EuiComboBox, EuiText } from '@elastic/eui';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import React, {
+ forwardRef,
+ useCallback,
+ useEffect,
+ useImperativeHandle,
+ useMemo,
+ useState,
+} from 'react';
import { SimpleSavedObject } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -15,21 +22,27 @@ import { useHistory, useLocation } from 'react-router-dom';
import { useSavedQueries } from './use_saved_queries';
+export interface SavedQueriesDropdownRef {
+ clearSelection: () => void;
+}
+
interface SavedQueriesDropdownProps {
disabled?: boolean;
onChange: (
- value: SimpleSavedObject<{
- id: string;
- description?: string | undefined;
- query: string;
- }>['attributes']
+ value:
+ | SimpleSavedObject<{
+ id: string;
+ description?: string | undefined;
+ query: string;
+ }>['attributes']
+ | null
) => void;
}
-const SavedQueriesDropdownComponent: React.FC = ({
- disabled,
- onChange,
-}) => {
+const SavedQueriesDropdownComponent = forwardRef<
+ SavedQueriesDropdownRef,
+ SavedQueriesDropdownProps
+>(({ disabled, onChange }, ref) => {
const { replace } = useHistory();
const location = useLocation();
const [selectedOptions, setSelectedOptions] = useState([]);
@@ -52,6 +65,12 @@ const SavedQueriesDropdownComponent: React.FC = ({
const handleSavedQueryChange = useCallback(
(newSelectedOptions) => {
+ if (!newSelectedOptions.length) {
+ onChange(null);
+ setSelectedOptions(newSelectedOptions);
+ return;
+ }
+
const selectedSavedQuery = find(
['attributes.id', newSelectedOptions[0].value.id],
data?.savedObjects
@@ -80,6 +99,8 @@ const SavedQueriesDropdownComponent: React.FC = ({
[]
);
+ const clearSelection = useCallback(() => setSelectedOptions([]), []);
+
useEffect(() => {
const savedQueryId = location.state?.form?.savedQueryId;
@@ -94,6 +115,14 @@ const SavedQueriesDropdownComponent: React.FC = ({
}
}, [handleSavedQueryChange, replace, location.state, queryOptions]);
+ useImperativeHandle(
+ ref,
+ () => ({
+ clearSelection,
+ }),
+ [clearSelection]
+ );
+
return (
= ({
/>
);
-};
+});
export const SavedQueriesDropdown = React.memo(SavedQueriesDropdownComponent);
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
index 32547bc5dd2d0..95a31efeaf135 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
@@ -71,10 +71,14 @@ const QueryFlyoutComponent: React.FC = ({
[integrationPackageVersion]
);
- const { submit, setFieldValue } = form;
+ const { submit, setFieldValue, reset } = form;
const handleSetQueryValue = useCallback(
(savedQuery) => {
+ if (!savedQuery) {
+ return reset();
+ }
+
setFieldValue('id', savedQuery.id);
setFieldValue('query', savedQuery.query);
@@ -94,7 +98,7 @@ const QueryFlyoutComponent: React.FC = ({
setFieldValue('version', [savedQuery.version]);
}
},
- [isFieldSupported, setFieldValue]
+ [isFieldSupported, setFieldValue, reset]
);
return (
From e88910a1c67c3eb3d6301fe90bf8f0e38a2af221 Mon Sep 17 00:00:00 2001
From: Spencer
Date: Mon, 12 Jul 2021 15:11:49 -0700
Subject: [PATCH 27/84] [kbn/optimizer] rewrite bazel-out paths to source paths
(#105154)
Co-authored-by: spalger
---
.../src/public_path_module_creator.ts | 9 +++
.../__snapshots__/parse_path.test.ts.snap | 38 +++++------
.../kbn-optimizer/src/common/parse_path.ts | 64 ++++++++++++++++---
.../basic_optimization.test.ts | 10 ++-
.../worker/populate_bundle_cache_plugin.ts | 59 ++++++++++++++---
src/dev/precommit_hook/casing_check_config.js | 6 +-
6 files changed, 141 insertions(+), 45 deletions(-)
create mode 100644 packages/kbn-optimizer/src/__fixtures__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts
diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts
new file mode 100644
index 0000000000000..b03ee16d2f746
--- /dev/null
+++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+// stub
diff --git a/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap b/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap
index f537674c3fff7..2a30694afb826 100644
--- a/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap
+++ b/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`parseDirPath() parses / 1`] = `
-Object {
+ParsedPath {
"dirs": Array [],
"filename": undefined,
"query": undefined,
@@ -10,7 +10,7 @@ Object {
`;
exports[`parseDirPath() parses /foo 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
],
@@ -21,7 +21,7 @@ Object {
`;
exports[`parseDirPath() parses /foo/bar/baz 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -34,7 +34,7 @@ Object {
`;
exports[`parseDirPath() parses /foo/bar/baz/ 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -47,7 +47,7 @@ Object {
`;
exports[`parseDirPath() parses c:\\ 1`] = `
-Object {
+ParsedPath {
"dirs": Array [],
"filename": undefined,
"query": undefined,
@@ -56,7 +56,7 @@ Object {
`;
exports[`parseDirPath() parses c:\\foo 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
],
@@ -67,7 +67,7 @@ Object {
`;
exports[`parseDirPath() parses c:\\foo\\bar\\baz 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -80,7 +80,7 @@ Object {
`;
exports[`parseDirPath() parses c:\\foo\\bar\\baz\\ 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -93,7 +93,7 @@ Object {
`;
exports[`parseFilePath() parses /foo 1`] = `
-Object {
+ParsedPath {
"dirs": Array [],
"filename": "foo",
"query": undefined,
@@ -102,7 +102,7 @@ Object {
`;
exports[`parseFilePath() parses /foo/bar/baz 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -114,7 +114,7 @@ Object {
`;
exports[`parseFilePath() parses /foo/bar/baz.json 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -126,7 +126,7 @@ Object {
`;
exports[`parseFilePath() parses /foo/bar/baz.json?light 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -140,7 +140,7 @@ Object {
`;
exports[`parseFilePath() parses /foo/bar/baz.json?light=true&dark=false 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -155,7 +155,7 @@ Object {
`;
exports[`parseFilePath() parses c:/foo/bar/baz.json 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -167,7 +167,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo 1`] = `
-Object {
+ParsedPath {
"dirs": Array [],
"filename": "foo",
"query": undefined,
@@ -176,7 +176,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo\\bar\\baz 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -188,7 +188,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo\\bar\\baz.json 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -200,7 +200,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo\\bar\\baz.json?dark 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -214,7 +214,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo\\bar\\baz.json?dark=true&light=false 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
diff --git a/packages/kbn-optimizer/src/common/parse_path.ts b/packages/kbn-optimizer/src/common/parse_path.ts
index 7ea0042db25c9..da3744ba477bd 100644
--- a/packages/kbn-optimizer/src/common/parse_path.ts
+++ b/packages/kbn-optimizer/src/common/parse_path.ts
@@ -9,17 +9,61 @@
import normalizePath from 'normalize-path';
import Qs from 'querystring';
+class ParsedPath {
+ constructor(
+ public readonly root: string,
+ public readonly dirs: string[],
+ public readonly query?: Record,
+ public readonly filename?: string
+ ) {}
+
+ private indexOfDir(match: string | RegExp, fromIndex: number = 0) {
+ for (let i = fromIndex; i < this.dirs.length; i++) {
+ if (this.matchDir(i, match)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private matchDir(i: number, match: string | RegExp) {
+ return typeof match === 'string' ? this.dirs[i] === match : match.test(this.dirs[i]);
+ }
+
+ matchDirs(...segments: Array) {
+ const [first, ...rest] = segments;
+ let fromIndex = 0;
+ while (true) {
+ // do the dirs include the first segment to match?
+ const startIndex = this.indexOfDir(first, fromIndex);
+ if (startIndex === -1) {
+ return;
+ }
+
+ // are all of the ...rest segments also matched at this point?
+ if (!rest.length || rest.every((seg, i) => this.matchDir(startIndex + 1 + i, seg))) {
+ return { startIndex, endIndex: startIndex + rest.length };
+ }
+
+ // no match, search again, this time looking at instances after the matched instance
+ fromIndex = startIndex + 1;
+ }
+ }
+}
+
/**
* Parse an absolute path, supporting normalized paths from webpack,
* into a list of directories and root
*/
export function parseDirPath(path: string) {
const filePath = parseFilePath(path);
- return {
- ...filePath,
- dirs: [...filePath.dirs, ...(filePath.filename ? [filePath.filename] : [])],
- filename: undefined,
- };
+ return new ParsedPath(
+ filePath.root,
+ [...filePath.dirs, ...(filePath.filename ? [filePath.filename] : [])],
+ filePath.query,
+ undefined
+ );
}
export function parseFilePath(path: string) {
@@ -32,10 +76,10 @@ export function parseFilePath(path: string) {
}
const [root, ...others] = normalized.split('/');
- return {
- root: root === '' ? '/' : root,
- dirs: others.slice(0, -1),
+ return new ParsedPath(
+ root === '' ? '/' : root,
+ others.slice(0, -1),
query,
- filename: others[others.length - 1] || undefined,
- };
+ others[others.length - 1] || undefined
+ );
}
diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
index 97a7f33be673d..48d36b706b831 100644
--- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
+++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
@@ -15,7 +15,7 @@ import cpy from 'cpy';
import del from 'del';
import { tap, filter } from 'rxjs/operators';
import { REPO_ROOT } from '@kbn/utils';
-import { ToolingLog, createReplaceSerializer } from '@kbn/dev-utils';
+import { ToolingLog } from '@kbn/dev-utils';
import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '../index';
import { allValuesFrom } from '../common';
@@ -29,8 +29,6 @@ expect.addSnapshotSerializer({
test: (value: any) => typeof value === 'string' && value.includes(REPO_ROOT),
});
-expect.addSnapshotSerializer(createReplaceSerializer(/\w+-fastbuild/, '-fastbuild'));
-
const log = new ToolingLog({
level: 'error',
writeTo: {
@@ -132,7 +130,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
expect(foo.cache.getModuleCount()).toBe(6);
expect(foo.cache.getReferencedFiles()).toMatchInlineSnapshot(`
Array [
- /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/kibana.json,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts,
@@ -155,7 +153,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
/node_modules/@kbn/optimizer/postcss.config.js,
/node_modules/css-loader/package.json,
/node_modules/style-loader/package.json,
- /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/kibana.json,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.scss,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.ts,
@@ -175,7 +173,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
expect(baz.cache.getReferencedFiles()).toMatchInlineSnapshot(`
Array [
- /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/public/index.ts,
/packages/kbn-optimizer/src/worker/entry_point_creator.ts,
diff --git a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts
index 8d890b31b639d..a3455d7ddf2b9 100644
--- a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts
+++ b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts
@@ -6,11 +6,11 @@
* Side Public License, v 1.
*/
-import webpack from 'webpack';
-
import Path from 'path';
import { inspect } from 'util';
+import webpack from 'webpack';
+
import { Bundle, WorkerConfig, ascending, parseFilePath } from '../common';
import { BundleRefModule } from './bundle_ref_module';
import {
@@ -21,6 +21,20 @@ import {
getModulePath,
} from './webpack_helpers';
+function tryToResolveRewrittenPath(from: string, toResolve: string) {
+ try {
+ return require.resolve(toResolve);
+ } catch (error) {
+ if (error.code === 'MODULE_NOT_FOUND') {
+ throw new Error(
+ `attempted to rewrite bazel-out path [${from}] to [${toResolve}] but couldn't find the rewrite target`
+ );
+ }
+
+ throw error;
+ }
+}
+
/**
* sass-loader creates about a 40% overhead on the overall optimizer runtime, and
* so this constant is used to indicate to assignBundlesToWorkers() that there is
@@ -57,17 +71,44 @@ export class PopulateBundleCachePlugin {
let path = getModulePath(module);
let parsedPath = parseFilePath(path);
- if (parsedPath.dirs.includes('bazel-out')) {
- const index = parsedPath.dirs.indexOf('bazel-out');
- path = Path.join(
- workerConfig.repoRoot,
- 'bazel-out',
- ...parsedPath.dirs.slice(index + 1),
- parsedPath.filename ?? ''
+ const bazelOut = parsedPath.matchDirs(
+ 'bazel-out',
+ /-fastbuild$/,
+ 'bin',
+ 'packages',
+ /.*/,
+ 'target'
+ );
+
+ // if the module is referenced from one of our packages and resolved to the `bazel-out` dir
+ // we should rewrite our reference to point to the source file so that we can track the
+ // modified time of that file rather than the built output which is rebuilt all the time
+ // without actually changing
+ if (bazelOut) {
+ const packageDir = parsedPath.dirs[bazelOut.endIndex - 1];
+ const subDirs = parsedPath.dirs.slice(bazelOut.endIndex + 1);
+ path = tryToResolveRewrittenPath(
+ path,
+ Path.join(
+ workerConfig.repoRoot,
+ 'packages',
+ packageDir,
+ 'src',
+ ...subDirs,
+ parsedPath.filename
+ ? Path.basename(parsedPath.filename, Path.extname(parsedPath.filename))
+ : ''
+ )
);
parsedPath = parseFilePath(path);
}
+ if (parsedPath.matchDirs('bazel-out')) {
+ throw new Error(
+ `a bazel-out dir is being referenced by module [${path}] and not getting rewritten to its source location`
+ );
+ }
+
if (!parsedPath.dirs.includes('node_modules')) {
referencedFiles.add(path);
diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js
index ba18c085b649d..57ae640da3c84 100644
--- a/src/dev/precommit_hook/casing_check_config.js
+++ b/src/dev/precommit_hook/casing_check_config.js
@@ -77,7 +77,11 @@ export const IGNORE_FILE_GLOBS = [
*
* @type {Array}
*/
-export const KEBAB_CASE_DIRECTORY_GLOBS = ['packages/*', 'x-pack'];
+export const KEBAB_CASE_DIRECTORY_GLOBS = [
+ 'packages/*',
+ 'x-pack',
+ 'packages/kbn-optimizer/src/__fixtures__/mock_repo/packages/kbn-ui-shared-deps',
+];
/**
* These patterns are matched against directories and indicate
From 9ab26cf089cebd66fe1a5a33f49b487b9acf7d79 Mon Sep 17 00:00:00 2001
From: Brandon Morelli
Date: Mon, 12 Jul 2021 15:12:56 -0700
Subject: [PATCH 28/84] docs: APM updates for 7.14 (#104232)
Co-authored-by: Nathan L Smith
---
docs/apm/agent-configuration.asciidoc | 1 +
docs/apm/apm-alerts.asciidoc | 115 ++++++++----------
docs/apm/filters.asciidoc | 1 +
docs/apm/images/apm-agent-configuration.png | Bin 274436 -> 263010 bytes
docs/apm/images/apm-alert.png | Bin 589227 -> 422758 bytes
docs/apm/images/apm-error-group.png | Bin 308559 -> 334561 bytes
docs/apm/images/apm-logs-tab.png | Bin 439344 -> 558203 bytes
docs/apm/images/apm-services-overview.png | Bin 410666 -> 287982 bytes
docs/apm/images/apm-settings.png | Bin 206908 -> 227438 bytes
docs/apm/images/apm-span-detail.png | Bin 171713 -> 195719 bytes
docs/apm/images/apm-traces.png | Bin 191564 -> 259441 bytes
.../images/apm-transaction-duration-dist.png | Bin 60209 -> 61932 bytes
.../images/apm-transaction-response-dist.png | Bin 742410 -> 471222 bytes
docs/apm/images/apm-transaction-sample.png | Bin 256043 -> 314695 bytes
docs/apm/images/apm-transactions-overview.png | Bin 577820 -> 543872 bytes
docs/apm/images/service-maps-java.png | Bin 571180 -> 416593 bytes
docs/apm/images/service-maps.png | Bin 486694 -> 314343 bytes
docs/apm/service-maps.asciidoc | 1 +
docs/apm/transactions.asciidoc | 12 +-
docs/apm/troubleshooting.asciidoc | 1 +
docs/settings/apm-settings.asciidoc | 2 +-
21 files changed, 61 insertions(+), 72 deletions(-)
diff --git a/docs/apm/agent-configuration.asciidoc b/docs/apm/agent-configuration.asciidoc
index 2574d254ac14c..f2e07412c4a38 100644
--- a/docs/apm/agent-configuration.asciidoc
+++ b/docs/apm/agent-configuration.asciidoc
@@ -43,6 +43,7 @@ Supported configurations are also tagged with the image:./images/dynamic-config.
[horizontal]
Go Agent:: {apm-go-ref}/configuration.html[Configuration reference]
+iOS agent:: _Not yet supported_
Java Agent:: {apm-java-ref}/configuration.html[Configuration reference]
.NET Agent:: {apm-dotnet-ref}/configuration.html[Configuration reference]
Node.js Agent:: {apm-node-ref}/configuration.html[Configuration reference]
diff --git a/docs/apm/apm-alerts.asciidoc b/docs/apm/apm-alerts.asciidoc
index 3e3e2b178ff10..42016ac08bfc7 100644
--- a/docs/apm/apm-alerts.asciidoc
+++ b/docs/apm/apm-alerts.asciidoc
@@ -1,69 +1,57 @@
[role="xpack"]
[[apm-alerts]]
-=== Alerts
+=== Alerts and rules
++++
Create an alert
++++
+The APM app allows you to define **rules** to detect complex conditions within your APM data
+and trigger built-in **actions** when those conditions are met.
-The APM app integrates with Kibana's {kibana-ref}/alerting-getting-started.html[alerting and actions] feature.
-It provides a set of built-in **actions** and APM specific threshold **alerts** for you to use
-and enables central management of all alerts from <>.
+The following **rules** are supported:
+
+* Latency anomaly rule:
+Alert when latency of a service is abnormal
+* Transaction error rate threshold rule:
+Alert when the service's transaction error rate is above the defined threshold
+* Error count threshold rule:
+Alert when the number of errors in a service exceeds a defined threshold
[role="screenshot"]
image::apm/images/apm-alert.png[Create an alert in the APM app]
-For a walkthrough of the alert flyout panel, including detailed information on each configurable property,
-see Kibana's <>.
-
-The APM app supports four different types of alerts:
-
-* Transaction duration anomaly:
-alerts when the service's transaction duration reaches a certain anomaly score
-* Transaction duration threshold:
-alerts when the service's transaction duration exceeds a given time limit over a given time frame
-* Transaction error rate threshold:
-alerts when the service's transaction error rate is above the selected rate over a given time frame
-* Error count threshold:
-alerts when service exceeds a selected number of errors over a given time frame
+For a complete walkthrough of the **Create rule** flyout panel, including detailed information on each configurable property,
+see Kibana's <>.
-Below, we'll walk through the creation of two of these alerts.
+Below, we'll walk through the creation of two APM rules.
[float]
[[apm-create-transaction-alert]]
-=== Example: create a transaction duration alert
+=== Example: create a latency anomaly rule
-Transaction duration alerts trigger when the duration of a specific transaction type in a service exceeds a defined threshold.
-This guide will create an alert for the `opbeans-java` service based on the following criteria:
+Latency anomaly rules trigger when the latency of a service is abnormal.
+This guide will create an alert for all services based on the following criteria:
-* Environment: Production
-* Transaction type: `transaction.type:request`
-* Average request is above `1500ms` for the last 5 minutes
-* Check every 10 minutes, and repeat the alert every 30 minutes
-* Send the alert via Slack
+* Environment: production
+* Severity level: critical
+* Run every five minutes
+* Send an alert to a Slack channel only when the rule status changes
-From the APM app, navigate to the `opbeans-java` service and select
-**Alerts** > **Create threshold alert** > **Transaction duration**.
+From any page in the APM app, select **Alerts and rules** > **Latency** > **Create anomaly rule**.
+Change the name of the alert, but do not edit the tags.
-`Transaction duration | opbeans-java` is automatically set as the name of the alert,
-and `apm` and `service.name:opbeans-java` are added as tags.
-It's fine to change the name of the alert, but do not edit the tags.
+Based on the criteria above, define the following rule details:
-Based on the alert criteria, define the following alert details:
+* **Check every** - `5 minutes`
+* **Notify** - "Only on status change"
+* **Environment** - `all`
+* **Has anomaly with severity** - `critical`
-* **Check every** - `10 minutes`
-* **Notify every** - `30 minutes`
-* **TYPE** - `request`
-* **WHEN** - `avg`
-* **IS ABOVE** - `1500ms`
-* **FOR THE LAST** - `5 minutes`
-
-Select an action type.
-Multiple action types can be selected, but in this example, we want to post to a Slack channel.
+Next, add a connector. Multiple connectors can be selected, but in this example we're interested in Slack.
Select **Slack** > **Create a connector**.
Enter a name for the connector,
-and paste the webhook URL.
+and paste your Slack webhook URL.
See Slack's webhook documentation if you need to create one.
A default message is provided as a starting point for your alert.
@@ -72,35 +60,32 @@ to pass additional alert values at the time a condition is detected to an action
A list of available variables can be accessed by selecting the
**add variable** button image:apm/images/add-variable.png[add variable button].
-Select **Save**. The alert has been created and is now active!
+Click **Save**. The rule has been created and is now active!
[float]
[[apm-create-error-alert]]
-=== Example: create an error rate alert
+=== Example: create an error count threshold alert
-Error rate alerts trigger when the number of errors in a service exceeds a defined threshold.
-This guide creates an alert for the `opbeans-python` service based on the following criteria:
+The error count threshold alert triggers when the number of errors in a service exceeds a defined threshold.
+This guide will create an alert for all services based on the following criteria:
-* Environment: Production
+* All environments
* Error rate is above 25 for the last minute
-* Check every 1 minute, and repeat the alert every 10 minutes
-* Send the alert via email to the `opbeans-python` team
-
-From the APM app, navigate to the `opbeans-python` service and select
-**Alerts** > **Create threshold alert** > **Error rate**.
+* Check every 1 minute, and alert every time the rule is active
+* Send the alert via email to the site reliability team
-`Error rate | opbeans-python` is automatically set as the name of the alert,
-and `apm` and `service.name:opbeans-python` are added as tags.
-It's fine to change the name of the alert, but do not edit the tags.
+From any page in the APM app, select **Alerts and rules** > **Error count** > **Create threshold rule**.
+Change the name of the alert, but do not edit the tags.
-Based on the alert criteria, define the following alert details:
+Based on the criteria above, define the following rule details:
* **Check every** - `1 minute`
-* **Notify every** - `10 minutes`
-* **IS ABOVE** - `25 errors`
-* **FOR THE LAST** - `1 minute`
+* **Notify** - "Every time alert is active"
+* **Environment** - `all`
+* **Is above** - `25 errors`
+* **For the last** - `1 minute`
-Select the **Email** action type and click **Create a connector**.
+Select the **Email** connector and click **Create a connector**.
Fill out the required details: sender, host, port, etc., and click **save**.
A default message is provided as a starting point for your alert.
@@ -109,14 +94,14 @@ to pass additional alert values at the time a condition is detected to an action
A list of available variables can be accessed by selecting the
**add variable** button image:apm/images/add-variable.png[add variable button].
-Select **Save**. The alert has been created and is now active!
+Click **Save**. The alert has been created and is now active!
[float]
[[apm-alert-manage]]
-=== Manage alerts and actions
+=== Manage alerts and rules
-From the APM app, select **Alerts** > **View active alerts** to be taken to the Kibana alerts and actions management page.
-From this page, you can create, edit, disable, mute, and delete alerts, and create, edit, and disable connectors.
+From the APM app, select **Alerts and rules** > **Manage rules** to be taken to the Kibana **Rules and Connectors** page.
+From this page, you can disable, mute, and delete APM alerts.
[float]
[[apm-alert-more-info]]
@@ -126,4 +111,4 @@ See {kibana-ref}/alerting-getting-started.html[alerting and actions] for more in
NOTE: If you are using an **on-premise** Elastic Stack deployment with security,
communication between Elasticsearch and Kibana must have TLS configured.
-More information is in the alerting {kibana-ref}/alerting-setup.html#alerting-prerequisites[prerequisites].
\ No newline at end of file
+More information is in the alerting {kibana-ref}/alerting-setup.html#alerting-prerequisites[prerequisites].
diff --git a/docs/apm/filters.asciidoc b/docs/apm/filters.asciidoc
index 56602ab7c05c9..c0ea81c87378b 100644
--- a/docs/apm/filters.asciidoc
+++ b/docs/apm/filters.asciidoc
@@ -36,6 +36,7 @@ It's vital to be consistent when naming environments in your agents.
To learn how to configure service environments, see the specific agent documentation:
* *Go:* {apm-go-ref}/configuration.html#config-environment[`ELASTIC_APM_ENVIRONMENT`]
+* *iOS agent:* _Not yet supported_
* *Java:* {apm-java-ref}/config-core.html#config-environment[`environment`]
* *.NET:* {apm-dotnet-ref}/config-core.html#config-environment[`Environment`]
* *Node.js:* {apm-node-ref}/configuration.html#environment[`environment`]
diff --git a/docs/apm/images/apm-agent-configuration.png b/docs/apm/images/apm-agent-configuration.png
index 07398f0609187d24616bef3e82e21e1eb57ba40e..22fd9d75c3d730c847839865215ac8cd2a2fdeea 100644
GIT binary patch
literal 263010
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