diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts
index 55e1475fcb03a..86bce5997cdd2 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts
@@ -25,7 +25,6 @@
*/
export { npSetup, npStart } from 'ui/new_platform';
-export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
export {
configureAppAngularModule,
migrateLegacyQuery,
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.test.ts
deleted file mode 100644
index 60ca1b39d29d6..0000000000000
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.test.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-jest.mock('../', () => ({
- DashboardConstants: {
- ADD_EMBEDDABLE_ID: 'addEmbeddableId',
- ADD_EMBEDDABLE_TYPE: 'addEmbeddableType',
- },
-}));
-
-jest.mock('../legacy_imports', () => {
- return {
- absoluteToParsedUrl: jest.fn(() => {
- return {
- basePath: '/pep',
- appId: 'kibana',
- appPath: '/dashboard?addEmbeddableType=lens&addEmbeddableId=123eb456cd&x=1&y=2&z=3',
- hostname: 'localhost',
- port: 5601,
- protocol: 'http:',
- addQueryParameter: () => {},
- getAbsoluteUrl: () => {
- return 'http://localhost:5601/pep/app/kibana#/dashboard?addEmbeddableType=lens&addEmbeddableId=123eb456cd&x=1&y=2&z=3';
- },
- };
- }),
- };
-});
-
-import {
- addEmbeddableToDashboardUrl,
- getLensUrlFromDashboardAbsoluteUrl,
- getUrlVars,
-} from './url_helper';
-
-describe('Dashboard URL Helper', () => {
- beforeEach(() => {
- jest.resetModules();
- });
-
- it('addEmbeddableToDashboardUrl', () => {
- const id = '123eb456cd';
- const type = 'lens';
- const urlVars = {
- x: '1',
- y: '2',
- z: '3',
- };
- const basePath = '/pep';
- const url =
- "http://localhost:5601/pep/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
- expect(addEmbeddableToDashboardUrl(url, basePath, id, urlVars, type)).toEqual(
- `http://localhost:5601/pep/app/kibana#/dashboard?addEmbeddableType=${type}&addEmbeddableId=${id}&x=1&y=2&z=3`
- );
- });
-
- it('getUrlVars', () => {
- let url =
- "http://localhost:5601/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
- expect(getUrlVars(url)).toEqual({
- _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))',
- _a: "(description:'',filters:!()",
- });
- url = 'http://mybusiness.mydomain.com/app/kibana#/dashboard?x=y&y=z';
- expect(getUrlVars(url)).toEqual({
- x: 'y',
- y: 'z',
- });
- url = 'http://localhost:5601/app/kibana#/dashboard/777182';
- expect(getUrlVars(url)).toEqual({});
- url =
- 'http://localhost:5601/app/kibana#/dashboard/777182?title=Some%20Dashboard%20With%20Spaces';
- expect(getUrlVars(url)).toEqual({ title: 'Some Dashboard With Spaces' });
- });
-
- it('getLensUrlFromDashboardAbsoluteUrl', () => {
- const id = '1244';
- const basePath = '/wev';
- let url =
- "http://localhost:5601/wev/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
- expect(getLensUrlFromDashboardAbsoluteUrl(url, basePath, id)).toEqual(
- 'http://localhost:5601/wev/app/kibana#/lens/edit/1244'
- );
-
- url =
- "http://localhost:5601/wev/app/kibana#/dashboard/625357282?_a=(description:'',filters:!()&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))";
- expect(getLensUrlFromDashboardAbsoluteUrl(url, basePath, id)).toEqual(
- 'http://localhost:5601/wev/app/kibana#/lens/edit/1244'
- );
-
- url = 'http://myserver.mydomain.com:5601/wev/app/kibana#/dashboard/777182';
- expect(getLensUrlFromDashboardAbsoluteUrl(url, basePath, id)).toEqual(
- 'http://myserver.mydomain.com:5601/wev/app/kibana#/lens/edit/1244'
- );
-
- url =
- "http://localhost:5601/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
- expect(getLensUrlFromDashboardAbsoluteUrl(url, '', id)).toEqual(
- 'http://localhost:5601/app/kibana#/lens/edit/1244'
- );
- });
-});
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts
deleted file mode 100644
index 73383f2ff3f68..0000000000000
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { parse } from 'url';
-import { absoluteToParsedUrl } from '../legacy_imports';
-import { DashboardConstants } from './dashboard_constants';
-/**
- * Return query params from URL
- * @param url given url
- */
-export function getUrlVars(url: string): Record {
- const vars: Record = {};
- for (const [, key, value] of url.matchAll(/[?&]+([^=&]+)=([^&]*)/gi)) {
- vars[key] = decodeURIComponent(value);
- }
- return vars;
-}
-
-/** *
- * Returns dashboard URL with added embeddableType and embeddableId query params
- * eg.
- * input: url: http://localhost:5601/lib/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now)), embeddableId: 12345, embeddableType: 'lens'
- * output: http://localhost:5601/lib/app/kibana#dashboard?addEmbeddableType=lens&addEmbeddableId=12345&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))
- * @param url dasbhoard absolute url
- * @param embeddableId id of the saved visualization
- * @param basePath current base path
- * @param urlVars url query params (optional)
- * @param embeddableType 'lens' or 'visualization' (optional, default is 'lens')
- */
-export function addEmbeddableToDashboardUrl(
- url: string | undefined,
- basePath: string,
- embeddableId: string,
- urlVars?: Record,
- embeddableType?: string
-): string | null {
- if (!url) {
- return null;
- }
- const dashboardUrl = getUrlWithoutQueryParams(url);
- const dashboardParsedUrl = absoluteToParsedUrl(dashboardUrl, basePath);
- if (urlVars) {
- const keys = Object.keys(urlVars).sort();
- keys.forEach(key => {
- dashboardParsedUrl.addQueryParameter(key, urlVars[key]);
- });
- }
- dashboardParsedUrl.addQueryParameter(
- DashboardConstants.ADD_EMBEDDABLE_TYPE,
- embeddableType || 'lens'
- );
- dashboardParsedUrl.addQueryParameter(DashboardConstants.ADD_EMBEDDABLE_ID, embeddableId);
- return dashboardParsedUrl.getAbsoluteUrl();
-}
-
-/**
- * Return Lens URL from dashboard absolute URL
- * @param dashboardAbsoluteUrl
- * @param basePath current base path
- * @param id Lens id
- */
-export function getLensUrlFromDashboardAbsoluteUrl(
- dashboardAbsoluteUrl: string | undefined | null,
- basePath: string | null | undefined,
- id: string
-): string | null {
- if (!dashboardAbsoluteUrl || basePath === null || basePath === undefined) {
- return null;
- }
- const { host, protocol } = parse(dashboardAbsoluteUrl);
- return `${protocol}//${host}${basePath}/app/kibana#/lens/edit/${id}`;
-}
-
-/**
- * Returns the portion of the URL without query params
- * eg.
- * input: http://localhost:5601/lib/app/kibana#/dashboard?param1=x¶m2=y¶m3=z
- * output:http://localhost:5601/lib/app/kibana#/dashboard
- * input: http://localhost:5601/lib/app/kibana#/dashboard/39292992?param1=x¶m2=y¶m3=z
- * output: http://localhost:5601/lib/app/kibana#/dashboard/39292992
- * @param url url to parse
- */
-function getUrlWithoutQueryParams(url: string): string {
- return url.split('?')[0];
-}
diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts
index f29f07ba4b20b..2ed7e3d43168c 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts
@@ -45,7 +45,6 @@ export interface VisualizeKibanaServices {
core: CoreStart;
data: DataPublicPluginStart;
embeddable: EmbeddableStart;
- getBasePath: () => string;
indexPatterns: IndexPatternsContract;
localStorage: Storage;
navigation: NavigationStart;
diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts
index a6774e2dd47e8..6a2034d9a62e4 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts
@@ -24,9 +24,6 @@
* directly where they are needed.
*/
-export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
-export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
-export { wrapInI18nContext } from 'ui/i18n';
export { DashboardConstants } from '../dashboard/np_ready/dashboard_constants';
export {
VisSavedObject,
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
index c5325ca3108b4..9ccd45dfc1b45 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
@@ -26,14 +26,14 @@ import { EventEmitter } from 'events';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { makeStateful, useVisualizeAppState } from './lib';
+import { makeStateful, useVisualizeAppState, addEmbeddableToDashboardUrl } from './lib';
import { VisualizeConstants } from '../visualize_constants';
import { getEditBreadcrumbs } from '../breadcrumbs';
import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util';
import { unhashUrl, removeQueryParam } from '../../../../../../../plugins/kibana_utils/public';
import { MarkdownSimple, toMountPoint } from '../../../../../../../plugins/kibana_react/public';
-import { addFatalError, kbnBaseUrl } from '../../../../../../../plugins/kibana_legacy/public';
+import { addFatalError } from '../../../../../../../plugins/kibana_legacy/public';
import {
SavedObjectSaveModal,
showSaveModal,
@@ -46,14 +46,7 @@ import {
import { initVisEditorDirective } from './visualization_editor';
import { initVisualizationDirective } from './visualization';
-import {
- VISUALIZE_EMBEDDABLE_TYPE,
- subscribeWithScope,
- absoluteToParsedUrl,
- KibanaParsedUrl,
- migrateLegacyQuery,
- DashboardConstants,
-} from '../../legacy_imports';
+import { subscribeWithScope, migrateLegacyQuery, DashboardConstants } from '../../legacy_imports';
import { getServices } from '../../kibana_services';
@@ -79,7 +72,6 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
data: { query: queryService },
toastNotifications,
chrome,
- getBasePath,
core: { docLinks, fatalErrors },
savedQueryService,
uiSettings,
@@ -653,29 +645,14 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
});
if ($scope.isAddToDashMode()) {
- const savedVisualizationParsedUrl = new KibanaParsedUrl({
- basePath: getBasePath(),
- appId: kbnBaseUrl.slice('/app/'.length),
- appPath: `${VisualizeConstants.EDIT_PATH}/${encodeURIComponent(savedVis.id)}`,
- });
+ const appPath = `${VisualizeConstants.EDIT_PATH}/${encodeURIComponent(savedVis.id)}`;
// Manually insert a new url so the back button will open the saved visualization.
- history.replace(savedVisualizationParsedUrl.appPath);
- setActiveUrl(savedVisualizationParsedUrl.appPath);
+ history.replace(appPath);
+ setActiveUrl(appPath);
- const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url;
- const dashboardParsedUrl = absoluteToParsedUrl(
- lastDashboardAbsoluteUrl,
- getBasePath()
- );
- dashboardParsedUrl.addQueryParameter(
- DashboardConstants.ADD_EMBEDDABLE_TYPE,
- VISUALIZE_EMBEDDABLE_TYPE
- );
- dashboardParsedUrl.addQueryParameter(
- DashboardConstants.ADD_EMBEDDABLE_ID,
- savedVis.id
- );
- history.push(dashboardParsedUrl.appPath);
+ const lastDashboardUrl = chrome.navLinks.get('kibana:dashboard').url;
+ const dashboardUrl = addEmbeddableToDashboardUrl(lastDashboardUrl, savedVis.id);
+ history.push(dashboardUrl);
} else if (savedVis.id === $route.current.params.id) {
chrome.docTitle.change(savedVis.lastSavedTitle);
chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs));
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/index.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/index.ts
index fa5b91b00edaf..6e2f759c73f2f 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/index.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/index.ts
@@ -19,3 +19,4 @@
export { useVisualizeAppState } from './visualize_app_state';
export { makeStateful } from './make_stateful';
+export { addEmbeddableToDashboardUrl } from './url_helper';
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/url_helper.test.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/url_helper.test.ts
new file mode 100644
index 0000000000000..e6974af9af832
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/url_helper.test.ts
@@ -0,0 +1,48 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { addEmbeddableToDashboardUrl } from './url_helper';
+
+jest.mock('../../../legacy_imports', () => ({
+ DashboardConstants: {
+ ADD_EMBEDDABLE_ID: 'addEmbeddableId',
+ ADD_EMBEDDABLE_TYPE: 'addEmbeddableType',
+ CREATE_NEW_DASHBOARD_URL: '/dashboard',
+ },
+ VISUALIZE_EMBEDDABLE_TYPE: 'visualization',
+}));
+
+describe('', () => {
+ it('addEmbeddableToDashboardUrl when dashboard is not saved', () => {
+ const id = '123eb456cd';
+ const url =
+ "/pep/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!())";
+ expect(addEmbeddableToDashboardUrl(url, id)).toEqual(
+ `/dashboard?_a=%28description%3A%27%27%2Cfilters%3A%21%28%29%29&_g=%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29&addEmbeddableId=${id}&addEmbeddableType=visualization`
+ );
+ });
+ it('addEmbeddableToDashboardUrl when dashboard is saved', () => {
+ const id = '123eb456cd';
+ const url =
+ "/pep/app/kibana#/dashboard/9b780cd0-3dd3-11e8-b2b9-5d5dc1715159?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!())";
+ expect(addEmbeddableToDashboardUrl(url, id)).toEqual(
+ `/dashboard/9b780cd0-3dd3-11e8-b2b9-5d5dc1715159?_a=%28description%3A%27%27%2Cfilters%3A%21%28%29%29&_g=%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29&addEmbeddableId=${id}&addEmbeddableType=visualization`
+ );
+ });
+});
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/url_helper.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/url_helper.ts
new file mode 100644
index 0000000000000..c7937c856184a
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/url_helper.ts
@@ -0,0 +1,39 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { parseUrl, stringify } from 'query-string';
+import { DashboardConstants, VISUALIZE_EMBEDDABLE_TYPE } from '../../../legacy_imports';
+
+/** *
+ * Returns relative dashboard URL with added embeddableType and embeddableId query params
+ * eg.
+ * input: url: lol/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now)), embeddableId: 12345
+ * output: /dashboard?addEmbeddableType=visualization&addEmbeddableId=12345&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))
+ * @param url dasbhoard absolute url
+ * @param embeddableId id of the saved visualization
+ */
+export function addEmbeddableToDashboardUrl(dashboardUrl: string, embeddableId: string) {
+ const { url, query } = parseUrl(dashboardUrl);
+ const [, dashboardId] = url.split(DashboardConstants.CREATE_NEW_DASHBOARD_URL);
+
+ query[DashboardConstants.ADD_EMBEDDABLE_TYPE] = VISUALIZE_EMBEDDABLE_TYPE;
+ query[DashboardConstants.ADD_EMBEDDABLE_ID] = embeddableId;
+
+ return `${DashboardConstants.CREATE_NEW_DASHBOARD_URL}${dashboardId}?${stringify(query)}`;
+}
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js
index 6c02afb672e4c..098633d046062 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js
@@ -18,19 +18,17 @@
*/
import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util';
-import { VisualizeListingTable } from './visualize_listing_table';
+import { withI18nContext } from './visualize_listing_table';
import { VisualizeConstants } from '../visualize_constants';
import { i18n } from '@kbn/i18n';
import { getServices } from '../../kibana_services';
-import { wrapInI18nContext } from '../../legacy_imports';
-
import { syncQueryStateWithUrl } from '../../../../../../../plugins/data/public';
-export function initListingDirective(app) {
+export function initListingDirective(app, I18nContext) {
app.directive('visualizeListingTable', reactDirective =>
- reactDirective(wrapInI18nContext(VisualizeListingTable))
+ reactDirective(withI18nContext(I18nContext))
);
}
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js
index b770625cd3d70..932ac8996e97e 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js
@@ -230,4 +230,10 @@ VisualizeListingTable.propTypes = {
listingLimit: PropTypes.number.isRequired,
};
-export { VisualizeListingTable };
+const withI18nContext = I18nContext => props => (
+
+
+
+);
+
+export { withI18nContext };
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_app.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_app.ts
index 1e7ac668697de..a4afac23f4842 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_app.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_app.ts
@@ -27,5 +27,5 @@ import { initListingDirective } from './listing/visualize_listing';
export function initVisualizeAppDirective(app: IModule, deps: VisualizeKibanaServices) {
initEditorDirective(app, deps);
- initListingDirective(app);
+ initListingDirective(app, deps.core.i18n.Context);
}
diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts
index 59b814c98dd08..6d32579f5c541 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts
@@ -140,7 +140,6 @@ export class VisualizePlugin implements Plugin {
chrome: coreStart.chrome,
data: dataStart,
embeddable,
- getBasePath: core.http.basePath.get,
indexPatterns: dataStart.indexPatterns,
localStorage: new Storage(localStorage),
navigation,
diff --git a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx b/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx
index 0b6d4e5982a00..58e67b5064da5 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx
@@ -20,7 +20,6 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { i18n } from '@kbn/i18n';
-import { I18nProvider } from '@kbn/i18n/react';
import { EventEmitter } from 'events';
import { EditorRenderProps } from 'src/legacy/core_plugins/kibana/public/visualize/np_ready/types';
@@ -83,7 +82,7 @@ class DefaultEditorController {
render({ data, core, ...props }: EditorRenderProps) {
render(
-
+
- ,
+ ,
this.el
);
}
diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts
index b0bb2f754d6cf..0c3947ade8221 100644
--- a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts
+++ b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts
@@ -197,6 +197,22 @@ describe('filter manager utilities', () => {
expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeTruthy();
});
+ test('should compare alias with alias true', () => {
+ const f1 = {
+ $state: { store: FilterStateStore.GLOBAL_STATE },
+ ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''),
+ };
+ const f2 = {
+ $state: { store: FilterStateStore.GLOBAL_STATE },
+ ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''),
+ };
+
+ f2.meta.alias = 'wassup';
+ f2.meta.alias = 'dog';
+
+ expect(compareFilters([f1], [f2], { alias: true })).toBeFalsy();
+ });
+
test('should compare alias with COMPARE_ALL_OPTIONS', () => {
const f1 = {
$state: { store: FilterStateStore.GLOBAL_STATE },
diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.ts b/src/plugins/data/common/query/filter_manager/compare_filters.ts
index e047d5e0665d5..3be52a9a60977 100644
--- a/src/plugins/data/common/query/filter_manager/compare_filters.ts
+++ b/src/plugins/data/common/query/filter_manager/compare_filters.ts
@@ -46,7 +46,7 @@ const mapFilter = (
if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate);
if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled);
- if (comparators.disabled) cleaned.alias = filter.meta?.alias;
+ if (comparators.alias) cleaned.alias = filter.meta?.alias;
return cleaned;
};
diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js
index 7017c01cc5634..c7fa0f40e1d0c 100644
--- a/test/plugin_functional/config.js
+++ b/test/plugin_functional/config.js
@@ -32,7 +32,6 @@ export default async function({ readConfigFile }) {
return {
testFiles: [
- require.resolve('./test_suites/app_plugins'),
require.resolve('./test_suites/custom_visualizations'),
require.resolve('./test_suites/panel_actions'),
require.resolve('./test_suites/embeddable_explorer'),
diff --git a/test/plugin_functional/plugins/kbn_top_nav/kibana.json b/test/plugin_functional/plugins/kbn_top_nav/kibana.json
new file mode 100644
index 0000000000000..b274e80b9ef65
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_top_nav/kibana.json
@@ -0,0 +1,9 @@
+{
+ "id": "kbn_top_nav",
+ "version": "0.0.1",
+ "kibanaVersion": "kibana",
+ "configPath": ["kbn_top_nav"],
+ "server": false,
+ "ui": true,
+ "requiredPlugins": ["navigation"]
+}
\ No newline at end of file
diff --git a/test/plugin_functional/plugins/kbn_top_nav/package.json b/test/plugin_functional/plugins/kbn_top_nav/package.json
new file mode 100644
index 0000000000000..510d681a4a75c
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_top_nav/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "kbn_top_nav",
+ "version": "1.0.0",
+ "main": "target/test/plugin_functional/plugins/kbn_top_nav",
+ "kibana": {
+ "version": "kibana",
+ "templateVersion": "1.0.0"
+ },
+ "license": "Apache-2.0",
+ "scripts": {
+ "kbn": "node ../../../../scripts/kbn.js",
+ "build": "rm -rf './target' && tsc"
+ },
+ "devDependencies": {
+ "typescript": "3.7.2"
+ }
+}
+
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx b/test/plugin_functional/plugins/kbn_top_nav/public/application.tsx
similarity index 71%
rename from test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx
rename to test/plugin_functional/plugins/kbn_top_nav/public/application.tsx
index f77db4fe1654e..0f65e6159796b 100644
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx
+++ b/test/plugin_functional/plugins/kbn_top_nav/public/application.tsx
@@ -18,11 +18,15 @@
*/
import React from 'react';
-import './initialize';
-import { npStart } from 'ui/new_platform';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { AppMountParameters } from 'kibana/public';
+import { AppPluginDependencies } from './types';
-export const AppWithTopNav = () => {
- const { TopNavMenu } = npStart.plugins.navigation.ui;
+export const renderApp = (
+ depsStart: AppPluginDependencies,
+ { appBasePath, element }: AppMountParameters
+) => {
+ const { TopNavMenu } = depsStart.navigation.ui;
const config = [
{
id: 'new',
@@ -32,10 +36,12 @@ export const AppWithTopNav = () => {
testId: 'demoNewButton',
},
];
-
- return (
+ render(
Hey
-
+ ,
+ element
);
+
+ return () => unmountComponentAtNode(element);
};
diff --git a/test/plugin_functional/plugins/kbn_tp_sample_app_plugin/public/app.js b/test/plugin_functional/plugins/kbn_top_nav/public/index.ts
similarity index 75%
rename from test/plugin_functional/plugins/kbn_tp_sample_app_plugin/public/app.js
rename to test/plugin_functional/plugins/kbn_top_nav/public/index.ts
index a7a516bb0cdbd..bd478f1dd3bdb 100644
--- a/test/plugin_functional/plugins/kbn_tp_sample_app_plugin/public/app.js
+++ b/test/plugin_functional/plugins/kbn_top_nav/public/index.ts
@@ -16,8 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
-import 'ui/autoload/all';
-import chrome from 'ui/chrome';
+import { PluginInitializer } from 'kibana/public';
+import { TopNavTestPlugin, TopNavTestPluginSetup, TopNavTestPluginStart } from './plugin';
-chrome.setRootTemplate('Super simple app plugin
');
+export const plugin: PluginInitializer = () =>
+ new TopNavTestPlugin();
diff --git a/test/plugin_functional/plugins/kbn_top_nav/public/plugin.tsx b/test/plugin_functional/plugins/kbn_top_nav/public/plugin.tsx
new file mode 100644
index 0000000000000..a433de98357fb
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_top_nav/public/plugin.tsx
@@ -0,0 +1,65 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { CoreSetup, Plugin, AppMountParameters } from 'kibana/public';
+import { NavigationPublicPluginSetup } from '../../../../../src/plugins/navigation/public';
+import { AppPluginDependencies } from './types';
+
+export class TopNavTestPlugin implements Plugin {
+ public setup(core: CoreSetup, { navigation }: { navigation: NavigationPublicPluginSetup }) {
+ const customExtension = {
+ id: 'registered-prop',
+ label: 'Registered Button',
+ description: 'Registered Demo',
+ run() {},
+ testId: 'demoRegisteredNewButton',
+ };
+
+ navigation.registerMenuItem(customExtension);
+
+ const customDiscoverExtension = {
+ id: 'registered-discover-prop',
+ label: 'Registered Discover Button',
+ description: 'Registered Discover Demo',
+ run() {},
+ testId: 'demoDiscoverRegisteredNewButton',
+ appName: 'discover',
+ };
+
+ navigation.registerMenuItem(customDiscoverExtension);
+
+ core.application.register({
+ id: 'topNavMenu',
+ title: 'Top nav menu example',
+ async mount(params: AppMountParameters) {
+ const { renderApp } = await import('./application');
+ const services = await core.getStartServices();
+ return renderApp(services[1] as AppPluginDependencies, params);
+ },
+ });
+
+ return {};
+ }
+
+ public start() {}
+ public stop() {}
+}
+
+export type TopNavTestPluginSetup = ReturnType;
+export type TopNavTestPluginStart = ReturnType;
diff --git a/test/plugin_functional/test_suites/app_plugins/index.js b/test/plugin_functional/plugins/kbn_top_nav/public/types.ts
similarity index 81%
rename from test/plugin_functional/test_suites/app_plugins/index.js
rename to test/plugin_functional/plugins/kbn_top_nav/public/types.ts
index 83faa7377c7ac..c70a78bedb54f 100644
--- a/test/plugin_functional/test_suites/app_plugins/index.js
+++ b/test/plugin_functional/plugins/kbn_top_nav/public/types.ts
@@ -17,8 +17,8 @@
* under the License.
*/
-export default function({ loadTestFile }) {
- describe('app plugins', () => {
- loadTestFile(require.resolve('./app_navigation'));
- });
+import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public';
+
+export interface AppPluginDependencies {
+ navigation: NavigationPublicPluginStart;
}
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json b/test/plugin_functional/plugins/kbn_top_nav/tsconfig.json
similarity index 100%
rename from test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json
rename to test/plugin_functional/plugins/kbn_top_nav/tsconfig.json
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/kibana.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/kibana.json
new file mode 100644
index 0000000000000..622cbd80090ba
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/kibana.json
@@ -0,0 +1,10 @@
+{
+ "id": "kbn_tp_custom_visualizations",
+ "version": "0.0.1",
+ "kibanaVersion": "kibana",
+ "requiredPlugins": [
+ "visualizations"
+ ],
+ "server": false,
+ "ui": true
+}
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
index 344aae30b5bbc..9ee7845816faa 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
@@ -1,6 +1,7 @@
{
"name": "kbn_tp_custom_visualizations",
"version": "1.0.0",
+ "main": "target/test/plugin_functional/plugins/kbn_tp_custom_visualizations",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
@@ -9,5 +10,13 @@
"dependencies": {
"@elastic/eui": "21.0.1",
"react": "^16.12.0"
+ },
+ "scripts": {
+ "kbn": "node ../../../../scripts/kbn.js",
+ "build": "rm -rf './target' && tsc"
+ },
+ "devDependencies": {
+ "@kbn/plugin-helpers": "9.0.2",
+ "typescript": "3.7.2"
}
}
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/index.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/index.ts
similarity index 68%
rename from test/plugin_functional/plugins/kbn_tp_top_nav/index.js
rename to test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/index.ts
index b4c3e05c28b66..cb821a2698479 100644
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/index.js
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/index.ts
@@ -17,15 +17,14 @@
* under the License.
*/
-export default function(kibana) {
- return new kibana.Plugin({
- uiExports: {
- app: {
- title: 'Top Nav Menu test',
- description: 'This is a sample plugin for the functional tests.',
- main: 'plugins/kbn_tp_top_nav/app',
- },
- hacks: ['plugins/kbn_tp_top_nav/initialize'],
- },
- });
-}
+import { PluginInitializer } from 'kibana/public';
+import {
+ CustomVisualizationsPublicPlugin,
+ CustomVisualizationsSetup,
+ CustomVisualizationsStart,
+} from './plugin';
+
+export { CustomVisualizationsPublicPlugin as Plugin };
+
+export const plugin: PluginInitializer = () =>
+ new CustomVisualizationsPublicPlugin();
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/plugin.ts b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/plugin.ts
new file mode 100644
index 0000000000000..1be4aa9ee42ae
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/plugin.ts
@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { CoreSetup, Plugin } from 'kibana/public';
+import { VisualizationsSetup } from '../../../../../src/plugins/visualizations/public';
+import { SelfChangingEditor } from './self_changing_vis/self_changing_editor';
+import { SelfChangingComponent } from './self_changing_vis/self_changing_components';
+
+export interface SetupDependencies {
+ visualizations: VisualizationsSetup;
+}
+
+export class CustomVisualizationsPublicPlugin
+ implements Plugin {
+ public setup(core: CoreSetup, setupDeps: SetupDependencies) {
+ setupDeps.visualizations.createReactVisualization({
+ name: 'self_changing_vis',
+ title: 'Self Changing Vis',
+ icon: 'controlsHorizontal',
+ description:
+ 'This visualization is able to change its own settings, that you could also set in the editor.',
+ visConfig: {
+ component: SelfChangingComponent,
+ defaults: {
+ counter: 0,
+ },
+ },
+ editorConfig: {
+ optionTabs: [
+ {
+ name: 'options',
+ title: 'Options',
+ editor: SelfChangingEditor,
+ },
+ ],
+ },
+ requestHandler: 'none',
+ });
+ }
+
+ public start() {}
+ public stop() {}
+}
+
+export type CustomVisualizationsSetup = ReturnType;
+export type CustomVisualizationsStart = ReturnType;
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.js
deleted file mode 100644
index c5b074db43a1b..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import React from 'react';
-
-import { EuiBadge } from '@elastic/eui';
-
-export class SelfChangingComponent extends React.Component {
- onClick = () => {
- this.props.vis.params.counter++;
- this.props.vis.updateState();
- };
-
- render() {
- return (
-
-
- {this.props.vis.params.counter}
-
-
- );
- }
-
- componentDidMount() {
- this.props.renderComplete();
- }
-
- componentDidUpdate() {
- this.props.renderComplete();
- }
-}
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/index.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.tsx
similarity index 59%
rename from test/plugin_functional/plugins/kbn_tp_custom_visualizations/index.js
rename to test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.tsx
index b2497a824ba2b..2f01908122457 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/index.js
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_components.tsx
@@ -17,10 +17,32 @@
* under the License.
*/
-export default function(kibana) {
- return new kibana.Plugin({
- uiExports: {
- hacks: ['plugins/kbn_tp_custom_visualizations/self_changing_vis/self_changing_vis'],
- },
+import React, { useEffect } from 'react';
+
+import { EuiBadge } from '@elastic/eui';
+
+interface SelfChangingComponentProps {
+ renderComplete: () => {};
+ visParams: {
+ counter: number;
+ };
+}
+
+export function SelfChangingComponent(props: SelfChangingComponentProps) {
+ useEffect(() => {
+ props.renderComplete();
});
+
+ return (
+
+ {}}
+ data-test-subj="counter"
+ onClickAriaLabel="Increase counter"
+ color="primary"
+ >
+ {props.visParams.counter}
+
+
+ );
}
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx
similarity index 76%
rename from test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.js
rename to test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx
index fa3a0c8b9f6fe..d3f66d708603c 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.js
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx
@@ -20,10 +20,15 @@
import React from 'react';
import { EuiFieldNumber, EuiFormRow } from '@elastic/eui';
+import { VisOptionsProps } from '../../../../../../src/legacy/core_plugins/vis_default_editor/public/vis_options_props';
-export class SelfChangingEditor extends React.Component {
- onCounterChange = ev => {
- this.props.setValue('counter', parseInt(ev.target.value));
+interface CounterParams {
+ counter: number;
+}
+
+export class SelfChangingEditor extends React.Component> {
+ onCounterChange = (ev: any) => {
+ this.props.setValue('counter', parseInt(ev.target.value, 10));
};
render() {
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json
new file mode 100644
index 0000000000000..d8096d9aab27a
--- /dev/null
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./target",
+ "skipLibCheck": true,
+ "types": [
+ "node",
+ "jest",
+ "react"
+ ]
+ },
+ "include": [
+ "index.ts",
+ "public/**/*.ts",
+ "public/**/*.tsx",
+ "../../../../typings/**/*",
+ ],
+ "exclude": []
+}
\ No newline at end of file
diff --git a/test/plugin_functional/plugins/kbn_tp_sample_app_plugin/index.js b/test/plugin_functional/plugins/kbn_tp_sample_app_plugin/index.js
deleted file mode 100644
index ff4be4113eeb3..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_sample_app_plugin/index.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export default function(kibana) {
- return new kibana.Plugin({
- uiExports: {
- app: {
- title: 'Test Plugin App',
- description: 'This is a sample plugin for the functional tests.',
- main: 'plugins/kbn_tp_sample_app_plugin/app',
- },
- },
- });
-}
diff --git a/test/plugin_functional/plugins/kbn_tp_sample_app_plugin/package.json b/test/plugin_functional/plugins/kbn_tp_sample_app_plugin/package.json
deleted file mode 100644
index 2537bb9a7ed5c..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_sample_app_plugin/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "kbn_tp_sample_app_plugin",
- "version": "1.0.0",
- "kibana": {
- "version": "kibana",
- "templateVersion": "1.0.0"
- },
- "license": "Apache-2.0"
-}
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/package.json b/test/plugin_functional/plugins/kbn_tp_top_nav/package.json
deleted file mode 100644
index 7102d24d3292d..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "kbn_tp_top_nav",
- "version": "1.0.0",
- "kibana": {
- "version": "kibana",
- "templateVersion": "1.0.0"
- },
- "license": "Apache-2.0"
-}
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js b/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js
deleted file mode 100644
index e7f97e68c086d..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-
-import { uiModules } from 'ui/modules';
-import chrome from 'ui/chrome';
-
-// This is required so some default styles and required scripts/Angular modules are loaded,
-// or the timezone setting is correctly applied.
-import 'ui/autoload/all';
-
-import { AppWithTopNav } from './top_nav';
-
-const app = uiModules.get('apps/topnavDemoPlugin', ['kibana']);
-
-app.config($locationProvider => {
- $locationProvider.html5Mode({
- enabled: false,
- requireBase: false,
- rewriteLinks: false,
- });
-});
-
-function RootController($scope, $element) {
- const domNode = $element[0];
-
- // render react to DOM
- render( , domNode);
-
- // unmount react on controller destroy
- $scope.$on('$destroy', () => {
- unmountComponentAtNode(domNode);
- });
-}
-
-chrome.setRootController('topnavDemoPlugin', RootController);
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/initialize.js b/test/plugin_functional/plugins/kbn_tp_top_nav/public/initialize.js
deleted file mode 100644
index d46e47f6d248a..0000000000000
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/public/initialize.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { npSetup } from 'ui/new_platform';
-
-const customExtension = {
- id: 'registered-prop',
- label: 'Registered Button',
- description: 'Registered Demo',
- run() {},
- testId: 'demoRegisteredNewButton',
-};
-
-npSetup.plugins.navigation.registerMenuItem(customExtension);
-
-const customDiscoverExtension = {
- id: 'registered-discover-prop',
- label: 'Registered Discover Button',
- description: 'Registered Discover Demo',
- run() {},
- testId: 'demoDiscoverRegisteredNewButton',
- appName: 'discover',
-};
-
-npSetup.plugins.navigation.registerMenuItem(customDiscoverExtension);
diff --git a/test/plugin_functional/test_suites/app_plugins/app_navigation.js b/test/plugin_functional/test_suites/app_plugins/app_navigation.js
deleted file mode 100644
index bb39e52287556..0000000000000
--- a/test/plugin_functional/test_suites/app_plugins/app_navigation.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-
-export default function({ getService, getPageObjects }) {
- const appsMenu = getService('appsMenu');
- const testSubjects = getService('testSubjects');
- const PageObjects = getPageObjects(['common', 'header', 'home']);
-
- describe('app navigation', function describeIndexTests() {
- before(async () => {
- await PageObjects.common.navigateToApp('settings');
- });
-
- it('should show nav link that navigates to the app', async () => {
- await appsMenu.clickLink('Test Plugin App');
- const pluginContent = await testSubjects.find('pluginContent');
- expect(await pluginContent.getVisibleText()).to.be('Super simple app plugin');
- });
- });
-}
diff --git a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js
index ef6f0a626bd15..83258a1ca3bdc 100644
--- a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js
+++ b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js
@@ -28,11 +28,7 @@ export default function({ getService, getPageObjects }) {
return await testSubjects.getVisibleText('counter');
}
- async function getEditorValue() {
- return await testSubjects.getAttribute('counterEditor', 'value');
- }
-
- describe.skip('self changing vis', function describeIndexTests() {
+ describe('self changing vis', function describeIndexTests() {
before(async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('self_changing_vis');
@@ -45,16 +41,17 @@ export default function({ getService, getPageObjects }) {
const isApplyEnabled = await PageObjects.visEditor.isApplyEnabled();
expect(isApplyEnabled).to.be(true);
await PageObjects.visEditor.clickGo();
+ await renderable.waitForRender();
const counter = await getCounterValue();
expect(counter).to.be('10');
});
- it('should allow changing params from within the vis', async () => {
+ it.skip('should allow changing params from within the vis', async () => {
await testSubjects.click('counter');
await renderable.waitForRender();
const visValue = await getCounterValue();
expect(visValue).to.be('11');
- const editorValue = await getEditorValue();
+ const editorValue = await testSubjects.getAttribute('counterEditor', 'value');
expect(editorValue).to.be('11');
// If changing a param from within the vis it should immediately apply and not bring editor in an unchanged state
const isApplyEnabled = await PageObjects.visEditor.isApplyEnabled();
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
new file mode 100644
index 0000000000000..938962cc9dd18
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
@@ -0,0 +1,76 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { TraceAPIResponse } from '../../../../../../../../../plugins/apm/server/lib/traces/get_trace';
+import { WaterfallContainer } from './index';
+import {
+ location,
+ urlParams,
+ simpleTrace,
+ traceWithErrors,
+ traceChildStartBeforeParent
+} from './waterfallContainer.stories.data';
+import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers';
+
+storiesOf('app/TransactionDetails/Waterfall', module).add(
+ 'simple',
+ () => {
+ const waterfall = getWaterfall(
+ simpleTrace as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { source: false } }
+);
+
+storiesOf('app/TransactionDetails/Waterfall', module).add(
+ 'with errors',
+ () => {
+ const waterfall = getWaterfall(
+ (traceWithErrors as unknown) as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { source: false } }
+);
+
+storiesOf('app/TransactionDetails/Waterfall', module).add(
+ 'child starts before parent',
+ () => {
+ const waterfall = getWaterfall(
+ traceChildStartBeforeParent as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { source: false } }
+);
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts
new file mode 100644
index 0000000000000..835183e73b298
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts
@@ -0,0 +1,1647 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Location } from 'history';
+import { IUrlParams } from '../../../../../context/UrlParamsContext/types';
+
+export const location = {
+ pathname: '/services/opbeans-go/transactions/view',
+ search:
+ '?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0&kuery=service.name%253A%2520%2522opbeans-java%2522%2520or%2520service.name%2520%253A%2520%2522opbeans-go%2522&traceId=513d33fafe99bbe6134749310c9b5322&transactionId=975c8d5bfd1dd20b&transactionName=GET%20%2Fapi%2Forders&transactionType=request',
+ hash: ''
+} as Location;
+
+export const urlParams = {
+ start: '2020-03-22T15:16:38.742Z',
+ end: '2020-03-23T15:16:38.742Z',
+ rangeFrom: 'now-24h',
+ rangeTo: 'now',
+ refreshPaused: true,
+ refreshInterval: 0,
+ page: 0,
+ transactionId: '975c8d5bfd1dd20b',
+ traceId: '513d33fafe99bbe6134749310c9b5322',
+ kuery: 'service.name: "opbeans-java" or service.name : "opbeans-go"',
+ transactionName: 'GET /api/orders',
+ transactionType: 'request',
+ processorEvent: 'transaction',
+ serviceName: 'opbeans-go'
+} as IUrlParams;
+
+export const simpleTrace = {
+ trace: {
+ items: [
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 46
+ }
+ },
+ source: {
+ ip: '172.19.0.13'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: '172.19.0.9',
+ full: 'http://172.19.0.9:3000/api/orders'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ type: 'apm-server',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ http: {
+ request: {
+ headers: {
+ Accept: ['*/*'],
+ 'User-Agent': ['Python/3.7 aiohttp/3.3.2'],
+ Host: ['172.19.0.9:3000'],
+ 'Accept-Encoding': ['gzip, deflate']
+ },
+ method: 'get',
+ socket: {
+ encrypted: false,
+ remote_address: '172.19.0.13'
+ },
+ body: {
+ original: '[REDACTED]'
+ }
+ },
+ response: {
+ headers: {
+ 'Transfer-Encoding': ['chunked'],
+ Date: ['Mon, 23 Mar 2020 15:04:28 GMT'],
+ 'Content-Type': ['application/json;charset=ISO-8859-1']
+ },
+ status_code: 200,
+ finished: true,
+ headers_sent: true
+ },
+ version: '1.1'
+ },
+ client: {
+ ip: '172.19.0.13'
+ },
+ transaction: {
+ duration: {
+ us: 18842
+ },
+ result: 'HTTP 2xx',
+ name: 'DispatcherServlet#doGet',
+ id: '49809ad3c26adf74',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ user_agent: {
+ original: 'Python/3.7 aiohttp/3.3.2',
+ name: 'Other',
+ device: {
+ name: 'Other'
+ }
+ },
+ timestamp: {
+ us: 1584975868785000
+ }
+ },
+ {
+ parent: {
+ id: 'fc107f7b556eb49b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ framework: {
+ name: 'gin',
+ version: 'v1.4.0'
+ },
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ duration: {
+ us: 16597
+ },
+ result: 'HTTP 2xx',
+ name: 'GET /api/orders',
+ id: '975c8d5bfd1dd20b',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ timestamp: {
+ us: 1584975868787052
+ }
+ },
+ {
+ parent: {
+ id: 'daae24d83c269918'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ timestamp: {
+ us: 1584975868788603
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ '@timestamp': '2020-03-23T15:04:28.788Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ result: 'HTTP 2xx',
+ duration: {
+ us: 14648
+ },
+ name: 'GET opbeans.views.orders',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ id: '6fb0ff7365b87298',
+ type: 'request',
+ sampled: true
+ }
+ },
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ parent: {
+ id: '49809ad3c26adf74'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 44
+ }
+ },
+ destination: {
+ address: 'opbeans-go',
+ port: 3000
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ type: 'apm-server',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ connection: {
+ hash:
+ "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}"
+ },
+ transaction: {
+ id: '49809ad3c26adf74'
+ },
+ timestamp: {
+ us: 1584975868785273
+ },
+ span: {
+ duration: {
+ us: 17530
+ },
+ subtype: 'http',
+ name: 'GET opbeans-go',
+ destination: {
+ service: {
+ resource: 'opbeans-go:3000',
+ name: 'http://opbeans-go:3000',
+ type: 'external'
+ }
+ },
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-go:3000/api/orders'
+ }
+ },
+ id: 'fc107f7b556eb49b',
+ type: 'external'
+ }
+ },
+ {
+ parent: {
+ id: '975c8d5bfd1dd20b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '975c8d5bfd1dd20b'
+ },
+ timestamp: {
+ us: 1584975868787174
+ },
+ span: {
+ duration: {
+ us: 16250
+ },
+ subtype: 'http',
+ destination: {
+ service: {
+ resource: 'opbeans-python:3000',
+ name: 'http://opbeans-python:3000',
+ type: 'external'
+ }
+ },
+ name: 'GET opbeans-python:3000',
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-python:3000/api/orders'
+ }
+ },
+ id: 'daae24d83c269918',
+ type: 'external'
+ }
+ },
+ {
+ container: {
+ id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ parent: {
+ id: '6fb0ff7365b87298'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.790Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '6fb0ff7365b87298'
+ },
+ timestamp: {
+ us: 1584975868790080
+ },
+ span: {
+ duration: {
+ us: 2519
+ },
+ subtype: 'postgresql',
+ name: 'SELECT FROM opbeans_order',
+ destination: {
+ service: {
+ resource: 'postgresql',
+ name: 'postgresql',
+ type: 'db'
+ }
+ },
+ action: 'query',
+ id: 'c9407abb4d08ead1',
+ type: 'db',
+ sync: true,
+ db: {
+ statement:
+ 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000',
+ type: 'sql'
+ }
+ }
+ }
+ ],
+ exceedsMax: false,
+ errorDocs: []
+ },
+ errorsPerTransaction: {}
+};
+
+export const traceWithErrors = {
+ trace: {
+ items: [
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 46
+ }
+ },
+ source: {
+ ip: '172.19.0.13'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: '172.19.0.9',
+ full: 'http://172.19.0.9:3000/api/orders'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ type: 'apm-server',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ http: {
+ request: {
+ headers: {
+ Accept: ['*/*'],
+ 'User-Agent': ['Python/3.7 aiohttp/3.3.2'],
+ Host: ['172.19.0.9:3000'],
+ 'Accept-Encoding': ['gzip, deflate']
+ },
+ method: 'get',
+ socket: {
+ encrypted: false,
+ remote_address: '172.19.0.13'
+ },
+ body: {
+ original: '[REDACTED]'
+ }
+ },
+ response: {
+ headers: {
+ 'Transfer-Encoding': ['chunked'],
+ Date: ['Mon, 23 Mar 2020 15:04:28 GMT'],
+ 'Content-Type': ['application/json;charset=ISO-8859-1']
+ },
+ status_code: 200,
+ finished: true,
+ headers_sent: true
+ },
+ version: '1.1'
+ },
+ client: {
+ ip: '172.19.0.13'
+ },
+ transaction: {
+ duration: {
+ us: 18842
+ },
+ result: 'HTTP 2xx',
+ name: 'DispatcherServlet#doGet',
+ id: '49809ad3c26adf74',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ user_agent: {
+ original: 'Python/3.7 aiohttp/3.3.2',
+ name: 'Other',
+ device: {
+ name: 'Other'
+ }
+ },
+ timestamp: {
+ us: 1584975868785000
+ }
+ },
+ {
+ parent: {
+ id: 'fc107f7b556eb49b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ framework: {
+ name: 'gin',
+ version: 'v1.4.0'
+ },
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ duration: {
+ us: 16597
+ },
+ result: 'HTTP 2xx',
+ name: 'GET /api/orders',
+ id: '975c8d5bfd1dd20b',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ timestamp: {
+ us: 1584975868787052
+ }
+ },
+ {
+ parent: {
+ id: 'daae24d83c269918'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ timestamp: {
+ us: 1584975868788603
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ '@timestamp': '2020-03-23T15:04:28.788Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ result: 'HTTP 2xx',
+ duration: {
+ us: 14648
+ },
+ name: 'GET opbeans.views.orders',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ id: '6fb0ff7365b87298',
+ type: 'request',
+ sampled: true
+ }
+ },
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ parent: {
+ id: '49809ad3c26adf74'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 44
+ }
+ },
+ destination: {
+ address: 'opbeans-go',
+ port: 3000
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ type: 'apm-server',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ connection: {
+ hash:
+ "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}"
+ },
+ transaction: {
+ id: '49809ad3c26adf74'
+ },
+ timestamp: {
+ us: 1584975868785273
+ },
+ span: {
+ duration: {
+ us: 17530
+ },
+ subtype: 'http',
+ name: 'GET opbeans-go',
+ destination: {
+ service: {
+ resource: 'opbeans-go:3000',
+ name: 'http://opbeans-go:3000',
+ type: 'external'
+ }
+ },
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-go:3000/api/orders'
+ }
+ },
+ id: 'fc107f7b556eb49b',
+ type: 'external'
+ }
+ },
+ {
+ parent: {
+ id: '975c8d5bfd1dd20b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '975c8d5bfd1dd20b'
+ },
+ timestamp: {
+ us: 1584975868787174
+ },
+ span: {
+ duration: {
+ us: 16250
+ },
+ subtype: 'http',
+ destination: {
+ service: {
+ resource: 'opbeans-python:3000',
+ name: 'http://opbeans-python:3000',
+ type: 'external'
+ }
+ },
+ name: 'GET opbeans-python:3000',
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-python:3000/api/orders'
+ }
+ },
+ id: 'daae24d83c269918',
+ type: 'external'
+ }
+ },
+ {
+ container: {
+ id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ parent: {
+ id: '6fb0ff7365b87298'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.790Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '6fb0ff7365b87298'
+ },
+ timestamp: {
+ us: 1584975868790080
+ },
+ span: {
+ duration: {
+ us: 2519
+ },
+ subtype: 'postgresql',
+ name: 'SELECT FROM opbeans_order',
+ destination: {
+ service: {
+ resource: 'postgresql',
+ name: 'postgresql',
+ type: 'db'
+ }
+ },
+ action: 'query',
+ id: 'c9407abb4d08ead1',
+ type: 'db',
+ sync: true,
+ db: {
+ statement:
+ 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000',
+ type: 'sql'
+ }
+ }
+ }
+ ],
+ exceedsMax: false,
+ errorDocs: [
+ {
+ parent: {
+ id: '975c8d5bfd1dd20b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ error: {
+ culprit: 'logrusMiddleware',
+ log: {
+ level: 'error',
+ message: 'GET //api/products (502)'
+ },
+ id: '1f3cb98206b5c54225cb7c8908a658da',
+ grouping_key: '4dba2ff58fe6c036a5dee2ce411e512a'
+ },
+ processor: {
+ name: 'error',
+ event: 'error'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T16:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '975c8d5bfd1dd20b',
+ sampled: false
+ },
+ timestamp: {
+ us: 1584975868787052
+ }
+ },
+ {
+ parent: {
+ id: '6fb0ff7365b87298'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ error: {
+ culprit: 'logrusMiddleware',
+ log: {
+ level: 'error',
+ message: 'GET //api/products (502)'
+ },
+ id: '1f3cb98206b5c54225cb7c8908a658d2',
+ grouping_key: '4dba2ff58fe6c036a5dee2ce411e512a'
+ },
+ processor: {
+ name: 'error',
+ event: 'error'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T16:04:28.790Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-python',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '6fb0ff7365b87298',
+ sampled: false
+ },
+ timestamp: {
+ us: 1584975868790000
+ }
+ }
+ ]
+ },
+ errorsPerTransaction: {
+ '975c8d5bfd1dd20b': 1,
+ '6fb0ff7365b87298': 1
+ }
+};
+
+export const traceChildStartBeforeParent = {
+ trace: {
+ items: [
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 46
+ }
+ },
+ source: {
+ ip: '172.19.0.13'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: '172.19.0.9',
+ full: 'http://172.19.0.9:3000/api/orders'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ type: 'apm-server',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ http: {
+ request: {
+ headers: {
+ Accept: ['*/*'],
+ 'User-Agent': ['Python/3.7 aiohttp/3.3.2'],
+ Host: ['172.19.0.9:3000'],
+ 'Accept-Encoding': ['gzip, deflate']
+ },
+ method: 'get',
+ socket: {
+ encrypted: false,
+ remote_address: '172.19.0.13'
+ },
+ body: {
+ original: '[REDACTED]'
+ }
+ },
+ response: {
+ headers: {
+ 'Transfer-Encoding': ['chunked'],
+ Date: ['Mon, 23 Mar 2020 15:04:28 GMT'],
+ 'Content-Type': ['application/json;charset=ISO-8859-1']
+ },
+ status_code: 200,
+ finished: true,
+ headers_sent: true
+ },
+ version: '1.1'
+ },
+ client: {
+ ip: '172.19.0.13'
+ },
+ transaction: {
+ duration: {
+ us: 18842
+ },
+ result: 'HTTP 2xx',
+ name: 'DispatcherServlet#doGet',
+ id: '49809ad3c26adf74',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ user_agent: {
+ original: 'Python/3.7 aiohttp/3.3.2',
+ name: 'Other',
+ device: {
+ name: 'Other'
+ }
+ },
+ timestamp: {
+ us: 1584975868785000
+ }
+ },
+ {
+ parent: {
+ id: 'fc107f7b556eb49b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ framework: {
+ name: 'gin',
+ version: 'v1.4.0'
+ },
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ duration: {
+ us: 16597
+ },
+ result: 'HTTP 2xx',
+ name: 'GET /api/orders',
+ id: '975c8d5bfd1dd20b',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ type: 'request',
+ sampled: true
+ },
+ timestamp: {
+ us: 1584975868787052
+ }
+ },
+ {
+ parent: {
+ id: 'daae24d83c269918'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ timestamp: {
+ us: 1584975868780000
+ },
+ processor: {
+ name: 'transaction',
+ event: 'transaction'
+ },
+ url: {
+ path: '/api/orders',
+ scheme: 'http',
+ port: 3000,
+ domain: 'opbeans-go',
+ full: 'http://opbeans-go:3000/api/orders'
+ },
+ '@timestamp': '2020-03-23T15:04:28.788Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ result: 'HTTP 2xx',
+ duration: {
+ us: 1464
+ },
+ name: 'I started before my parent 😰',
+ span_count: {
+ dropped: 0,
+ started: 1
+ },
+ id: '6fb0ff7365b87298',
+ type: 'request',
+ sampled: true
+ }
+ },
+ {
+ container: {
+ id: '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ parent: {
+ id: '49809ad3c26adf74'
+ },
+ process: {
+ pid: 6,
+ title: '/usr/lib/jvm/java-10-openjdk-amd64/bin/java',
+ ppid: 1
+ },
+ agent: {
+ name: 'java',
+ ephemeral_id: '99ce8403-5875-4945-b074-d37dc10563eb',
+ version: '1.14.1-SNAPSHOT'
+ },
+ internal: {
+ sampler: {
+ value: 44
+ }
+ },
+ destination: {
+ address: 'opbeans-go',
+ port: 3000
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ observer: {
+ hostname: 'f37f48d8b60b',
+ id: 'd8522e1f-be8e-43c2-b290-ac6b6c0f171e',
+ type: 'apm-server',
+ ephemeral_id: '6ed88f14-170e-478d-a4f5-ea5e7f4b16b9',
+ version: '8.0.0',
+ version_major: 8
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.785Z',
+ ecs: {
+ version: '1.4.0'
+ },
+ service: {
+ node: {
+ name:
+ '4cf84d094553201997ddb7fea344b7c6ef18dcb8233eba39278946ee8449794e'
+ },
+ environment: 'production',
+ name: 'opbeans-java',
+ runtime: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ language: {
+ name: 'Java',
+ version: '10.0.2'
+ },
+ version: 'None'
+ },
+ host: {
+ hostname: '4cf84d094553',
+ os: {
+ platform: 'Linux'
+ },
+ ip: '172.19.0.9',
+ name: '4cf84d094553',
+ architecture: 'amd64'
+ },
+ connection: {
+ hash:
+ "{service.environment:'production'}/{service.name:'opbeans-java'}/{span.subtype:'http'}/{destination.address:'opbeans-go'}/{span.type:'external'}"
+ },
+ transaction: {
+ id: '49809ad3c26adf74'
+ },
+ timestamp: {
+ us: 1584975868785273
+ },
+ span: {
+ duration: {
+ us: 17530
+ },
+ subtype: 'http',
+ name: 'GET opbeans-go',
+ destination: {
+ service: {
+ resource: 'opbeans-go:3000',
+ name: 'http://opbeans-go:3000',
+ type: 'external'
+ }
+ },
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-go:3000/api/orders'
+ }
+ },
+ id: 'fc107f7b556eb49b',
+ type: 'external'
+ }
+ },
+ {
+ parent: {
+ id: '975c8d5bfd1dd20b'
+ },
+ agent: {
+ name: 'go',
+ version: '1.7.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.787Z',
+ service: {
+ node: {
+ name:
+ 'e948a08b8f5efe99b5da01f50da48c7d8aee3bbf4701f3da85ebe760c2ffef29'
+ },
+ environment: 'production',
+ name: 'opbeans-go',
+ runtime: {
+ name: 'gc',
+ version: 'go1.14.1'
+ },
+ language: {
+ name: 'go',
+ version: 'go1.14.1'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '975c8d5bfd1dd20b'
+ },
+ timestamp: {
+ us: 1584975868787174
+ },
+ span: {
+ duration: {
+ us: 16250
+ },
+ subtype: 'http',
+ destination: {
+ service: {
+ resource: 'opbeans-python:3000',
+ name: 'http://opbeans-python:3000',
+ type: 'external'
+ }
+ },
+ name: 'I am his 👇🏻 parent 😡',
+ http: {
+ response: {
+ status_code: 200
+ },
+ url: {
+ original: 'http://opbeans-python:3000/api/orders'
+ }
+ },
+ id: 'daae24d83c269918',
+ type: 'external'
+ }
+ },
+ {
+ container: {
+ id: 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ parent: {
+ id: '6fb0ff7365b87298'
+ },
+ agent: {
+ name: 'python',
+ version: '5.5.2'
+ },
+ processor: {
+ name: 'transaction',
+ event: 'span'
+ },
+ trace: {
+ id: '513d33fafe99bbe6134749310c9b5322'
+ },
+ '@timestamp': '2020-03-23T15:04:28.790Z',
+ service: {
+ node: {
+ name:
+ 'a636915f1f6eec81ab44342b13a3ea9597ef03a24391e4e55f34ae2e20b30f51'
+ },
+ environment: 'production',
+ framework: {
+ name: 'django',
+ version: '2.1.13'
+ },
+ name: 'opbeans-python',
+ runtime: {
+ name: 'CPython',
+ version: '3.6.10'
+ },
+ language: {
+ name: 'python',
+ version: '3.6.10'
+ },
+ version: 'None'
+ },
+ transaction: {
+ id: '6fb0ff7365b87298'
+ },
+ timestamp: {
+ us: 1584975868781000
+ },
+ span: {
+ duration: {
+ us: 2519
+ },
+ subtype: 'postgresql',
+ name: 'I am using my parents skew 😇',
+ destination: {
+ service: {
+ resource: 'postgresql',
+ name: 'postgresql',
+ type: 'db'
+ }
+ },
+ action: 'query',
+ id: 'c9407abb4d08ead1',
+ type: 'db',
+ sync: true,
+ db: {
+ statement:
+ 'SELECT "opbeans_order"."id", "opbeans_order"."customer_id", "opbeans_customer"."full_name", "opbeans_order"."created_at" FROM "opbeans_order" INNER JOIN "opbeans_customer" ON ("opbeans_order"."customer_id" = "opbeans_customer"."id") LIMIT 1000',
+ type: 'sql'
+ }
+ }
+ }
+ ],
+ exceedsMax: false,
+ errorDocs: []
+ },
+ errorsPerTransaction: {}
+};
diff --git a/x-pack/legacy/plugins/graph/index.ts b/x-pack/legacy/plugins/graph/index.ts
index 53d32a836cfa1..5c7f8fa46c18b 100644
--- a/x-pack/legacy/plugins/graph/index.ts
+++ b/x-pack/legacy/plugins/graph/index.ts
@@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { i18n } from '@kbn/i18n';
-
// @ts-ignore
import migrations from './migrations';
import mappings from './mappings.json';
@@ -30,40 +28,5 @@ export const graph: LegacyPluginInitializer = kibana => {
.default('configAndData'),
}).default();
},
-
- init(server) {
- server.plugins.xpack_main.registerFeature({
- id: 'graph',
- name: i18n.translate('xpack.graph.featureRegistry.graphFeatureName', {
- defaultMessage: 'Graph',
- }),
- order: 1200,
- icon: 'graphApp',
- navLinkId: 'graph',
- app: ['graph', 'kibana'],
- catalogue: ['graph'],
- validLicenses: ['platinum', 'enterprise', 'trial'],
- privileges: {
- all: {
- app: ['graph', 'kibana'],
- catalogue: ['graph'],
- savedObject: {
- all: ['graph-workspace'],
- read: ['index-pattern'],
- },
- ui: ['save', 'delete'],
- },
- read: {
- app: ['graph', 'kibana'],
- catalogue: ['graph'],
- savedObject: {
- all: [],
- read: ['index-pattern', 'graph-workspace'],
- },
- ui: [],
- },
- },
- });
- },
});
};
diff --git a/x-pack/legacy/plugins/lens/public/helpers/index.ts b/x-pack/legacy/plugins/lens/public/helpers/index.ts
new file mode 100644
index 0000000000000..f464b5dcc97a3
--- /dev/null
+++ b/x-pack/legacy/plugins/lens/public/helpers/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { addEmbeddableToDashboardUrl, getUrlVars } from './url_helper';
diff --git a/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts b/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts
new file mode 100644
index 0000000000000..9c59c9a96d00f
--- /dev/null
+++ b/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+jest.mock('../legacy_imports', () => ({
+ DashboardConstants: {
+ ADD_EMBEDDABLE_ID: 'addEmbeddableId',
+ ADD_EMBEDDABLE_TYPE: 'addEmbeddableType',
+ },
+}));
+
+import { addEmbeddableToDashboardUrl, getUrlVars } from './url_helper';
+
+describe('Dashboard URL Helper', () => {
+ it('addEmbeddableToDashboardUrl', () => {
+ const id = '123eb456cd';
+ const urlVars = {
+ x: '1',
+ y: '2',
+ z: '3',
+ };
+ const url =
+ "/pep/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!())";
+ expect(addEmbeddableToDashboardUrl(url, id, urlVars)).toEqual(
+ `/pep/app/kibana#/dashboard?_a=%28description%3A%27%27%2Cfilters%3A%21%28%29%29&_g=%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29&addEmbeddableId=${id}&addEmbeddableType=lens&x=1&y=2&z=3`
+ );
+ });
+
+ it('getUrlVars', () => {
+ let url =
+ "http://localhost:5601/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
+ expect(getUrlVars(url)).toEqual({
+ _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))',
+ _a: "(description:'',filters:!()",
+ });
+ url = 'http://mybusiness.mydomain.com/app/kibana#/dashboard?x=y&y=z';
+ expect(getUrlVars(url)).toEqual({
+ x: 'y',
+ y: 'z',
+ });
+ url = 'http://localhost:5601/app/kibana#/dashboard/777182';
+ expect(getUrlVars(url)).toEqual({});
+ url =
+ 'http://localhost:5601/app/kibana#/dashboard/777182?title=Some%20Dashboard%20With%20Spaces';
+ expect(getUrlVars(url)).toEqual({ title: 'Some Dashboard With Spaces' });
+ });
+});
diff --git a/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts b/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts
new file mode 100644
index 0000000000000..fca44195b98c4
--- /dev/null
+++ b/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { parseUrl, stringify } from 'query-string';
+import { DashboardConstants } from '../legacy_imports';
+
+type UrlVars = Record;
+
+/**
+ * Return query params from URL
+ * @param url given url
+ */
+export function getUrlVars(url: string): Record {
+ const vars: UrlVars = {};
+ for (const [, key, value] of url.matchAll(/[?&]+([^=&]+)=([^&]*)/gi)) {
+ vars[key] = decodeURIComponent(value);
+ }
+ return vars;
+}
+
+/** *
+ * Returns dashboard URL with added embeddableType and embeddableId query params
+ * eg.
+ * input: url: /lol/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now)), embeddableId: 12345
+ * output: /lol/app/kibana#/dashboard?addEmbeddableType=lens&addEmbeddableId=12345&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))
+ * @param url dasbhoard absolute url
+ * @param embeddableId id of the saved visualization
+ * @param urlVars url query params
+ */
+export function addEmbeddableToDashboardUrl(url: string, embeddableId: string, urlVars: UrlVars) {
+ const dashboardParsedUrl = parseUrl(url);
+ const keys = Object.keys(urlVars).sort();
+
+ keys.forEach(key => {
+ dashboardParsedUrl.query[key] = urlVars[key];
+ });
+ dashboardParsedUrl.query[DashboardConstants.ADD_EMBEDDABLE_TYPE] = 'lens';
+ dashboardParsedUrl.query[DashboardConstants.ADD_EMBEDDABLE_ID] = embeddableId;
+ const query = stringify(dashboardParsedUrl.query);
+
+ return `${dashboardParsedUrl.url}?${query}`;
+}
diff --git a/x-pack/legacy/plugins/lens/public/legacy_imports.ts b/x-pack/legacy/plugins/lens/public/legacy_imports.ts
index 5c5afc1a87df0..857443ae0fa1c 100644
--- a/x-pack/legacy/plugins/lens/public/legacy_imports.ts
+++ b/x-pack/legacy/plugins/lens/public/legacy_imports.ts
@@ -7,3 +7,4 @@
import { npSetup } from 'ui/new_platform';
export const { visualizations } = npSetup.plugins;
export { VisualizationsSetup } from '../../../../../src/plugins/visualizations/public';
+export { DashboardConstants } from '../../../../../src/legacy/core_plugins/kibana/public/dashboard';
diff --git a/x-pack/legacy/plugins/lens/public/plugin.tsx b/x-pack/legacy/plugins/lens/public/plugin.tsx
index fad1371199e6a..45817fdc3c05f 100644
--- a/x-pack/legacy/plugins/lens/public/plugin.tsx
+++ b/x-pack/legacy/plugins/lens/public/plugin.tsx
@@ -8,10 +8,14 @@ import React from 'react';
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom';
import { render, unmountComponentAtNode } from 'react-dom';
-import { AppMountParameters, CoreSetup, CoreStart } from 'src/core/public';
-import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public';
import rison, { RisonObject, RisonValue } from 'rison-node';
import { isObject } from 'lodash';
+
+import { AppMountParameters, CoreSetup, CoreStart } from 'src/core/public';
+import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public';
+import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public';
+import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public';
+import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public';
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import { EditorFrameService } from './editor_frame_service';
import { IndexPatternDatasource } from './indexpattern_datasource';
@@ -19,7 +23,6 @@ import { addHelpMenuToAppChrome } from './help_menu_util';
import { SavedObjectIndexStore } from './persistence';
import { XyVisualization } from './xy_visualization';
import { MetricVisualization } from './metric_visualization';
-import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public';
import { DatatableVisualization } from './datatable_visualization';
import { App } from './app_plugin';
import {
@@ -30,17 +33,12 @@ import {
} from './lens_ui_telemetry';
import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
-import { KibanaLegacySetup } from '../../../../../src/plugins/kibana_legacy/public';
import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../../../plugins/lens/common';
-import {
- addEmbeddableToDashboardUrl,
- getUrlVars,
- getLensUrlFromDashboardAbsoluteUrl,
-} from '../../../../../src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper';
-import { EmbeddableSetup, EmbeddableStart } from '../../../../../src/plugins/embeddable/public';
+import { addEmbeddableToDashboardUrl, getUrlVars } from './helpers';
import { EditorFrameStart } from './types';
import { getLensAliasConfig } from './vis_type_alias';
-import { VisualizationsSetup } from './legacy_imports';
+import { VisualizationsSetup, DashboardConstants } from './legacy_imports';
+
export interface LensPluginSetupDependencies {
kibanaLegacy: KibanaLegacySetup;
expressions: ExpressionsSetup;
@@ -144,40 +142,24 @@ export class LensPlugin {
routeProps.history.push(`/lens/edit/${id}`);
} else if (addToDashboardMode && id) {
routeProps.history.push(`/lens/edit/${id}`);
- const url = coreStart.chrome.navLinks.get('kibana:dashboard');
- if (!url) {
+ const lastDashboardLink = coreStart.chrome.navLinks.get('kibana:dashboard');
+ if (!lastDashboardLink || !lastDashboardLink.url) {
throw new Error('Cannot get last dashboard url');
}
- const lastDashboardAbsoluteUrl = url.url;
- const basePath = coreStart.http.basePath.get();
- const lensUrl = getLensUrlFromDashboardAbsoluteUrl(
- lastDashboardAbsoluteUrl,
- basePath,
- id
- );
- if (!lastDashboardAbsoluteUrl || !lensUrl) {
- throw new Error('Cannot get last dashboard url');
- }
- window.history.pushState({}, '', lensUrl);
- const urlVars = getUrlVars(lastDashboardAbsoluteUrl);
+ const urlVars = getUrlVars(lastDashboardLink.url);
updateUrlTime(urlVars); // we need to pass in timerange in query params directly
- const dashboardParsedUrl = addEmbeddableToDashboardUrl(
- lastDashboardAbsoluteUrl,
- basePath,
- id,
- urlVars
- );
- if (!dashboardParsedUrl) {
- throw new Error('Problem parsing dashboard url');
- }
- window.history.pushState({}, '', dashboardParsedUrl);
+ const dashboardUrl = addEmbeddableToDashboardUrl(lastDashboardLink.url, id, urlVars);
+ window.history.pushState({}, '', dashboardUrl);
}
};
const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => {
trackUiEvent('loaded');
const addToDashboardMode =
- !!routeProps.location.search && routeProps.location.search.includes('addToDashboard');
+ !!routeProps.location.search &&
+ routeProps.location.search.includes(
+ DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM
+ );
return (
{
.invoke('text')
.should('eql', expectedTags);
+ cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE)
+ .eq(INVESTIGATION_NOTES_TOGGLE)
+ .click({ force: true });
+ cy.get(ABOUT_INVESTIGATION_NOTES)
+ .invoke('text')
+ .should('eql', INVESTIGATION_NOTES_MARKDOWN);
+
cy.get(DEFINITION_INDEX_PATTERNS).then(patterns => {
cy.wrap(patterns).each((pattern, index) => {
cy.wrap(pattern)
diff --git a/x-pack/legacy/plugins/siem/cypress/objects/rule.ts b/x-pack/legacy/plugins/siem/cypress/objects/rule.ts
index a3c648c9cc934..37c325c3b8030 100644
--- a/x-pack/legacy/plugins/siem/cypress/objects/rule.ts
+++ b/x-pack/legacy/plugins/siem/cypress/objects/rule.ts
@@ -22,6 +22,7 @@ export interface CustomRule {
referenceUrls: string[];
falsePositivesExamples: string[];
mitre: Mitre[];
+ note: string;
}
export interface MachineLearningRule {
@@ -36,6 +37,7 @@ export interface MachineLearningRule {
referenceUrls: string[];
falsePositivesExamples: string[];
mitre: Mitre[];
+ note: string;
}
const mitre1: Mitre = {
@@ -58,6 +60,7 @@ export const newRule: CustomRule = {
referenceUrls: ['https://www.google.com/', 'https://elastic.co/'],
falsePositivesExamples: ['False1', 'False2'],
mitre: [mitre1, mitre2],
+ note: '# test markdown',
};
export const machineLearningRule: MachineLearningRule = {
@@ -71,4 +74,5 @@ export const machineLearningRule: MachineLearningRule = {
referenceUrls: ['https://elastic.co/'],
falsePositivesExamples: ['False1'],
mitre: [mitre1],
+ note: '# test markdown',
};
diff --git a/x-pack/legacy/plugins/siem/cypress/screens/create_new_rule.ts b/x-pack/legacy/plugins/siem/cypress/screens/create_new_rule.ts
index e603e2ee5158e..db9866cdf7f63 100644
--- a/x-pack/legacy/plugins/siem/cypress/screens/create_new_rule.ts
+++ b/x-pack/legacy/plugins/siem/cypress/screens/create_new_rule.ts
@@ -24,7 +24,8 @@ export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]';
export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]';
-export const SCHEDULE_CONTINUE_BUTTON = '[data-test-subj="schedule-continue"]';
+export const INVESTIGATION_NOTES_TEXTAREA =
+ '[data-test-subj="detectionEngineStepAboutRuleNote"] textarea';
export const FALSE_POSITIVES_INPUT =
'[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] input';
@@ -53,6 +54,8 @@ export const RULE_DESCRIPTION_INPUT =
export const RULE_NAME_INPUT =
'[data-test-subj="detectionEngineStepAboutRuleName"] [data-test-subj="input"]';
+export const SCHEDULE_CONTINUE_BUTTON = '[data-test-subj="schedule-continue"]';
+
export const SEVERITY_DROPDOWN =
'[data-test-subj="detectionEngineStepAboutRuleSeverity"] [data-test-subj="select"]';
diff --git a/x-pack/legacy/plugins/siem/cypress/screens/rule_details.ts b/x-pack/legacy/plugins/siem/cypress/screens/rule_details.ts
index fc9e4c56dd824..ec57e142125da 100644
--- a/x-pack/legacy/plugins/siem/cypress/screens/rule_details.ts
+++ b/x-pack/legacy/plugins/siem/cypress/screens/rule_details.ts
@@ -6,6 +6,8 @@
export const ABOUT_FALSE_POSITIVES = 3;
+export const ABOUT_INVESTIGATION_NOTES = '[data-test-subj="stepAboutDetailsNoteContent"]';
+
export const ABOUT_MITRE = 4;
export const ABOUT_RULE_DESCRIPTION = '[data-test-subj=stepAboutRuleDetailsToggleDescriptionText]';
@@ -32,10 +34,16 @@ export const DEFINITION_INDEX_PATTERNS =
export const DEFINITION_STEP =
'[data-test-subj=definitionRule] [data-test-subj="listItemColumnStepRuleDescription"] .euiDescriptionList__description';
+export const INVESTIGATION_NOTES_MARKDOWN = 'test markdown';
+
+export const INVESTIGATION_NOTES_TOGGLE = 1;
+
export const MACHINE_LEARNING_JOB_ID = '[data-test-subj="machineLearningJobId"]';
export const MACHINE_LEARNING_JOB_STATUS = '[data-test-subj="machineLearningJobStatus" ]';
+export const RULE_ABOUT_DETAILS_HEADER_TOGGLE = '[data-test-subj="stepAboutDetailsToggle"]';
+
export const RULE_NAME_HEADER = '[data-test-subj="header-page-title"]';
export const RULE_TYPE = 0;
diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/create_new_rule.ts b/x-pack/legacy/plugins/siem/cypress/tasks/create_new_rule.ts
index 59ed156bf56b1..a20ad372a689c 100644
--- a/x-pack/legacy/plugins/siem/cypress/tasks/create_new_rule.ts
+++ b/x-pack/legacy/plugins/siem/cypress/tasks/create_new_rule.ts
@@ -14,6 +14,7 @@ import {
CUSTOM_QUERY_INPUT,
DEFINE_CONTINUE_BUTTON,
FALSE_POSITIVES_INPUT,
+ INVESTIGATION_NOTES_TEXTAREA,
MACHINE_LEARNING_DROPDOWN,
MACHINE_LEARNING_LIST,
MACHINE_LEARNING_TYPE,
@@ -82,6 +83,8 @@ export const fillAboutRuleAndContinue = (rule: CustomRule | MachineLearningRule)
cy.get(MITRE_BTN).click({ force: true });
});
+ cy.get(INVESTIGATION_NOTES_TEXTAREA).type(rule.note, { force: true });
+
cy.get(ABOUT_CONTINUE_BTN)
.should('exist')
.click({ force: true });
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.test.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.test.ts
index 60ebd2578b7c0..a779d579bf4d1 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.test.ts
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.test.ts
@@ -4,16 +4,48 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { cloneDeep, omit } from 'lodash/fp';
+import { Dispatch } from 'redux';
-import { mockTimelineResults } from '../../mock/timeline_results';
+import {
+ mockTimelineResults,
+ mockTimelineResult,
+ mockTimelineModel,
+} from '../../mock/timeline_results';
import { timelineDefaults } from '../../store/timeline/defaults';
+import { setTimelineRangeDatePicker as dispatchSetTimelineRangeDatePicker } from '../../store/inputs/actions';
+import {
+ setKqlFilterQueryDraft as dispatchSetKqlFilterQueryDraft,
+ applyKqlFilterQuery as dispatchApplyKqlFilterQuery,
+ addTimeline as dispatchAddTimeline,
+ addNote as dispatchAddGlobalTimelineNote,
+} from '../../store/timeline/actions';
+import {
+ addNotes as dispatchAddNotes,
+ updateNote as dispatchUpdateNote,
+} from '../../store/app/actions';
import {
defaultTimelineToTimelineModel,
getNotesCount,
getPinnedEventCount,
isUntitled,
+ omitTypenameInTimeline,
+ dispatchUpdateTimeline,
} from './helpers';
-import { OpenTimelineResult } from './types';
+import { OpenTimelineResult, DispatchUpdateTimeline } from './types';
+import { KueryFilterQueryKind } from '../../store/model';
+import { Note } from '../../lib/note';
+import moment from 'moment';
+import sinon from 'sinon';
+
+jest.mock('../../store/inputs/actions');
+jest.mock('../../store/timeline/actions');
+jest.mock('../../store/app/actions');
+jest.mock('uuid', () => {
+ return {
+ v1: jest.fn(() => 'uuid.v1()'),
+ v4: jest.fn(() => 'uuid.v4()'),
+ };
+});
describe('helpers', () => {
let mockResults: OpenTimelineResult[];
@@ -620,4 +652,229 @@ describe('helpers', () => {
});
});
});
+
+ describe('omitTypenameInTimeline', () => {
+ test('it does not modify the passed in timeline if no __typename exists', () => {
+ const result = omitTypenameInTimeline(mockTimelineResult);
+
+ expect(result).toEqual(mockTimelineResult);
+ });
+
+ test('it returns timeline with __typename removed when it exists', () => {
+ const mockTimeline = {
+ ...mockTimelineResult,
+ __typename: 'something, something',
+ };
+ const result = omitTypenameInTimeline(mockTimeline);
+ const expectedTimeline = {
+ ...mockTimeline,
+ __typename: undefined,
+ };
+
+ expect(result).toEqual(expectedTimeline);
+ });
+ });
+
+ describe('dispatchUpdateTimeline', () => {
+ const dispatch = jest.fn() as Dispatch;
+ const anchor = '2020-03-27T20:34:51.337Z';
+ const unix = moment(anchor).valueOf();
+ let clock: sinon.SinonFakeTimers;
+ let timelineDispatch: DispatchUpdateTimeline;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ clock = sinon.useFakeTimers(unix);
+ timelineDispatch = dispatchUpdateTimeline(dispatch);
+ });
+
+ afterEach(function() {
+ clock.restore();
+ });
+
+ test('it invokes date range picker dispatch', () => {
+ timelineDispatch({
+ duplicate: true,
+ id: 'timeline-1',
+ from: 1585233356356,
+ to: 1585233716356,
+ notes: [],
+ timeline: mockTimelineModel,
+ })();
+
+ expect(dispatchSetTimelineRangeDatePicker).toHaveBeenCalledWith({
+ from: 1585233356356,
+ to: 1585233716356,
+ });
+ });
+
+ test('it invokes add timeline dispatch', () => {
+ timelineDispatch({
+ duplicate: true,
+ id: 'timeline-1',
+ from: 1585233356356,
+ to: 1585233716356,
+ notes: [],
+ timeline: mockTimelineModel,
+ })();
+
+ expect(dispatchAddTimeline).toHaveBeenCalledWith({
+ id: 'timeline-1',
+ timeline: mockTimelineModel,
+ });
+ });
+
+ test('it does not invoke kql filter query dispatches if timeline.kqlQuery.filterQuery is null', () => {
+ timelineDispatch({
+ duplicate: true,
+ id: 'timeline-1',
+ from: 1585233356356,
+ to: 1585233716356,
+ notes: [],
+ timeline: mockTimelineModel,
+ })();
+
+ expect(dispatchSetKqlFilterQueryDraft).not.toHaveBeenCalled();
+ expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled();
+ });
+
+ test('it does not invoke notes dispatch if duplicate is true', () => {
+ timelineDispatch({
+ duplicate: true,
+ id: 'timeline-1',
+ from: 1585233356356,
+ to: 1585233716356,
+ notes: [],
+ timeline: mockTimelineModel,
+ })();
+
+ expect(dispatchAddNotes).not.toHaveBeenCalled();
+ });
+
+ test('it does not invoke kql filter query dispatches if timeline.kqlQuery.kuery is null', () => {
+ const mockTimeline = {
+ ...mockTimelineModel,
+ kqlQuery: {
+ filterQuery: {
+ kuery: null,
+ serializedQuery: 'some-serialized-query',
+ },
+ filterQueryDraft: null,
+ },
+ };
+ timelineDispatch({
+ duplicate: true,
+ id: 'timeline-1',
+ from: 1585233356356,
+ to: 1585233716356,
+ notes: [],
+ timeline: mockTimeline,
+ })();
+
+ expect(dispatchSetKqlFilterQueryDraft).not.toHaveBeenCalled();
+ expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled();
+ });
+
+ test('it invokes kql filter query dispatches if timeline.kqlQuery.filterQuery.kuery is not null', () => {
+ const mockTimeline = {
+ ...mockTimelineModel,
+ kqlQuery: {
+ filterQuery: {
+ kuery: { expression: 'expression', kind: 'kuery' as KueryFilterQueryKind },
+ serializedQuery: 'some-serialized-query',
+ },
+ filterQueryDraft: null,
+ },
+ };
+ timelineDispatch({
+ duplicate: true,
+ id: 'timeline-1',
+ from: 1585233356356,
+ to: 1585233716356,
+ notes: [],
+ timeline: mockTimeline,
+ })();
+
+ expect(dispatchSetKqlFilterQueryDraft).toHaveBeenCalledWith({
+ id: 'timeline-1',
+ filterQueryDraft: {
+ kind: 'kuery',
+ expression: 'expression',
+ },
+ });
+ expect(dispatchApplyKqlFilterQuery).toHaveBeenCalledWith({
+ id: 'timeline-1',
+ filterQuery: {
+ kuery: {
+ kind: 'kuery',
+ expression: 'expression',
+ },
+ serializedQuery: 'some-serialized-query',
+ },
+ });
+ });
+
+ test('it invokes dispatchAddNotes if duplicate is false', () => {
+ timelineDispatch({
+ duplicate: false,
+ id: 'timeline-1',
+ from: 1585233356356,
+ to: 1585233716356,
+ notes: [
+ {
+ created: 1585233356356,
+ updated: 1585233356356,
+ noteId: 'note-id',
+ note: 'I am a note',
+ },
+ ],
+ timeline: mockTimelineModel,
+ })();
+
+ expect(dispatchAddGlobalTimelineNote).not.toHaveBeenCalled();
+ expect(dispatchUpdateNote).not.toHaveBeenCalled();
+ expect(dispatchAddNotes).toHaveBeenCalledWith({
+ notes: [
+ {
+ created: new Date('2020-03-26T14:35:56.356Z'),
+ id: 'note-id',
+ lastEdit: new Date('2020-03-26T14:35:56.356Z'),
+ note: 'I am a note',
+ user: 'unknown',
+ saveObjectId: 'note-id',
+ version: undefined,
+ },
+ ],
+ });
+ });
+
+ test('it invokes dispatch to create a timeline note if duplicate is true and ruleNote exists', () => {
+ timelineDispatch({
+ duplicate: true,
+ id: 'timeline-1',
+ from: 1585233356356,
+ to: 1585233716356,
+ notes: [],
+ timeline: mockTimelineModel,
+ ruleNote: '# this would be some markdown',
+ })();
+ const expectedNote: Note = {
+ created: new Date(anchor),
+ id: 'uuid.v4()',
+ lastEdit: null,
+ note: '# this would be some markdown',
+ saveObjectId: null,
+ user: 'elastic',
+ version: null,
+ };
+
+ expect(dispatchAddNotes).not.toHaveBeenCalled();
+ expect(dispatchUpdateNote).toHaveBeenCalledWith({ note: expectedNote });
+ expect(dispatchAddGlobalTimelineNote).toHaveBeenLastCalledWith({
+ id: 'timeline-1',
+ noteId: 'uuid.v4()',
+ });
+ });
+ });
});
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts
index 4f7d6cd64f1d9..16ba2de872bd1 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts
@@ -5,18 +5,23 @@
*/
import ApolloClient from 'apollo-client';
-import { getOr, set } from 'lodash/fp';
+import { getOr, set, isEmpty } from 'lodash/fp';
import { Action } from 'typescript-fsa';
+import uuid from 'uuid';
import { Dispatch } from 'redux';
import { oneTimelineQuery } from '../../containers/timeline/one/index.gql_query';
import { TimelineResult, GetOneTimeline, NoteResult } from '../../graphql/types';
-import { addNotes as dispatchAddNotes } from '../../store/app/actions';
+import {
+ addNotes as dispatchAddNotes,
+ updateNote as dispatchUpdateNote,
+} from '../../store/app/actions';
import { setTimelineRangeDatePicker as dispatchSetTimelineRangeDatePicker } from '../../store/inputs/actions';
import {
setKqlFilterQueryDraft as dispatchSetKqlFilterQueryDraft,
applyKqlFilterQuery as dispatchApplyKqlFilterQuery,
addTimeline as dispatchAddTimeline,
+ addNote as dispatchAddGlobalTimelineNote,
} from '../../store/timeline/actions';
import { ColumnHeaderOptions, TimelineModel } from '../../store/timeline/model';
@@ -32,6 +37,7 @@ import {
import { OpenTimelineResult, UpdateTimeline, DispatchUpdateTimeline } from './types';
import { getTimeRangeSettings } from '../../utils/default_date_settings';
+import { createNote } from '../notes/helpers';
export const OPEN_TIMELINE_CLASS_NAME = 'open-timeline';
@@ -250,6 +256,7 @@ export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeli
notes,
timeline,
to,
+ ruleNote,
}: UpdateTimeline): (() => void) => () => {
dispatch(dispatchSetTimelineRangeDatePicker({ from, to }));
dispatch(dispatchAddTimeline({ id, timeline }));
@@ -281,6 +288,14 @@ export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeli
})
);
}
+
+ if (duplicate && ruleNote != null && !isEmpty(ruleNote)) {
+ const getNewNoteId = (): string => uuid.v4();
+ const newNote = createNote({ newNote: ruleNote, getNewNoteId });
+ dispatch(dispatchUpdateNote({ note: newNote }));
+ dispatch(dispatchAddGlobalTimelineNote({ noteId: newNote.id, id }));
+ }
+
if (!duplicate) {
dispatch(
dispatchAddNotes({
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx
index 8805037ecc4ca..b0f8963dd501e 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx
@@ -70,6 +70,25 @@ describe('#getActionsColumns', () => {
expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBe(true);
});
+ test('it renders only duplicate icon (without heading)', () => {
+ const testProps: TimelinesTableProps = {
+ ...getMockTimelinesTableProps(mockResults),
+ actionTimelineToShow: ['duplicate'],
+ };
+ const wrapper = mountWithIntl(
+
+
+
+ );
+
+ expect(
+ wrapper
+ .find('[data-test-subj="open-duplicate"]')
+ .first()
+ .text()
+ ).toEqual('');
+ });
+
test('it does NOT render the duplicate timeline when actionTimelineToShow is NOT including the action duplicate)', () => {
const testProps: TimelinesTableProps = {
...getMockTimelinesTableProps(mockResults),
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx
index 8588beed64b79..746503308c833 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx
@@ -42,6 +42,7 @@ export const getActionsColumns = ({
timelineId: savedObjectId ?? '',
});
},
+ type: 'icon',
enabled: ({ savedObjectId }: OpenTimelineResult) => savedObjectId != null,
description: i18n.OPEN_AS_DUPLICATE,
'data-test-subj': 'open-duplicate',
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts
index 51c72681c0863..b7cc92ebd183f 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts
@@ -173,6 +173,7 @@ export interface UpdateTimeline {
notes: NoteResult[] | null | undefined;
timeline: TimelineModel;
to: number;
+ ruleNote?: string;
}
export type DispatchUpdateTimeline = ({
@@ -182,4 +183,5 @@ export type DispatchUpdateTimeline = ({
notes,
timeline,
to,
+ ruleNote,
}: UpdateTimeline) => () => void;
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx
index fa474c4d601ad..cf1a4ebec9bb6 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiButtonIcon, EuiPopover, EuiSelectableOption } from '@elastic/eui';
+import { EuiButtonIcon, EuiPopover, EuiSelectableOption, EuiToolTip } from '@elastic/eui';
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { useDispatch } from 'react-redux';
@@ -62,13 +62,15 @@ export const InsertTimelinePopoverComponent: React.FC = ({
const insertTimelineButton = useMemo(
() => (
-
+ {i18n.INSERT_TIMELINE}
}>
+
+
),
[handleOpenPopover, isDisabled]
);
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/translations.ts
index de3e3c8e792fe..101837168350f 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/translations.ts
@@ -25,5 +25,5 @@ export const SEARCH_BOX_TIMELINE_PLACEHOLDER = i18n.translate(
);
export const INSERT_TIMELINE = i18n.translate('xpack.siem.insert.timeline.insertTimelineButton', {
- defaultMessage: 'Insert Timeline…',
+ defaultMessage: 'Insert timeline link',
});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/translations.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/translations.ts
new file mode 100644
index 0000000000000..dbd618f40155d
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/translations.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export * from '../translations';
+
+export const SUCCESS_CONFIGURE = i18n.translate('xpack.siem.case.configure.successSaveToast', {
+ defaultMessage: 'Saved external connection settings',
+});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
index b25667f070fdf..6524c40a8e6e4 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
@@ -7,8 +7,8 @@
import { useState, useEffect, useCallback } from 'react';
import { getCaseConfigure, patchCaseConfigure, postCaseConfigure } from './api';
-import { useStateToaster, errorToToaster } from '../../../components/toasters';
-import * as i18n from '../translations';
+import { useStateToaster, errorToToaster, displaySuccessToast } from '../../../components/toasters';
+import * as i18n from './translations';
import { ClosureType } from './types';
import { CurrentConfiguration } from '../../../pages/case/components/configure_cases/reducer';
@@ -124,6 +124,8 @@ export const useCaseConfigure = ({
closureType: res.closureType,
});
}
+
+ displaySuccessToast(i18n.SUCCESS_CONFIGURE, dispatchToaster);
}
} catch (error) {
if (!didCancel) {
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/translations.ts b/x-pack/legacy/plugins/siem/public/containers/case/translations.ts
index 601db373f041e..a453be32480e2 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/translations.ts
@@ -10,6 +10,46 @@ export const ERROR_TITLE = i18n.translate('xpack.siem.containers.case.errorTitle
defaultMessage: 'Error fetching data',
});
+export const ERROR_DELETING = i18n.translate('xpack.siem.containers.case.errorDeletingTitle', {
+ defaultMessage: 'Error deleting data',
+});
+
+export const UPDATED_CASE = (caseTitle: string) =>
+ i18n.translate('xpack.siem.containers.case.updatedCase', {
+ values: { caseTitle },
+ defaultMessage: 'Updated "{caseTitle}"',
+ });
+
+export const DELETED_CASES = (totalCases: number, caseTitle?: string) =>
+ i18n.translate('xpack.siem.containers.case.deletedCases', {
+ values: { caseTitle, totalCases },
+ defaultMessage: 'Deleted {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
+ });
+
+export const CLOSED_CASES = ({
+ totalCases,
+ caseTitle,
+}: {
+ totalCases: number;
+ caseTitle?: string;
+}) =>
+ i18n.translate('xpack.siem.containers.case.closedCases', {
+ values: { caseTitle, totalCases },
+ defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
+ });
+
+export const REOPENED_CASES = ({
+ totalCases,
+ caseTitle,
+}: {
+ totalCases: number;
+ caseTitle?: string;
+}) =>
+ i18n.translate('xpack.siem.containers.case.reopenedCases', {
+ values: { caseTitle, totalCases },
+ defaultMessage: 'Reopened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
+ });
+
export const TAG_FETCH_FAILURE = i18n.translate(
'xpack.siem.containers.case.tagFetchFailDescription',
{
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/types.ts
index bb215d6ac271c..cb3df78257dc1 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/types.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/types.ts
@@ -114,3 +114,8 @@ export interface ActionLicense {
enabledInConfig: boolean;
enabledInLicense: boolean;
}
+
+export interface DeleteCase {
+ id: string;
+ title?: string;
+}
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx
index f1129bae9f537..7d040c49f1971 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_bulk_update_case.tsx
@@ -5,7 +5,7 @@
*/
import { useCallback, useReducer } from 'react';
-import { errorToToaster, useStateToaster } from '../../components/toasters';
+import { displaySuccessToast, errorToToaster, useStateToaster } from '../../components/toasters';
import * as i18n from './translations';
import { patchCasesStatus } from './api';
import { BulkUpdateStatus, Case } from './types';
@@ -71,9 +71,22 @@ export const useUpdateCases = (): UseUpdateCase => {
const patchData = async () => {
try {
dispatch({ type: 'FETCH_INIT' });
- await patchCasesStatus(cases, abortCtrl.signal);
+ const patchResponse = await patchCasesStatus(cases, abortCtrl.signal);
if (!cancel) {
+ const resultCount = Object.keys(patchResponse).length;
+ const firstTitle = patchResponse[0].title;
+
dispatch({ type: 'FETCH_SUCCESS', payload: true });
+ const messageArgs = {
+ totalCases: resultCount,
+ caseTitle: resultCount === 1 ? firstTitle : '',
+ };
+ const message =
+ resultCount && patchResponse[0].status === 'open'
+ ? i18n.REOPENED_CASES(messageArgs)
+ : i18n.CLOSED_CASES(messageArgs);
+
+ displaySuccessToast(message, dispatchToaster);
}
} catch (error) {
if (!cancel) {
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx
index b44e01d06acaf..07e3786758aeb 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_delete_cases.tsx
@@ -5,9 +5,10 @@
*/
import { useCallback, useReducer } from 'react';
-import { errorToToaster, useStateToaster } from '../../components/toasters';
+import { displaySuccessToast, errorToToaster, useStateToaster } from '../../components/toasters';
import * as i18n from './translations';
import { deleteCases } from './api';
+import { DeleteCase } from './types';
interface DeleteState {
isDisplayConfirmDeleteModal: boolean;
@@ -57,9 +58,10 @@ const dataFetchReducer = (state: DeleteState, action: Action): DeleteState => {
return state;
}
};
+
interface UseDeleteCase extends DeleteState {
dispatchResetIsDeleted: () => void;
- handleOnDeleteConfirm: (caseIds: string[]) => void;
+ handleOnDeleteConfirm: (caseIds: DeleteCase[]) => void;
handleToggleModal: () => void;
}
@@ -72,21 +74,26 @@ export const useDeleteCases = (): UseDeleteCase => {
});
const [, dispatchToaster] = useStateToaster();
- const dispatchDeleteCases = useCallback((caseIds: string[]) => {
+ const dispatchDeleteCases = useCallback((cases: DeleteCase[]) => {
let cancel = false;
const abortCtrl = new AbortController();
const deleteData = async () => {
try {
dispatch({ type: 'FETCH_INIT' });
+ const caseIds = cases.map(theCase => theCase.id);
await deleteCases(caseIds, abortCtrl.signal);
if (!cancel) {
dispatch({ type: 'FETCH_SUCCESS', payload: true });
+ displaySuccessToast(
+ i18n.DELETED_CASES(cases.length, cases.length === 1 ? cases[0].title : ''),
+ dispatchToaster
+ );
}
} catch (error) {
if (!cancel) {
errorToToaster({
- title: i18n.ERROR_TITLE,
+ title: i18n.ERROR_DELETING,
error: error.body && error.body.message ? new Error(error.body.message) : error,
dispatchToaster,
});
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx
index 85ad4fd3fc47a..4973deef4d91a 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx
@@ -5,8 +5,8 @@
*/
import { useReducer, useCallback } from 'react';
+import { displaySuccessToast, errorToToaster, useStateToaster } from '../../components/toasters';
import { CasePatchRequest } from '../../../../../../plugins/case/common/api';
-import { errorToToaster, useStateToaster } from '../../components/toasters';
import { patchCase } from './api';
import * as i18n from './translations';
@@ -94,6 +94,7 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase =>
updateCase(response[0]);
}
dispatch({ type: 'FETCH_SUCCESS' });
+ displaySuccessToast(i18n.UPDATED_CASE(response[0].title), dispatchToaster);
}
} catch (error) {
if (!cancel) {
diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/timeline/index.gql_query.ts
index c54238c5d8687..53d0b98570bcb 100644
--- a/x-pack/legacy/plugins/siem/public/containers/timeline/index.gql_query.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/timeline/index.gql_query.ts
@@ -206,6 +206,7 @@ export const timelineQuery = gql`
query
to
filters
+ note
}
}
suricata {
diff --git a/x-pack/legacy/plugins/siem/public/graphql/introspection.json b/x-pack/legacy/plugins/siem/public/graphql/introspection.json
index 5d43024625d0d..2a9dd8f2aacfe 100644
--- a/x-pack/legacy/plugins/siem/public/graphql/introspection.json
+++ b/x-pack/legacy/plugins/siem/public/graphql/introspection.json
@@ -4696,6 +4696,14 @@
"type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "note",
+ "description": "",
+ "args": [],
+ "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"inputFields": null,
diff --git a/x-pack/legacy/plugins/siem/public/graphql/types.ts b/x-pack/legacy/plugins/siem/public/graphql/types.ts
index a5d1e3fbcba27..e15c099a007ad 100644
--- a/x-pack/legacy/plugins/siem/public/graphql/types.ts
+++ b/x-pack/legacy/plugins/siem/public/graphql/types.ts
@@ -1012,6 +1012,8 @@ export interface RuleField {
updated_by?: Maybe;
version?: Maybe;
+
+ note?: Maybe;
}
export interface SuricataEcsFields {
@@ -4660,6 +4662,8 @@ export namespace GetTimelineQuery {
to: Maybe;
filters: Maybe;
+
+ note: Maybe;
};
export type Suricata = {
diff --git a/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx b/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx
index d67007399abea..536798ffad41b 100644
--- a/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx
+++ b/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx
@@ -181,6 +181,7 @@ const ServiceNowConnectorFields: React.FunctionComponent
+
+
+
-
column.id !== 'event.action'),
+ dateRange: { start: 1584539198929, end: 1584539558929 },
+ description: 'This is a sample rule description',
+ eventType: 'all',
+ filters: [
+ {
+ meta: {
+ key: 'host.name',
+ negate: false,
+ params: '"{"query":"placeholder"}"',
+ type: 'phrase',
+ },
+ query: '"{"match_phrase":{"host.name":"placeholder"}}"',
+ },
+ ],
+ kqlMode: 'filter',
+ title: 'Test rule',
+ savedQueryId: null,
+ sort: { columnId: '@timestamp', sortDirection: 'desc' },
+ version: '1',
+};
+
+export const mockTimelineApolloResult = {
+ data: {
+ getOneTimeline: mockTimelineResult,
+ },
+ loading: false,
+ networkStatus: 7,
+ stale: false,
+};
+
+export const defaultTimelineProps: CreateTimelineProps = {
+ from: 1541444305937,
+ timeline: {
+ columns: [
+ { columnHeaderType: 'not-filtered', id: '@timestamp', width: 190 },
+ { columnHeaderType: 'not-filtered', id: 'message', width: 180 },
+ { columnHeaderType: 'not-filtered', id: 'event.category', width: 180 },
+ { columnHeaderType: 'not-filtered', id: 'event.action', width: 180 },
+ { columnHeaderType: 'not-filtered', id: 'host.name', width: 180 },
+ { columnHeaderType: 'not-filtered', id: 'source.ip', width: 180 },
+ { columnHeaderType: 'not-filtered', id: 'destination.ip', width: 180 },
+ { columnHeaderType: 'not-filtered', id: 'user.name', width: 180 },
+ ],
+ dataProviders: [
+ {
+ and: [],
+ enabled: true,
+ excluded: false,
+ id:
+ 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-1',
+ kqlQuery: '',
+ name: '1',
+ queryMatch: { field: '_id', operator: ':', value: '1' },
+ },
+ ],
+ dateRange: { end: 1541444605937, start: 1541444305937 },
+ deletedEventIds: [],
+ description: '',
+ eventIdToNoteIds: {},
+ eventType: 'all',
+ filters: [],
+ highlightedDropAndProviderId: '',
+ historyIds: [],
+ id: 'timeline-1',
+ isFavorite: false,
+ isLive: false,
+ isLoading: false,
+ isSaving: false,
+ isSelectAllChecked: false,
+ itemsPerPage: 25,
+ itemsPerPageOptions: [10, 25, 50, 100],
+ kqlMode: 'filter',
+ kqlQuery: {
+ filterQuery: { kuery: { expression: '', kind: 'kuery' }, serializedQuery: '' },
+ filterQueryDraft: { expression: '', kind: 'kuery' },
+ },
+ loadingEventIds: [],
+ noteIds: [],
+ pinnedEventIds: {},
+ pinnedEventsSaveObject: {},
+ savedObjectId: null,
+ selectedEventIds: {},
+ show: false,
+ showCheckboxes: false,
+ showRowRenderers: true,
+ sort: { columnId: '@timestamp', sortDirection: Direction.desc },
+ title: '',
+ version: null,
+ width: 1100,
+ },
+ to: 1541444605937,
+ ruleNote: '# this is some markdown documentation',
+};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx
index bdcb87b483851..5f61ccf68fc86 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx
@@ -202,7 +202,7 @@ describe('AllCases', () => {
.last()
.simulate('click');
expect(handleOnDeleteConfirm.mock.calls[0][0]).toStrictEqual(
- useGetCasesMockState.data.cases.map(theCase => theCase.id)
+ useGetCasesMockState.data.cases.map(({ id }) => ({ id }))
);
});
it('Bulk close status update', () => {
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx
index 27316ab8427cb..dcfa1712c6ef9 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx
@@ -21,7 +21,7 @@ import styled, { css } from 'styled-components';
import * as i18n from './translations';
import { getCasesColumns } from './columns';
-import { Case, FilterOptions, SortFieldCase } from '../../../../containers/case/types';
+import { Case, DeleteCase, FilterOptions, SortFieldCase } from '../../../../containers/case/types';
import { useGetCases, UpdateCase } from '../../../../containers/case/use_get_cases';
import { useGetCasesStatus } from '../../../../containers/case/use_get_cases_status';
import { useDeleteCases } from '../../../../containers/case/use_delete_cases';
@@ -107,11 +107,24 @@ export const AllCases = React.memo(() => {
isDisplayConfirmDeleteModal,
} = useDeleteCases();
- const { dispatchResetIsUpdated, isUpdated, updateBulkStatus } = useUpdateCases();
+ // Update case
+ const {
+ dispatchResetIsUpdated,
+ isLoading: isUpdating,
+ isUpdated,
+ updateBulkStatus,
+ } = useUpdateCases();
+ const [deleteThisCase, setDeleteThisCase] = useState({
+ title: '',
+ id: '',
+ });
+ const [deleteBulk, setDeleteBulk] = useState([]);
const refreshCases = useCallback(() => {
refetchCases(filterOptions, queryParams);
fetchCasesStatus();
+ setSelectedCases([]);
+ setDeleteBulk([]);
}, [filterOptions, queryParams]);
useEffect(() => {
@@ -124,11 +137,6 @@ export const AllCases = React.memo(() => {
dispatchResetIsUpdated();
}
}, [isDeleted, isUpdated]);
- const [deleteThisCase, setDeleteThisCase] = useState({
- title: '',
- id: '',
- });
- const [deleteBulk, setDeleteBulk] = useState([]);
const confirmDeleteModal = useMemo(
() => (
{
onCancel={handleToggleModal}
onConfirm={handleOnDeleteConfirm.bind(
null,
- deleteBulk.length > 0 ? deleteBulk : [deleteThisCase.id]
+ deleteBulk.length > 0 ? deleteBulk : [deleteThisCase]
)}
/>
),
@@ -150,10 +158,20 @@ export const AllCases = React.memo(() => {
setDeleteThisCase(deleteCase);
}, []);
- const toggleBulkDeleteModal = useCallback((deleteCases: string[]) => {
- handleToggleModal();
- setDeleteBulk(deleteCases);
- }, []);
+ const toggleBulkDeleteModal = useCallback(
+ (caseIds: string[]) => {
+ handleToggleModal();
+ if (caseIds.length === 1) {
+ const singleCase = selectedCases.find(theCase => theCase.id === caseIds[0]);
+ if (singleCase) {
+ return setDeleteThisCase({ id: singleCase.id, title: singleCase.title });
+ }
+ }
+ const convertToDeleteCases: DeleteCase[] = caseIds.map(id => ({ id }));
+ setDeleteBulk(convertToDeleteCases);
+ },
+ [selectedCases]
+ );
const handleUpdateCaseStatus = useCallback(
(status: string) => {
@@ -289,7 +307,7 @@ export const AllCases = React.memo(() => {
- {(isCasesLoading || isDeleting) && !isDataEmpty && (
+ {(isCasesLoading || isDeleting || isUpdating) && !isDataEmpty && (
)}
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx
index 1be0d6a3b5fcc..49f5f44cba271 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx
@@ -60,6 +60,6 @@ describe('CaseView actions', () => {
expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeTruthy();
wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click');
- expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([data.id]);
+ expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([{ id: data.id, title: data.title }]);
});
});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx
index 1d90470eab0e1..04b79967aa36e 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx
@@ -34,7 +34,7 @@ const CaseViewActionsComponent: React.FC = ({ caseData }) => {
isModalVisible={isDisplayConfirmDeleteModal}
isPlural={false}
onCancel={handleToggleModal}
- onConfirm={handleOnDeleteConfirm.bind(null, [caseData.id])}
+ onConfirm={handleOnDeleteConfirm.bind(null, [{ id: caseData.id, title: caseData.title }])}
/>
),
[isDisplayConfirmDeleteModal, caseData]
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
index a1f24275df6cd..18d5191fe6d33 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
@@ -57,8 +57,12 @@ const FormWrapper = styled.div`
margin-top 40px;
}
- padding-top: ${theme.eui.paddingSizes.l};
- padding-bottom: ${theme.eui.paddingSizes.l};
+ & > :first-child {
+ margin-top: 0;
+ }
+
+ padding-top: ${theme.eui.paddingSizes.xl};
+ padding-bottom: ${theme.eui.paddingSizes.xl};
`}
`;
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts
index d1f04a34b7bad..49caeae1c3a34 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts
@@ -155,7 +155,7 @@ export const WARNING_NO_CONNECTOR_MESSAGE = i18n.translate(
'xpack.siem.case.configureCases.warningMessage',
{
defaultMessage:
- 'Configuration seems to be invalid. The selected connector is missing. Did you delete the connector?',
+ 'The selected connector has been deleted. Either select a different connector or create a new one.',
}
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/translations.ts
index 0ca6bcff513fc..066145f7762c9 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/translations.ts
@@ -22,13 +22,13 @@ export const REQUIRED_UPDATE_TO_SERVICE = i18n.translate(
}
);
-export const COPY_LINK_COMMENT = i18n.translate('xpack.siem.case.caseView.copyCommentLinkAria', {
- defaultMessage: 'click to copy comment link',
+export const COPY_REFERENCE_LINK = i18n.translate('xpack.siem.case.caseView.copyCommentLinkAria', {
+ defaultMessage: 'Copy reference link',
});
export const MOVE_TO_ORIGINAL_COMMENT = i18n.translate(
'xpack.siem.case.caseView.moveToCommentAria',
{
- defaultMessage: 'click to highlight the reference comment',
+ defaultMessage: 'Highlight the referenced comment',
}
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx
index cc36e791e35b4..340e24e8fa55b 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx
@@ -154,6 +154,7 @@ export const UserActionItem = ({
labelQuoteAction={labelQuoteAction}
labelTitle={labelTitle ?? <>>}
linkId={linkId}
+ fullName={fullName}
username={username}
updatedAt={updatedAt}
onEdit={onEdit}
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx
index 94185cb4d130c..af1a1fdff26ce 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx
@@ -4,7 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, EuiText, EuiButtonIcon } from '@elastic/eui';
+import {
+ EuiLoadingSpinner,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiText,
+ EuiButtonIcon,
+ EuiToolTip,
+} from '@elastic/eui';
import { FormattedRelative } from '@kbn/i18n/react';
import copy from 'copy-to-clipboard';
import { isEmpty } from 'lodash/fp';
@@ -33,6 +40,7 @@ interface UserActionTitleProps {
labelQuoteAction?: string;
labelTitle: JSX.Element;
linkId?: string | null;
+ fullName?: string | null;
updatedAt?: string | null;
username: string;
onEdit?: (id: string) => void;
@@ -48,6 +56,7 @@ export const UserActionTitle = ({
labelQuoteAction,
labelTitle,
linkId,
+ fullName,
username,
updatedAt,
onEdit,
@@ -105,7 +114,9 @@ export const UserActionTitle = ({
- {username}
+ {fullName ?? username}}>
+ {username}
+
{labelTitle}
@@ -137,20 +148,24 @@ export const UserActionTitle = ({
{!isEmpty(linkId) && (
-
+ {i18n.MOVE_TO_ORIGINAL_COMMENT}}>
+
+
)}
-
+ {i18n.COPY_REFERENCE_LINK}}>
+
+
{propertyActions.length > 0 && (
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx
index 3109f2382c362..87a446c45d891 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx
@@ -13,6 +13,7 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
+ EuiToolTip,
} from '@elastic/eui';
import styled, { css } from 'styled-components';
import { ElasticUser } from '../../../../containers/case/types';
@@ -40,8 +41,8 @@ const MyFlexGroup = styled(EuiFlexGroup)`
const renderUsers = (
users: ElasticUser[],
handleSendEmail: (emailAddress: string | undefined | null) => void
-) => {
- return users.map(({ fullName, username, email }, key) => (
+) =>
+ users.map(({ fullName, username, email }, key) => (
@@ -49,11 +50,13 @@ const renderUsers = (
-
-
- {username}
-
-
+ {fullName ?? username}}>
+
+
+ {username}
+
+
+
@@ -63,11 +66,11 @@ const renderUsers = (
onClick={handleSendEmail.bind(null, email)}
iconType="email"
aria-label="email"
+ isDisabled={email == null}
/>
));
-};
export const UserList = React.memo(({ email, headline, loading, users }: UserListProps) => {
const handleSendEmail = useCallback(
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.test.tsx
new file mode 100644
index 0000000000000..8aaed08a0a0a1
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.test.tsx
@@ -0,0 +1,380 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import sinon from 'sinon';
+import moment from 'moment';
+
+import { sendSignalToTimelineAction, determineToAndFrom } from './actions';
+import {
+ mockEcsDataWithSignal,
+ defaultTimelineProps,
+ apolloClient,
+ mockTimelineApolloResult,
+} from '../../../../mock/';
+import { CreateTimeline, UpdateTimelineLoading } from './types';
+import { Ecs } from '../../../../graphql/types';
+
+jest.mock('apollo-client');
+
+describe('signals actions', () => {
+ const anchor = '2020-03-01T17:59:46.349Z';
+ const unix = moment(anchor).valueOf();
+ let createTimeline: CreateTimeline;
+ let updateTimelineIsLoading: UpdateTimelineLoading;
+ let clock: sinon.SinonFakeTimers;
+
+ beforeEach(() => {
+ // jest carries state between mocked implementations when using
+ // spyOn. So now we're doing all three of these.
+ // https://github.com/facebook/jest/issues/7136#issuecomment-565976599
+ jest.resetAllMocks();
+ jest.restoreAllMocks();
+ jest.clearAllMocks();
+
+ createTimeline = jest.fn() as jest.Mocked;
+ updateTimelineIsLoading = jest.fn() as jest.Mocked;
+
+ jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResult);
+
+ clock = sinon.useFakeTimers(unix);
+ });
+
+ afterEach(() => {
+ clock.restore();
+ });
+
+ describe('sendSignalToTimelineAction', () => {
+ describe('timeline id is NOT empty string and apollo client exists', () => {
+ test('it invokes updateTimelineIsLoading to set to true', async () => {
+ await sendSignalToTimelineAction({
+ apolloClient,
+ createTimeline,
+ ecsData: mockEcsDataWithSignal,
+ updateTimelineIsLoading,
+ });
+
+ expect(updateTimelineIsLoading).toHaveBeenCalledTimes(1);
+ expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: 'timeline-1', isLoading: true });
+ });
+
+ test('it invokes createTimeline with designated timeline template if "timelineTemplate" exists', async () => {
+ await sendSignalToTimelineAction({
+ apolloClient,
+ createTimeline,
+ ecsData: mockEcsDataWithSignal,
+ updateTimelineIsLoading,
+ });
+ const expected = {
+ from: 1541444305937,
+ timeline: {
+ columns: [
+ {
+ aggregatable: undefined,
+ category: undefined,
+ columnHeaderType: 'not-filtered',
+ description: undefined,
+ example: undefined,
+ id: '@timestamp',
+ placeholder: undefined,
+ type: undefined,
+ width: 190,
+ },
+ {
+ aggregatable: undefined,
+ category: undefined,
+ columnHeaderType: 'not-filtered',
+ description: undefined,
+ example: undefined,
+ id: 'message',
+ placeholder: undefined,
+ type: undefined,
+ width: 180,
+ },
+ {
+ aggregatable: undefined,
+ category: undefined,
+ columnHeaderType: 'not-filtered',
+ description: undefined,
+ example: undefined,
+ id: 'event.category',
+ placeholder: undefined,
+ type: undefined,
+ width: 180,
+ },
+ {
+ aggregatable: undefined,
+ category: undefined,
+ columnHeaderType: 'not-filtered',
+ description: undefined,
+ example: undefined,
+ id: 'host.name',
+ placeholder: undefined,
+ type: undefined,
+ width: 180,
+ },
+ {
+ aggregatable: undefined,
+ category: undefined,
+ columnHeaderType: 'not-filtered',
+ description: undefined,
+ example: undefined,
+ id: 'source.ip',
+ placeholder: undefined,
+ type: undefined,
+ width: 180,
+ },
+ {
+ aggregatable: undefined,
+ category: undefined,
+ columnHeaderType: 'not-filtered',
+ description: undefined,
+ example: undefined,
+ id: 'destination.ip',
+ placeholder: undefined,
+ type: undefined,
+ width: 180,
+ },
+ {
+ aggregatable: undefined,
+ category: undefined,
+ columnHeaderType: 'not-filtered',
+ description: undefined,
+ example: undefined,
+ id: 'user.name',
+ placeholder: undefined,
+ type: undefined,
+ width: 180,
+ },
+ ],
+ dataProviders: [],
+ dateRange: {
+ end: 1541444605937,
+ start: 1541444305937,
+ },
+ deletedEventIds: [],
+ description: 'This is a sample rule description',
+ eventIdToNoteIds: {},
+ eventType: 'all',
+ filters: [
+ {
+ $state: {
+ store: 'appState',
+ },
+ meta: {
+ key: 'host.name',
+ negate: false,
+ params: {
+ query: 'apache',
+ },
+ type: 'phrase',
+ },
+ query: {
+ match_phrase: {
+ 'host.name': 'apache',
+ },
+ },
+ },
+ ],
+ highlightedDropAndProviderId: '',
+ historyIds: [],
+ id: '',
+ isFavorite: false,
+ isLive: false,
+ isLoading: false,
+ isSaving: false,
+ isSelectAllChecked: false,
+ itemsPerPage: 25,
+ itemsPerPageOptions: [10, 25, 50, 100],
+ kqlMode: 'filter',
+ kqlQuery: {
+ filterQuery: {
+ kuery: {
+ expression: '',
+ kind: 'kuery',
+ },
+ serializedQuery: '',
+ },
+ filterQueryDraft: {
+ expression: '',
+ kind: 'kuery',
+ },
+ },
+ loadingEventIds: [],
+ noteIds: [],
+ pinnedEventIds: {},
+ pinnedEventsSaveObject: {},
+ savedObjectId: null,
+ selectedEventIds: {},
+ show: true,
+ showCheckboxes: false,
+ showRowRenderers: true,
+ sort: {
+ columnId: '@timestamp',
+ sortDirection: 'desc',
+ },
+ title: '',
+ version: null,
+ width: 1100,
+ },
+ to: 1541444605937,
+ ruleNote: '# this is some markdown documentation',
+ };
+
+ expect(createTimeline).toHaveBeenCalledWith(expected);
+ });
+
+ test('it invokes createTimeline with kqlQuery.filterQuery.kuery.kind as "kuery" if not specified in returned timeline template', async () => {
+ const mockTimelineApolloResultModified = {
+ ...mockTimelineApolloResult,
+ kqlQuery: {
+ filterQuery: {
+ kuery: {
+ expression: [''],
+ },
+ },
+ filterQueryDraft: {
+ expression: [''],
+ },
+ },
+ };
+ jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified);
+
+ await sendSignalToTimelineAction({
+ apolloClient,
+ createTimeline,
+ ecsData: mockEcsDataWithSignal,
+ updateTimelineIsLoading,
+ });
+ // @ts-ignore
+ const createTimelineArg = createTimeline.mock.calls[0][0];
+
+ expect(createTimeline).toHaveBeenCalledTimes(1);
+ expect(createTimelineArg.timeline.kqlQuery.filterQuery.kuery.kind).toEqual('kuery');
+ });
+
+ test('it invokes createTimeline with kqlQuery.filterQueryDraft.kuery.kind as "kuery" if not specified in returned timeline template', async () => {
+ const mockTimelineApolloResultModified = {
+ ...mockTimelineApolloResult,
+ kqlQuery: {
+ filterQuery: {
+ kuery: {
+ expression: [''],
+ },
+ },
+ filterQueryDraft: {
+ expression: [''],
+ },
+ },
+ };
+ jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified);
+
+ await sendSignalToTimelineAction({
+ apolloClient,
+ createTimeline,
+ ecsData: mockEcsDataWithSignal,
+ updateTimelineIsLoading,
+ });
+ // @ts-ignore
+ const createTimelineArg = createTimeline.mock.calls[0][0];
+
+ expect(createTimeline).toHaveBeenCalledTimes(1);
+ expect(createTimelineArg.timeline.kqlQuery.filterQueryDraft.kind).toEqual('kuery');
+ });
+
+ test('it invokes createTimeline with default timeline if apolloClient throws', async () => {
+ jest.spyOn(apolloClient, 'query').mockImplementation(() => {
+ throw new Error('Test error');
+ });
+
+ await sendSignalToTimelineAction({
+ apolloClient,
+ createTimeline,
+ ecsData: mockEcsDataWithSignal,
+ updateTimelineIsLoading,
+ });
+
+ expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: 'timeline-1', isLoading: true });
+ expect(updateTimelineIsLoading).toHaveBeenCalledWith({
+ id: 'timeline-1',
+ isLoading: false,
+ });
+ expect(createTimeline).toHaveBeenCalledTimes(1);
+ expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps);
+ });
+ });
+
+ describe('timelineId is empty string', () => {
+ test('it invokes createTimeline with timelineDefaults', async () => {
+ const ecsDataMock: Ecs = {
+ ...mockEcsDataWithSignal,
+ signal: {
+ rule: {
+ ...mockEcsDataWithSignal.signal?.rule!,
+ timeline_id: null,
+ },
+ },
+ };
+
+ await sendSignalToTimelineAction({
+ apolloClient,
+ createTimeline,
+ ecsData: ecsDataMock,
+ updateTimelineIsLoading,
+ });
+
+ expect(updateTimelineIsLoading).not.toHaveBeenCalled();
+ expect(createTimeline).toHaveBeenCalledTimes(1);
+ expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps);
+ });
+ });
+
+ describe('apolloClient is not defined', () => {
+ test('it invokes createTimeline with timelineDefaults', async () => {
+ const ecsDataMock: Ecs = {
+ ...mockEcsDataWithSignal,
+ signal: {
+ rule: {
+ ...mockEcsDataWithSignal.signal?.rule!,
+ timeline_id: [''],
+ },
+ },
+ };
+
+ await sendSignalToTimelineAction({
+ createTimeline,
+ ecsData: ecsDataMock,
+ updateTimelineIsLoading,
+ });
+
+ expect(updateTimelineIsLoading).not.toHaveBeenCalled();
+ expect(createTimeline).toHaveBeenCalledTimes(1);
+ expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps);
+ });
+ });
+ });
+
+ describe('determineToAndFrom', () => {
+ test('it uses ecs.Data.timestamp if one is provided', () => {
+ const ecsDataMock: Ecs = {
+ ...mockEcsDataWithSignal,
+ timestamp: '2020-03-20T17:59:46.349Z',
+ };
+ const result = determineToAndFrom({ ecsData: ecsDataMock });
+
+ expect(result.from).toEqual(1584726886349);
+ expect(result.to).toEqual(1584727186349);
+ });
+
+ test('it uses current time timestamp if ecsData.timestamp is not provided', () => {
+ const { timestamp, ...ecsDataMock } = {
+ ...mockEcsDataWithSignal,
+ };
+ const result = determineToAndFrom({ ecsData: ecsDataMock });
+
+ expect(result.from).toEqual(1583085286349);
+ expect(result.to).toEqual(1583085586349);
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx
index b23b051e8b2e8..c71ede32d8403 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx
@@ -10,7 +10,7 @@ import moment from 'moment';
import { updateSignalStatus } from '../../../../containers/detection_engine/signals/api';
import { SendSignalToTimelineActionProps, UpdateSignalStatusActionProps } from './types';
-import { TimelineNonEcsData, GetOneTimeline, TimelineResult } from '../../../../graphql/types';
+import { TimelineNonEcsData, GetOneTimeline, TimelineResult, Ecs } from '../../../../graphql/types';
import { oneTimelineQuery } from '../../../../containers/timeline/one/index.gql_query';
import {
omitTypenameInTimeline,
@@ -72,16 +72,7 @@ export const updateSignalStatusAction = async ({
}
};
-export const sendSignalToTimelineAction = async ({
- apolloClient,
- createTimeline,
- ecsData,
- updateTimelineIsLoading,
-}: SendSignalToTimelineActionProps) => {
- let openSignalInBasicTimeline = true;
- const timelineId =
- ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : '';
-
+export const determineToAndFrom = ({ ecsData }: { ecsData: Ecs }) => {
const ellapsedTimeRule = moment.duration(
moment().diff(
dateMath.parse(ecsData.signal?.rule?.from != null ? ecsData.signal?.rule?.from[0] : 'now-0s')
@@ -93,6 +84,21 @@ export const sendSignalToTimelineAction = async ({
.valueOf();
const to = moment(ecsData.timestamp ?? new Date()).valueOf();
+ return { to, from };
+};
+
+export const sendSignalToTimelineAction = async ({
+ apolloClient,
+ createTimeline,
+ ecsData,
+ updateTimelineIsLoading,
+}: SendSignalToTimelineActionProps) => {
+ let openSignalInBasicTimeline = true;
+ const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : '';
+ const timelineId =
+ ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : '';
+ const { to, from } = determineToAndFrom({ ecsData });
+
if (timelineId !== '' && apolloClient != null) {
try {
updateTimelineIsLoading({ id: 'timeline-1', isLoading: true });
@@ -106,10 +112,10 @@ export const sendSignalToTimelineAction = async ({
id: timelineId,
},
});
- const timelineTemplate: TimelineResult = omitTypenameInTimeline(
- getOr({}, 'data.getOneTimeline', responseTimeline)
- );
- if (!isEmpty(timelineTemplate)) {
+ const resultingTimeline: TimelineResult = getOr({}, 'data.getOneTimeline', responseTimeline);
+
+ if (!isEmpty(resultingTimeline)) {
+ const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline);
openSignalInBasicTimeline = false;
const { timeline } = formatTimelineResultToModel(timelineTemplate, true);
const query = replaceTemplateFieldFromQuery(
@@ -148,6 +154,7 @@ export const sendSignalToTimelineAction = async ({
show: true,
},
to,
+ ruleNote: noteContent,
});
}
} catch {
@@ -197,6 +204,7 @@ export const sendSignalToTimelineAction = async ({
},
},
to,
+ ruleNote: noteContent,
});
}
};
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.test.tsx
new file mode 100644
index 0000000000000..6212cad7e1845
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.test.tsx
@@ -0,0 +1,193 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import { mount, ReactWrapper } from 'enzyme';
+import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+
+import { Filter } from '../../../../../../../../../src/plugins/data/common/es_query';
+import { TimelineAction } from '../../../../components/timeline/body/actions';
+import { buildSignalsRuleIdFilter, getSignalsActions } from './default_config';
+import {
+ CreateTimeline,
+ SetEventsDeletedProps,
+ SetEventsLoadingProps,
+ UpdateTimelineLoading,
+} from './types';
+import { mockEcsDataWithSignal } from '../../../../mock/mock_ecs';
+import { sendSignalToTimelineAction, updateSignalStatusAction } from './actions';
+import * as i18n from './translations';
+
+jest.mock('./actions');
+
+describe('signals default_config', () => {
+ describe('buildSignalsRuleIdFilter', () => {
+ test('given a rule id this will return an array with a single filter', () => {
+ const filters: Filter[] = buildSignalsRuleIdFilter('rule-id-1');
+ const expectedFilter: Filter = {
+ meta: {
+ alias: null,
+ negate: false,
+ disabled: false,
+ type: 'phrase',
+ key: 'signal.rule.id',
+ params: {
+ query: 'rule-id-1',
+ },
+ },
+ query: {
+ match_phrase: {
+ 'signal.rule.id': 'rule-id-1',
+ },
+ },
+ };
+ expect(filters).toHaveLength(1);
+ expect(filters[0]).toEqual(expectedFilter);
+ });
+ });
+
+ describe('getSignalsActions', () => {
+ let setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void;
+ let setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void;
+ let createTimeline: CreateTimeline;
+ let updateTimelineIsLoading: UpdateTimelineLoading;
+
+ beforeEach(() => {
+ setEventsLoading = jest.fn();
+ setEventsDeleted = jest.fn();
+ createTimeline = jest.fn();
+ updateTimelineIsLoading = jest.fn();
+ });
+
+ describe('timeline tooltip', () => {
+ test('it invokes sendSignalToTimelineAction when button clicked', () => {
+ const signalsActions = getSignalsActions({
+ canUserCRUD: true,
+ hasIndexWrite: true,
+ setEventsLoading,
+ setEventsDeleted,
+ createTimeline,
+ status: 'open',
+ updateTimelineIsLoading,
+ });
+ const timelineAction = signalsActions[0].getAction({
+ eventId: 'even-id',
+ ecsData: mockEcsDataWithSignal,
+ });
+ const wrapper = mount(timelineAction as React.ReactElement);
+ wrapper.find(EuiButtonIcon).simulate('click');
+
+ expect(sendSignalToTimelineAction).toHaveBeenCalled();
+ });
+ });
+
+ describe('signal open action', () => {
+ let signalsActions: TimelineAction[];
+ let signalOpenAction: JSX.Element;
+ let wrapper: ReactWrapper;
+
+ beforeEach(() => {
+ signalsActions = getSignalsActions({
+ canUserCRUD: true,
+ hasIndexWrite: true,
+ setEventsLoading,
+ setEventsDeleted,
+ createTimeline,
+ status: 'open',
+ updateTimelineIsLoading,
+ });
+
+ signalOpenAction = signalsActions[1].getAction({
+ eventId: 'event-id',
+ ecsData: mockEcsDataWithSignal,
+ });
+
+ wrapper = mount(signalOpenAction as React.ReactElement);
+ });
+
+ afterEach(() => {
+ wrapper.unmount();
+ });
+
+ test('it invokes updateSignalStatusAction when button clicked', () => {
+ wrapper.find(EuiButtonIcon).simulate('click');
+
+ expect(updateSignalStatusAction).toHaveBeenCalledWith({
+ signalIds: ['event-id'],
+ status: 'open',
+ setEventsLoading,
+ setEventsDeleted,
+ });
+ });
+
+ test('it displays expected text on hover', () => {
+ const openSignal = wrapper.find(EuiToolTip);
+ openSignal.simulate('mouseOver');
+ const tooltip = wrapper.find('.euiToolTipPopover').text();
+
+ expect(tooltip).toEqual(i18n.ACTION_OPEN_SIGNAL);
+ });
+
+ test('it displays expected icon', () => {
+ const icon = wrapper.find(EuiButtonIcon).props().iconType;
+
+ expect(icon).toEqual('securitySignalDetected');
+ });
+ });
+
+ describe('signal close action', () => {
+ let signalsActions: TimelineAction[];
+ let signalCloseAction: JSX.Element;
+ let wrapper: ReactWrapper;
+
+ beforeEach(() => {
+ signalsActions = getSignalsActions({
+ canUserCRUD: true,
+ hasIndexWrite: true,
+ setEventsLoading,
+ setEventsDeleted,
+ createTimeline,
+ status: 'closed',
+ updateTimelineIsLoading,
+ });
+
+ signalCloseAction = signalsActions[1].getAction({
+ eventId: 'event-id',
+ ecsData: mockEcsDataWithSignal,
+ });
+
+ wrapper = mount(signalCloseAction as React.ReactElement);
+ });
+
+ afterEach(() => {
+ wrapper.unmount();
+ });
+
+ test('it invokes updateSignalStatusAction when status button clicked', () => {
+ wrapper.find(EuiButtonIcon).simulate('click');
+
+ expect(updateSignalStatusAction).toHaveBeenCalledWith({
+ signalIds: ['event-id'],
+ status: 'closed',
+ setEventsLoading,
+ setEventsDeleted,
+ });
+ });
+
+ test('it displays expected text on hover', () => {
+ const closeSignal = wrapper.find(EuiToolTip);
+ closeSignal.simulate('mouseOver');
+ const tooltip = wrapper.find('.euiToolTipPopover').text();
+ expect(tooltip).toEqual(i18n.ACTION_CLOSE_SIGNAL);
+ });
+
+ test('it displays expected icon', () => {
+ const icon = wrapper.find(EuiButtonIcon).props().iconType;
+
+ expect(icon).toEqual('securitySignalResolved');
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx
index 44c48b1879e89..fd3b9a6f68e82 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx
@@ -23,7 +23,12 @@ import { timelineDefaults } from '../../../../store/timeline/defaults';
import { FILTER_OPEN } from './signals_filter_group';
import { sendSignalToTimelineAction, updateSignalStatusAction } from './actions';
import * as i18n from './translations';
-import { CreateTimeline, SetEventsDeletedProps, SetEventsLoadingProps } from './types';
+import {
+ CreateTimeline,
+ SetEventsDeletedProps,
+ SetEventsLoadingProps,
+ UpdateTimelineLoading,
+} from './types';
export const signalsOpenFilters: Filter[] = [
{
@@ -198,13 +203,13 @@ export const getSignalsActions = ({
setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void;
createTimeline: CreateTimeline;
status: 'open' | 'closed';
- updateTimelineIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) => void;
+ updateTimelineIsLoading: UpdateTimelineLoading;
}): TimelineAction[] => [
{
getAction: ({ ecsData }: TimelineActionProps): JSX.Element => (
{
let localValueToChange = valueToChange;
- if (keuryNode.function === 'is' && templateFields.includes(keuryNode.arguments[0].value)) {
+ if (kueryNode.function === 'is' && templateFields.includes(kueryNode.arguments[0].value)) {
localValueToChange = [
...localValueToChange,
{
- field: keuryNode.arguments[0].value,
- valueToChange: keuryNode.arguments[1].value,
+ field: kueryNode.arguments[0].value,
+ valueToChange: kueryNode.arguments[1].value,
},
];
}
- return keuryNode.arguments.reduce(
+ return kueryNode.arguments.reduce(
(addValueToChange: FindValueToChangeInQuery[], ast: KueryNode) => {
if (ast.function === 'is' && templateFields.includes(ast.arguments[0].value)) {
return [
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx
index afd325f539966..6cdb2f326901e 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx
@@ -114,7 +114,7 @@ const SignalsTableComponent: React.FC = ({
// Callback for creating a new timeline -- utilized by row/batch actions
const createTimelineCallback = useCallback(
- ({ from: fromTimeline, timeline, to: toTimeline }: CreateTimelineProps) => {
+ ({ from: fromTimeline, timeline, to: toTimeline, ruleNote }: CreateTimelineProps) => {
updateTimelineIsLoading({ id: 'timeline-1', isLoading: false });
updateTimeline({
duplicate: true,
@@ -126,6 +126,7 @@ const SignalsTableComponent: React.FC = ({
show: true,
},
to: toTimeline,
+ ruleNote,
})();
},
[updateTimeline, updateTimelineIsLoading]
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts
index c2807db179780..f68dcd932bc32 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts
@@ -95,9 +95,9 @@ export const ACTION_CLOSE_SIGNAL = i18n.translate(
}
);
-export const ACTION_VIEW_IN_TIMELINE = i18n.translate(
- 'xpack.siem.detectionEngine.signals.actions.viewInTimelineTitle',
+export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate(
+ 'xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle',
{
- defaultMessage: 'View in timeline',
+ defaultMessage: 'Investigate in timeline',
}
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts
index b3e7ed75cfb99..909b217646746 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts
@@ -45,13 +45,16 @@ export interface SendSignalToTimelineActionProps {
apolloClient?: ApolloClient<{}>;
createTimeline: CreateTimeline;
ecsData: Ecs;
- updateTimelineIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) => void;
+ updateTimelineIsLoading: UpdateTimelineLoading;
}
+export type UpdateTimelineLoading = ({ id, isLoading }: { id: string; isLoading: boolean }) => void;
+
export interface CreateTimelineProps {
from: number;
timeline: TimelineModel;
to: number;
+ ruleNote?: string;
}
export type CreateTimeline = ({ from, timeline, to }: CreateTimelineProps) => void;
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/__snapshots__/index.test.tsx.snap
index 9a534297e5e29..31abea53462fa 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/__snapshots__/index.test.tsx.snap
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/__snapshots__/index.test.tsx.snap
@@ -145,7 +145,7 @@ exports[`description_step StepRuleDescriptionComponent renders correctly against
# this is some markdown documentation
,
- "title": "Investigation notes",
+ "title": "Investigation guide",
},
]
}
@@ -287,7 +287,7 @@ exports[`description_step StepRuleDescriptionComponent renders correctly against
# this is some markdown documentation
,
- "title": "Investigation notes",
+ "title": "Investigation guide",
},
]
}
@@ -430,7 +430,7 @@ exports[`description_step StepRuleDescriptionComponent renders correctly against
# this is some markdown documentation
,
- "title": "Investigation notes",
+ "title": "Investigation guide",
},
]
}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.test.tsx
index a01aec0ccf2cf..8e8927cb7bbd1 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.test.tsx
@@ -461,12 +461,12 @@ describe('description_step', () => {
test('returns default "note" description', () => {
const result: ListItems[] = getDescriptionItem(
'note',
- 'Investigation notes',
+ 'Investigation guide',
mockAboutStep,
mockFilterManager
);
- expect(result[0].title).toEqual('Investigation notes');
+ expect(result[0].title).toEqual('Investigation guide');
expect(React.isValidElement(result[0].description)).toBeTruthy();
});
});
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx
index 8cb38b9dc7393..7c088c068c9b2 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx
@@ -178,12 +178,12 @@ export const schema: FormSchema = {
},
note: {
type: FIELD_TYPES.TEXTAREA,
- label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.noteLabel', {
- defaultMessage: 'Investigation notes',
+ label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.guideLabel', {
+ defaultMessage: 'Investigation guide',
}),
- helpText: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.noteHelpText', {
+ helpText: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.guideHelpText', {
defaultMessage:
- 'Provide helpful information for analysts that are performing a signal investigation. These notes will appear on both the rule details page and in timelines created from signals generated by this rule.',
+ 'Provide helpful information for analysts that are performing a signal investigation. This guide will appear on both the rule details page and in timelines created from signals generated by this rule.',
}),
labelAppend: OptionalFieldLabel,
},
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts
index dfa60268e903a..0b1e712c663f3 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts
@@ -72,6 +72,6 @@ export const URL_FORMAT_INVALID = i18n.translate(
export const ADD_RULE_NOTE_HELP_TEXT = i18n.translate(
'xpack.siem.detectionEngine.createRule.stepAboutrule.noteHelpText',
{
- defaultMessage: 'Add rule investigation notes...',
+ defaultMessage: 'Add rule investigation guide...',
}
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule_details/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule_details/index.test.tsx
index bbd037af10c3f..76a3c590a62a6 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule_details/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule_details/index.test.tsx
@@ -136,7 +136,7 @@ describe('StepAboutRuleToggleDetails', () => {
expect(wrapper.find('EuiButtonGroup[idSelected="notes"]').exists()).toBeFalsy();
wrapper
- .find('input[title="Investigation notes"]')
+ .find('input[title="Investigation guide"]')
.at(0)
.simulate('change', { target: { value: 'notes' } });
@@ -159,7 +159,7 @@ describe('StepAboutRuleToggleDetails', () => {
);
wrapper
- .find('input[title="Investigation notes"]')
+ .find('input[title="Investigation guide"]')
.at(0)
.simulate('change', { target: { value: 'notes' } });
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule_details/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule_details/translations.ts
index fa725366210de..79c5eb12d4663 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule_details/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule_details/translations.ts
@@ -20,8 +20,8 @@ export const ABOUT_TEXT = i18n.translate(
);
export const ABOUT_PANEL_NOTES_TAB = i18n.translate(
- 'xpack.siem.detectionEngine.details.stepAboutRule.investigationNotesLabel',
+ 'xpack.siem.detectionEngine.details.stepAboutRule.investigationGuideLabel',
{
- defaultMessage: 'Investigation notes',
+ defaultMessage: 'Investigation guide',
}
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx
new file mode 100644
index 0000000000000..62399891c9606
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { TimelinesPageComponent } from './timelines_page';
+import { useKibana } from '../../lib/kibana';
+import { shallow, ShallowWrapper } from 'enzyme';
+import React from 'react';
+import ApolloClient from 'apollo-client';
+
+jest.mock('../../lib/kibana', () => {
+ return {
+ useKibana: jest.fn(),
+ };
+});
+describe('TimelinesPageComponent', () => {
+ const mockAppollloClient = {} as ApolloClient;
+ let wrapper: ShallowWrapper;
+
+ describe('If the user is authorised', () => {
+ beforeAll(() => {
+ ((useKibana as unknown) as jest.Mock).mockReturnValue({
+ services: {
+ application: {
+ capabilities: {
+ siem: {
+ crud: true,
+ },
+ },
+ },
+ },
+ });
+ wrapper = shallow( );
+ });
+
+ afterAll(() => {
+ ((useKibana as unknown) as jest.Mock).mockReset();
+ });
+
+ test('should not show the import timeline modal by default', () => {
+ expect(
+ wrapper.find('[data-test-subj="stateful-open-timeline"]').prop('importDataModalToggle')
+ ).toEqual(false);
+ });
+
+ test('should show the import timeline button', () => {
+ expect(wrapper.find('[data-test-subj="open-import-data-modal-btn"]').exists()).toEqual(true);
+ });
+
+ test('should show the import timeline modal after user clicking on the button', () => {
+ wrapper.find('[data-test-subj="open-import-data-modal-btn"]').simulate('click');
+ expect(
+ wrapper.find('[data-test-subj="stateful-open-timeline"]').prop('importDataModalToggle')
+ ).toEqual(true);
+ });
+ });
+
+ describe('If the user is not authorised', () => {
+ beforeAll(() => {
+ ((useKibana as unknown) as jest.Mock).mockReturnValue({
+ services: {
+ application: {
+ capabilities: {
+ siem: {
+ crud: false,
+ },
+ },
+ },
+ },
+ });
+ wrapper = shallow( );
+ });
+
+ afterAll(() => {
+ ((useKibana as unknown) as jest.Mock).mockReset();
+ });
+ test('should not show the import timeline modal by default', () => {
+ expect(
+ wrapper.find('[data-test-subj="stateful-open-timeline"]').prop('importDataModalToggle')
+ ).toEqual(false);
+ });
+
+ test('should not show the import timeline button', () => {
+ expect(wrapper.find('[data-test-subj="open-import-data-modal-btn"]').exists()).toEqual(false);
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx
index 75bef7a04a4c9..73070d2b94aac 100644
--- a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx
@@ -28,7 +28,7 @@ type OwnProps = TimelinesProps;
export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
-const TimelinesPageComponent: React.FC = ({ apolloClient }) => {
+export const TimelinesPageComponent: React.FC = ({ apolloClient }) => {
const [importDataModalToggle, setImportDataModalToggle] = useState(false);
const onImportTimelineBtnClick = useCallback(() => {
setImportDataModalToggle(true);
@@ -43,7 +43,11 @@ const TimelinesPageComponent: React.FC = ({ apolloClient }) => {
{capabilitiesCanUserCRUD && (
-
+
{i18n.ALL_TIMELINES_IMPORT_TIMELINE_TITLE}
)}
@@ -57,6 +61,7 @@ const TimelinesPageComponent: React.FC = ({ apolloClient }) => {
importDataModalToggle={importDataModalToggle && capabilitiesCanUserCRUD}
setImportDataModalToggle={setImportDataModalToggle}
title={i18n.ALL_TIMELINES_PANEL_TITLE}
+ data-test-subj="stateful-open-timeline"
/>
diff --git a/x-pack/legacy/plugins/siem/scripts/optimize_tsconfig/tsconfig.json b/x-pack/legacy/plugins/siem/scripts/optimize_tsconfig/tsconfig.json
index bec6988bdebd9..c4705c8b8c16a 100644
--- a/x-pack/legacy/plugins/siem/scripts/optimize_tsconfig/tsconfig.json
+++ b/x-pack/legacy/plugins/siem/scripts/optimize_tsconfig/tsconfig.json
@@ -4,7 +4,8 @@
"plugins/siem/**/*",
"legacy/plugins/siem/**/*",
"plugins/apm/typings/numeral.d.ts",
- "legacy/plugins/canvas/types/webpack.d.ts"
+ "legacy/plugins/canvas/types/webpack.d.ts",
+ "plugins/triggers_actions_ui/**/*"
],
"exclude": [
"test/**/*",
diff --git a/x-pack/legacy/plugins/siem/server/graphql/ecs/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/ecs/schema.gql.ts
index f897236b3470e..9bf55cfe1ed2a 100644
--- a/x-pack/legacy/plugins/siem/server/graphql/ecs/schema.gql.ts
+++ b/x-pack/legacy/plugins/siem/server/graphql/ecs/schema.gql.ts
@@ -410,6 +410,7 @@ export const ecsSchema = gql`
created_by: ToStringArray
updated_by: ToStringArray
version: ToStringArray
+ note: ToStringArray
}
type SignalField {
diff --git a/x-pack/legacy/plugins/siem/server/graphql/types.ts b/x-pack/legacy/plugins/siem/server/graphql/types.ts
index e2b365f8bfa5b..d272b7ff59b79 100644
--- a/x-pack/legacy/plugins/siem/server/graphql/types.ts
+++ b/x-pack/legacy/plugins/siem/server/graphql/types.ts
@@ -1014,6 +1014,8 @@ export interface RuleField {
updated_by?: Maybe;
version?: Maybe;
+
+ note?: Maybe;
}
export interface SuricataEcsFields {
@@ -4822,6 +4824,8 @@ export namespace RuleFieldResolvers {
updated_by?: UpdatedByResolver, TypeParent, TContext>;
version?: VersionResolver, TypeParent, TContext>;
+
+ note?: NoteResolver, TypeParent, TContext>;
}
export type IdResolver<
@@ -4974,6 +4978,11 @@ export namespace RuleFieldResolvers {
Parent = RuleField,
TContext = SiemContext
> = Resolver;
+ export type NoteResolver<
+ R = Maybe,
+ Parent = RuleField,
+ TContext = SiemContext
+ > = Resolver;
}
export namespace SuricataEcsFieldsResolvers {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts
index 36764439462c3..3195483013c19 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts
@@ -30,9 +30,13 @@ export const createIndexRoute = (router: IRouter) => {
try {
const clusterClient = context.core.elasticsearch.dataClient;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
const callCluster = clusterClient.callAsCurrentUser;
+ if (!siemClient) {
+ return siemResponse.error({ statusCode: 404 });
+ }
+
const index = siemClient.signalsIndex;
const indexExists = await getIndexExists(callCluster, index);
if (indexExists) {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts
index aa418c11d9d16..c667e7ae9c463 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts
@@ -38,7 +38,11 @@ export const deleteIndexRoute = (router: IRouter) => {
try {
const clusterClient = context.core.elasticsearch.dataClient;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
+
+ if (!siemClient) {
+ return siemResponse.error({ statusCode: 404 });
+ }
const callCluster = clusterClient.callAsCurrentUser;
const index = siemClient.signalsIndex;
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts
index 4fc5a4e1f347f..047176f155611 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts
@@ -23,7 +23,11 @@ export const readIndexRoute = (router: IRouter) => {
try {
const clusterClient = context.core.elasticsearch.dataClient;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
+
+ if (!siemClient) {
+ return siemResponse.error({ statusCode: 404 });
+ }
const index = siemClient.signalsIndex;
const indexExists = await getIndexExists(clusterClient.callAsCurrentUser, index);
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts
index aa4f6150889f9..3209f5ce9f519 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts
@@ -62,6 +62,13 @@ describe('read_privileges route', () => {
expect(response.status).toEqual(500);
expect(response.body).toEqual({ message: 'Test error', status_code: 500 });
});
+
+ it('returns 404 if siem client is unavailable', async () => {
+ const { siem, ...contextWithoutSiem } = context;
+ const response = await server.inject(getPrivilegeRequest(), contextWithoutSiem);
+ expect(response.status).toEqual(404);
+ expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
+ });
});
describe('when security plugin is disabled', () => {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts
index 2f5ea4d1ec767..d86880de65386 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts
@@ -27,9 +27,14 @@ export const readPrivilegesRoute = (
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
+
try {
const clusterClient = context.core.elasticsearch.dataClient;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
+
+ if (!siemClient) {
+ return siemResponse.error({ statusCode: 404 });
+ }
const index = siemClient.signalsIndex;
const clusterPrivileges = await readPrivileges(clusterClient.callAsCurrentUser, index);
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts
index f53efc8a3234d..f0b975379388f 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts
@@ -63,7 +63,7 @@ describe('add_prepackaged_rules_route', () => {
addPrepackedRulesRoute(server.router);
});
- describe('status codes with actionClient and alertClient', () => {
+ describe('status codes', () => {
test('returns 200 when creating with a valid actionClient and alertClient', async () => {
const request = addPrepackagedRulesRequest();
const response = await server.inject(request, context);
@@ -96,6 +96,13 @@ describe('add_prepackaged_rules_route', () => {
),
});
});
+
+ it('returns 404 if siem client is unavailable', async () => {
+ const { siem, ...contextWithoutSiem } = context;
+ const response = await server.inject(addPrepackagedRulesRequest(), contextWithoutSiem);
+ expect(response.status).toEqual(404);
+ expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
+ });
});
describe('responses', () => {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts
index 4e08188af0d12..3eba04debb21f 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts
@@ -33,16 +33,13 @@ export const addPrepackedRulesRoute = (router: IRouter) => {
const siemResponse = buildSiemResponse(response);
try {
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
const clusterClient = context.core.elasticsearch.dataClient;
const savedObjectsClient = context.core.savedObjects.client;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
- if (!actionsClient || !alertsClient) {
+ if (!siemClient || !actionsClient || !alertsClient) {
return siemResponse.error({ statusCode: 404 });
}
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts
index 32b8eca298229..e6facf6f3b7a8 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts
@@ -42,7 +42,7 @@ describe('create_rules_bulk', () => {
createRulesBulkRoute(server.router);
});
- describe('status codes with actionClient and alertClient', () => {
+ describe('status codes', () => {
test('returns 200 when creating a single rule with a valid actionClient and alertClient', async () => {
const response = await server.inject(getReadBulkRequest(), context);
expect(response.status).toEqual(200);
@@ -54,6 +54,13 @@ describe('create_rules_bulk', () => {
expect(response.status).toEqual(404);
expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
});
+
+ it('returns 404 if siem client is unavailable', async () => {
+ const { siem, ...contextWithoutSiem } = context;
+ const response = await server.inject(getReadBulkRequest(), contextWithoutSiem);
+ expect(response.status).toEqual(404);
+ expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
+ });
});
describe('unhappy paths', () => {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts
index 1ca9f7ef9075e..daeb11e88508b 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts
@@ -37,15 +37,12 @@ export const createRulesBulkRoute = (router: IRouter) => {
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
const clusterClient = context.core.elasticsearch.dataClient;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
- if (!actionsClient || !alertsClient) {
+ if (!siemClient || !actionsClient || !alertsClient) {
return siemResponse.error({ statusCode: 404 });
}
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts
index 4da879d12f809..a77911bbb35e8 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts
@@ -60,6 +60,13 @@ describe('create_rules', () => {
expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
});
+ it('returns 404 if siem client is unavailable', async () => {
+ const { siem, ...contextWithoutSiem } = context;
+ const response = await server.inject(getCreateRequest(), contextWithoutSiem);
+ expect(response.status).toEqual(404);
+ expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
+ });
+
it('returns 200 if license is not platinum', async () => {
(context.licensing.license.hasAtLeast as jest.Mock).mockReturnValue(false);
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts
index edf37bcb8dbe7..f68f204c12730 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts
@@ -72,16 +72,13 @@ export const createRulesRoute = (router: IRouter): void => {
try {
validateLicenseForRuleType({ license: context.licensing.license, ruleType: type });
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
const clusterClient = context.core.elasticsearch.dataClient;
const savedObjectsClient = context.core.savedObjects.client;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
- if (!actionsClient || !alertsClient) {
+ if (!siemClient || !actionsClient || !alertsClient) {
return siemResponse.error({ statusCode: 404 });
}
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts
index 85cfeefdceead..33ffc245e7668 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts
@@ -35,11 +35,8 @@ export const deleteRulesBulkRoute = (router: IRouter) => {
const handler: Handler = async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
const savedObjectsClient = context.core.savedObjects.client;
if (!actionsClient || !alertsClient) {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts
index 6fd50abd9364a..a4e659da76bb2 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts
@@ -34,12 +34,9 @@ export const deleteRulesRoute = (router: IRouter) => {
try {
const { id, rule_id: ruleId } = request.query;
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
const savedObjectsClient = context.core.savedObjects.client;
if (!actionsClient || !alertsClient) {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts
index c434f42780e47..50eafe163c265 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts
@@ -28,10 +28,7 @@ export const exportRulesRoute = (router: IRouter, config: LegacyServices['config
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
- if (!context.alerting) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
if (!alertsClient) {
return siemResponse.error({ statusCode: 404 });
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts
index 961859417ef1b..77351d2e0751b 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts
@@ -32,10 +32,7 @@ export const findRulesRoute = (router: IRouter) => {
try {
const { query } = request;
- if (!context.alerting) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
const savedObjectsClient = context.core.savedObjects.client;
if (!alertsClient) {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts
index 4f4ae7c2c1fa6..6fee4d71a904e 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts
@@ -35,10 +35,7 @@ export const findRulesStatusesRoute = (router: IRouter) => {
async (context, request, response) => {
const { query } = request;
const siemResponse = buildSiemResponse(response);
- if (!context.alerting) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
const savedObjectsClient = context.core.savedObjects.client;
if (!alertsClient) {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts
index 7e16b4495593e..7f0bf4bf81179 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts
@@ -29,10 +29,7 @@ export const getPrepackagedRulesStatusRoute = (router: IRouter) => {
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
- if (!context.alerting) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
if (!alertsClient) {
return siemResponse.error({ statusCode: 404 });
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.test.ts
index aacf83b9ec58a..61f5e6faf1bdb 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.test.ts
@@ -101,6 +101,13 @@ describe('import_rules_route', () => {
expect(response.status).toEqual(404);
expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
});
+
+ it('returns 404 if siem client is unavailable', async () => {
+ const { siem, ...contextWithoutSiem } = context;
+ const response = await server.inject(request, contextWithoutSiem);
+ expect(response.status).toEqual(404);
+ expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
+ });
});
describe('unhappy paths', () => {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts
index 2e6c72a87ec7f..d9fc89740c9ef 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts
@@ -57,30 +57,27 @@ export const importRulesRoute = (router: IRouter, config: LegacyServices['config
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
- const clusterClient = context.core.elasticsearch.dataClient;
- const savedObjectsClient = context.core.savedObjects.client;
- const siemClient = context.siem.getSiemClient();
+ try {
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
+ const clusterClient = context.core.elasticsearch.dataClient;
+ const savedObjectsClient = context.core.savedObjects.client;
+ const siemClient = context.siem?.getSiemClient();
- if (!actionsClient || !alertsClient) {
- return siemResponse.error({ statusCode: 404 });
- }
+ if (!siemClient || !actionsClient || !alertsClient) {
+ return siemResponse.error({ statusCode: 404 });
+ }
- const { filename } = request.body.file.hapi;
- const fileExtension = extname(filename).toLowerCase();
- if (fileExtension !== '.ndjson') {
- return siemResponse.error({
- statusCode: 400,
- body: `Invalid file extension ${fileExtension}`,
- });
- }
+ const { filename } = request.body.file.hapi;
+ const fileExtension = extname(filename).toLowerCase();
+ if (fileExtension !== '.ndjson') {
+ return siemResponse.error({
+ statusCode: 400,
+ body: `Invalid file extension ${fileExtension}`,
+ });
+ }
- const objectLimit = config().get('savedObjects.maxImportExportSize');
- try {
+ const objectLimit = config().get('savedObjects.maxImportExportSize');
const readStream = createRulesStreamFromNdJson(objectLimit);
const parsedObjects = await createPromiseFromStreams([
request.body.file,
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts
index 645dbdadf8cab..b19039321a6d8 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts
@@ -37,11 +37,8 @@ export const patchRulesBulkRoute = (router: IRouter) => {
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
const savedObjectsClient = context.core.savedObjects.client;
if (!actionsClient || !alertsClient) {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts
index 620bcd8fc17b0..fab53079361ad 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts
@@ -74,12 +74,8 @@ export const patchRulesRoute = (router: IRouter) => {
validateLicenseForRuleType({ license: context.licensing.license, ruleType: type });
}
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
-
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
const savedObjectsClient = context.core.savedObjects.client;
if (!actionsClient || !alertsClient) {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts
index e4117166ed4fa..bc52445feee76 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts
@@ -32,10 +32,7 @@ export const readRulesRoute = (router: IRouter) => {
const { id, rule_id: ruleId } = request.query;
const siemResponse = buildSiemResponse(response);
- if (!context.alerting) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
const savedObjectsClient = context.core.savedObjects.client;
try {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts
index 611b38ccbae8b..332a47d0c0fc2 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts
@@ -69,6 +69,13 @@ describe('update_rules_bulk', () => {
expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
});
+ it('returns 404 if siem client is unavailable', async () => {
+ const { siem, ...contextWithoutSiem } = context;
+ const response = await server.inject(getUpdateBulkRequest(), contextWithoutSiem);
+ expect(response.status).toEqual(404);
+ expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
+ });
+
test('returns an error if update throws', async () => {
clients.alertsClient.update.mockImplementation(() => {
throw new Error('Test error');
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts
index 4abeb840c8c0a..789f7d1ca0744 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts
@@ -37,15 +37,12 @@ export const updateRulesBulkRoute = (router: IRouter) => {
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
const savedObjectsClient = context.core.savedObjects.client;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
- if (!actionsClient || !alertsClient) {
+ if (!siemClient || !actionsClient || !alertsClient) {
return siemResponse.error({ statusCode: 404 });
}
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts
index 717f2cc4a52fe..454fe1f0706cb 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts
@@ -67,6 +67,13 @@ describe('update_rules', () => {
expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
});
+ it('returns 404 if siem client is unavailable', async () => {
+ const { siem, ...contextWithoutSiem } = context;
+ const response = await server.inject(getUpdateRequest(), contextWithoutSiem);
+ expect(response.status).toEqual(404);
+ expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
+ });
+
test('returns error when updating non-rule', async () => {
clients.alertsClient.find.mockResolvedValue(nonRuleFindResult());
const response = await server.inject(getUpdateRequest(), context);
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts
index f0d5f08c5f636..5856575eb9799 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts
@@ -74,15 +74,12 @@ export const updateRulesRoute = (router: IRouter) => {
try {
validateLicenseForRuleType({ license: context.licensing.license, ruleType: type });
- if (!context.alerting || !context.actions) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
- const actionsClient = context.actions.getActionsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
+ const actionsClient = context.actions?.getActionsClient();
const savedObjectsClient = context.core.savedObjects.client;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
- if (!actionsClient || !alertsClient) {
+ if (!siemClient || !actionsClient || !alertsClient) {
return siemResponse.error({ statusCode: 404 });
}
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts
index 612d08c09785a..72f3c89f660c7 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts
@@ -49,6 +49,13 @@ describe('set signal status', () => {
expect(response.status).toEqual(200);
});
+ it('returns 404 if siem client is unavailable', async () => {
+ const { siem, ...contextWithoutSiem } = context;
+ const response = await server.inject(getSetSignalStatusByQueryRequest(), contextWithoutSiem);
+ expect(response.status).toEqual(404);
+ expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
+ });
+
test('catches error if callAsCurrentUser throws error', async () => {
clients.clusterClient.callAsCurrentUser.mockImplementation(async () => {
throw new Error('Test error');
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts
index c1cba641de3ef..2daf63c468593 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts
@@ -24,9 +24,13 @@ export const setSignalsStatusRoute = (router: IRouter) => {
async (context, request, response) => {
const { signal_ids: signalIds, query, status } = request.body;
const clusterClient = context.core.elasticsearch.dataClient;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem?.getSiemClient();
const siemResponse = buildSiemResponse(response);
+ if (!siemClient) {
+ return siemResponse.error({ statusCode: 404 });
+ }
+
let queryObject;
if (signalIds) {
queryObject = { ids: { values: signalIds } };
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts
index 77b62b058fa54..f05f494619b9c 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts
@@ -24,7 +24,7 @@ export const querySignalsRoute = (router: IRouter) => {
async (context, request, response) => {
const { query, aggs, _source, track_total_hits, size } = request.body;
const clusterClient = context.core.elasticsearch.dataClient;
- const siemClient = context.siem.getSiemClient();
+ const siemClient = context.siem!.getSiemClient();
const siemResponse = buildSiemResponse(response);
try {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/tags/read_tags_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/tags/read_tags_route.ts
index e12bf50169c17..adabc62a9456f 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/tags/read_tags_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/tags/read_tags_route.ts
@@ -20,11 +20,7 @@ export const readTagsRoute = (router: IRouter) => {
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
-
- if (!context.alerting) {
- return siemResponse.error({ statusCode: 404 });
- }
- const alertsClient = context.alerting.getAlertsClient();
+ const alertsClient = context.alerting?.getAlertsClient();
if (!alertsClient) {
return siemResponse.error({ statusCode: 404 });
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts
index ada11174c5340..68716bb4e3795 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts
@@ -16,7 +16,6 @@ import {
import { AlertsClient, PartialAlert } from '../../../../../../../plugins/alerting/server';
import { Alert } from '../../../../../../../plugins/alerting/common';
import { SIGNALS_ID } from '../../../../common/constants';
-import { LegacyRequest } from '../../../types';
import { ActionsClient } from '../../../../../../../plugins/actions/server';
import { RuleAlertParams, RuleTypeParams, RuleAlertParamsRest } from '../types';
@@ -39,14 +38,6 @@ export interface FindParamsRest {
filter: string;
}
-export interface PatchRulesRequest extends LegacyRequest {
- payload: PatchRuleAlertParamsRest;
-}
-
-export interface UpdateRulesRequest extends LegacyRequest {
- payload: UpdateRuleAlertParamsRest;
-}
-
export interface RuleAlertType extends Alert {
params: RuleTypeParams;
}
diff --git a/x-pack/legacy/plugins/siem/server/lib/ecs_fields/index.ts b/x-pack/legacy/plugins/siem/server/lib/ecs_fields/index.ts
index eb483de000915..f2662c79d3393 100644
--- a/x-pack/legacy/plugins/siem/server/lib/ecs_fields/index.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/ecs_fields/index.ts
@@ -316,6 +316,7 @@ export const signalFieldsMap: Readonly> = {
'signal.rule.created_by': 'signal.rule.created_by',
'signal.rule.updated_by': 'signal.rule.updated_by',
'signal.rule.version': 'signal.rule.version',
+ 'signal.rule.note': 'signal.rule.note',
};
export const ruleFieldsMap: Readonly> = {
diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts
index 63aee97729141..6552f973a66fa 100644
--- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts
@@ -6,14 +6,14 @@
import Joi from 'joi';
const allowEmptyString = Joi.string().allow([null, '']);
-const columnHeaderType = Joi.string();
+const columnHeaderType = allowEmptyString;
export const created = Joi.number().allow(null);
-export const createdBy = Joi.string();
+export const createdBy = allowEmptyString;
export const description = allowEmptyString;
export const end = Joi.number();
export const eventId = allowEmptyString;
-export const eventType = Joi.string();
+export const eventType = allowEmptyString;
export const filters = Joi.array()
.items(
@@ -24,19 +24,11 @@ export const filters = Joi.array()
disabled: Joi.boolean().allow(null),
field: allowEmptyString,
formattedValue: allowEmptyString,
- index: {
- type: 'keyword',
- },
- key: {
- type: 'keyword',
- },
- negate: {
- type: 'boolean',
- },
+ index: allowEmptyString,
+ key: allowEmptyString,
+ negate: Joi.boolean().allow(null),
params: allowEmptyString,
- type: {
- type: 'keyword',
- },
+ type: allowEmptyString,
value: allowEmptyString,
}),
exists: allowEmptyString,
@@ -68,22 +60,22 @@ export const version = allowEmptyString;
export const columns = Joi.array().items(
Joi.object({
aggregatable: Joi.boolean().allow(null),
- category: Joi.string(),
+ category: allowEmptyString,
columnHeaderType,
description,
example: allowEmptyString,
indexes: allowEmptyString,
- id: Joi.string(),
+ id: allowEmptyString,
name,
placeholder: allowEmptyString,
searchable: Joi.boolean().allow(null),
- type: Joi.string(),
+ type: allowEmptyString,
}).required()
);
export const dataProviders = Joi.array()
.items(
Joi.object({
- id: Joi.string(),
+ id: allowEmptyString,
name: allowEmptyString,
enabled: Joi.boolean().allow(null),
excluded: Joi.boolean().allow(null),
@@ -98,7 +90,7 @@ export const dataProviders = Joi.array()
and: Joi.array()
.items(
Joi.object({
- id: Joi.string(),
+ id: allowEmptyString,
name,
enabled: Joi.boolean().allow(null),
excluded: Joi.boolean().allow(null),
@@ -122,9 +114,9 @@ export const dateRange = Joi.object({
});
export const favorite = Joi.array().items(
Joi.object({
- keySearch: Joi.string(),
- fullName: Joi.string(),
- userName: Joi.string(),
+ keySearch: allowEmptyString,
+ fullName: allowEmptyString,
+ userName: allowEmptyString,
favoriteDate: Joi.number(),
}).allow(null)
);
@@ -141,26 +133,26 @@ const noteItem = Joi.object({
});
export const eventNotes = Joi.array().items(noteItem);
export const globalNotes = Joi.array().items(noteItem);
-export const kqlMode = Joi.string();
+export const kqlMode = allowEmptyString;
export const kqlQuery = Joi.object({
filterQuery: Joi.object({
kuery: Joi.object({
- kind: Joi.string(),
+ kind: allowEmptyString,
expression: allowEmptyString,
}),
serializedQuery: allowEmptyString,
}),
});
export const pinnedEventIds = Joi.array()
- .items(Joi.string())
+ .items(allowEmptyString)
.allow(null);
export const sort = Joi.object({
- columnId: Joi.string(),
- sortDirection: Joi.string(),
+ columnId: allowEmptyString,
+ sortDirection: allowEmptyString,
});
/* eslint-disable @typescript-eslint/camelcase */
-export const ids = Joi.array().items(Joi.string());
+export const ids = Joi.array().items(allowEmptyString);
export const exclude_export_details = Joi.boolean();
export const file_name = allowEmptyString;
diff --git a/x-pack/legacy/plugins/siem/server/types.ts b/x-pack/legacy/plugins/siem/server/types.ts
index 4119645a5af47..a52322f5f830c 100644
--- a/x-pack/legacy/plugins/siem/server/types.ts
+++ b/x-pack/legacy/plugins/siem/server/types.ts
@@ -7,12 +7,8 @@
import { Legacy } from 'kibana';
import { SiemClient } from './client';
-export { LegacyRequest } from '../../../../../src/core/server';
-
export interface LegacyServices {
- alerting?: Legacy.Server['plugins']['alerting'];
config: Legacy.Server['config'];
- route: Legacy.Server['route'];
}
export { SiemClient };
@@ -23,6 +19,6 @@ export interface SiemRequestContext {
declare module 'src/core/server' {
interface RequestHandlerContext {
- siem: SiemRequestContext;
+ siem?: SiemRequestContext;
}
}
diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap
index 354521e7c55b9..ead27425c26f3 100644
--- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap
+++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap
@@ -53,9 +53,18 @@ exports[`ML Flyout component renders without errors 1`] = `
+
+
+ Cancel
+
+
@@ -206,8 +215,26 @@ exports[`ML Flyout component shows license info if no ml available 1`] = `
class="euiFlyoutFooter"
>
+
+
+
+
+ Cancel
+
+
+
+
diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout.tsx b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout.tsx
index 917367f3e8dad..fdecfbf20810c 100644
--- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout.tsx
+++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout.tsx
@@ -7,6 +7,7 @@
import React, { useContext } from 'react';
import {
EuiButton,
+ EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
@@ -64,11 +65,15 @@ export function MLFlyoutView({ isCreatingJob, onClickCreate, onClose, canCreateM
{labels.TAKE_SOME_TIME_TEXT}
-
-
+
+
+ onClose()} disabled={isCreatingJob || isLoadingMLJob}>
+ {labels.CANCEL_LABEL}
+
+
onClickCreate()}
diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/translations.tsx b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/translations.tsx
index 570dd9d1bfa26..32374674771e8 100644
--- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/translations.tsx
+++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/translations.tsx
@@ -124,6 +124,13 @@ export const CREATE_NEW_JOB = i18n.translate(
}
);
+export const CANCEL_LABEL = i18n.translate(
+ 'xpack.uptime.ml.enableAnomalyDetectionPanel.cancelLabel',
+ {
+ defaultMessage: 'Cancel',
+ }
+);
+
export const CREAT_ML_JOB_DESC = i18n.translate(
'xpack.uptime.ml.enableAnomalyDetectionPanel.createMLJobDescription',
{
diff --git a/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.test.ts b/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.test.ts
index 01d6a2e2e81bc..572d73e368c7a 100644
--- a/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.test.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.test.ts
@@ -140,4 +140,21 @@ describe('dedupeConnections', () => {
// @ts-ignore
expect(nodejsNode?.data[SPAN_SUBTYPE]).toBe('aa');
});
+
+ it('processes connections without a matching "service" aggregation', () => {
+ const response: ServiceMapResponse = {
+ services: [javaService],
+ discoveredServices: [],
+ connections: [
+ {
+ source: javaService,
+ destination: nodejsService
+ }
+ ]
+ };
+
+ const { elements } = dedupeConnections(response);
+
+ expect(elements.length).toBe(3);
+ });
});
diff --git a/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.ts b/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.ts
index 6a433367d8217..e5d7c0b2de10c 100644
--- a/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.ts
@@ -88,7 +88,7 @@ export function dedupeConnections(response: ServiceMapResponse) {
serviceName = node[SERVICE_NAME];
}
- const matchedServiceNodes = services.filter(
+ const matchedServiceNodes = serviceNodes.filter(
serviceNode => serviceNode[SERVICE_NAME] === serviceName
);
diff --git a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts
index c493e8ce86781..70bdcdfd3cf1f 100644
--- a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts
+++ b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts
@@ -33,7 +33,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider;
diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts
index a88b1d9049c76..528b9a69327fa 100644
--- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts
+++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts
@@ -49,6 +49,8 @@ export interface LogEntriesAroundParams {
export const LOG_ENTRIES_PAGE_SIZE = 200;
+const FIELDS_FROM_CONTEXT = ['log.file.path', 'host.name', 'container.id'] as const;
+
export class InfraLogEntriesDomain {
constructor(
private readonly adapter: LogEntriesAdapter,
@@ -154,6 +156,14 @@ export class InfraLogEntriesDomain {
}
}
),
+ context: FIELDS_FROM_CONTEXT.reduce((ctx, field) => {
+ // Users might have different types here in their mappings.
+ const value = doc.fields[field];
+ if (typeof value === 'string') {
+ ctx[field] = value;
+ }
+ return ctx;
+ }, {}),
};
});
@@ -329,7 +339,9 @@ const getRequiredFields = (
);
const fieldsFromFormattingRules = messageFormattingRules.requiredFields;
- return Array.from(new Set([...fieldsFromCustomColumns, ...fieldsFromFormattingRules]));
+ return Array.from(
+ new Set([...fieldsFromCustomColumns, ...fieldsFromFormattingRules, ...FIELDS_FROM_CONTEXT])
+ );
};
const createHighlightQueryDsl = (phrase: string, fields: string[]) => ({
diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts
index 8623d02e72862..727d26b5868de 100644
--- a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts
@@ -76,7 +76,8 @@ export const getFileHandler: RequestHandler {
try {
const { pkgkey, filePath } = request.params;
- const registryResponse = await getFile(`/package/${pkgkey}/${filePath}`);
+ const [pkgName, pkgVersion] = pkgkey.split('-');
+ const registryResponse = await getFile(`/package/${pkgName}/${pkgVersion}/${filePath}`);
const contentType = registryResponse.headers.get('Content-Type');
const customResponseObj: CustomHttpResponseOptions = {
body: registryResponse.body,
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts
index 5153f9205dde7..6d5ca036aeb13 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts
@@ -11,19 +11,18 @@ const tests = [
{
package: {
assets: [
- '/package/coredns-1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-plaintext.json',
- '/package/coredns-1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-json.json',
+ '/package/coredns/1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-plaintext.json',
+ '/package/coredns/1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-json.json',
],
- name: 'coredns',
- version: '1.0.1',
+ path: '/package/coredns/1.0.1',
},
dataset: 'log',
filter: (path: string) => {
return true;
},
expected: [
- '/package/coredns-1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-plaintext.json',
- '/package/coredns-1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-json.json',
+ '/package/coredns/1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-plaintext.json',
+ '/package/coredns/1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-json.json',
],
},
{
@@ -32,8 +31,7 @@ const tests = [
'/package/coredns-1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-plaintext.json',
'/package/coredns-1.0.1/dataset/log/elasticsearch/ingest-pipeline/pipeline-json.json',
],
- name: 'coredns',
- version: '1.0.1',
+ path: '/package/coredns/1.0.1',
},
// Non existant dataset
dataset: 'foo',
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts
index e36c2de1b4e80..d7a5c5569986e 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts
@@ -9,14 +9,16 @@ import * as Registry from '../registry';
import { cacheHas } from '../registry/cache';
// paths from RegistryPackage are routes to the assets on EPR
-// e.g. `/package/nginx-1.2.0/dataset/access/fields/fields.yml`
+// e.g. `/package/nginx/1.2.0/dataset/access/fields/fields.yml`
// paths for ArchiveEntry are routes to the assets in the archive
// e.g. `nginx-1.2.0/dataset/access/fields/fields.yml`
// RegistryPackage paths have a `/package/` prefix compared to ArchiveEntry paths
+// and different package and version structure
const EPR_PATH_PREFIX = '/package';
function registryPathToArchivePath(registryPath: RegistryPackage['path']): string {
- const archivePath = registryPath.replace(`${EPR_PATH_PREFIX}/`, '');
- return archivePath;
+ const path = registryPath.replace(`${EPR_PATH_PREFIX}/`, '');
+ const [pkgName, pkgVersion] = path.split('/');
+ return path.replace(`${pkgName}/${pkgVersion}`, `${pkgName}-${pkgVersion}`);
}
export function getAssets(
@@ -35,7 +37,7 @@ export function getAssets(
// if dataset, filter for them
if (datasetName) {
- const comparePath = `${EPR_PATH_PREFIX}/${packageInfo.name}-${packageInfo.version}/dataset/${datasetName}/`;
+ const comparePath = `${packageInfo.path}/dataset/${datasetName}/`;
if (!path.includes(comparePath)) {
continue;
}
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts
index 7c315f7616e1f..36a04b88bba29 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts
@@ -6,7 +6,6 @@
import { Response } from 'node-fetch';
import { URL } from 'url';
-import { sortBy } from 'lodash';
import {
AssetParts,
AssetsGroupedByServiceByType,
@@ -51,11 +50,7 @@ export async function fetchFindLatestPackage(
const res = await fetchUrl(url.toString());
const searchResults = JSON.parse(res);
if (searchResults.length) {
- // sort by version, then get the last (most recent)
- const latestPackage = sortBy(searchResults, ['version'])[
- searchResults.length - 1
- ];
- return latestPackage;
+ return searchResults[0];
} else {
throw new Error('package not found');
}
@@ -63,7 +58,8 @@ export async function fetchFindLatestPackage(
export async function fetchInfo(pkgkey: string): Promise {
const registryUrl = appContextService.getConfig()?.epm.registryUrl;
- return fetchUrl(`${registryUrl}/package/${pkgkey}`).then(JSON.parse);
+ // change pkg-version to pkg/version
+ return fetchUrl(`${registryUrl}/package/${pkgkey.replace('-', '/')}`).then(JSON.parse);
}
export async function fetchFile(filePath: string): Promise {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
index 95a8dfbb308f8..9791cd9210fe2 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
@@ -62,6 +62,9 @@ export interface LoadExploreDataArg {
export const SEARCH_SIZE = 1000;
+export const TRAINING_PERCENT_MIN = 1;
+export const TRAINING_PERCENT_MAX = 100;
+
export const defaultSearchQuery = {
match_all: {},
};
@@ -172,6 +175,19 @@ export const getDependentVar = (analysis: AnalysisConfig) => {
return depVar;
};
+export const getTrainingPercent = (analysis: AnalysisConfig) => {
+ let trainingPercent;
+
+ if (isRegressionAnalysis(analysis)) {
+ trainingPercent = analysis.regression.training_percent;
+ }
+
+ if (isClassificationAnalysis(analysis)) {
+ trainingPercent = analysis.classification.training_percent;
+ }
+ return trainingPercent;
+};
+
export const getPredictionFieldName = (analysis: AnalysisConfig) => {
// If undefined will be defaulted to dependent_variable when config is created
let predictionFieldName;
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration.tsx
index 263d43ceb2630..41430b163c029 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/classification_exploration.tsx
@@ -18,6 +18,7 @@ import { getIndexPatternIdFromName } from '../../../../../util/index_utils';
import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common/index_patterns';
import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
import { useMlContext } from '../../../../../contexts/ml';
+import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics';
export const ExplorationTitle: React.FC<{ jobId: string }> = ({ jobId }) => (
@@ -47,11 +48,11 @@ const jobCapsErrorTitle = i18n.translate(
interface Props {
jobId: string;
- jobStatus: DATA_FRAME_TASK_STATE;
}
-export const ClassificationExploration: FC = ({ jobId, jobStatus }) => {
+export const ClassificationExploration: FC = ({ jobId }) => {
const [jobConfig, setJobConfig] = useState(undefined);
+ const [jobStatus, setJobStatus] = useState(undefined);
const [isLoadingJobConfig, setIsLoadingJobConfig] = useState(false);
const [isInitialized, setIsInitialized] = useState(false);
const [jobConfigErrorMessage, setJobConfigErrorMessage] = useState(undefined);
@@ -65,6 +66,15 @@ export const ClassificationExploration: FC = ({ jobId, jobStatus }) => {
setIsLoadingJobConfig(true);
try {
const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId);
+ const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId);
+ const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
+ ? analyticsStats.data_frame_analytics[0]
+ : undefined;
+
+ if (stats !== undefined && stats.state) {
+ setJobStatus(stats.state);
+ }
+
if (
Array.isArray(analyticsConfigs.data_frame_analytics) &&
analyticsConfigs.data_frame_analytics.length > 0
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx
index 1c5563bdb4f83..91dae49ba5c49 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx
@@ -50,10 +50,47 @@ const defaultPanelWidth = 500;
interface Props {
jobConfig: DataFrameAnalyticsConfig;
- jobStatus: DATA_FRAME_TASK_STATE;
+ jobStatus?: DATA_FRAME_TASK_STATE;
searchQuery: ResultsSearchQuery;
}
+enum SUBSET_TITLE {
+ TRAINING = 'training',
+ TESTING = 'testing',
+ ENTIRE = 'entire',
+}
+
+const entireDatasetHelpText = i18n.translate(
+ 'xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixEntireHelpText',
+ {
+ defaultMessage: 'Normalized confusion matrix for entire dataset',
+ }
+);
+
+const testingDatasetHelpText = i18n.translate(
+ 'xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTestingHelpText',
+ {
+ defaultMessage: 'Normalized confusion matrix for testing dataset',
+ }
+);
+
+const trainingDatasetHelpText = i18n.translate(
+ 'xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTrainingHelpText',
+ {
+ defaultMessage: 'Normalized confusion matrix for training dataset',
+ }
+);
+
+function getHelpText(dataSubsetTitle: string) {
+ let helpText = entireDatasetHelpText;
+ if (dataSubsetTitle === SUBSET_TITLE.TESTING) {
+ helpText = testingDatasetHelpText;
+ } else if (dataSubsetTitle === SUBSET_TITLE.TRAINING) {
+ helpText = trainingDatasetHelpText;
+ }
+ return helpText;
+}
+
export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) => {
const {
services: { docLinks },
@@ -66,6 +103,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery })
const [popoverContents, setPopoverContents] = useState([]);
const [docsCount, setDocsCount] = useState(null);
const [error, setError] = useState(null);
+ const [dataSubsetTitle, setDataSubsetTitle] = useState(SUBSET_TITLE.ENTIRE);
const [panelWidth, setPanelWidth] = useState(defaultPanelWidth);
// Column visibility
const [visibleColumns, setVisibleColumns] = useState(() =>
@@ -197,6 +235,18 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery })
hasIsTrainingClause[0] &&
hasIsTrainingClause[0].match[`${resultsField}.is_training`];
+ const noTrainingQuery = isTrainingClause === false || isTrainingClause === undefined;
+
+ if (noTrainingQuery) {
+ setDataSubsetTitle(SUBSET_TITLE.ENTIRE);
+ } else {
+ setDataSubsetTitle(
+ isTrainingClause && isTrainingClause.query === 'true'
+ ? SUBSET_TITLE.TRAINING
+ : SUBSET_TITLE.TESTING
+ );
+ }
+
loadData({ isTrainingClause });
}, [JSON.stringify(searchQuery)]);
@@ -268,9 +318,11 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery })
-
- {getTaskStateBadge(jobStatus)}
-
+ {jobStatus !== undefined && (
+
+ {getTaskStateBadge(jobStatus)}
+
+ )}
@@ -302,14 +354,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery })
-
- {i18n.translate(
- 'xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixHelpText',
- {
- defaultMessage: 'Normalized confusion matrix',
- }
- )}
-
+ {getHelpText(dataSubsetTitle)}
>;
}
@@ -381,9 +381,11 @@ export const ResultsTable: FC = React.memo(
-
- {getTaskStateBadge(jobStatus)}
-
+ {jobStatus !== undefined && (
+
+ {getTaskStateBadge(jobStatus)}
+
+ )}
= React.memo(
-
- {getTaskStateBadge(jobStatus)}
-
+ {jobStatus !== undefined && (
+
+ {getTaskStateBadge(jobStatus)}
+
+ )}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.test.tsx
index 030447873f6a5..7cdd15e49bd14 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.test.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.test.tsx
@@ -6,7 +6,6 @@
import { shallow } from 'enzyme';
import React from 'react';
-import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
import { MlContext } from '../../../../../contexts/ml';
import { kibanaContextValueMock } from '../../../../../contexts/ml/__mocks__/kibana_context_value';
@@ -22,7 +21,7 @@ describe('Data Frame Analytics: ', () => {
test('Minimal initialization', () => {
const wrapper = shallow(
-
+
);
// Without the jobConfig being loaded, the component will just return empty.
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx
index 214bc01c6a2ef..d686c605f1912 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx
@@ -27,7 +27,6 @@ import {
import { sortColumns, INDEX_STATUS, defaultSearchQuery } from '../../../../common';
-import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns';
import { useExploreData, TableItem } from '../../hooks/use_explore_data';
@@ -50,7 +49,6 @@ const ExplorationTitle: FC<{ jobId: string }> = ({ jobId }) => (
interface ExplorationProps {
jobId: string;
- jobStatus: DATA_FRAME_TASK_STATE;
}
const getFeatureCount = (resultsField: string, tableItems: TableItem[] = []) => {
@@ -63,11 +61,12 @@ const getFeatureCount = (resultsField: string, tableItems: TableItem[] = []) =>
).length;
};
-export const OutlierExploration: FC = React.memo(({ jobId, jobStatus }) => {
+export const OutlierExploration: FC = React.memo(({ jobId }) => {
const {
errorMessage,
indexPattern,
jobConfig,
+ jobStatus,
pagination,
searchQuery,
selectedFields,
@@ -173,9 +172,11 @@ export const OutlierExploration: FC = React.memo(({ jobId, job
-
- {getTaskStateBadge(jobStatus)}
-
+ {jobStatus !== undefined && (
+
+ {getTaskStateBadge(jobStatus)}
+
+ )}
{(columns.length > 0 || searchQuery !== defaultSearchQuery) && (
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx
index 74937bf761285..9f235ae6c45c0 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx
@@ -39,7 +39,7 @@ import {
interface Props {
jobConfig: DataFrameAnalyticsConfig;
- jobStatus: DATA_FRAME_TASK_STATE;
+ jobStatus?: DATA_FRAME_TASK_STATE;
searchQuery: ResultsSearchQuery;
}
@@ -248,9 +248,11 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery })
-
- {getTaskStateBadge(jobStatus)}
-
+ {jobStatus !== undefined && (
+
+ {getTaskStateBadge(jobStatus)}
+
+ )}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx
index 3dfd95a27f8a7..4f3c4048d40d5 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx
@@ -18,6 +18,7 @@ import { getIndexPatternIdFromName } from '../../../../../util/index_utils';
import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common/index_patterns';
import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
import { useMlContext } from '../../../../../contexts/ml';
+import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics';
export const ExplorationTitle: React.FC<{ jobId: string }> = ({ jobId }) => (
@@ -47,11 +48,11 @@ const jobCapsErrorTitle = i18n.translate(
interface Props {
jobId: string;
- jobStatus: DATA_FRAME_TASK_STATE;
}
-export const RegressionExploration: FC = ({ jobId, jobStatus }) => {
+export const RegressionExploration: FC = ({ jobId }) => {
const [jobConfig, setJobConfig] = useState(undefined);
+ const [jobStatus, setJobStatus] = useState(undefined);
const [isLoadingJobConfig, setIsLoadingJobConfig] = useState(false);
const [isInitialized, setIsInitialized] = useState(false);
const [jobConfigErrorMessage, setJobConfigErrorMessage] = useState(undefined);
@@ -65,6 +66,15 @@ export const RegressionExploration: FC = ({ jobId, jobStatus }) => {
setIsLoadingJobConfig(true);
try {
const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId);
+ const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId);
+ const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
+ ? analyticsStats.data_frame_analytics[0]
+ : undefined;
+
+ if (stats !== undefined && stats.state) {
+ setJobStatus(stats.state);
+ }
+
if (
Array.isArray(analyticsConfigs.data_frame_analytics) &&
analyticsConfigs.data_frame_analytics.length > 0
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx
index 7a6b2b23ba7a3..b896c34a582f7 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx
@@ -86,7 +86,7 @@ const showingFirstDocs = i18n.translate(
interface Props {
jobConfig: DataFrameAnalyticsConfig;
- jobStatus: DATA_FRAME_TASK_STATE;
+ jobStatus?: DATA_FRAME_TASK_STATE;
setEvaluateSearchQuery: React.Dispatch>;
}
@@ -381,9 +381,11 @@ export const ResultsTable: FC = React.memo(
-
- {getTaskStateBadge(jobStatus)}
-
+ {jobStatus !== undefined && (
+
+ {getTaskStateBadge(jobStatus)}
+
+ )}
= React.memo(
-
- {getTaskStateBadge(jobStatus)}
-
+ {jobStatus !== undefined && (
+
+ {getTaskStateBadge(jobStatus)}
+
+ )}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts
index 6ad0a1822e490..d637057a4430d 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts
@@ -19,6 +19,7 @@ import { newJobCapsService } from '../../../../../services/new_job_capabilities_
import { getIndexPatternIdFromName } from '../../../../../util/index_utils';
import { getNestedProperty } from '../../../../../util/object_utils';
import { useMlContext } from '../../../../../contexts/ml';
+import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics';
import {
getDefaultSelectableFields,
@@ -31,6 +32,7 @@ import {
import { isKeywordAndTextType } from '../../../../common/fields';
import { getOutlierScoreFieldName } from './common';
+import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
export type TableItem = Record;
@@ -40,6 +42,7 @@ interface UseExploreDataReturnType {
errorMessage: string;
indexPattern: IndexPattern | undefined;
jobConfig: DataFrameAnalyticsConfig | undefined;
+ jobStatus: DATA_FRAME_TASK_STATE | undefined;
pagination: Pagination;
searchQuery: SavedSearchQuery;
selectedFields: EsFieldName[];
@@ -74,6 +77,7 @@ export const useExploreData = (jobId: string): UseExploreDataReturnType => {
const [indexPattern, setIndexPattern] = useState(undefined);
const [jobConfig, setJobConfig] = useState(undefined);
+ const [jobStatus, setJobStatus] = useState(undefined);
const [errorMessage, setErrorMessage] = useState('');
const [status, setStatus] = useState(INDEX_STATUS.UNUSED);
@@ -90,6 +94,15 @@ export const useExploreData = (jobId: string): UseExploreDataReturnType => {
useEffect(() => {
(async function() {
const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId);
+ const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId);
+ const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
+ ? analyticsStats.data_frame_analytics[0]
+ : undefined;
+
+ if (stats !== undefined && stats.state) {
+ setJobStatus(stats.state);
+ }
+
if (
Array.isArray(analyticsConfigs.data_frame_analytics) &&
analyticsConfigs.data_frame_analytics.length > 0
@@ -215,6 +228,7 @@ export const useExploreData = (jobId: string): UseExploreDataReturnType => {
errorMessage,
indexPattern,
jobConfig,
+ jobStatus,
pagination,
rowCount,
searchQuery,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx
index efbebc1564bf9..c8349084dbda8 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx
@@ -27,13 +27,11 @@ import { RegressionExploration } from './components/regression_exploration';
import { ClassificationExploration } from './components/classification_exploration';
import { ANALYSIS_CONFIG_TYPE } from '../../common/analytics';
-import { DATA_FRAME_TASK_STATE } from '../analytics_management/components/analytics_list/common';
export const Page: FC<{
jobId: string;
analysisType: ANALYSIS_CONFIG_TYPE;
- jobStatus: DATA_FRAME_TASK_STATE;
-}> = ({ jobId, analysisType, jobStatus }) => (
+}> = ({ jobId, analysisType }) => (
@@ -68,13 +66,13 @@ export const Page: FC<{
{analysisType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION && (
-
+
)}
{analysisType === ANALYSIS_CONFIG_TYPE.REGRESSION && (
-
+
)}
{analysisType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && (
-
+
)}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
index 425e3bc903d04..4e19df9ae22a8 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
@@ -33,13 +33,12 @@ export const AnalyticsViewAction = {
isPrimary: true,
render: (item: DataFrameAnalyticsListRow) => {
const analysisType = getAnalysisType(item.config.analysis);
- const jobStatus = item.stats.state;
const isDisabled =
!isRegressionAnalysis(item.config.analysis) &&
!isOutlierAnalysis(item.config.analysis) &&
!isClassificationAnalysis(item.config.analysis);
- const url = getResultsUrl(item.id, analysisType, jobStatus);
+ const url = getResultsUrl(item.id, analysisType);
return (
= ({ actions, state }) => {
- const {
- resetAdvancedEditorMessages,
- setAdvancedEditorRawString,
- setFormState,
- setJobConfig,
- } = actions;
+ const { setAdvancedEditorRawString, setFormState } = actions;
const { advancedEditorMessages, advancedEditorRawString, isJobCreated, requestMessages } = state;
@@ -45,12 +39,6 @@ export const CreateAnalyticsAdvancedEditor: FC = ({ ac
const onChange = (str: string) => {
setAdvancedEditorRawString(str);
- try {
- const resultJobConfig = JSON.parse(collapseLiteralStrings(str));
- setJobConfig(resultJobConfig);
- } catch (e) {
- resetAdvancedEditorMessages();
- }
};
// Temp effect to close the context menu popover on Clone button click
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx
index 32384e1949d0a..b0f13e398cc50 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx
@@ -26,7 +26,14 @@ export const CreateAnalyticsFlyout: FC = ({
state,
}) => {
const { closeModal, createAnalyticsJob, startAnalyticsJob } = actions;
- const { isJobCreated, isJobStarted, isModalButtonDisabled, isValid, cloneJob } = state;
+ const {
+ isJobCreated,
+ isJobStarted,
+ isModalButtonDisabled,
+ isValid,
+ isAdvancedEditorValidJson,
+ cloneJob,
+ } = state;
const headerText = !!cloneJob
? i18n.translate('xpack.ml.dataframe.analytics.clone.flyoutHeaderTitle', {
@@ -61,7 +68,7 @@ export const CreateAnalyticsFlyout: FC = ({
{!isJobCreated && !isJobStarted && (
= ({ actions, sta
})}
>
setFormState({ trainingPercent: e.target.value })}
+ onChange={e => setFormState({ trainingPercent: +e.target.value })}
data-test-subj="mlAnalyticsCreateJobFlyoutTrainingPercentSlider"
/>
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts
index 8112a0fdb9e29..c40ab31f6615f 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts
@@ -16,9 +16,11 @@ type SourceIndex = DataFrameAnalyticsConfig['source']['index'];
const getMockState = ({
index,
+ trainingPercent = 75,
modelMemoryLimit = '100mb',
}: {
index: SourceIndex;
+ trainingPercent?: number;
modelMemoryLimit?: string;
}) =>
merge(getInitialState(), {
@@ -31,7 +33,9 @@ const getMockState = ({
jobConfig: {
source: { index },
dest: { index: 'the-destination-index' },
- analysis: {},
+ analysis: {
+ classification: { dependent_variable: 'the-variable', training_percent: trainingPercent },
+ },
model_memory_limit: modelMemoryLimit,
},
});
@@ -151,6 +155,24 @@ describe('useCreateAnalyticsForm', () => {
.isValid
).toBe(false);
});
+
+ test('validateAdvancedEditor(): check training percent validation', () => {
+ // valid training_percent value
+ expect(
+ validateAdvancedEditor(getMockState({ index: 'the-source-index', trainingPercent: 75 }))
+ .isValid
+ ).toBe(true);
+ // invalid training_percent numeric value
+ expect(
+ validateAdvancedEditor(getMockState({ index: 'the-source-index', trainingPercent: 102 }))
+ .isValid
+ ).toBe(false);
+ // invalid training_percent numeric value if 0
+ expect(
+ validateAdvancedEditor(getMockState({ index: 'the-source-index', trainingPercent: 0 }))
+ .isValid
+ ).toBe(false);
+ });
});
describe('validateMinMML', () => {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
index d045749a1a0dd..28d8afbcd88cc 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
@@ -11,6 +11,8 @@ import numeral from '@elastic/numeral';
import { isEmpty } from 'lodash';
import { isValidIndexName } from '../../../../../../../common/util/es_utils';
+import { collapseLiteralStrings } from '../../../../../../../../../../src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools';
+
import { Action, ACTION } from './actions';
import { getInitialState, getJobConfigFromFormState, State } from './state';
import {
@@ -29,9 +31,12 @@ import {
} from '../../../../../../../common/constants/validation';
import {
getDependentVar,
+ getTrainingPercent,
isRegressionAnalysis,
isClassificationAnalysis,
ANALYSIS_CONFIG_TYPE,
+ TRAINING_PERCENT_MIN,
+ TRAINING_PERCENT_MAX,
} from '../../../../common/analytics';
import { indexPatterns } from '../../../../../../../../../../src/plugins/data/public';
@@ -141,6 +146,7 @@ export const validateAdvancedEditor = (state: State): State => {
let dependentVariableEmpty = false;
let excludesValid = true;
+ let trainingPercentValid = true;
if (
jobConfig.analysis === undefined &&
@@ -169,6 +175,30 @@ export const validateAdvancedEditor = (state: State): State => {
message: '',
});
}
+
+ const trainingPercent = getTrainingPercent(jobConfig.analysis);
+ if (
+ trainingPercent !== undefined &&
+ (isNaN(trainingPercent) ||
+ trainingPercent < TRAINING_PERCENT_MIN ||
+ trainingPercent > TRAINING_PERCENT_MAX)
+ ) {
+ trainingPercentValid = false;
+
+ state.advancedEditorMessages.push({
+ error: i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.advancedEditorMessage.trainingPercentInvalid',
+ {
+ defaultMessage: 'The training percent must be a value between {min} and {max}.',
+ values: {
+ min: TRAINING_PERCENT_MIN,
+ max: TRAINING_PERCENT_MAX,
+ },
+ }
+ ),
+ message: '',
+ });
+ }
}
if (sourceIndexNameEmpty) {
@@ -249,6 +279,7 @@ export const validateAdvancedEditor = (state: State): State => {
state.isValid =
maxDistinctValuesError === undefined &&
excludesValid &&
+ trainingPercentValid &&
state.form.modelMemoryLimitUnitValid &&
!jobIdEmpty &&
jobIdValid &&
@@ -365,7 +396,23 @@ export function reducer(state: State, action: Action): State {
return getInitialState();
case ACTION.SET_ADVANCED_EDITOR_RAW_STRING:
- return { ...state, advancedEditorRawString: action.advancedEditorRawString };
+ let resultJobConfig;
+ try {
+ resultJobConfig = JSON.parse(collapseLiteralStrings(action.advancedEditorRawString));
+ } catch (e) {
+ return {
+ ...state,
+ advancedEditorRawString: action.advancedEditorRawString,
+ isAdvancedEditorValidJson: false,
+ advancedEditorMessages: [],
+ };
+ }
+
+ return {
+ ...validateAdvancedEditor({ ...state, jobConfig: resultJobConfig }),
+ advancedEditorRawString: action.advancedEditorRawString,
+ isAdvancedEditorValidJson: true,
+ };
case ACTION.SET_FORM_STATE:
const newFormState = { ...state.form, ...action.payload };
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
index 719bb6c5b07c7..fe741fe9a92d4 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
@@ -82,6 +82,7 @@ export interface State {
indexNames: EsIndexName[];
indexPatternsMap: SourceIndexMap;
isAdvancedEditorEnabled: boolean;
+ isAdvancedEditorValidJson: boolean;
isJobCreated: boolean;
isJobStarted: boolean;
isModalButtonDisabled: boolean;
@@ -140,6 +141,7 @@ export const getInitialState = (): State => ({
indexNames: [],
indexPatternsMap: {},
isAdvancedEditorEnabled: false,
+ isAdvancedEditorValidJson: true,
isJobCreated: false,
isJobStarted: false,
isModalVisible: false,
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js
index 37b9fe5e1f2d0..1f2a57f999775 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js
@@ -388,17 +388,23 @@ function getUrlVars(url) {
}
export function getSelectedJobIdFromUrl(url) {
- if (typeof url === 'string' && url.includes('mlManagement') && url.includes('jobId')) {
- const urlParams = getUrlVars(url);
- const decodedJson = rison.decode(urlParams.mlManagement);
- return decodedJson.jobId;
+ if (typeof url === 'string') {
+ url = decodeURIComponent(url);
+ if (url.includes('mlManagement') && url.includes('jobId')) {
+ const urlParams = getUrlVars(url);
+ const decodedJson = rison.decode(urlParams.mlManagement);
+ return decodedJson.jobId;
+ }
}
}
export function clearSelectedJobIdFromUrl(url) {
- if (typeof url === 'string' && url.includes('mlManagement') && url.includes('jobId')) {
- const urlParams = getUrlVars(url);
- const clearedParams = `ml#/jobs?_g=${urlParams._g}`;
- window.history.replaceState({}, document.title, clearedParams);
+ if (typeof url === 'string') {
+ url = decodeURIComponent(url);
+ if (url.includes('mlManagement') && url.includes('jobId')) {
+ const urlParams = getUrlVars(url);
+ const clearedParams = `ml#/jobs?_g=${urlParams._g}`;
+ window.history.replaceState({}, document.title, clearedParams);
+ }
}
}
diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx
index e00ff0333bb73..2dde5426ec9a0 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx
@@ -13,7 +13,6 @@ import { useResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_exploration';
import { ANALYSIS_CONFIG_TYPE } from '../../../data_frame_analytics/common/analytics';
-import { DATA_FRAME_TASK_STATE } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
import { ML_BREADCRUMB } from '../../breadcrumbs';
const breadcrumbs = [
@@ -46,11 +45,10 @@ const PageWrapper: FC = ({ location, deps }) => {
}
const jobId: string = globalState.ml.jobId;
const analysisType: ANALYSIS_CONFIG_TYPE = globalState.ml.analysisType;
- const jobStatus: DATA_FRAME_TASK_STATE = globalState.ml.jobStatus;
return (
-
+
);
};
diff --git a/x-pack/plugins/ml/public/application/services/job_service.js b/x-pack/plugins/ml/public/application/services/job_service.js
index a84400f236134..0454d40e78923 100644
--- a/x-pack/plugins/ml/public/application/services/job_service.js
+++ b/x-pack/plugins/ml/public/application/services/job_service.js
@@ -586,6 +586,7 @@ class JobService {
const data = {
index: job.datafeed_config.indices,
body,
+ ...(job.datafeed_config.indices_options || {}),
};
ml.esSearch(data)
diff --git a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts
index 7fd8b4a894989..a204bd44901b7 100644
--- a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts
+++ b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts
@@ -112,6 +112,40 @@ describe('cluster_serialization', () => {
},
'localhost:9300'
)
+ ).toEqual({
+ name: 'test_cluster',
+ proxyAddress: 'localhost:9300',
+ mode: 'proxy',
+ hasDeprecatedProxySetting: true,
+ isConnected: true,
+ connectedNodesCount: 1,
+ maxConnectionsPerCluster: 3,
+ initialConnectTimeout: '30s',
+ skipUnavailable: false,
+ transportPingSchedule: '-1',
+ transportCompress: false,
+ });
+ });
+
+ it('should deserialize a cluster that contains a deprecated proxy address and is in cloud', () => {
+ expect(
+ deserializeCluster(
+ 'test_cluster',
+ {
+ seeds: ['localhost:9300'],
+ connected: true,
+ num_nodes_connected: 1,
+ max_connections_per_cluster: 3,
+ initial_connect_timeout: '30s',
+ skip_unavailable: false,
+ transport: {
+ ping_schedule: '-1',
+ compress: false,
+ },
+ },
+ 'localhost:9300',
+ true
+ )
).toEqual({
name: 'test_cluster',
proxyAddress: 'localhost:9300',
diff --git a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts
index 3d8ffa13b8218..07dbe8da28d8a 100644
--- a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts
+++ b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts
@@ -68,7 +68,8 @@ export interface ClusterSettingsPayloadEs {
export function deserializeCluster(
name: string,
esClusterObject: ClusterInfoEs,
- deprecatedProxyAddress?: string | undefined
+ deprecatedProxyAddress?: string | undefined,
+ isCloudEnabled?: boolean | undefined
): Cluster {
if (!name || !esClusterObject || typeof esClusterObject !== 'object') {
throw new Error('Unable to deserialize cluster');
@@ -117,7 +118,7 @@ export function deserializeCluster(
// If a user has a remote cluster with the deprecated proxy setting,
// we transform the data to support the new implementation and also flag the deprecation
if (deprecatedProxyAddress) {
- // Create server name (address, without port), since field doesn't exist in deprecated implementation
+ // Cloud-specific logic: Create default server name, since field doesn't exist in deprecated implementation
const defaultServerName = deprecatedProxyAddress.split(':')[0];
deserializedClusterObject = {
@@ -126,7 +127,7 @@ export function deserializeCluster(
seeds: undefined,
hasDeprecatedProxySetting: true,
mode: PROXY_MODE,
- serverName: defaultServerName,
+ serverName: isCloudEnabled ? defaultServerName : undefined,
};
}
diff --git a/x-pack/plugins/remote_clusters/kibana.json b/x-pack/plugins/remote_clusters/kibana.json
index 8922bf621aa03..f1b9d20f762d3 100644
--- a/x-pack/plugins/remote_clusters/kibana.json
+++ b/x-pack/plugins/remote_clusters/kibana.json
@@ -11,7 +11,8 @@
"indexManagement"
],
"optionalPlugins": [
- "usageCollection"
+ "usageCollection",
+ "cloud"
],
"server": true,
"ui": true
diff --git a/x-pack/plugins/remote_clusters/public/application/app_context.tsx b/x-pack/plugins/remote_clusters/public/application/app_context.tsx
new file mode 100644
index 0000000000000..86c0b401d416d
--- /dev/null
+++ b/x-pack/plugins/remote_clusters/public/application/app_context.tsx
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React, { createContext } from 'react';
+
+export interface Context {
+ isCloudEnabled: boolean;
+}
+
+export const AppContext = createContext({} as any);
+
+export const AppContextProvider = ({
+ children,
+ context,
+}: {
+ children: React.ReactNode;
+ context: Context;
+}) => {
+ return {children} ;
+};
diff --git a/x-pack/plugins/remote_clusters/public/application/index.d.ts b/x-pack/plugins/remote_clusters/public/application/index.d.ts
index b5c5ad5522134..b021dca51bacd 100644
--- a/x-pack/plugins/remote_clusters/public/application/index.d.ts
+++ b/x-pack/plugins/remote_clusters/public/application/index.d.ts
@@ -8,5 +8,8 @@ import { RegisterManagementAppArgs, I18nStart } from '../types';
export declare const renderApp: (
elem: HTMLElement | null,
- I18nContext: I18nStart['Context']
+ I18nContext: I18nStart['Context'],
+ appDependencies: {
+ isCloudEnabled?: boolean;
+ }
) => ReturnType;
diff --git a/x-pack/plugins/remote_clusters/public/application/index.js b/x-pack/plugins/remote_clusters/public/application/index.js
index 0b8b26ace5daa..f2d788c741342 100644
--- a/x-pack/plugins/remote_clusters/public/application/index.js
+++ b/x-pack/plugins/remote_clusters/public/application/index.js
@@ -11,14 +11,17 @@ import { Provider } from 'react-redux';
import { App } from './app';
import { remoteClustersStore } from './store';
+import { AppContextProvider } from './app_context';
-export const renderApp = (elem, I18nContext) => {
+export const renderApp = (elem, I18nContext, appDependencies) => {
render(
-
-
-
+
+
+
+
+
,
elem
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap
index 6ff8c538ca89c..4c109c557fdb0 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap
+++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap
@@ -5,7 +5,6 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
disabledFields={Object {}}
fields={
Object {
- "mode": "sniff",
"name": "",
"nodeConnections": 3,
"proxyAddress": "",
@@ -805,6 +804,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
data-test-subj="remoteClusterFormServerNameFormRow"
describedByIds={Array []}
display="row"
+ error={null}
fullWidth={true}
hasChildLabel={true}
hasEmptyLabelSpace={false}
@@ -827,10 +827,11 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
}
/>
}
+ isInvalid={false}
label={
}
@@ -845,18 +846,21 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
className="euiFormRow__labelWrapper"
>
Server name (optional)
@@ -871,6 +875,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
aria-describedby="mockId-help"
fullWidth={true}
id="mockId"
+ isInvalid={false}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
@@ -886,7 +891,9 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
-
+
+ isCloudEnabled ? (
+
+ ) : (
+
+ )
}
helpText={
this.onFieldsChange({ serverName: e.target.value })}
+ isInvalid={Boolean(areErrorsVisible && errorServerName)}
fullWidth
/>
@@ -452,6 +480,8 @@ export class RemoteClusterForm extends Component {
fields: { mode },
} = this.state;
+ const { isCloudEnabled } = this.context;
+
return (
+ {isCloudEnabled && mode === PROXY_MODE ? (
+ <>
+
+
+ }
+ iconType="pin"
+ size="s"
+ />
+ >
+ ) : null}
>
}
fullWidth
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.js
index ec5f0b1166ce5..a731da362382b 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.js
+++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.js
@@ -8,3 +8,4 @@ export { validateName } from './validate_name';
export { validateProxy } from './validate_proxy';
export { validateSeeds } from './validate_seeds';
export { validateSeed } from './validate_seed';
+export { validateServerName } from './validate_server_name';
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_server_name.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_server_name.js
new file mode 100644
index 0000000000000..197d36445d756
--- /dev/null
+++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_server_name.js
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+export function validateServerName(serverName) {
+ if (!serverName || !serverName.trim()) {
+ return (
+
+ );
+ }
+
+ return null;
+}
diff --git a/x-pack/plugins/remote_clusters/public/plugin.ts b/x-pack/plugins/remote_clusters/public/plugin.ts
index 0fc70cdb60f95..d110c461c1e3f 100644
--- a/x-pack/plugins/remote_clusters/public/plugin.ts
+++ b/x-pack/plugins/remote_clusters/public/plugin.ts
@@ -19,7 +19,7 @@ export class RemoteClustersUIPlugin implements Plugin
async setup(
{ http, elasticsearch: elasticsearchService }: CoreSetup,
- { licensing }: Dependencies
+ { licensing, cloud }: Dependencies
) {
const elasticsearch = await elasticsearchService.adminClient;
const router = http.createRouter();
@@ -41,6 +41,9 @@ export class RemoteClustersServerPlugin implements Plugin
elasticsearchService,
router,
getLicenseStatus: () => this.licenseStatus,
+ config: {
+ isCloudEnabled: Boolean(cloud?.isCloudEnabled),
+ },
};
// Register routes
diff --git a/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts
index 34d741aa4b7da..2b8b3bf1a5e59 100644
--- a/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts
+++ b/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts
@@ -34,6 +34,9 @@ describe('ADD remote clusters', () => {
getLicenseStatus: () => licenseCheckResult,
elasticsearchService: elasticsearchServiceMock.createInternalSetup(),
elasticsearch: elasticsearchMock,
+ config: {
+ isCloudEnabled: false,
+ },
};
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts
index ec14336da08d4..69f84b7ef5e16 100644
--- a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts
+++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts
@@ -36,6 +36,9 @@ describe('DELETE remote clusters', () => {
getLicenseStatus: () => licenseCheckResult,
elasticsearchService: elasticsearchServiceMock.createInternalSetup(),
elasticsearch: elasticsearchMock,
+ config: {
+ isCloudEnabled: false,
+ },
};
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts
index d81b50f1148de..deaf251c80c17 100644
--- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts
+++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts
@@ -35,6 +35,9 @@ describe('GET remote clusters', () => {
getLicenseStatus: () => licenseCheckResult,
elasticsearchService: elasticsearchServiceMock.createInternalSetup(),
elasticsearch: elasticsearchMock,
+ config: {
+ isCloudEnabled: false,
+ },
};
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts
index 8938f342674f0..078e1073d1568 100644
--- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts
+++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts
@@ -33,6 +33,7 @@ export const register = (deps: RouteDependencies): void => {
const cluster = clustersByName[clusterName];
const isTransient = transientClusterNames.includes(clusterName);
const isPersistent = persistentClusterNames.includes(clusterName);
+ const { config } = deps;
// If the cluster hasn't been stored in the cluster state, then it's defined by the
// node's config file.
@@ -46,7 +47,12 @@ export const register = (deps: RouteDependencies): void => {
: undefined;
return {
- ...deserializeCluster(clusterName, cluster, deprecatedProxyAddress),
+ ...deserializeCluster(
+ clusterName,
+ cluster,
+ deprecatedProxyAddress,
+ config.isCloudEnabled
+ ),
isConfiguredByNode,
};
});
diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts
index 84ba9587ddfa6..b2a443c41fbf9 100644
--- a/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts
+++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts
@@ -43,6 +43,9 @@ describe('UPDATE remote clusters', () => {
getLicenseStatus: () => licenseCheckResult,
elasticsearchService: elasticsearchServiceMock.createInternalSetup(),
elasticsearch: elasticsearchMock,
+ config: {
+ isCloudEnabled: false,
+ },
};
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
@@ -171,6 +174,85 @@ describe('UPDATE remote clusters', () => {
},
},
});
+ updateRemoteClustersTest('updates v1 proxy cluster', {
+ apiResponses: [
+ async () => ({
+ test: {
+ connected: true,
+ initial_connect_timeout: '30s',
+ skip_unavailable: false,
+ seeds: ['127.0.0.1:9300'],
+ },
+ }),
+ async () => ({
+ acknowledged: true,
+ persistent: {
+ cluster: {
+ remote: {
+ test: {
+ connected: true,
+ proxy_address: '127.0.0.1:9300',
+ initial_connect_timeout: '30s',
+ skip_unavailable: true,
+ mode: 'proxy',
+ proxy_socket_connections: 18,
+ },
+ },
+ },
+ },
+ transient: {},
+ }),
+ ],
+ params: {
+ name: 'test',
+ },
+ payload: {
+ proxyAddress: '127.0.0.1:9300',
+ skipUnavailable: true,
+ mode: 'proxy',
+ hasDeprecatedProxySetting: true,
+ serverName: '',
+ proxySocketConnections: 18,
+ },
+ asserts: {
+ apiArguments: [
+ ['cluster.remoteInfo'],
+ [
+ 'cluster.putSettings',
+ {
+ body: {
+ persistent: {
+ cluster: {
+ remote: {
+ test: {
+ proxy_address: '127.0.0.1:9300',
+ skip_unavailable: true,
+ mode: 'proxy',
+ node_connections: null,
+ seeds: null,
+ proxy_socket_connections: 18,
+ server_name: null,
+ proxy: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ ],
+ statusCode: 200,
+ result: {
+ initialConnectTimeout: '30s',
+ isConfiguredByNode: false,
+ isConnected: true,
+ proxyAddress: '127.0.0.1:9300',
+ name: 'test',
+ skipUnavailable: true,
+ mode: 'proxy',
+ },
+ },
+ });
});
describe('failure', () => {
diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts
index 47b0d4ad2def2..02a63783154df 100644
--- a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts
+++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts
@@ -24,6 +24,7 @@ const bodyValidation = schema.object({
proxyAddress: schema.nullable(schema.string()),
proxySocketConnections: schema.nullable(schema.number()),
serverName: schema.nullable(schema.string()),
+ hasDeprecatedProxySetting: schema.maybe(schema.boolean()),
});
const paramsValidation = schema.object({
diff --git a/x-pack/plugins/remote_clusters/server/types.ts b/x-pack/plugins/remote_clusters/server/types.ts
index 708b1daf4bbad..85678cba92f19 100644
--- a/x-pack/plugins/remote_clusters/server/types.ts
+++ b/x-pack/plugins/remote_clusters/server/types.ts
@@ -6,9 +6,11 @@
import { IRouter, ElasticsearchServiceSetup, IClusterClient } from 'kibana/server';
import { LicensingPluginSetup } from '../../licensing/server';
+import { CloudSetup } from '../../cloud/server';
export interface Dependencies {
licensing: LicensingPluginSetup;
+ cloud: CloudSetup;
}
export interface RouteDependencies {
@@ -16,6 +18,9 @@ export interface RouteDependencies {
getLicenseStatus: () => LicenseStatus;
elasticsearchService: ElasticsearchServiceSetup;
elasticsearch: IClusterClient;
+ config: {
+ isCloudEnabled: boolean;
+ };
}
export interface LicenseStatus {
diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/__snapshots__/login_form.test.tsx.snap b/x-pack/plugins/security/public/authentication/login/components/login_form/__snapshots__/login_form.test.tsx.snap
index a25498a637c2f..7b8283b7bec0e 100644
--- a/x-pack/plugins/security/public/authentication/login/components/login_form/__snapshots__/login_form.test.tsx.snap
+++ b/x-pack/plugins/security/public/authentication/login/components/login_form/__snapshots__/login_form.test.tsx.snap
@@ -51,6 +51,7 @@ exports[`LoginForm login selector renders as expected with login form 1`] = `
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
+ isInvalid={false}
label={
-
@@ -170,6 +174,7 @@ exports[`LoginForm renders as expected 1`] = `
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
+ isInvalid={false}
label={
-
diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx
index a028eb1ba4b70..01f5c40a69aeb 100644
--- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx
+++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx
@@ -9,6 +9,7 @@ import ReactMarkdown from 'react-markdown';
import {
EuiButton,
EuiCallOut,
+ EuiFieldPassword,
EuiFieldText,
EuiFormRow,
EuiPanel,
@@ -18,6 +19,7 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { HttpStart, IHttpFetchError, NotificationsStart } from 'src/core/public';
+import { LoginValidator, LoginValidationResult } from './validate_login';
import { parseNext } from '../../../../../common/parse_next';
import { LoginSelector } from '../../../../../common/login_state';
@@ -40,6 +42,7 @@ interface State {
message:
| { type: MessageType.None }
| { type: MessageType.Danger | MessageType.Info; content: string };
+ formError: LoginValidationResult | null;
}
enum LoadingStateType {
@@ -55,14 +58,21 @@ enum MessageType {
}
export class LoginForm extends Component {
- public state: State = {
- loadingState: { type: LoadingStateType.None },
- username: '',
- password: '',
- message: this.props.infoMessage
- ? { type: MessageType.Info, content: this.props.infoMessage }
- : { type: MessageType.None },
- };
+ private readonly validator: LoginValidator;
+
+ constructor(props: Props) {
+ super(props);
+ this.validator = new LoginValidator({ shouldValidate: false });
+ this.state = {
+ loadingState: { type: LoadingStateType.None },
+ username: '',
+ password: '',
+ message: this.props.infoMessage
+ ? { type: MessageType.Info, content: this.props.infoMessage }
+ : { type: MessageType.None },
+ formError: null,
+ };
+ }
public render() {
return (
@@ -90,6 +100,7 @@ export class LoginForm extends Component {
defaultMessage="Username"
/>
}
+ {...this.validator.validateUsername(this.state.username)}
>
{
defaultMessage="Password"
/>
}
+ {...this.validator.validatePassword(this.state.password)}
>
- {
}
}
- private isFormValid = () => {
- const { username, password } = this.state;
-
- return username && password;
- };
-
private onUsernameChange = (e: ChangeEvent) => {
this.setState({
username: e.target.value,
@@ -271,8 +276,15 @@ export class LoginForm extends Component {
) => {
e.preventDefault();
- if (!this.isFormValid()) {
+ this.validator.enableValidation();
+
+ const { username, password } = this.state;
+ const result = this.validator.validateForLogin(username, password);
+ if (result.isInvalid) {
+ this.setState({ formError: result });
return;
+ } else {
+ this.setState({ formError: null });
}
this.setState({
@@ -281,7 +293,6 @@ export class LoginForm extends Component {
});
const { http } = this.props;
- const { username, password } = this.state;
try {
await http.post('/internal/security/login', { body: JSON.stringify({ username, password }) });
diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/validate_login.test.ts b/x-pack/plugins/security/public/authentication/login/components/login_form/validate_login.test.ts
new file mode 100644
index 0000000000000..6cd582bbcb4c0
--- /dev/null
+++ b/x-pack/plugins/security/public/authentication/login/components/login_form/validate_login.test.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { LoginValidator, LoginValidationResult } from './validate_login';
+
+function expectValid(result: LoginValidationResult) {
+ expect(result.isInvalid).toBe(false);
+}
+
+function expectInvalid(result: LoginValidationResult) {
+ expect(result.isInvalid).toBe(true);
+}
+
+describe('LoginValidator', () => {
+ describe('#validateUsername', () => {
+ it(`returns 'valid' if validation is disabled`, () => {
+ expectValid(new LoginValidator().validateUsername(''));
+ });
+
+ it(`returns 'invalid' if username is missing`, () => {
+ expectInvalid(new LoginValidator({ shouldValidate: true }).validateUsername(''));
+ });
+
+ it(`returns 'valid' for correct usernames`, () => {
+ expectValid(new LoginValidator({ shouldValidate: true }).validateUsername('u'));
+ });
+ });
+
+ describe('#validatePassword', () => {
+ it(`returns 'valid' if validation is disabled`, () => {
+ expectValid(new LoginValidator().validatePassword(''));
+ });
+
+ it(`returns 'invalid' if password is missing`, () => {
+ expectInvalid(new LoginValidator({ shouldValidate: true }).validatePassword(''));
+ });
+
+ it(`returns 'valid' for correct passwords`, () => {
+ expectValid(new LoginValidator({ shouldValidate: true }).validatePassword('p'));
+ });
+ });
+
+ describe('#validateForLogin', () => {
+ it(`returns 'valid' if validation is disabled`, () => {
+ expectValid(new LoginValidator().validateForLogin('', ''));
+ });
+
+ it(`returns 'invalid' if username is invalid`, () => {
+ expectInvalid(new LoginValidator({ shouldValidate: true }).validateForLogin('', 'p'));
+ });
+
+ it(`returns 'invalid' if password is invalid`, () => {
+ expectInvalid(new LoginValidator({ shouldValidate: true }).validateForLogin('u', ''));
+ });
+
+ it(`returns 'valid' if username and password are valid`, () => {
+ expectValid(new LoginValidator({ shouldValidate: true }).validateForLogin('u', 'p'));
+ });
+ });
+});
diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/validate_login.ts b/x-pack/plugins/security/public/authentication/login/components/login_form/validate_login.ts
new file mode 100644
index 0000000000000..0873098a0ff1d
--- /dev/null
+++ b/x-pack/plugins/security/public/authentication/login/components/login_form/validate_login.ts
@@ -0,0 +1,97 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+interface LoginValidatorOptions {
+ shouldValidate?: boolean;
+}
+
+export interface LoginValidationResult {
+ isInvalid: boolean;
+ error?: string;
+}
+
+export class LoginValidator {
+ private shouldValidate?: boolean;
+
+ constructor(options: LoginValidatorOptions = {}) {
+ this.shouldValidate = options.shouldValidate;
+ }
+
+ public enableValidation() {
+ this.shouldValidate = true;
+ }
+
+ public disableValidation() {
+ this.shouldValidate = false;
+ }
+
+ public validateUsername(username: string): LoginValidationResult {
+ if (!this.shouldValidate) {
+ return valid();
+ }
+
+ if (!username) {
+ // Elasticsearch has more stringent requirements for usernames in the Native realm. However, the login page is used for other realms,
+ // such as LDAP and Active Directory. Because of that, the best validation we can do here is to ensure the username is not empty.
+ return invalid(
+ i18n.translate(
+ 'xpack.security.authentication.login.validateLogin.requiredUsernameErrorMessage',
+ {
+ defaultMessage: 'Username is required',
+ }
+ )
+ );
+ }
+
+ return valid();
+ }
+
+ public validatePassword(password: string): LoginValidationResult {
+ if (!this.shouldValidate) {
+ return valid();
+ }
+
+ if (!password) {
+ // Elasticsearch has more stringent requirements for passwords in the Native realm. However, the login page is used for other realms,
+ // such as LDAP and Active Directory. Because of that, the best validation we can do here is to ensure the password is not empty.
+ return invalid(
+ i18n.translate(
+ 'xpack.security.authentication.login.validateLogin.requiredPasswordErrorMessage',
+ {
+ defaultMessage: 'Password is required',
+ }
+ )
+ );
+ }
+ return valid();
+ }
+
+ public validateForLogin(username: string, password: string): LoginValidationResult {
+ const { isInvalid: isUsernameInvalid } = this.validateUsername(username);
+ const { isInvalid: isPasswordInvalid } = this.validatePassword(password);
+
+ if (isUsernameInvalid || isPasswordInvalid) {
+ return invalid();
+ }
+
+ return valid();
+ }
+}
+
+function invalid(error?: string): LoginValidationResult {
+ return {
+ isInvalid: true,
+ error,
+ };
+}
+
+function valid(): LoginValidationResult {
+ return {
+ isInvalid: false,
+ };
+}
diff --git a/x-pack/plugins/snapshot_restore/public/application/constants/index.ts b/x-pack/plugins/snapshot_restore/public/application/constants/index.ts
index 9c8fb3d288d24..ea6f5c80b9343 100644
--- a/x-pack/plugins/snapshot_restore/public/application/constants/index.ts
+++ b/x-pack/plugins/snapshot_restore/public/application/constants/index.ts
@@ -14,9 +14,9 @@ export type Section = 'repositories' | 'snapshots' | 'restore_status' | 'policie
export const MINIMUM_TIMEOUT_MS = 300;
export enum REPOSITORY_DOC_PATHS {
- default = 'modules-snapshots.html',
- fs = 'modules-snapshots.html#_shared_file_system_repository',
- url = 'modules-snapshots.html#_read_only_url_repository',
+ default = 'snapshot-restore.html',
+ fs = 'snapshots-register-repository.html#snapshots-filesystem-repository',
+ url = 'snapshots-register-repository.html#snapshots-read-only-repository',
source = 'snapshots-register-repository.html#snapshots-source-only-repository',
s3 = 'repository-s3.html',
hdfs = 'repository-hdfs.html',
diff --git a/x-pack/plugins/snapshot_restore/public/application/services/documentation/documentation_links.ts b/x-pack/plugins/snapshot_restore/public/application/services/documentation/documentation_links.ts
index 5e59685d6be47..daeb14c39f68b 100644
--- a/x-pack/plugins/snapshot_restore/public/application/services/documentation/documentation_links.ts
+++ b/x-pack/plugins/snapshot_restore/public/application/services/documentation/documentation_links.ts
@@ -46,15 +46,15 @@ class DocumentationLinksService {
}
public getSnapshotDocUrl() {
- return `${this.esDocBasePath}modules-snapshots.html#snapshots-take-snapshot`;
+ return `${this.esDocBasePath}snapshots-take-snapshot.html`;
}
public getRestoreDocUrl() {
- return `${this.esDocBasePath}modules-snapshots.html#restore-snapshot`;
+ return `${this.esDocBasePath}snapshots-restore-snapshot.html`;
}
public getRestoreIndexSettingsUrl() {
- return `${this.esDocBasePath}modules-snapshots.html#_changing_index_settings_during_restore`;
+ return `${this.esDocBasePath}snapshots-restore-snapshot.html#_changing_index_settings_during_restore`;
}
public getIndexSettingsUrl() {
diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js
index 242ee890d4847..b0ca33b00fde8 100644
--- a/x-pack/scripts/functional_tests.js
+++ b/x-pack/scripts/functional_tests.js
@@ -27,6 +27,7 @@ const onlyNotInCoverageTests = [
require.resolve('../test/oidc_api_integration/implicit_flow.config.ts'),
require.resolve('../test/pki_api_integration/config.ts'),
require.resolve('../test/login_selector_api_integration/config.ts'),
+ require.resolve('../test/encrypted_saved_objects_api_integration/config.ts'),
require.resolve('../test/spaces_api_integration/spaces_only/config.ts'),
require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial.ts'),
require.resolve('../test/spaces_api_integration/security_and_spaces/config_basic.ts'),
diff --git a/x-pack/test/api_integration/apis/apm/custom_link.ts b/x-pack/test/api_integration/apis/apm/custom_link.ts
new file mode 100644
index 0000000000000..8aefadd811775
--- /dev/null
+++ b/x-pack/test/api_integration/apis/apm/custom_link.ts
@@ -0,0 +1,144 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+// import querystring from 'querystring';
+// import {isEmpty} from 'lodash'
+import URL from 'url';
+import expect from '@kbn/expect';
+import { CustomLink } from '../../../../plugins/apm/common/custom_link/custom_link_types';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function customLinksTests({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+ const log = getService('log');
+
+ function searchCustomLinks(filters?: any) {
+ const path = URL.format({
+ pathname: `/api/apm/settings/custom_links`,
+ query: filters,
+ });
+ return supertest.get(path).set('kbn-xsrf', 'foo');
+ }
+
+ async function createCustomLink(customLink: CustomLink) {
+ log.debug('creating configuration', customLink);
+ const res = await supertest
+ .post(`/api/apm/settings/custom_links`)
+ .send(customLink)
+ .set('kbn-xsrf', 'foo');
+
+ throwOnError(res);
+
+ return res;
+ }
+
+ async function updateCustomLink(id: string, customLink: CustomLink) {
+ log.debug('updating configuration', id, customLink);
+ const res = await supertest
+ .put(`/api/apm/settings/custom_links/${id}`)
+ .send(customLink)
+ .set('kbn-xsrf', 'foo');
+
+ throwOnError(res);
+
+ return res;
+ }
+
+ async function deleteCustomLink(id: string) {
+ log.debug('deleting configuration', id);
+ const res = await supertest
+ .delete(`/api/apm/settings/custom_links/${id}`)
+ .set('kbn-xsrf', 'foo');
+
+ throwOnError(res);
+
+ return res;
+ }
+
+ function throwOnError(res: any) {
+ const { statusCode, req, body } = res;
+ if (statusCode !== 200) {
+ throw new Error(`
+ Endpoint: ${req.method} ${req.path}
+ Service: ${JSON.stringify(res.request._data.service)}
+ Status code: ${statusCode}
+ Response: ${body.message}`);
+ }
+ }
+
+ describe('custom links', () => {
+ before(async () => {
+ const customLink = {
+ url: 'https://elastic.co',
+ label: 'with filters',
+ filters: [
+ { key: 'service.name', value: 'baz' },
+ { key: 'transaction.type', value: 'qux' },
+ ],
+ } as CustomLink;
+ await createCustomLink(customLink);
+ });
+ it('fetches a custom link', async () => {
+ const { status, body } = await searchCustomLinks({
+ 'service.name': 'baz',
+ 'transaction.type': 'qux',
+ });
+ const { label, url, filters } = body[0];
+
+ expect(status).to.equal(200);
+ expect({ label, url, filters }).to.eql({
+ label: 'with filters',
+ url: 'https://elastic.co',
+ filters: [
+ { key: 'service.name', value: 'baz' },
+ { key: 'transaction.type', value: 'qux' },
+ ],
+ });
+ });
+ it('updates a custom link', async () => {
+ let { status, body } = await searchCustomLinks({
+ 'service.name': 'baz',
+ 'transaction.type': 'qux',
+ });
+ expect(status).to.equal(200);
+ await updateCustomLink(body[0].id, {
+ label: 'foo',
+ url: 'https://elastic.co?service.name={{service.name}}',
+ filters: [
+ { key: 'service.name', value: 'quz' },
+ { key: 'transaction.name', value: 'bar' },
+ ],
+ });
+ ({ status, body } = await searchCustomLinks({
+ 'service.name': 'quz',
+ 'transaction.name': 'bar',
+ }));
+ const { label, url, filters } = body[0];
+ expect(status).to.equal(200);
+ expect({ label, url, filters }).to.eql({
+ label: 'foo',
+ url: 'https://elastic.co?service.name={{service.name}}',
+ filters: [
+ { key: 'service.name', value: 'quz' },
+ { key: 'transaction.name', value: 'bar' },
+ ],
+ });
+ });
+ it('deletes a custom link', async () => {
+ let { status, body } = await searchCustomLinks({
+ 'service.name': 'quz',
+ 'transaction.name': 'bar',
+ });
+ expect(status).to.equal(200);
+ await deleteCustomLink(body[0].id);
+ ({ status, body } = await searchCustomLinks({
+ 'service.name': 'quz',
+ 'transaction.name': 'bar',
+ }));
+ expect(status).to.equal(200);
+ expect(body).to.eql([]);
+ });
+ });
+}
diff --git a/x-pack/test/api_integration/apis/apm/feature_controls.ts b/x-pack/test/api_integration/apis/apm/feature_controls.ts
index 8ce55b8fb1d5f..9f76941935bb7 100644
--- a/x-pack/test/api_integration/apis/apm/feature_controls.ts
+++ b/x-pack/test/api_integration/apis/apm/feature_controls.ts
@@ -149,12 +149,27 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
log.error(JSON.stringify(res, null, 2));
},
},
+ {
+ req: {
+ url: `/api/apm/settings/custom_links`,
+ },
+ expectForbidden: expect404,
+ expectResponse: expect200,
+ },
+ {
+ req: {
+ url: `/api/apm/settings/custom_links/transaction`,
+ },
+ expectForbidden: expect404,
+ expectResponse: expect200,
+ },
];
const elasticsearchPrivileges = {
indices: [
{ names: ['apm-*'], privileges: ['read', 'view_index_metadata'] },
{ names: ['.apm-agent-configuration'], privileges: ['read', 'write', 'view_index_metadata'] },
+ { names: ['.apm-custom-link'], privileges: ['read', 'write', 'view_index_metadata'] },
],
};
diff --git a/x-pack/test/api_integration/apis/apm/index.ts b/x-pack/test/api_integration/apis/apm/index.ts
index 6f41f4abfecc3..4a4265cfd0739 100644
--- a/x-pack/test/api_integration/apis/apm/index.ts
+++ b/x-pack/test/api_integration/apis/apm/index.ts
@@ -10,5 +10,6 @@ export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderCont
describe('APM specs', () => {
loadTestFile(require.resolve('./feature_controls'));
loadTestFile(require.resolve('./agent_configuration'));
+ loadTestFile(require.resolve('./custom_link'));
});
}
diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js
index 0a87dcb4b5bb0..10c06adea8564 100644
--- a/x-pack/test/api_integration/apis/index.js
+++ b/x-pack/test/api_integration/apis/index.js
@@ -27,7 +27,6 @@ export default function({ loadTestFile }) {
loadTestFile(require.resolve('./siem'));
loadTestFile(require.resolve('./short_urls'));
loadTestFile(require.resolve('./lens'));
- loadTestFile(require.resolve('./fleet'));
loadTestFile(require.resolve('./ingest'));
loadTestFile(require.resolve('./endpoint'));
loadTestFile(require.resolve('./ml'));
diff --git a/x-pack/test/api_integration/apis/infra/log_entries.ts b/x-pack/test/api_integration/apis/infra/log_entries.ts
index 4f447d518a751..3c12f5e4dc789 100644
--- a/x-pack/test/api_integration/apis/infra/log_entries.ts
+++ b/x-pack/test/api_integration/apis/infra/log_entries.ts
@@ -126,6 +126,32 @@ export default function({ getService }: FtrProviderContext) {
expect(messageColumn.message.length).to.be.greaterThan(0);
});
+ it('Returns the context fields', async () => {
+ const { body } = await supertest
+ .post(LOG_ENTRIES_PATH)
+ .set(COMMON_HEADERS)
+ .send(
+ logEntriesRequestRT.encode({
+ sourceId: 'default',
+ startTimestamp: EARLIEST_KEY_WITH_DATA.time,
+ endTimestamp: LATEST_KEY_WITH_DATA.time,
+ center: KEY_WITHIN_DATA_RANGE,
+ })
+ )
+ .expect(200);
+
+ const logEntriesResponse = pipe(
+ logEntriesResponseRT.decode(body),
+ fold(throwErrors(createPlainError), identity)
+ );
+
+ const entries = logEntriesResponse.data.entries;
+ const entry = entries[0];
+
+ expect(entry.context).to.have.property('host.name');
+ expect(entry.context['host.name']).to.be('demo-stack-nginx-01');
+ });
+
it('Paginates correctly with `after`', async () => {
const { body: firstPageBody } = await supertest
.post(LOG_ENTRIES_PATH)
diff --git a/x-pack/test/encrypted_saved_objects_api_integration/config.ts b/x-pack/test/encrypted_saved_objects_api_integration/config.ts
new file mode 100644
index 0000000000000..c1be2e98b3b99
--- /dev/null
+++ b/x-pack/test/encrypted_saved_objects_api_integration/config.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { resolve } from 'path';
+import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
+import { services } from './services';
+
+export default async function({ readConfigFile }: FtrConfigProviderContext) {
+ const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
+
+ return {
+ testFiles: [require.resolve('./tests')],
+ servers: xPackAPITestsConfig.get('servers'),
+ services,
+ junit: {
+ reportName: 'X-Pack Encrypted Saved Objects API Integration Tests',
+ },
+ esTestCluster: xPackAPITestsConfig.get('esTestCluster'),
+ kbnTestServer: {
+ ...xPackAPITestsConfig.get('kbnTestServer'),
+ serverArgs: [
+ ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'),
+ `--plugin-path=${resolve(__dirname, './fixtures/api_consumer_plugin')}`,
+ ],
+ },
+ };
+}
diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/kibana.json b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/kibana.json
new file mode 100644
index 0000000000000..92449d0136ce5
--- /dev/null
+++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/kibana.json
@@ -0,0 +1,8 @@
+{
+ "id": "eso",
+ "version": "8.0.0",
+ "kibanaVersion": "kibana",
+ "requiredPlugins": ["encryptedSavedObjects", "spaces"],
+ "server": true,
+ "ui": false
+}
diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts
new file mode 100644
index 0000000000000..170b7e0c6d09d
--- /dev/null
+++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { CoreSetup, PluginInitializer } from '../../../../../../src/core/server';
+import { deepFreeze } from '../../../../../../src/core/utils';
+import {
+ EncryptedSavedObjectsPluginSetup,
+ EncryptedSavedObjectsPluginStart,
+} from '../../../../../plugins/encrypted_saved_objects/server';
+import { SpacesPluginSetup } from '../../../../../plugins/spaces/server';
+
+const SAVED_OBJECT_WITH_SECRET_TYPE = 'saved-object-with-secret';
+
+interface PluginsSetup {
+ encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
+ spaces: SpacesPluginSetup;
+}
+
+interface PluginsStart {
+ encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
+ spaces: never;
+}
+
+export const plugin: PluginInitializer = () => ({
+ setup(core: CoreSetup, deps) {
+ core.savedObjects.registerType({
+ name: SAVED_OBJECT_WITH_SECRET_TYPE,
+ hidden: false,
+ namespaceAgnostic: false,
+ mappings: deepFreeze({
+ properties: {
+ publicProperty: { type: 'keyword' },
+ publicPropertyExcludedFromAAD: { type: 'keyword' },
+ privateProperty: { type: 'binary' },
+ },
+ }),
+ });
+
+ deps.encryptedSavedObjects.registerType({
+ type: SAVED_OBJECT_WITH_SECRET_TYPE,
+ attributesToEncrypt: new Set(['privateProperty']),
+ attributesToExcludeFromAAD: new Set(['publicPropertyExcludedFromAAD']),
+ });
+
+ core.http.createRouter().get(
+ {
+ path: '/api/saved_objects/get-decrypted-as-internal-user/{id}',
+ validate: { params: value => ({ value }) },
+ },
+ async (context, request, response) => {
+ const [, { encryptedSavedObjects }] = await core.getStartServices();
+ const spaceId = deps.spaces.spacesService.getSpaceId(request);
+ const namespace = deps.spaces.spacesService.spaceIdToNamespace(spaceId);
+
+ try {
+ return response.ok({
+ body: await encryptedSavedObjects.getDecryptedAsInternalUser(
+ SAVED_OBJECT_WITH_SECRET_TYPE,
+ request.params.id,
+ { namespace }
+ ),
+ });
+ } catch (err) {
+ if (encryptedSavedObjects.isEncryptionError(err)) {
+ return response.badRequest({ body: 'Failed to encrypt attributes' });
+ }
+
+ return response.customError({ body: err, statusCode: 500 });
+ }
+ }
+ );
+ },
+ start() {},
+ stop() {},
+});
diff --git a/x-pack/test/encrypted_saved_objects_api_integration/ftr_provider_context.d.ts b/x-pack/test/encrypted_saved_objects_api_integration/ftr_provider_context.d.ts
new file mode 100644
index 0000000000000..e3add3748f56d
--- /dev/null
+++ b/x-pack/test/encrypted_saved_objects_api_integration/ftr_provider_context.d.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { GenericFtrProviderContext } from '@kbn/test/types/ftr';
+
+import { services } from './services';
+
+export type FtrProviderContext = GenericFtrProviderContext;
diff --git a/x-pack/test/encrypted_saved_objects_api_integration/services.ts b/x-pack/test/encrypted_saved_objects_api_integration/services.ts
new file mode 100644
index 0000000000000..b7398349cce5d
--- /dev/null
+++ b/x-pack/test/encrypted_saved_objects_api_integration/services.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { services } from '../api_integration/services';
diff --git a/x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/encrypted_saved_objects_api.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts
similarity index 99%
rename from x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/encrypted_saved_objects_api.ts
rename to x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts
index ab9f7d2cdd339..7fe3d28911211 100644
--- a/x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/encrypted_saved_objects_api.ts
+++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts
@@ -6,7 +6,7 @@
import expect from '@kbn/expect';
import { SavedObject } from 'src/core/server';
-import { FtrProviderContext } from '../../ftr_provider_context';
+import { FtrProviderContext } from '../ftr_provider_context';
export default function({ getService }: FtrProviderContext) {
const es = getService('legacyEs');
diff --git a/x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/index.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts
similarity index 88%
rename from x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/index.ts
rename to x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts
index 424160e84495e..8c816a3404ddb 100644
--- a/x-pack/test/plugin_api_integration/test_suites/encrypted_saved_objects/index.ts
+++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { FtrProviderContext } from '../../ftr_provider_context';
+import { FtrProviderContext } from '../ftr_provider_context';
export default function({ loadTestFile }: FtrProviderContext) {
describe('encryptedSavedObjects', function encryptedSavedObjectsSuite() {
diff --git a/x-pack/test/epm_api_integration/apis/file.ts b/x-pack/test/epm_api_integration/apis/file.ts
index 2989263af40a7..c67f472e8fb78 100644
--- a/x-pack/test/epm_api_integration/apis/file.ts
+++ b/x-pack/test/epm_api_integration/apis/file.ts
@@ -19,7 +19,7 @@ export default function({ getService }: FtrProviderContext) {
it('fetches a .png screenshot image', async () => {
server.on({
method: 'GET',
- path: '/package/auditd-2.0.4/img/screenshots/auditbeat-file-integrity-dashboard.png',
+ path: '/package/auditd/2.0.4/img/screenshots/auditbeat-file-integrity-dashboard.png',
reply: {
headers: { 'content-type': 'image/png' },
},
@@ -38,7 +38,7 @@ export default function({ getService }: FtrProviderContext) {
it('fetches an .svg icon image', async () => {
server.on({
method: 'GET',
- path: '/package/auditd-2.0.4/img/icon.svg',
+ path: '/package/auditd/2.0.4/img/icon.svg',
reply: {
headers: { 'content-type': 'image/svg' },
},
@@ -54,7 +54,7 @@ export default function({ getService }: FtrProviderContext) {
it('fetches an auditbeat .conf rule file', async () => {
server.on({
method: 'GET',
- path: '/package/auditd-2.0.4/auditbeat/rules/sample-rules-linux-32bit.conf',
+ path: '/package/auditd/2.0.4/auditbeat/rules/sample-rules-linux-32bit.conf',
});
const supertest = getService('supertest');
@@ -70,7 +70,7 @@ export default function({ getService }: FtrProviderContext) {
it('fetches an auditbeat .yml config file', async () => {
server.on({
method: 'GET',
- path: '/package/auditd-2.0.4/auditbeat/config/config.yml',
+ path: '/package/auditd/2.0.4/auditbeat/config/config.yml',
reply: {
headers: { 'content-type': 'text/yaml; charset=UTF-8' },
},
@@ -88,7 +88,7 @@ export default function({ getService }: FtrProviderContext) {
server.on({
method: 'GET',
path:
- '/package/auditd-2.0.4/kibana/visualization/b21e0c70-c252-11e7-8692-232bd1143e8a-ecs.json',
+ '/package/auditd/2.0.4/kibana/visualization/b21e0c70-c252-11e7-8692-232bd1143e8a-ecs.json',
});
const supertest = getService('supertest');
@@ -105,7 +105,7 @@ export default function({ getService }: FtrProviderContext) {
server.on({
method: 'GET',
path:
- '/package/auditd-2.0.4/kibana/dashboard/7de391b0-c1ca-11e7-8995-936807a28b16-ecs.json',
+ '/package/auditd/2.0.4/kibana/dashboard/7de391b0-c1ca-11e7-8995-936807a28b16-ecs.json',
});
const supertest = getService('supertest');
@@ -121,7 +121,7 @@ export default function({ getService }: FtrProviderContext) {
it('fetches an .json index pattern file', async () => {
server.on({
method: 'GET',
- path: '/package/auditd-2.0.4/kibana/index-pattern/auditbeat-*.json',
+ path: '/package/auditd/2.0.4/kibana/index-pattern/auditbeat-*.json',
});
const supertest = getService('supertest');
@@ -135,7 +135,7 @@ export default function({ getService }: FtrProviderContext) {
it('fetches a .json search file', async () => {
server.on({
method: 'GET',
- path: '/package/auditd-2.0.4/kibana/search/0f10c430-c1c3-11e7-8995-936807a28b16-ecs.json',
+ path: '/package/auditd/2.0.4/kibana/search/0f10c430-c1c3-11e7-8995-936807a28b16-ecs.json',
});
const supertest = getService('supertest');
diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts
index 1f22ca59ab2d4..7e15ff436d12c 100644
--- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts
+++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts
@@ -138,7 +138,8 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
});
});
- describe('no advanced_settings privileges', function() {
+ // FLAKY: https://github.com/elastic/kibana/issues/57377
+ describe.skip('no advanced_settings privileges', function() {
this.tags(['skipCoverage']);
before(async () => {
await security.role.create('no_advanced_settings_privileges_role', {
diff --git a/x-pack/test/login_selector_api_integration/config.ts b/x-pack/test/login_selector_api_integration/config.ts
index 6ca9d19b74c17..d8e42b4583bed 100644
--- a/x-pack/test/login_selector_api_integration/config.ts
+++ b/x-pack/test/login_selector_api_integration/config.ts
@@ -130,11 +130,6 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
saml2: { order: 5, realm: 'saml2', maxRedirectURLSize: '100b' },
},
})}`,
- '--server.xsrf.whitelist',
- JSON.stringify([
- '/api/oidc_provider/token_endpoint',
- '/api/oidc_provider/userinfo_endpoint',
- ]),
],
},
};
diff --git a/x-pack/test/oidc_api_integration/config.ts b/x-pack/test/oidc_api_integration/config.ts
index 557dea4d51b0e..9ef00320f0e60 100644
--- a/x-pack/test/oidc_api_integration/config.ts
+++ b/x-pack/test/oidc_api_integration/config.ts
@@ -51,12 +51,6 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
`--plugin-path=${plugin}`,
'--xpack.security.authc.providers=["oidc"]',
'--xpack.security.authc.oidc.realm="oidc1"',
- '--server.xsrf.whitelist',
- JSON.stringify([
- '/api/security/oidc/initiate_login',
- '/api/oidc_provider/token_endpoint',
- '/api/oidc_provider/userinfo_endpoint',
- ]),
],
},
};
diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/init_routes.js b/x-pack/test/oidc_api_integration/fixtures/oidc_provider/init_routes.js
deleted file mode 100644
index 3023479f7be9d..0000000000000
--- a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/init_routes.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import Joi from 'joi';
-import { createTokens } from '../oidc_tools';
-
-export function initRoutes(server) {
- let nonce = '';
-
- server.route({
- path: '/api/oidc_provider/setup',
- method: 'POST',
- config: {
- auth: false,
- validate: {
- payload: Joi.object({
- nonce: Joi.string().required(),
- }),
- },
- },
- handler: request => {
- nonce = request.payload.nonce;
- return {};
- },
- });
-
- server.route({
- path: '/api/oidc_provider/token_endpoint',
- method: 'POST',
- // Token endpoint needs authentication (with the client credentials) but we don't attempt to
- // validate this OIDC behavior here
- config: {
- auth: false,
- validate: {
- payload: Joi.object({
- grant_type: Joi.string().optional(),
- code: Joi.string().optional(),
- redirect_uri: Joi.string().optional(),
- }),
- },
- },
- async handler(request) {
- const userId = request.payload.code.substring(4);
- const { accessToken, idToken } = createTokens(userId, nonce);
- try {
- const userId = request.payload.code.substring(4);
- return {
- access_token: accessToken,
- token_type: 'Bearer',
- refresh_token: `valid-refresh-token${userId}`,
- expires_in: 3600,
- id_token: idToken,
- };
- } catch (err) {
- return err;
- }
- },
- });
-
- server.route({
- path: '/api/oidc_provider/userinfo_endpoint',
- method: 'GET',
- config: {
- auth: false,
- },
- handler: request => {
- const accessToken = request.headers.authorization.substring(7);
- if (accessToken === 'valid-access-token1') {
- return {
- sub: 'user1',
- name: 'Tony Stark',
- given_name: 'Tony',
- family_name: 'Stark',
- preferred_username: 'ironman',
- email: 'ironman@avengers.com',
- };
- }
- if (accessToken === 'valid-access-token2') {
- return {
- sub: 'user2',
- name: 'Peter Parker',
- given_name: 'Peter',
- family_name: 'Parker',
- preferred_username: 'spiderman',
- email: 'spiderman@avengers.com',
- };
- }
- if (accessToken === 'valid-access-token3') {
- return {
- sub: 'user3',
- name: 'Bruce Banner',
- given_name: 'Bruce',
- family_name: 'Banner',
- preferred_username: 'hulk',
- email: 'hulk@avengers.com',
- };
- }
- return {};
- },
- });
-}
diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/kibana.json b/x-pack/test/oidc_api_integration/fixtures/oidc_provider/kibana.json
new file mode 100644
index 0000000000000..faaa0b9165828
--- /dev/null
+++ b/x-pack/test/oidc_api_integration/fixtures/oidc_provider/kibana.json
@@ -0,0 +1,7 @@
+{
+ "id": "oidc_provider_plugin",
+ "version": "8.0.0",
+ "kibanaVersion": "kibana",
+ "server": true,
+ "ui": false
+}
diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/package.json b/x-pack/test/oidc_api_integration/fixtures/oidc_provider/package.json
deleted file mode 100644
index 358c6e2020afe..0000000000000
--- a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "oidc_provider_plugin",
- "version": "1.0.0",
- "kibana": {
- "version": "kibana",
- "templateVersion": "1.0.0"
- },
- "license": "Apache-2.0",
- "dependencies": {
- "joi": "^13.5.2",
- "jsonwebtoken": "^8.3.0"
- }
-}
diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/index.js b/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts
similarity index 55%
rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/index.js
rename to x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts
index 17d45527397b8..456abecd201be 100644
--- a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/index.js
+++ b/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts
@@ -4,16 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { PluginInitializer } from '../../../../../../src/core/server';
import { initRoutes } from './init_routes';
-export default function(kibana) {
- return new kibana.Plugin({
- name: 'oidcProvider',
- id: 'oidcProvider',
- require: ['elasticsearch'],
-
- init(server) {
- initRoutes(server);
- },
- });
-}
+export const plugin: PluginInitializer = () => ({
+ setup: core => initRoutes(core.http.createRouter()),
+ start: () => {},
+ stop: () => {},
+});
diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts b/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts
new file mode 100644
index 0000000000000..6d3248f4377b1
--- /dev/null
+++ b/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts
@@ -0,0 +1,98 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { IRouter } from '../../../../../../src/core/server';
+import { createTokens } from '../../oidc_tools';
+
+export function initRoutes(router: IRouter) {
+ let nonce = '';
+
+ router.post(
+ {
+ path: '/api/oidc_provider/setup',
+ validate: { body: value => ({ value }) },
+ options: { authRequired: false },
+ },
+ (context, request, response) => {
+ nonce = request.body.nonce;
+ return response.ok({ body: {} });
+ }
+ );
+
+ router.post(
+ {
+ path: '/api/oidc_provider/token_endpoint',
+ validate: { body: value => ({ value }) },
+ // Token endpoint needs authentication (with the client credentials) but we don't attempt to
+ // validate this OIDC behavior here
+ options: { authRequired: false, xsrfRequired: false },
+ },
+ (context, request, response) => {
+ const userId = request.body.code.substring(4);
+ const { accessToken, idToken } = createTokens(userId, nonce);
+ return response.ok({
+ body: {
+ access_token: accessToken,
+ token_type: 'Bearer',
+ refresh_token: `valid-refresh-token${userId}`,
+ expires_in: 3600,
+ id_token: idToken,
+ },
+ });
+ }
+ );
+
+ router.get(
+ {
+ path: '/api/oidc_provider/userinfo_endpoint',
+ validate: false,
+ options: { authRequired: false },
+ },
+ (context, request, response) => {
+ const accessToken = (request.headers.authorization as string).substring(7);
+ if (accessToken === 'valid-access-token1') {
+ return response.ok({
+ body: {
+ sub: 'user1',
+ name: 'Tony Stark',
+ given_name: 'Tony',
+ family_name: 'Stark',
+ preferred_username: 'ironman',
+ email: 'ironman@avengers.com',
+ },
+ });
+ }
+
+ if (accessToken === 'valid-access-token2') {
+ return response.ok({
+ body: {
+ sub: 'user2',
+ name: 'Peter Parker',
+ given_name: 'Peter',
+ family_name: 'Parker',
+ preferred_username: 'spiderman',
+ email: 'spiderman@avengers.com',
+ },
+ });
+ }
+
+ if (accessToken === 'valid-access-token3') {
+ return response.ok({
+ body: {
+ sub: 'user3',
+ name: 'Bruce Banner',
+ given_name: 'Bruce',
+ family_name: 'Banner',
+ preferred_username: 'hulk',
+ email: 'hulk@avengers.com',
+ },
+ });
+ }
+
+ return response.ok({ body: {} });
+ }
+ );
+}
diff --git a/x-pack/test/plugin_api_integration/config.js b/x-pack/test/plugin_api_integration/config.js
index 830933278f2bc..83e8b1f84a9e0 100644
--- a/x-pack/test/plugin_api_integration/config.js
+++ b/x-pack/test/plugin_api_integration/config.js
@@ -18,10 +18,7 @@ export default async function({ readConfigFile }) {
);
return {
- testFiles: [
- require.resolve('./test_suites/task_manager'),
- require.resolve('./test_suites/encrypted_saved_objects'),
- ],
+ testFiles: [require.resolve('./test_suites/task_manager')],
services,
servers: integrationConfig.get('servers'),
esTestCluster: integrationConfig.get('esTestCluster'),
diff --git a/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/index.ts b/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/index.ts
deleted file mode 100644
index e61b8f24a1f69..0000000000000
--- a/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/index.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { Request } from 'hapi';
-import { boomify, badRequest } from 'boom';
-import { Legacy } from 'kibana';
-import {
- EncryptedSavedObjectsPluginSetup,
- EncryptedSavedObjectsPluginStart,
-} from '../../../../plugins/encrypted_saved_objects/server';
-
-const SAVED_OBJECT_WITH_SECRET_TYPE = 'saved-object-with-secret';
-
-// eslint-disable-next-line import/no-default-export
-export default function esoPlugin(kibana: any) {
- return new kibana.Plugin({
- id: 'eso',
- require: ['encryptedSavedObjects'],
- uiExports: { mappings: require('./mappings.json') },
- init(server: Legacy.Server) {
- server.route({
- method: 'GET',
- path: '/api/saved_objects/get-decrypted-as-internal-user/{id}',
- async handler(request: Request) {
- const encryptedSavedObjectsStart = server.newPlatform.start.plugins
- .encryptedSavedObjects as EncryptedSavedObjectsPluginStart;
- const namespace = server.plugins.spaces && server.plugins.spaces.getSpaceId(request);
- try {
- return await encryptedSavedObjectsStart.getDecryptedAsInternalUser(
- SAVED_OBJECT_WITH_SECRET_TYPE,
- request.params.id,
- { namespace: namespace === 'default' ? undefined : namespace }
- );
- } catch (err) {
- if (encryptedSavedObjectsStart.isEncryptionError(err)) {
- return badRequest('Failed to encrypt attributes');
- }
-
- return boomify(err);
- }
- },
- });
-
- (server.newPlatform.setup.plugins
- .encryptedSavedObjects as EncryptedSavedObjectsPluginSetup).registerType({
- type: SAVED_OBJECT_WITH_SECRET_TYPE,
- attributesToEncrypt: new Set(['privateProperty']),
- attributesToExcludeFromAAD: new Set(['publicPropertyExcludedFromAAD']),
- });
- },
- });
-}
diff --git a/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/mappings.json b/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/mappings.json
deleted file mode 100644
index b727850793bbe..0000000000000
--- a/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/mappings.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "saved-object-with-secret": {
- "properties": {
- "publicProperty": {
- "type": "keyword"
- },
- "publicPropertyExcludedFromAAD": {
- "type": "keyword"
- },
- "privateProperty": {
- "type": "binary"
- }
- }
- }
-}
diff --git a/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/package.json b/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/package.json
deleted file mode 100644
index 723904757ae8a..0000000000000
--- a/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "eso",
- "version": "kibana"
-}
\ No newline at end of file
diff --git a/x-pack/test/saml_api_integration/config.ts b/x-pack/test/saml_api_integration/config.ts
index 0580c28555d16..a92f11363b0fc 100644
--- a/x-pack/test/saml_api_integration/config.ts
+++ b/x-pack/test/saml_api_integration/config.ts
@@ -50,7 +50,6 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
serverArgs: [
...xPackAPITestsConfig.get('kbnTestServer.serverArgs'),
'--optimize.enabled=false',
- '--server.xsrf.whitelist=["/api/security/saml/callback"]',
`--xpack.security.authc.providers=${JSON.stringify(['saml', 'basic'])}`,
'--xpack.security.authc.saml.realm=saml1',
'--xpack.security.authc.saml.maxRedirectURLSize=100b',
diff --git a/yarn.lock b/yarn.lock
index b5e72e07f1efe..2e57d6d475f39 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5313,9 +5313,9 @@ acorn-walk@^7.0.0:
integrity sha512-7Bv1We7ZGuU79zZbb6rRqcpxo3OY+zrdtloZWoyD8fmGX+FeXRjE+iuGkZjSXLVovLzrsvMGMy0EkwA0E0umxg==
acorn@5.X, acorn@^5.0.0, acorn@^5.0.3, acorn@^5.1.2, acorn@^5.5.0:
- version "5.7.3"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
- integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
+ version "5.7.4"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
+ integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
acorn@^3.0.4, acorn@^3.1.0:
version "3.3.0"
@@ -5327,15 +5327,10 @@ acorn@^4.0.4, acorn@~4.0.2:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=
-acorn@^6.0.1:
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
- integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==
-
-acorn@^6.2.1:
- version "6.3.0"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e"
- integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==
+acorn@^6.0.1, acorn@^6.2.1:
+ version "6.4.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
+ integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
acorn@^7.0.0, acorn@^7.1.0:
version "7.1.1"