From 46dcc0bd4929b406ec01012a4465f1f3d892ea67 Mon Sep 17 00:00:00 2001 From: Katrin Freihofner Date: Mon, 27 Jul 2020 19:10:03 +0200 Subject: [PATCH 01/36] Adds styling changes to uptime overview and details page (#71840) Co-authored-by: Elastic Machine --- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../ping_histogram.test.tsx.snap | 9 ++- .../common/charts/duration_chart.tsx | 7 +-- .../common/charts/ping_histogram.tsx | 9 +-- .../ml_integerations.test.tsx.snap | 6 +- .../__snapshots__/ml_manage_job.test.tsx.snap | 6 +- .../components/monitor/ml/manage_ml_job.tsx | 10 ++-- .../components/monitor/monitor_charts.tsx | 2 +- .../monitor_duration/monitor_duration.tsx | 9 +-- .../__snapshots__/ping_list.test.tsx.snap | 2 +- .../monitor/ping_list/ping_list.tsx | 2 +- .../monitor_status.bar.test.tsx.snap | 10 ++-- .../status_by_location.test.tsx.snap | 58 ++++++++----------- .../status_bar/status_by_location.tsx | 10 ++-- .../__snapshots__/monitor_list.test.tsx.snap | 47 +++++---------- .../monitor_list/monitor_list_header.tsx | 38 ++++-------- 17 files changed, 90 insertions(+), 137 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8baebbb4939be..cf79f463b35cb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -16283,7 +16283,6 @@ "xpack.uptime.ml.enableAnomalyDetectionPanel.manageMLJobDescription.noteText": "注:ジョブが結果の計算を開始するまでに少し時間がかかる場合があります。", "xpack.uptime.ml.enableAnomalyDetectionPanel.startTrial": "無料の 14 日トライアルを開始", "xpack.uptime.ml.enableAnomalyDetectionPanel.startTrialDesc": "期間異常検知機能を利用するには、Elastic Platinum ライセンスが必要です。", - "xpack.uptime.monitorCharts.durationChart.bottomAxis.title": "タイムスタンプ", "xpack.uptime.monitorCharts.durationChart.leftAxis.title": "期間ms", "xpack.uptime.monitorCharts.monitorDuration.titleLabel": "監視期間", "xpack.uptime.monitorCharts.monitorDuration.titleLabelWithAnomaly": "監視期間 (異常: {noOfAnomalies})", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5b81804faf715..b45fe1baa9e9a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -16290,7 +16290,6 @@ "xpack.uptime.ml.enableAnomalyDetectionPanel.manageMLJobDescription.noteText": "注意:可能要过几分钟后,作业才会开始计算结果。", "xpack.uptime.ml.enableAnomalyDetectionPanel.startTrial": "开始为期 14 天的免费试用", "xpack.uptime.ml.enableAnomalyDetectionPanel.startTrialDesc": "要访问持续时间异常检测,必须订阅 Elastic 白金级许可证。", - "xpack.uptime.monitorCharts.durationChart.bottomAxis.title": "鏃堕棿鎴", "xpack.uptime.monitorCharts.durationChart.leftAxis.title": "持续时间 (ms)", "xpack.uptime.monitorCharts.monitorDuration.titleLabel": "监测持续时间(毫秒)", "xpack.uptime.monitorCharts.monitorDuration.titleLabelWithAnomaly": "监测持续时间(异常:{noOfAnomalies})", diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/ping_histogram.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/ping_histogram.test.tsx.snap index fe20071ced4cb..7fdb2e4ede75b 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/ping_histogram.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/ping_histogram.test.tsx.snap @@ -2,11 +2,14 @@ exports[`PingHistogram component renders the component without errors 1`] = ` Array [ -

Pings over time -

, + , +
,
getTickFormat(d)} title={i18n.translate('xpack.uptime.monitorCharts.durationChart.leftAxis.title', { - defaultMessage: 'Duration ms', + defaultMessage: 'Duration in ms', })} /> diff --git a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx index d5f3b1b164ad9..39b8a38f60982 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx @@ -13,7 +13,7 @@ import { timeFormatter, BrushEndListener, } from '@elastic/charts'; -import { EuiTitle } from '@elastic/eui'; +import { EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -173,14 +173,15 @@ export const PingHistogramComponent: React.FC = ({ return ( <> - -

+ +

-

+

+ {content} ); diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_integerations.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_integerations.test.tsx.snap index 24c4e818a0592..15f5c03512bf1 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_integerations.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_integerations.test.tsx.snap @@ -8,18 +8,18 @@ exports[`ML Integrations renders without errors 1`] = ` class="euiPopover__anchor" >
,
-

- Down in 2 Locations -

-
+ Down in 2 locations + `; exports[`StatusByLocation component renders properly against props 1`] = ` - +

-
+ `; exports[`StatusByLocation component renders when down in some locations 1`] = ` -
-

- Down in 1/2 Locations -

-
+ Down in 1/2 locations + `; exports[`StatusByLocation component renders when only one location and it is down 1`] = ` -
-

- Down in 1 Location -

-
+ Down in 1 location + `; exports[`StatusByLocation component renders when only one location and it is up 1`] = ` -
-

- Up in 1 Location -

-
+ Up in 1 location + `; exports[`StatusByLocation component renders when up in all locations 1`] = ` -
-

- Up in 2 Locations -

-
+ Up in 2 locations + `; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_by_location.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_by_location.tsx index 461ffc10124fd..fb2a55bb4059b 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_by_location.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_by_location.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { EuiText } from '@elastic/eui'; +import { EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { MonitorLocation } from '../../../../../common/runtime_types'; @@ -43,7 +43,7 @@ export const StatusByLocations = ({ locations }: StatusByLocationsProps) => { } return ( - +

{locations.length <= 1 ? ( { status, loc: statusMessage, }} - defaultMessage="{status} in {loc} Location" + defaultMessage="{status} in {loc} location" /> ) : ( { status, loc: statusMessage, }} - defaultMessage="{status} in {loc} Locations" + defaultMessage="{status} in {loc} locations" /> )}

-
+ ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap index b6ce1eceb62a7..42ac821c10c7a 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap @@ -813,7 +813,13 @@ exports[`MonitorList component renders the monitor list 1`] = ` } .c1 { - margin-left: auto; + position: absolute; + right: 16px; + top: 16px; +} + +.c0 { + position: relative; } .c4 { @@ -828,26 +834,6 @@ exports[`MonitorList component renders the monitor list 1`] = ` } } -@media only screen and (max-width:768px) { - .c0.c0 > :first-child { - -webkit-flex-basis: 40% !important; - -ms-flex-preferred-size: 40% !important; - flex-basis: 40% !important; - } - - .c0.c0 > :nth-child(2) { - -webkit-order: 3; - -ms-flex-order: 3; - order: 3; - } - - .c0.c0 > :nth-child(3) { - -webkit-flex-basis: 60% !important; - -ms-flex-preferred-size: 60% !important; - flex-basis: 60% !important; - } -} -
@@ -936,20 +922,13 @@ exports[`MonitorList component renders the monitor list 1`] = `
- + Certificates status +
:first-child { - flex-basis: 40% !important; - } - > :nth-child(2) { - order: 3; - } - > :nth-child(3) { - flex-basis: 60% !important; - } - } - } + position: relative; `; export const MonitorListHeader: React.FC = () => { @@ -48,18 +38,12 @@ export const MonitorListHeader: React.FC = () => { - - -
- - - -
-
-
+ + + ); }; From e9fa2f35427926dde0421242e067c02ebcf96e10 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 27 Jul 2020 12:15:56 -0500 Subject: [PATCH 02/36] [build] fix chmod errors (#72768) * inherit data permisions * tmp add all-platforms flag * Revert "inherit data permisions" This reverts commit ce30dd7b3aae1ee92acb3a2b49cc4ce681d0975c. * silent chmod, move to configure sectino * simplify chown and fix babel cache * rm empty lines * Revert "tmp add all-platforms flag" This reverts commit f1ae815ca9966b873fc735fde03fd8bfdc256aa4. Co-authored-by: Elastic Machine --- .../package_scripts/post_install.sh | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh index 10f11ff51874e..c49b291d1a0c9 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh @@ -3,6 +3,22 @@ set -e export KBN_PATH_CONF=${KBN_PATH_CONF:-<%= configDir %>} +set_chmod() { + chmod -f 660 ${KBN_PATH_CONF}/kibana.yml || true + chmod -f 2750 <%= dataDir %> || true + chmod -f 2750 ${KBN_PATH_CONF} || true +} + +set_chown() { + chown -R <%= user %>:<%= group %> <%= dataDir %> + chown -R root:<%= group %> ${KBN_PATH_CONF} +} + +set_access() { + set_chmod + set_chown +} + case $1 in # Debian configure) @@ -14,6 +30,8 @@ case $1 in adduser --quiet --system --no-create-home --disabled-password \ --ingroup "<%= group %>" --shell /bin/false "<%= user %>" fi + + set_access ;; abort-deconfigure|abort-upgrade|abort-remove) ;; @@ -28,6 +46,8 @@ case $1 in useradd -r -g "<%= group %>" -M -s /sbin/nologin \ -c "kibana service user" "<%= user %>" fi + + set_access ;; *) @@ -35,12 +55,3 @@ case $1 in exit 1 ;; esac - -chown -R <%= user %>:<%= group %> <%= dataDir %> -chmod 2750 <%= dataDir %> -chmod -R 2755 <%= dataDir %>/* - -chown :<%= group %> ${KBN_PATH_CONF} -chown :<%= group %> ${KBN_PATH_CONF}/kibana.yml -chmod 2750 ${KBN_PATH_CONF} -chmod 660 ${KBN_PATH_CONF}/kibana.yml From 6631a296ef8ee2934a5d93e160c4465f47874bee Mon Sep 17 00:00:00 2001 From: Poff Poffenberger Date: Mon, 27 Jul 2020 12:18:33 -0500 Subject: [PATCH 03/36] [Canvas] Fix top left elements being automatically selected on workpad page loads (#72121) * Fix top left elements being automatically selected on workpad page loads * Remove unnecessary code Co-authored-by: Elastic Machine --- .../components/workpad_page/workpad_interactive_page/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js index 41f78165a7394..632ec1ad5e004 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js @@ -127,7 +127,7 @@ const componentLayoutState = ({ gestureState: aeroStore ? aeroStore.getCurrentState().currentScene.gestureState : { - cursor: { x: 0, y: 0 }, + cursor: { x: Infinity, y: Infinity }, mouseIsDown: false, mouseButtonState: { buttonState: 'up', downX: null, downY: null }, }, From d66cc3515c227cfe81def49d52dc2dda3ba20f30 Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 27 Jul 2020 10:43:18 -0700 Subject: [PATCH 04/36] [canvas] migrate tests away from karma (#73121) Co-authored-by: spalger Co-authored-by: Elastic Machine --- .../components/loading/__tests__/loading.js | 25 -- .../components/loading/loading.test.tsx | 36 +++ .../public/functions/__tests__/asset.js | 28 -- .../lib/__tests__/find_expression_type.js | 95 ------- .../public/lib/__tests__/history_provider.js | 240 ------------------ .../public/lib/__tests__/modify_path.js | 34 --- .../get_pretty_shortcut.test.ts | 2 +- .../canvas/public/lib/modify_path.test.ts | 33 +++ .../lib/{modify_path.js => modify_path.ts} | 8 +- .../{__tests__ => }/readable_color.test.ts | 2 +- .../resolved_arg.js => resolved_arg.test.ts} | 22 +- .../lib/{resolved_arg.js => resolved_arg.ts} | 6 +- .../lib/{__tests__ => }/time_interval.test.ts | 2 +- .../__tests__/elements.get_sibling_context.js | 107 -------- .../public/state/actions/elements.test.js | 106 ++++++++ .../public/state/middleware/in_flight.ts | 1 - .../action_creator.js | 0 .../elements.js => elements.test.js} | 10 +- ...resolved_args.js => resolved_args.test.js} | 126 +++++---- ...resolved_args.js => resolved_args.test.js} | 22 +- .../public/state/selectors/resolved_args.ts | 2 - .../{__tests__/workpad.js => workpad.test.js} | 60 ++--- .../canvas/public/state/selectors/workpad.ts | 1 - 23 files changed, 311 insertions(+), 657 deletions(-) delete mode 100644 x-pack/plugins/canvas/public/components/loading/__tests__/loading.js create mode 100644 x-pack/plugins/canvas/public/components/loading/loading.test.tsx delete mode 100644 x-pack/plugins/canvas/public/functions/__tests__/asset.js delete mode 100644 x-pack/plugins/canvas/public/lib/__tests__/find_expression_type.js delete mode 100644 x-pack/plugins/canvas/public/lib/__tests__/history_provider.js delete mode 100644 x-pack/plugins/canvas/public/lib/__tests__/modify_path.js rename x-pack/plugins/canvas/public/lib/{__tests__ => }/get_pretty_shortcut.test.ts (98%) create mode 100644 x-pack/plugins/canvas/public/lib/modify_path.test.ts rename x-pack/plugins/canvas/public/lib/{modify_path.js => modify_path.ts} (61%) rename x-pack/plugins/canvas/public/lib/{__tests__ => }/readable_color.test.ts (94%) rename x-pack/plugins/canvas/public/lib/{__tests__/resolved_arg.js => resolved_arg.test.ts} (57%) rename x-pack/plugins/canvas/public/lib/{resolved_arg.js => resolved_arg.ts} (75%) rename x-pack/plugins/canvas/public/lib/{__tests__ => }/time_interval.test.ts (98%) delete mode 100644 x-pack/plugins/canvas/public/state/actions/__tests__/elements.get_sibling_context.js create mode 100644 x-pack/plugins/canvas/public/state/actions/elements.test.js rename x-pack/plugins/canvas/public/state/reducers/{__tests__/fixtures => __fixtures__}/action_creator.js (100%) rename x-pack/plugins/canvas/public/state/reducers/{__tests__/elements.js => elements.test.js} (78%) rename x-pack/plugins/canvas/public/state/reducers/{__tests__/resolved_args.js => resolved_args.test.js} (62%) rename x-pack/plugins/canvas/public/state/selectors/{__tests__/resolved_args.js => resolved_args.test.js} (55%) rename x-pack/plugins/canvas/public/state/selectors/{__tests__/workpad.js => workpad.test.js} (80%) diff --git a/x-pack/plugins/canvas/public/components/loading/__tests__/loading.js b/x-pack/plugins/canvas/public/components/loading/__tests__/loading.js deleted file mode 100644 index c159f478766ce..0000000000000 --- a/x-pack/plugins/canvas/public/components/loading/__tests__/loading.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import expect from '@kbn/expect'; -import { shallow } from 'enzyme'; -import { EuiLoadingSpinner, EuiIcon } from '@elastic/eui'; -import { Loading } from '../loading'; - -describe('', () => { - it('uses EuiIcon by default', () => { - const wrapper = shallow(); - expect(wrapper.contains()).to.be.ok; - expect(wrapper.contains()).to.not.be.ok; - }); - - it('uses EuiLoadingSpinner when animating', () => { - const wrapper = shallow(); - expect(wrapper.contains()).to.not.be.ok; - expect(wrapper.contains()).to.be.ok; - }); -}); diff --git a/x-pack/plugins/canvas/public/components/loading/loading.test.tsx b/x-pack/plugins/canvas/public/components/loading/loading.test.tsx new file mode 100644 index 0000000000000..004ecc19c42e2 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/loading/loading.test.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { Loading } from './loading'; + +describe('', () => { + it('uses EuiIcon by default', () => { + expect(shallow()).toMatchInlineSnapshot(` +
+ +
+ `); + }); + + it('uses EuiLoadingSpinner when animating', () => { + expect(shallow()).toMatchInlineSnapshot(` +
+ +
+ `); + }); +}); diff --git a/x-pack/plugins/canvas/public/functions/__tests__/asset.js b/x-pack/plugins/canvas/public/functions/__tests__/asset.js deleted file mode 100644 index c21faf9a2e227..0000000000000 --- a/x-pack/plugins/canvas/public/functions/__tests__/asset.js +++ /dev/null @@ -1,28 +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 expect from '@kbn/expect'; -import { functionWrapper } from '../../../__tests__/helpers/function_wrapper'; -import { asset } from '../asset'; - -// TODO: restore this test -// will require the ability to mock the store, or somehow remove the function's dependency on getState -describe.skip('asset', () => { - const fn = functionWrapper(asset); - - it('throws if asset could not be retrieved by ID', () => { - const throwsErr = () => { - return fn(null, { id: 'boo' }); - }; - expect(throwsErr).to.throwException((err) => { - expect(err.message).to.be('Could not get the asset by ID: boo'); - }); - }); - - it('returns the asset for found asset ID', () => { - expect(fn(null, { id: 'yay' })).to.be('here is your image'); - }); -}); diff --git a/x-pack/plugins/canvas/public/lib/__tests__/find_expression_type.js b/x-pack/plugins/canvas/public/lib/__tests__/find_expression_type.js deleted file mode 100644 index 58f5c5eb303bd..0000000000000 --- a/x-pack/plugins/canvas/public/lib/__tests__/find_expression_type.js +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// import expect from 'expect.js'; -// import proxyquire from 'proxyquire'; -// import { Registry } from '../../../common/lib/registry'; - -// const registries = { -// datasource: new Registry(), -// transform: new Registry(), -// model: new Registry(), -// view: new Registry(), -// }; - -// const { findExpressionType } = proxyquire.noCallThru().load('../find_expression_type', { -// '../expression_types/datasource': { -// datasourceRegistry: registries.datasource, -// }, -// '../expression_types/transform': { -// transformRegistry: registries.transform, -// }, -// '../expression_types/model': { -// modelRegistry: registries.model, -// }, -// '../expression_types/view': { -// viewRegistry: registries.view, -// }, -// }); - -// describe('findExpressionType', () => { -// let expTypes; - -// beforeEach(() => { -// expTypes = []; -// const keys = Object.keys(registries); -// keys.forEach(key => { -// const reg = registries[key]; -// reg.reset(); - -// const expObj = () => ({ -// name: `__test_${key}`, -// key, -// }); -// expTypes.push(expObj); -// reg.register(expObj); -// }); -// }); - -// describe('all types', () => { -// it('returns the matching item, by name', () => { -// const match = findExpressionType('__test_model'); -// expect(match).to.eql(expTypes[2]()); -// }); - -// it('returns null when nothing is found', () => { -// const match = findExpressionType('@@nope_nope_nope'); -// expect(match).to.equal(null); -// }); - -// it('throws with multiple matches', () => { -// const commonName = 'commonName'; -// registries.transform.register(() => ({ -// name: commonName, -// })); -// registries.model.register(() => ({ -// name: commonName, -// })); - -// const check = () => { -// findExpressionType(commonName); -// }; -// expect(check).to.throwException(/Found multiple expressions/i); -// }); -// }); - -// describe('specific type', () => { -// it('return the match item, by name and type', () => { -// const match = findExpressionType('__test_view', 'view'); -// expect(match).to.eql(expTypes[3]()); -// }); - -// it('returns null with no match by name and type', () => { -// const match = findExpressionType('__test_view', 'datasource'); -// expect(match).to.equal(null); -// }); -// }); -// }); - -// TODO: restore this test -// proxyquire can not be used to inject mock registries - -describe.skip('findExpressionType', () => {}); diff --git a/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js b/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js deleted file mode 100644 index 99d8305768240..0000000000000 --- a/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js +++ /dev/null @@ -1,240 +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 expect from '@kbn/expect'; -import lzString from 'lz-string'; -import { historyProvider } from '../history_provider'; - -function createState() { - return { - transient: { - selectedPage: 'page-f3ce-4bb7-86c8-0417606d6592', - selectedToplevelNodes: ['element-d88c-4bbd-9453-db22e949b92e'], - resolvedArgs: {}, - }, - persistent: { - schemaVersion: 0, - time: new Date().getTime(), - }, - }; -} - -describe.skip('historyProvider', () => { - let history; - let state; - - beforeEach(() => { - history = historyProvider(); - state = createState(); - }); - - describe('instances', () => { - it('should return the same instance for the same window object', () => { - expect(historyProvider()).to.equal(history); - }); - - it('should return different instance for different window object', () => { - const newWindow = {}; - expect(historyProvider(newWindow)).not.to.be(history); - }); - }); - - describe('push updates', () => { - beforeEach(() => { - history.push(state); - }); - - afterEach(() => { - // reset state back to initial after each test - history.undo(); - }); - - describe('push', () => { - it('should add state to location', () => { - expect(history.getLocation().state).to.eql(state); - }); - - it('should push compressed state into history', () => { - const hist = history.historyInstance; - expect(hist.location.state).to.equal(lzString.compress(JSON.stringify(state))); - }); - }); - - describe.skip('undo', () => { - it('should move history back', () => { - // pushed location has state value - expect(history.getLocation().state).to.eql(state); - - // back to initial location with null state - history.undo(); - expect(history.getLocation().state).to.be(null); - }); - }); - - describe.skip('redo', () => { - it('should move history forward', () => { - // back to initial location, with null state - history.undo(); - expect(history.getLocation().state).to.be(null); - - // return to pushed location, with state value - history.redo(); - expect(history.getLocation().state).to.eql(state); - }); - }); - }); - - describe.skip('replace updates', () => { - beforeEach(() => { - history.replace(state); - }); - - afterEach(() => { - // reset history to default after each test - history.replace(null); - }); - - describe('replace', () => { - it('should replace state in window history', () => { - expect(history.getLocation().state).to.eql(state); - }); - - it('should replace compressed state into history', () => { - const hist = history.historyInstance; - expect(hist.location.state).to.equal(lzString.compress(JSON.stringify(state))); - }); - }); - }); - - describe('onChange', () => { - const createOnceHandler = (history, done, fn) => { - const teardown = history.onChange((location, prevLocation) => { - if (typeof fn === 'function') { - fn(location, prevLocation); - } - teardown(); - done(); - }); - }; - - it('should return a method to remove the listener', () => { - const handler = () => 'hello world'; - const teardownFn = history.onChange(handler); - - expect(teardownFn).to.be.a('function'); - - // teardown the listener - teardownFn(); - }); - - it('should call handler on state change', (done) => { - createOnceHandler(history, done, (loc) => { - expect(loc).to.be.a('object'); - }); - - history.push({}); - }); - - it('should pass location object to handler', (done) => { - createOnceHandler(history, done, (location) => { - expect(location.pathname).to.be.a('string'); - expect(location.hash).to.be.a('string'); - expect(location.state).to.be.an('object'); - expect(location.action).to.equal('push'); - }); - - history.push(state); - }); - - it('should pass decompressed state to handler', (done) => { - createOnceHandler(history, done, ({ state: curState }) => { - expect(curState).to.eql(state); - }); - - history.push(state); - }); - - it('should pass in the previous location object to handler', (done) => { - createOnceHandler(history, done, (location, prevLocation) => { - expect(prevLocation.pathname).to.be.a('string'); - expect(prevLocation.hash).to.be.a('string'); - expect(prevLocation.state).to.be(null); - expect(prevLocation.action).to.equal('push'); - }); - - history.push(state); - }); - }); - - describe('resetOnChange', () => { - // the history onChange handler was made async and now there's no way to know when the handler was called - // TODO: restore these tests. - it.skip('removes listeners', () => { - const createHandler = () => { - let callCount = 0; - - function handlerFn() { - callCount += 1; - } - handlerFn.getCallCount = () => callCount; - - return handlerFn; - }; - - const handler1 = createHandler(); - const handler2 = createHandler(); - - // attach and test the first handler - history.onChange(handler1); - - expect(handler1.getCallCount()).to.equal(0); - history.push({}); - expect(handler1.getCallCount()).to.equal(1); - - // attach and test the second handler - history.onChange(handler2); - - expect(handler2.getCallCount()).to.equal(0); - history.push({}); - expect(handler1.getCallCount()).to.equal(2); - expect(handler2.getCallCount()).to.equal(1); - - // remove all handlers - history.resetOnChange(); - history.push({}); - expect(handler1.getCallCount()).to.equal(2); - expect(handler2.getCallCount()).to.equal(1); - }); - }); - - describe('parse', () => { - it('returns the decompressed object', () => { - history.push(state); - - const hist = history.historyInstance; - const rawState = hist.location.state; - - expect(rawState).to.be.a('string'); - expect(history.parse(rawState)).to.eql(state); - }); - - it('returns null with invalid JSON', () => { - expect(history.parse('hello')).to.be(null); - }); - }); - - describe('encode', () => { - it('returns the compressed string', () => { - history.push(state); - - const hist = history.historyInstance; - const rawState = hist.location.state; - - expect(rawState).to.be.a('string'); - expect(history.encode(state)).to.eql(rawState); - }); - }); -}); diff --git a/x-pack/plugins/canvas/public/lib/__tests__/modify_path.js b/x-pack/plugins/canvas/public/lib/__tests__/modify_path.js deleted file mode 100644 index 75454890f9717..0000000000000 --- a/x-pack/plugins/canvas/public/lib/__tests__/modify_path.js +++ /dev/null @@ -1,34 +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 expect from '@kbn/expect'; -import { prepend, append } from '../modify_path'; - -describe('modify paths', () => { - describe('prepend', () => { - it('prepends a string path', () => { - expect(prepend('a.b.c', '0')).to.eql([0, 'a', 'b', 'c']); - expect(prepend('a.b.c', ['0', '1'])).to.eql([0, 1, 'a', 'b', 'c']); - }); - - it('prepends an array path', () => { - expect(prepend(['a', 1, 'last'], '0')).to.eql([0, 'a', 1, 'last']); - expect(prepend(['a', 1, 'last'], [0, 1])).to.eql([0, 1, 'a', 1, 'last']); - }); - }); - - describe('append', () => { - it('appends to a string path', () => { - expect(append('one.2.3', 'zero')).to.eql(['one', 2, 3, 'zero']); - expect(append('one.2.3', ['zero', 'one'])).to.eql(['one', 2, 3, 'zero', 'one']); - }); - - it('appends to an array path', () => { - expect(append(['testString'], 'huzzah')).to.eql(['testString', 'huzzah']); - expect(append(['testString'], ['huzzah', 'yosh'])).to.eql(['testString', 'huzzah', 'yosh']); - }); - }); -}); diff --git a/x-pack/plugins/canvas/public/lib/__tests__/get_pretty_shortcut.test.ts b/x-pack/plugins/canvas/public/lib/get_pretty_shortcut.test.ts similarity index 98% rename from x-pack/plugins/canvas/public/lib/__tests__/get_pretty_shortcut.test.ts rename to x-pack/plugins/canvas/public/lib/get_pretty_shortcut.test.ts index 783b085f3da7e..95cffefde7b1c 100644 --- a/x-pack/plugins/canvas/public/lib/__tests__/get_pretty_shortcut.test.ts +++ b/x-pack/plugins/canvas/public/lib/get_pretty_shortcut.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getPrettyShortcut } from '../get_pretty_shortcut'; +import { getPrettyShortcut } from './get_pretty_shortcut'; describe('getPrettyShortcut', () => { test('uppercases shortcuts', () => { diff --git a/x-pack/plugins/canvas/public/lib/modify_path.test.ts b/x-pack/plugins/canvas/public/lib/modify_path.test.ts new file mode 100644 index 0000000000000..245b91ca4ccd4 --- /dev/null +++ b/x-pack/plugins/canvas/public/lib/modify_path.test.ts @@ -0,0 +1,33 @@ +/* + * 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 { prepend, append } from './modify_path'; + +describe('modify paths', () => { + describe('prepend', () => { + it('prepends a string path', () => { + expect(prepend('a.b.c', '0')).toEqual(['0', 'a', 'b', 'c']); + expect(prepend('a.b.c', ['0', '1'])).toEqual(['0', '1', 'a', 'b', 'c']); + }); + + it('prepends an array path', () => { + expect(prepend(['a', 1, 'last'], '0')).toEqual(['0', 'a', '1', 'last']); + expect(prepend(['a', 1, 'last'], [0, 1])).toEqual(['0', '1', 'a', '1', 'last']); + }); + }); + + describe('append', () => { + it('appends to a string path', () => { + expect(append('one.2.3', 'zero')).toEqual(['one', '2', '3', 'zero']); + expect(append('one.2.3', ['zero', 'one'])).toEqual(['one', '2', '3', 'zero', 'one']); + }); + + it('appends to an array path', () => { + expect(append(['testString'], 'huzzah')).toEqual(['testString', 'huzzah']); + expect(append(['testString'], ['huzzah', 'yosh'])).toEqual(['testString', 'huzzah', 'yosh']); + }); + }); +}); diff --git a/x-pack/plugins/canvas/public/lib/modify_path.js b/x-pack/plugins/canvas/public/lib/modify_path.ts similarity index 61% rename from x-pack/plugins/canvas/public/lib/modify_path.js rename to x-pack/plugins/canvas/public/lib/modify_path.ts index 714a616679bc9..a5b8f0316d23e 100644 --- a/x-pack/plugins/canvas/public/lib/modify_path.js +++ b/x-pack/plugins/canvas/public/lib/modify_path.ts @@ -6,14 +6,16 @@ import { toPath } from 'lodash'; -export function prepend(path, value) { +export type Path = Array; + +export function prepend(path: string | Path, value: string | Path): Path { return toPath(value).concat(toPath(path)); } -export function append(path, value) { +export function append(path: string | Path, value: string | Path): Path { return toPath(path).concat(toPath(value)); } -export function convert(path) { +export function convert(path: string | Path): Path { return toPath(path); } diff --git a/x-pack/plugins/canvas/public/lib/__tests__/readable_color.test.ts b/x-pack/plugins/canvas/public/lib/readable_color.test.ts similarity index 94% rename from x-pack/plugins/canvas/public/lib/__tests__/readable_color.test.ts rename to x-pack/plugins/canvas/public/lib/readable_color.test.ts index bd79655ca727b..ce7cf03c2889c 100644 --- a/x-pack/plugins/canvas/public/lib/__tests__/readable_color.test.ts +++ b/x-pack/plugins/canvas/public/lib/readable_color.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { readableColor } from '../readable_color'; +import { readableColor } from './readable_color'; describe('readableColor', () => { test('light', () => { diff --git a/x-pack/plugins/canvas/public/lib/__tests__/resolved_arg.js b/x-pack/plugins/canvas/public/lib/resolved_arg.test.ts similarity index 57% rename from x-pack/plugins/canvas/public/lib/__tests__/resolved_arg.js rename to x-pack/plugins/canvas/public/lib/resolved_arg.test.ts index 9e582ddd1858b..fac2023fb2ce5 100644 --- a/x-pack/plugins/canvas/public/lib/__tests__/resolved_arg.js +++ b/x-pack/plugins/canvas/public/lib/resolved_arg.test.ts @@ -4,39 +4,38 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { getState, getValue, getError } from '../resolved_arg'; +import { getState, getValue, getError } from './resolved_arg'; describe('resolved arg helper', () => { describe('getState', () => { it('returns pending by default', () => { - expect(getState()).to.be(null); + expect(getState()).toBe(null); }); it('returns the state', () => { - expect(getState({ state: 'pending' })).to.equal('pending'); - expect(getState({ state: 'ready' })).to.equal('ready'); - expect(getState({ state: 'error' })).to.equal('error'); + expect(getState({ state: 'pending' })).toEqual('pending'); + expect(getState({ state: 'ready' })).toEqual('ready'); + expect(getState({ state: 'error' })).toEqual('error'); }); }); describe('getValue', () => { it('returns null by default', () => { - expect(getValue()).to.be(null); + expect(getValue()).toBe(null); }); it('returns the value', () => { - expect(getValue({ value: 'hello test' })).to.equal('hello test'); + expect(getValue({ value: 'hello test' })).toEqual('hello test'); }); }); describe('getError', () => { it('returns null by default', () => { - expect(getError()).to.be(null); + expect(getError()).toBe(null); }); it('returns null when state is not error', () => { - expect(getError({ state: 'pending', error: 'nope' })).to.be(null); + expect(getError({ state: 'pending', error: 'nope' })).toBe(null); }); it('returns the error', () => { @@ -46,8 +45,7 @@ describe('resolved arg helper', () => { error: new Error('i failed'), }; - expect(getError(arg)).to.be.an(Error); - expect(getError(arg).toString()).to.match(/i failed/); + expect(getError(arg)).toMatchInlineSnapshot(`[Error: i failed]`); }); }); }); diff --git a/x-pack/plugins/canvas/public/lib/resolved_arg.js b/x-pack/plugins/canvas/public/lib/resolved_arg.ts similarity index 75% rename from x-pack/plugins/canvas/public/lib/resolved_arg.js rename to x-pack/plugins/canvas/public/lib/resolved_arg.ts index 8a9da8e466f7e..77f8a6cf8e5e0 100644 --- a/x-pack/plugins/canvas/public/lib/resolved_arg.js +++ b/x-pack/plugins/canvas/public/lib/resolved_arg.ts @@ -6,15 +6,15 @@ import { get } from 'lodash'; -export function getState(resolvedArg) { +export function getState(resolvedArg?: any): any { return get(resolvedArg, 'state', null); } -export function getValue(resolvedArg) { +export function getValue(resolvedArg?: any): any { return get(resolvedArg, 'value', null); } -export function getError(resolvedArg) { +export function getError(resolvedArg?: any): any { if (getState(resolvedArg) !== 'error') { return null; } diff --git a/x-pack/plugins/canvas/public/lib/__tests__/time_interval.test.ts b/x-pack/plugins/canvas/public/lib/time_interval.test.ts similarity index 98% rename from x-pack/plugins/canvas/public/lib/__tests__/time_interval.test.ts rename to x-pack/plugins/canvas/public/lib/time_interval.test.ts index 2dab00631cce1..8a057793ead79 100644 --- a/x-pack/plugins/canvas/public/lib/__tests__/time_interval.test.ts +++ b/x-pack/plugins/canvas/public/lib/time_interval.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getTimeInterval, createTimeInterval, isValidTimeInterval } from '../time_interval'; +import { getTimeInterval, createTimeInterval, isValidTimeInterval } from './time_interval'; describe('time_interval', () => { test('getTimeInterval', () => { diff --git a/x-pack/plugins/canvas/public/state/actions/__tests__/elements.get_sibling_context.js b/x-pack/plugins/canvas/public/state/actions/__tests__/elements.get_sibling_context.js deleted file mode 100644 index 198ccb2ffc381..0000000000000 --- a/x-pack/plugins/canvas/public/state/actions/__tests__/elements.get_sibling_context.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { getSiblingContext } from '../elements'; - -const state = { - transient: { - resolvedArgs: { - 'element-foo': { - expressionContext: { - '0': { - state: 'ready', - value: { - type: 'datatable', - columns: [ - { name: 'project', type: 'string' }, - { name: 'cost', type: 'string' }, - { name: 'age', type: 'string' }, - ], - rows: [ - { project: 'pandas', cost: '500', age: '18' }, - { project: 'tigers', cost: '200', age: '12' }, - ], - }, - error: null, - }, - '1': { - state: 'ready', - value: { - type: 'datatable', - columns: [ - { name: 'project', type: 'string' }, - { name: 'cost', type: 'string' }, - { name: 'age', type: 'string' }, - ], - rows: [ - { project: 'tigers', cost: '200', age: '12' }, - { project: 'pandas', cost: '500', age: '18' }, - ], - }, - error: null, - }, - '2': { - state: 'ready', - value: { - type: 'pointseries', - columns: { - x: { type: 'string', role: 'dimension', expression: 'cost' }, - y: { type: 'string', role: 'dimension', expression: 'project' }, - color: { type: 'string', role: 'dimension', expression: 'project' }, - }, - rows: [ - { x: '200', y: 'tigers', color: 'tigers' }, - { x: '500', y: 'pandas', color: 'pandas' }, - ], - }, - error: null, - }, - }, - }, - }, - }, -}; - -describe('actions/elements getSiblingContext', () => { - it('should find context when a previous context value is found', () => { - // pointseries map - expect(getSiblingContext(state, 'element-foo', 2)).to.eql({ - index: 2, - context: { - type: 'pointseries', - columns: { - x: { type: 'string', role: 'dimension', expression: 'cost' }, - y: { type: 'string', role: 'dimension', expression: 'project' }, - color: { type: 'string', role: 'dimension', expression: 'project' }, - }, - rows: [ - { x: '200', y: 'tigers', color: 'tigers' }, - { x: '500', y: 'pandas', color: 'pandas' }, - ], - }, - }); - }); - - it('should find context when a previous context value is not found', () => { - // pointseries map - expect(getSiblingContext(state, 'element-foo', 1000)).to.eql({ - index: 2, - context: { - type: 'pointseries', - columns: { - x: { type: 'string', role: 'dimension', expression: 'cost' }, - y: { type: 'string', role: 'dimension', expression: 'project' }, - color: { type: 'string', role: 'dimension', expression: 'project' }, - }, - rows: [ - { x: '200', y: 'tigers', color: 'tigers' }, - { x: '500', y: 'pandas', color: 'pandas' }, - ], - }, - }); - }); -}); diff --git a/x-pack/plugins/canvas/public/state/actions/elements.test.js b/x-pack/plugins/canvas/public/state/actions/elements.test.js new file mode 100644 index 0000000000000..a790e81e65e25 --- /dev/null +++ b/x-pack/plugins/canvas/public/state/actions/elements.test.js @@ -0,0 +1,106 @@ +/* + * 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 { getSiblingContext } from './elements'; + +describe('getSiblingContext', () => { + const state = { + transient: { + resolvedArgs: { + 'element-foo': { + expressionContext: { + '0': { + state: 'ready', + value: { + type: 'datatable', + columns: [ + { name: 'project', type: 'string' }, + { name: 'cost', type: 'string' }, + { name: 'age', type: 'string' }, + ], + rows: [ + { project: 'pandas', cost: '500', age: '18' }, + { project: 'tigers', cost: '200', age: '12' }, + ], + }, + error: null, + }, + '1': { + state: 'ready', + value: { + type: 'datatable', + columns: [ + { name: 'project', type: 'string' }, + { name: 'cost', type: 'string' }, + { name: 'age', type: 'string' }, + ], + rows: [ + { project: 'tigers', cost: '200', age: '12' }, + { project: 'pandas', cost: '500', age: '18' }, + ], + }, + error: null, + }, + '2': { + state: 'ready', + value: { + type: 'pointseries', + columns: { + x: { type: 'string', role: 'dimension', expression: 'cost' }, + y: { type: 'string', role: 'dimension', expression: 'project' }, + color: { type: 'string', role: 'dimension', expression: 'project' }, + }, + rows: [ + { x: '200', y: 'tigers', color: 'tigers' }, + { x: '500', y: 'pandas', color: 'pandas' }, + ], + }, + error: null, + }, + }, + }, + }, + }, + }; + + it('should find context when a previous context value is found', () => { + // pointseries map + expect(getSiblingContext(state, 'element-foo', 2)).toEqual({ + index: 2, + context: { + type: 'pointseries', + columns: { + x: { type: 'string', role: 'dimension', expression: 'cost' }, + y: { type: 'string', role: 'dimension', expression: 'project' }, + color: { type: 'string', role: 'dimension', expression: 'project' }, + }, + rows: [ + { x: '200', y: 'tigers', color: 'tigers' }, + { x: '500', y: 'pandas', color: 'pandas' }, + ], + }, + }); + }); + + it('should find context when a previous context value is not found', () => { + // pointseries map + expect(getSiblingContext(state, 'element-foo', 1000)).toEqual({ + index: 2, + context: { + type: 'pointseries', + columns: { + x: { type: 'string', role: 'dimension', expression: 'cost' }, + y: { type: 'string', role: 'dimension', expression: 'project' }, + color: { type: 'string', role: 'dimension', expression: 'project' }, + }, + rows: [ + { x: '200', y: 'tigers', color: 'tigers' }, + { x: '500', y: 'pandas', color: 'pandas' }, + ], + }, + }); + }); +}); diff --git a/x-pack/plugins/canvas/public/state/middleware/in_flight.ts b/x-pack/plugins/canvas/public/state/middleware/in_flight.ts index 028b9f214133f..d564a44b0b5f7 100644 --- a/x-pack/plugins/canvas/public/state/middleware/in_flight.ts +++ b/x-pack/plugins/canvas/public/state/middleware/in_flight.ts @@ -9,7 +9,6 @@ import { loadingIndicator as defaultLoadingIndicator, LoadingIndicatorInterface, } from '../../lib/loading_indicator'; -// @ts-expect-error import { convert } from '../../lib/modify_path'; interface InFlightMiddlewareOptions { diff --git a/x-pack/plugins/canvas/public/state/reducers/__tests__/fixtures/action_creator.js b/x-pack/plugins/canvas/public/state/reducers/__fixtures__/action_creator.js similarity index 100% rename from x-pack/plugins/canvas/public/state/reducers/__tests__/fixtures/action_creator.js rename to x-pack/plugins/canvas/public/state/reducers/__fixtures__/action_creator.js diff --git a/x-pack/plugins/canvas/public/state/reducers/__tests__/elements.js b/x-pack/plugins/canvas/public/state/reducers/elements.test.js similarity index 78% rename from x-pack/plugins/canvas/public/state/reducers/__tests__/elements.js rename to x-pack/plugins/canvas/public/state/reducers/elements.test.js index e1f7509325a7a..23f684879ce06 100644 --- a/x-pack/plugins/canvas/public/state/reducers/__tests__/elements.js +++ b/x-pack/plugins/canvas/public/state/reducers/elements.test.js @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { get } from 'lodash'; -import { elementsReducer } from '../elements'; -import { actionCreator } from './fixtures/action_creator'; +import { elementsReducer } from './elements'; +import { actionCreator } from './__fixtures__/action_creator'; describe('elements reducer', () => { let state; @@ -46,8 +44,8 @@ describe('elements reducer', () => { }); const newState = elementsReducer(state, action); - const newElement = get(newState, ['pages', 0, 'elements', 1]); + const newElement = newState?.pages?.[0]?.elements?.[1]; - expect(newElement).to.eql(expected); + expect(newElement).toEqual(expected); }); }); diff --git a/x-pack/plugins/canvas/public/state/reducers/__tests__/resolved_args.js b/x-pack/plugins/canvas/public/state/reducers/resolved_args.test.js similarity index 62% rename from x-pack/plugins/canvas/public/state/reducers/__tests__/resolved_args.js rename to x-pack/plugins/canvas/public/state/reducers/resolved_args.test.js index ee1d0fc1ca9ba..74f1544403e67 100644 --- a/x-pack/plugins/canvas/public/state/reducers/__tests__/resolved_args.js +++ b/x-pack/plugins/canvas/public/state/reducers/resolved_args.test.js @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import * as actions from '../../actions/resolved_args'; -import { flushContextAfterIndex } from '../../actions/elements'; -import { resolvedArgsReducer } from '../resolved_args'; -import { actionCreator } from './fixtures/action_creator'; +import * as actions from '../actions/resolved_args'; +import { flushContextAfterIndex } from '../actions/elements'; +import { resolvedArgsReducer } from './resolved_args'; +import { actionCreator } from './__fixtures__/action_creator'; describe('resolved args reducer', () => { let state; @@ -41,13 +40,15 @@ describe('resolved args reducer', () => { }); const newState = resolvedArgsReducer(state, action); - expect(newState.resolvedArgs['element-1']).to.eql([ - { - state: 'pending', - value: null, - error: null, - }, - ]); + expect(newState.resolvedArgs['element-1']).toMatchInlineSnapshot(` + Object { + "0": Object { + "error": null, + "state": "pending", + "value": null, + }, + } + `); }); it('sets state to loading, with array path', () => { @@ -56,13 +57,15 @@ describe('resolved args reducer', () => { }); const newState = resolvedArgsReducer(state, action); - expect(newState.resolvedArgs['element-1']).to.eql([ - { - state: 'pending', - value: null, - error: null, - }, - ]); + expect(newState.resolvedArgs['element-1']).toMatchInlineSnapshot(` + Object { + "0": Object { + "error": null, + "state": "pending", + "value": null, + }, + } + `); }); }); @@ -75,13 +78,15 @@ describe('resolved args reducer', () => { }); const newState = resolvedArgsReducer(state, action); - expect(newState.resolvedArgs['element-1']).to.eql([ - { - state: 'ready', - value, - error: null, - }, - ]); + expect(newState.resolvedArgs['element-1']).toMatchInlineSnapshot(` + Object { + "0": Object { + "error": null, + "state": "ready", + "value": "hello world", + }, + } + `); }); it('handles error values', () => { @@ -92,13 +97,15 @@ describe('resolved args reducer', () => { }); const newState = resolvedArgsReducer(state, action); - expect(newState.resolvedArgs['element-1']).to.eql([ - { - state: 'error', - value: null, - error: err, - }, - ]); + expect(newState.resolvedArgs['element-1']).toMatchInlineSnapshot(` + Object { + "0": Object { + "error": [Error: farewell world], + "state": "error", + "value": null, + }, + } + `); }); it('preserves old value on error', () => { @@ -109,11 +116,13 @@ describe('resolved args reducer', () => { }); const newState = resolvedArgsReducer(state, action); - expect(newState.resolvedArgs['element-0'][0]).to.eql({ - state: 'error', - value: 'testing', - error: err, - }); + expect(newState.resolvedArgs['element-0'][0]).toMatchInlineSnapshot(` + Object { + "error": [Error: farewell world], + "state": "error", + "value": "testing", + } + `); }); }); @@ -124,14 +133,15 @@ describe('resolved args reducer', () => { }); const newState = resolvedArgsReducer(state, action); - expect(newState.resolvedArgs['element-0']).to.have.length(1); - expect(newState.resolvedArgs['element-0']).to.eql([ - { - state: 'ready', - value: 'testing', - error: null, - }, - ]); + expect(newState.resolvedArgs['element-0']).toMatchInlineSnapshot(` + Array [ + Object { + "error": null, + "state": "ready", + "value": "testing", + }, + ] + `); }); it('deeply removes resolved values', () => { @@ -140,7 +150,7 @@ describe('resolved args reducer', () => { }); const newState = resolvedArgsReducer(state, action); - expect(newState.resolvedArgs['element-0']).to.be(undefined); + expect(newState.resolvedArgs['element-0']).toBe(undefined); }); }); @@ -183,12 +193,22 @@ describe('resolved args reducer', () => { }); const newState = resolvedArgsReducer(state, action); - expect(newState.resolvedArgs['element-1']).to.eql({ - expressionContext: { - '1': { state: 'ready', value: 'test-1', error: null }, - '2': { state: 'ready', value: 'test-2', error: null }, - }, - }); + expect(newState.resolvedArgs['element-1']).toMatchInlineSnapshot(` + Object { + "expressionContext": Object { + "1": Object { + "error": null, + "state": "ready", + "value": "test-1", + }, + "2": Object { + "error": null, + "state": "ready", + "value": "test-2", + }, + }, + } + `); }); }); }); diff --git a/x-pack/plugins/canvas/public/state/selectors/__tests__/resolved_args.js b/x-pack/plugins/canvas/public/state/selectors/resolved_args.test.js similarity index 55% rename from x-pack/plugins/canvas/public/state/selectors/__tests__/resolved_args.js rename to x-pack/plugins/canvas/public/state/selectors/resolved_args.test.js index 3157201927854..1710d6704f980 100644 --- a/x-pack/plugins/canvas/public/state/selectors/__tests__/resolved_args.js +++ b/x-pack/plugins/canvas/public/state/selectors/resolved_args.test.js @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import * as selector from '../resolved_args'; +import * as selector from './resolved_args'; describe('resolved args selector', () => { let state; @@ -35,21 +34,20 @@ describe('resolved args selector', () => { }); it('getValue returns the state', () => { - expect(selector.getState(state, 'test1')).to.equal('ready'); - expect(selector.getState(state, 'test2')).to.equal('pending'); - expect(selector.getState(state, 'test3')).to.equal('error'); + expect(selector.getState(state, 'test1')).toEqual('ready'); + expect(selector.getState(state, 'test2')).toEqual('pending'); + expect(selector.getState(state, 'test3')).toEqual('error'); }); it('getValue returns the value', () => { - expect(selector.getValue(state, 'test1')).to.equal('test value'); - expect(selector.getValue(state, 'test2')).to.equal(null); - expect(selector.getValue(state, 'test3')).to.equal('some old value'); + expect(selector.getValue(state, 'test1')).toEqual('test value'); + expect(selector.getValue(state, 'test2')).toEqual(null); + expect(selector.getValue(state, 'test3')).toEqual('some old value'); }); it('getError returns the error', () => { - expect(selector.getError(state, 'test1')).to.equal(null); - expect(selector.getError(state, 'test2')).to.equal(null); - expect(selector.getError(state, 'test3')).to.be.an(Error); - expect(selector.getError(state, 'test3').toString()).to.match(/i\ have\ failed$/); + expect(selector.getError(state, 'test1')).toEqual(null); + expect(selector.getError(state, 'test2')).toEqual(null); + expect(selector.getError(state, 'test3')).toMatchInlineSnapshot(`[Error: i have failed]`); }); }); diff --git a/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts b/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts index 770d4403f8587..b557ff04921ca 100644 --- a/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts +++ b/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts @@ -5,9 +5,7 @@ */ import { get } from 'lodash'; -// @ts-expect-error untyped local import * as argHelper from '../../lib/resolved_arg'; -// @ts-expect-error untyped local import { prepend } from '../../lib/modify_path'; import { State } from '../../../types'; diff --git a/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js b/x-pack/plugins/canvas/public/state/selectors/workpad.test.js similarity index 80% rename from x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js rename to x-pack/plugins/canvas/public/state/selectors/workpad.test.js index 5fdc662c592cc..d5f7e003af858 100644 --- a/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js +++ b/x-pack/plugins/canvas/public/state/selectors/workpad.test.js @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import * as selector from '../workpad'; +import * as selector from './workpad'; describe('workpad selectors', () => { let asts; @@ -125,42 +124,42 @@ describe('workpad selectors', () => { describe('empty state', () => { it('returns undefined', () => { - expect(selector.getSelectedPage({})).to.be(undefined); - expect(selector.getPageById({}, 'page-1')).to.be(undefined); - expect(selector.getSelectedElement({})).to.be(undefined); - expect(selector.getElementById({}, 'element-1')).to.be(undefined); - expect(selector.getResolvedArgs({}, 'element-1')).to.be(undefined); - expect(selector.getSelectedResolvedArgs({})).to.be(undefined); - expect(selector.isWriteable({})).to.be(true); + expect(selector.getSelectedPage({})).toBe(undefined); + expect(selector.getPageById({}, 'page-1')).toBe(undefined); + expect(selector.getSelectedElement({})).toBe(undefined); + expect(selector.getElementById({}, 'element-1')).toBe(undefined); + expect(selector.getResolvedArgs({}, 'element-1')).toBe(undefined); + expect(selector.getSelectedResolvedArgs({})).toBe(undefined); + expect(selector.isWriteable({})).toBe(true); }); }); describe('getSelectedPage', () => { it('returns the selected page', () => { - expect(selector.getSelectedPage(state)).to.equal('page-1'); + expect(selector.getSelectedPage(state)).toEqual('page-1'); }); }); describe('getPages', () => { it('return an empty array with no pages', () => { - expect(selector.getPages({})).to.eql([]); + expect(selector.getPages({})).toEqual([]); }); it('returns all pages in persisent state', () => { - expect(selector.getPages(state)).to.eql(state.persistent.workpad.pages); + expect(selector.getPages(state)).toEqual(state.persistent.workpad.pages); }); }); describe('getPageById', () => { it('should return matching page', () => { - expect(selector.getPageById(state, 'page-1')).to.eql(state.persistent.workpad.pages[0]); + expect(selector.getPageById(state, 'page-1')).toEqual(state.persistent.workpad.pages[0]); }); }); describe('getSelectedElement', () => { it('returns selected element', () => { const { elements } = state.persistent.workpad.pages[0]; - expect(selector.getSelectedElement(state)).to.eql({ + expect(selector.getSelectedElement(state)).toEqual({ ...elements[1], ast: asts['element-1'], }); @@ -169,7 +168,7 @@ describe('workpad selectors', () => { describe('getElements', () => { it('is an empty array with no state', () => { - expect(selector.getElements({})).to.eql([]); + expect(selector.getElements({})).toEqual([]); }); it('returns all elements on the page', () => { @@ -179,18 +178,18 @@ describe('workpad selectors', () => { ...element, ast: asts[element.id], })); - expect(selector.getElements(state)).to.eql(expected); + expect(selector.getElements(state)).toEqual(expected); }); }); describe('getElementById', () => { it('returns element matching id', () => { const { elements } = state.persistent.workpad.pages[0]; - expect(selector.getElementById(state, 'element-0')).to.eql({ + expect(selector.getElementById(state, 'element-0')).toEqual({ ...elements[0], ast: asts['element-0'], }); - expect(selector.getElementById(state, 'element-1')).to.eql({ + expect(selector.getElementById(state, 'element-1')).toEqual({ ...elements[1], ast: asts['element-1'], }); @@ -199,18 +198,18 @@ describe('workpad selectors', () => { describe('getResolvedArgs', () => { it('returns resolved args by element id', () => { - expect(selector.getResolvedArgs(state, 'element-0')).to.equal('test resolved arg, el 0'); + expect(selector.getResolvedArgs(state, 'element-0')).toEqual('test resolved arg, el 0'); }); it('returns resolved args at given path', () => { const arg = selector.getResolvedArgs(state, 'element-2', 'example1'); - expect(arg).to.equal('first thing'); + expect(arg).toEqual('first thing'); }); }); describe('getSelectedResolvedArgs', () => { it('returns resolved args for selected element', () => { - expect(selector.getSelectedResolvedArgs(state)).to.equal('test resolved arg, el 1'); + expect(selector.getSelectedResolvedArgs(state)).toEqual('test resolved arg, el 1'); }); it('returns resolved args at given path', () => { @@ -222,7 +221,7 @@ describe('workpad selectors', () => { }, }; const arg = selector.getSelectedResolvedArgs(tmpState, 'example2'); - expect(arg).to.eql(['why not', 'an array?']); + expect(arg).toEqual(['why not', 'an array?']); }); it('returns resolved args at given deep path', () => { @@ -234,14 +233,14 @@ describe('workpad selectors', () => { }, }; const arg = selector.getSelectedResolvedArgs(tmpState, ['example3', 'deeper', 'object']); - expect(arg).to.be(true); + expect(arg).toBe(true); }); }); describe('getGlobalFilters', () => { it('gets filters from all elements', () => { const filters = selector.getGlobalFilters(state); - expect(filters).to.eql([ + expect(filters).toEqual([ 'exactly value="beats" column="project"', 'timefilter filterGroup=one column=@timestamp from=now-24h to=now', ]); @@ -249,17 +248,14 @@ describe('workpad selectors', () => { it('gets returns empty array with no elements', () => { const filters = selector.getGlobalFilters({}); - expect(filters).to.be.an(Array); - expect(filters).to.have.length(0); + expect(filters).toEqual([]); }); }); describe('getGlobalFilterGroups', () => { it('gets filter group from elements', () => { const filterGroups = selector.getGlobalFilterGroups(state); - expect(filterGroups).to.be.an(Array); - expect(filterGroups).to.have.length(1); - expect(filterGroups[0]).to.equal('one'); + expect(filterGroups).toEqual(['one']); }); it('gets all unique filter groups', () => { @@ -282,7 +278,7 @@ describe('workpad selectors', () => { }); // filters are alphabetical - expect(filterGroups).to.eql(['one', 'two']); + expect(filterGroups).toEqual(['one', 'two']); }); it('gets filter groups in filter function args', () => { @@ -311,13 +307,13 @@ describe('workpad selectors', () => { // {string two} is skipped, only primitive values are extracted // filterGroup=one and {filters one} are de-duped // filters are alphabetical - expect(filterGroups).to.eql(['four', 'one', 'three']); + expect(filterGroups).toEqual(['four', 'one', 'three']); }); }); describe('isWriteable', () => { it('returns boolean for if the workpad is writeable', () => { - expect(selector.isWriteable(state)).to.equal(false); + expect(selector.isWriteable(state)).toEqual(false); }); }); }); diff --git a/x-pack/plugins/canvas/public/state/selectors/workpad.ts b/x-pack/plugins/canvas/public/state/selectors/workpad.ts index a677bcaf29e61..b05615b7930c5 100644 --- a/x-pack/plugins/canvas/public/state/selectors/workpad.ts +++ b/x-pack/plugins/canvas/public/state/selectors/workpad.ts @@ -7,7 +7,6 @@ import { get, omit } from 'lodash'; // @ts-expect-error untyped local import { safeElementFromExpression, fromExpression } from '@kbn/interpreter/common'; -// @ts-expect-error untyped local import { append } from '../../lib/modify_path'; import { getAssets } from './assets'; import { From dd4796cfdd2a7e6081a8c16d0e5fe0c8593b87f3 Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 27 Jul 2020 11:07:58 -0700 Subject: [PATCH 05/36] Remove karma (#73126) Co-authored-by: spalger --- .../contributing/development-tests.asciidoc | 2 - .../development-unit-tests.asciidoc | 33 - package.json | 11 - .../lib/get_webpack_config.js | 4 - packages/kbn-plugin-generator/README.md | 4 - packages/kbn-plugin-helpers/README.md | 1 - packages/kbn-plugin-helpers/src/cli.ts | 19 - packages/kbn-plugin-helpers/src/lib/tasks.ts | 6 - .../src/tasks/test/all/README.md | 3 - .../src/tasks/test/all/index.ts | 20 - .../src/tasks/test/all/test_all_task.ts | 25 - .../src/tasks/test/karma/README.md | 60 -- .../src/tasks/test/karma/index.ts | 20 - .../src/tasks/test/karma/test_karma_task.ts | 42 -- .../__fixtures__/index.ts | 1 - .../__fixtures__/karma_report.xml | 33 - .../add_messages_to_report.test.ts | 84 +-- .../get_failures.test.ts | 27 +- .../report_metadata.test.ts | 5 +- packages/kbn-ui-shared-deps/theme.ts | 2 +- src/core/MIGRATION.md | 8 - src/core/public/legacy/legacy_service.ts | 2 +- src/core/server/http/http_config.ts | 16 +- src/dev/build/tasks/copy_source_task.ts | 1 - src/dev/jest/config.js | 1 - src/dev/precommit_hook/casing_check_config.js | 1 - .../tests_bundle/find_source_files.js | 64 -- src/legacy/core_plugins/tests_bundle/index.js | 180 ----- .../core_plugins/tests_bundle/package.json | 4 - .../tests_bundle/public/index.scss | 4 - .../tests_bundle/tests_entry_template.js | 158 ----- .../webpackShims/angular-mocks.js | 21 - src/legacy/ui/public/test_harness/.eslintrc | 2 - src/legacy/ui/public/test_harness/index.js | 20 - .../ui/public/test_harness/test_harness.css | 22 - .../ui/public/test_harness/test_harness.js | 89 --- .../test_sharding/find_test_bundle_url.js | 39 -- .../test_sharding/get_shard_num.js | 47 -- .../get_sharding_params_from_url.js | 45 -- .../test_harness/test_sharding/index.js | 20 - .../test_sharding/setup_test_sharding.js | 69 -- .../setup_top_level_describe_filter.js | 125 ---- .../ui/ui_exports/ui_export_defaults.js | 1 - tasks/config/karma.js | 207 ------ tasks/config/run.js | 72 +- tasks/jenkins.js | 1 - tasks/test.js | 27 +- test/scripts/jenkins_xpack.sh | 6 +- x-pack/README.md | 8 - x-pack/gulpfile.js | 4 - x-pack/package.json | 2 - x-pack/plugins/canvas/scripts/test_browser.js | 7 - x-pack/plugins/canvas/scripts/test_dev.js | 7 - x-pack/tasks/test.ts | 31 - yarn.lock | 629 ++---------------- 55 files changed, 76 insertions(+), 2266 deletions(-) delete mode 100644 packages/kbn-plugin-helpers/src/tasks/test/all/README.md delete mode 100644 packages/kbn-plugin-helpers/src/tasks/test/all/index.ts delete mode 100644 packages/kbn-plugin-helpers/src/tasks/test/all/test_all_task.ts delete mode 100644 packages/kbn-plugin-helpers/src/tasks/test/karma/README.md delete mode 100644 packages/kbn-plugin-helpers/src/tasks/test/karma/index.ts delete mode 100644 packages/kbn-plugin-helpers/src/tasks/test/karma/test_karma_task.ts delete mode 100644 packages/kbn-test/src/failed_tests_reporter/__fixtures__/karma_report.xml delete mode 100644 src/legacy/core_plugins/tests_bundle/find_source_files.js delete mode 100644 src/legacy/core_plugins/tests_bundle/index.js delete mode 100644 src/legacy/core_plugins/tests_bundle/package.json delete mode 100644 src/legacy/core_plugins/tests_bundle/public/index.scss delete mode 100644 src/legacy/core_plugins/tests_bundle/tests_entry_template.js delete mode 100644 src/legacy/core_plugins/tests_bundle/webpackShims/angular-mocks.js delete mode 100644 src/legacy/ui/public/test_harness/.eslintrc delete mode 100644 src/legacy/ui/public/test_harness/index.js delete mode 100644 src/legacy/ui/public/test_harness/test_harness.css delete mode 100644 src/legacy/ui/public/test_harness/test_harness.js delete mode 100644 src/legacy/ui/public/test_harness/test_sharding/find_test_bundle_url.js delete mode 100644 src/legacy/ui/public/test_harness/test_sharding/get_shard_num.js delete mode 100644 src/legacy/ui/public/test_harness/test_sharding/get_sharding_params_from_url.js delete mode 100644 src/legacy/ui/public/test_harness/test_sharding/index.js delete mode 100644 src/legacy/ui/public/test_harness/test_sharding/setup_test_sharding.js delete mode 100644 src/legacy/ui/public/test_harness/test_sharding/setup_top_level_describe_filter.js delete mode 100644 tasks/config/karma.js delete mode 100644 x-pack/plugins/canvas/scripts/test_browser.js delete mode 100644 x-pack/plugins/canvas/scripts/test_dev.js delete mode 100644 x-pack/tasks/test.ts diff --git a/docs/developer/contributing/development-tests.asciidoc b/docs/developer/contributing/development-tests.asciidoc index 78a2a90b69ce5..2e40f664faba9 100644 --- a/docs/developer/contributing/development-tests.asciidoc +++ b/docs/developer/contributing/development-tests.asciidoc @@ -26,8 +26,6 @@ root) |Functional |`test/*integration/**/config.js` `test/*functional/**/config.js` `test/accessibility/config.js` |`yarn test:ftr:server --config test/[directory]/config.js``yarn test:ftr:runner --config test/[directory]/config.js --grep=regexp` - -|Karma |`src/**/public/__tests__/*.js` |`yarn test:karma:debug` |=== For X-Pack tests located in `x-pack/` see diff --git a/docs/developer/contributing/development-unit-tests.asciidoc b/docs/developer/contributing/development-unit-tests.asciidoc index 8b4954150bb5b..5322106b17ac1 100644 --- a/docs/developer/contributing/development-unit-tests.asciidoc +++ b/docs/developer/contributing/development-unit-tests.asciidoc @@ -95,38 +95,6 @@ to proceed in this mode. node scripts/mocha --debug ---- -With `yarn test:karma`, you can run only the browser tests. Coverage -reports are available for browser tests by running -`yarn test:coverage`. You can find the results under the `coverage/` -directory that will be created upon completion. - -[source,bash] ----- -yarn test:karma ----- - -Using `yarn test:karma:debug` initializes an environment for debugging -the browser tests. Includes an dedicated instance of the {kib} server -for building the test bundle, and a karma server. When running this task -the build is optimized for the first time and then a karma-owned -instance of the browser is opened. Click the "`debug`" button to open a -new tab that executes the unit tests. - -[source,bash] ----- -yarn test:karma:debug ----- - -In the screenshot below, you’ll notice the URL is -`localhost:9876/debug.html`. You can append a `grep` query parameter -to this URL and set it to a string value which will be used to exclude -tests which don’t match. For example, if you changed the URL to -`localhost:9876/debug.html?query=my test` and then refreshed the -browser, you’d only see tests run which contain "`my test`" in the test -description. - -image:http://i.imgur.com/DwHxgfq.png[Browser test debugging] - [discrete] === Unit Testing Plugins @@ -141,5 +109,4 @@ command from your plugin: [source,bash] ---- yarn test:mocha -yarn test:karma:debug # remove the debug flag to run them once and close ---- \ No newline at end of file diff --git a/package.json b/package.json index ee91c59a8fda6..0c49ec26be194 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,6 @@ "kbn": "node scripts/kbn", "es": "node scripts/es", "test": "grunt test", - "test:karma": "grunt test:karma", - "test:karma:debug": "grunt test:karmaDebug", "test:jest": "node scripts/jest", "test:jest_integration": "node scripts/jest_integration", "test:mocha": "node scripts/mocha", @@ -447,7 +445,6 @@ "grunt-available-tasks": "^0.6.3", "grunt-cli": "^1.2.0", "grunt-contrib-watch": "^1.1.0", - "grunt-karma": "^3.0.2", "grunt-peg": "^2.0.1", "grunt-run": "0.8.1", "gulp-babel": "^8.0.0", @@ -465,14 +462,6 @@ "jest-raw-loader": "^1.0.1", "jimp": "^0.9.6", "json5": "^1.0.1", - "karma": "5.0.2", - "karma-chrome-launcher": "2.2.0", - "karma-coverage": "1.1.2", - "karma-firefox-launcher": "1.1.0", - "karma-ie-launcher": "1.0.0", - "karma-junit-reporter": "1.2.0", - "karma-mocha": "2.0.0", - "karma-safari-launcher": "1.0.0", "license-checker": "^16.0.0", "listr": "^0.14.1", "load-grunt-config": "^3.0.1", diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js index 6cb2f3d2901d3..baf5baaf916aa 100755 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js @@ -28,13 +28,9 @@ exports.getWebpackConfig = function (kibanaPath, projectRoot, config) { const alias = { // Kibana defaults https://github.com/elastic/kibana/blob/6998f074542e8c7b32955db159d15661aca253d7/src/legacy/ui/ui_bundler_env.js#L30-L36 ui: fromKibana('src/legacy/ui/public'), - test_harness: fromKibana('src/test_harness/public'), // Dev defaults for test bundle https://github.com/elastic/kibana/blob/6998f074542e8c7b32955db159d15661aca253d7/src/core_plugins/tests_bundle/index.js#L73-L78 ng_mock$: fromKibana('src/test_utils/public/ng_mock'), - 'angular-mocks$': fromKibana( - 'src/legacy/core_plugins/tests_bundle/webpackShims/angular-mocks.js' - ), fixtures: fromKibana('src/fixtures'), test_utils: fromKibana('src/test_utils/public'), }; diff --git a/packages/kbn-plugin-generator/README.md b/packages/kbn-plugin-generator/README.md index 95de0e93fd075..6ad665f9b87f8 100644 --- a/packages/kbn-plugin-generator/README.md +++ b/packages/kbn-plugin-generator/README.md @@ -71,10 +71,6 @@ Generated plugins receive a handful of scripts that can be used during developme Build a distributable archive of your plugin. - - `yarn test:karma` - - Run the browser tests in a real web browser. - - `yarn test:mocha` Run the server tests using mocha. diff --git a/packages/kbn-plugin-helpers/README.md b/packages/kbn-plugin-helpers/README.md index 4c648fd9bde8c..d7ed3106c1ceb 100644 --- a/packages/kbn-plugin-helpers/README.md +++ b/packages/kbn-plugin-helpers/README.md @@ -30,7 +30,6 @@ $ plugin-helpers help start Start kibana and have it include this plugin build [options] [files...] Build a distributable archive test Run the server and browser tests - test:karma [options] Run the browser tests in a real web browser test:mocha [files...] Run the server tests using mocha Options: diff --git a/packages/kbn-plugin-helpers/src/cli.ts b/packages/kbn-plugin-helpers/src/cli.ts index b894f854a484f..18ddc62cba8a6 100644 --- a/packages/kbn-plugin-helpers/src/cli.ts +++ b/packages/kbn-plugin-helpers/src/cli.ts @@ -62,25 +62,6 @@ program })) ); -program - .command('test') - .description('Run the server and browser tests') - .on('--help', docs('test/all')) - .action(createCommanderAction('testAll')); - -program - .command('test:karma') - .description('Run the browser tests in a real web browser') - .option('--dev', 'Enable dev mode, keeps the test server running') - .option('-p, --plugins ', "Manually specify which plugins' test bundles to run") - .on('--help', docs('test/karma')) - .action( - createCommanderAction('testKarma', (command) => ({ - dev: Boolean(command.dev), - plugins: command.plugins, - })) - ); - program .command('test:mocha [files...]') .description('Run the server tests using mocha') diff --git a/packages/kbn-plugin-helpers/src/lib/tasks.ts b/packages/kbn-plugin-helpers/src/lib/tasks.ts index 7817838760a2e..bd86bb670ff39 100644 --- a/packages/kbn-plugin-helpers/src/lib/tasks.ts +++ b/packages/kbn-plugin-helpers/src/lib/tasks.ts @@ -19,23 +19,17 @@ import { buildTask } from '../tasks/build'; import { startTask } from '../tasks/start'; -import { testAllTask } from '../tasks/test/all'; -import { testKarmaTask } from '../tasks/test/karma'; import { testMochaTask } from '../tasks/test/mocha'; // define a tasks interface that we can extend in the tests export interface Tasks { build: typeof buildTask; start: typeof startTask; - testAll: typeof testAllTask; - testKarma: typeof testKarmaTask; testMocha: typeof testMochaTask; } export const tasks: Tasks = { build: buildTask, start: startTask, - testAll: testAllTask, - testKarma: testKarmaTask, testMocha: testMochaTask, }; diff --git a/packages/kbn-plugin-helpers/src/tasks/test/all/README.md b/packages/kbn-plugin-helpers/src/tasks/test/all/README.md deleted file mode 100644 index 4f5a72ac0d523..0000000000000 --- a/packages/kbn-plugin-helpers/src/tasks/test/all/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Runs both the mocha and karma tests, in that order. - -This is just a simple caller to both `test/mocha` and `test/karma` \ No newline at end of file diff --git a/packages/kbn-plugin-helpers/src/tasks/test/all/index.ts b/packages/kbn-plugin-helpers/src/tasks/test/all/index.ts deleted file mode 100644 index be8db50825fc9..0000000000000 --- a/packages/kbn-plugin-helpers/src/tasks/test/all/index.ts +++ /dev/null @@ -1,20 +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 * from './test_all_task'; diff --git a/packages/kbn-plugin-helpers/src/tasks/test/all/test_all_task.ts b/packages/kbn-plugin-helpers/src/tasks/test/all/test_all_task.ts deleted file mode 100644 index d07c19291d2cb..0000000000000 --- a/packages/kbn-plugin-helpers/src/tasks/test/all/test_all_task.ts +++ /dev/null @@ -1,25 +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 { TaskContext } from '../../../lib'; - -export function testAllTask({ run }: TaskContext) { - run('testMocha'); - run('testKarma'); -} diff --git a/packages/kbn-plugin-helpers/src/tasks/test/karma/README.md b/packages/kbn-plugin-helpers/src/tasks/test/karma/README.md deleted file mode 100644 index 8d921e8312344..0000000000000 --- a/packages/kbn-plugin-helpers/src/tasks/test/karma/README.md +++ /dev/null @@ -1,60 +0,0 @@ -writing tests -============= - -Browser tests are written just like server tests, they are just executed differently. - - - place tests near the code they test, in `__tests__` directories throughout - the public directory - - - Use the same bdd-style `describe()` and `it()` - api to define the suites and cases of your tests. - - ```js - describe('some portion of your code', function () { - it('should do this thing', function () { - expect(true).to.be(false); - }); - }); - ``` - - -starting the test runner -======================== - -Under the covers this command uses the `test:karma` task from kibana. This will execute -your tasks once and exit when complete. - -When run with the `--dev` option, the command uses the `test:karma:debug` task from kibana. -This task sets-up a test runner that will watch your code for changes and rebuild your -tests when necessary. You access the test runner through a browser that it starts itself -(via Karma). - -If your plugin consists of a number of internal plugins, you may wish to keep the tests -isolated to a specific plugin or plugins, instead of executing all of the tests. To do this, -use `--plugins` and passing the plugins you would like to test. Multiple plugins can be -specified by separating them with commas. - - -running the tests -================= - -Once the test runner has started you a new browser window should be opened and you should -see a message saying "connected". Next to that is a "DEBUG" button. This button will open -an interactive version of your tests that you can refresh, inspects, and otherwise debug -while you write your tests. - - -focus on the task at hand -========================= - -To limit the tests that run you can either: - - 1. use the ?grep= query string to filter the test cases/suites by name - 2. Click the suite title or (play) button next to test output - 3. Add `.only` to your `describe()` or `it()` calls: - - ```js - describe.only('suite name', function () { - // ... - }); - ``` diff --git a/packages/kbn-plugin-helpers/src/tasks/test/karma/index.ts b/packages/kbn-plugin-helpers/src/tasks/test/karma/index.ts deleted file mode 100644 index 3089357b49991..0000000000000 --- a/packages/kbn-plugin-helpers/src/tasks/test/karma/index.ts +++ /dev/null @@ -1,20 +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 * from './test_karma_task'; diff --git a/packages/kbn-plugin-helpers/src/tasks/test/karma/test_karma_task.ts b/packages/kbn-plugin-helpers/src/tasks/test/karma/test_karma_task.ts deleted file mode 100644 index 2fe8134209894..0000000000000 --- a/packages/kbn-plugin-helpers/src/tasks/test/karma/test_karma_task.ts +++ /dev/null @@ -1,42 +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 { execFileSync } from 'child_process'; - -import { TaskContext } from '../../../lib'; -import { winCmd } from '../../../lib/win_cmd'; - -export function testKarmaTask({ plugin, options }: TaskContext) { - options = options || {}; - - const kbnServerArgs = ['--kbnServer.plugin-path=' + plugin.root]; - - if (options.plugins) { - kbnServerArgs.push('--kbnServer.tests_bundle.pluginId=' + options.plugins); - } else { - kbnServerArgs.push('--kbnServer.tests_bundle.pluginId=' + plugin.id); - } - - const task = options.dev ? 'test:karma:debug' : 'test:karma'; - const args = [task].concat(kbnServerArgs); - execFileSync(winCmd('yarn'), args, { - cwd: plugin.kibanaRoot, - stdio: ['ignore', 1, 2], - }); -} diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts index 16ebe10ad5426..11d6cb6a2b47b 100644 --- a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts +++ b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts @@ -21,6 +21,5 @@ const Fs = jest.requireActual('fs'); export const FTR_REPORT = Fs.readFileSync(require.resolve('./ftr_report.xml'), 'utf8'); export const JEST_REPORT = Fs.readFileSync(require.resolve('./jest_report.xml'), 'utf8'); -export const KARMA_REPORT = Fs.readFileSync(require.resolve('./karma_report.xml'), 'utf8'); export const MOCHA_REPORT = Fs.readFileSync(require.resolve('./mocha_report.xml'), 'utf8'); export const CYPRESS_REPORT = Fs.readFileSync(require.resolve('./cypress_report.xml'), 'utf8'); diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/karma_report.xml b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/karma_report.xml deleted file mode 100644 index 5c4bdb9f50adf..0000000000000 --- a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/karma_report.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - Error: expected 7069 to be below 64 - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11) - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8) - at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15) - at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60) - at tryCatch (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:62:40) - at Generator.invoke [as _invoke] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:288:22) - at Generator.prototype.<computed> [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21) - at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103) - at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194) - - - - - - - - - diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts index 53a74f6cc6af2..505e898c62adf 100644 --- a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts @@ -39,13 +39,7 @@ jest.mock('fs', () => { }; }); -import { - FTR_REPORT, - JEST_REPORT, - MOCHA_REPORT, - KARMA_REPORT, - CYPRESS_REPORT, -} from './__fixtures__'; +import { FTR_REPORT, JEST_REPORT, MOCHA_REPORT, CYPRESS_REPORT } from './__fixtures__'; import { parseTestReport } from './test_report'; import { addMessagesToReport } from './add_messages_to_report'; @@ -338,79 +332,3 @@ it('rewrites cypress reports with minimal changes', async () => { `); }); - -it('rewrites karma reports with minimal changes', async () => { - const xml = await addMessagesToReport({ - report: await parseTestReport(KARMA_REPORT), - messages: [ - { - name: - 'CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK', - classname: 'Browser Unit Tests.CoordinateMapsVisualizationTest', - message: 'foo bar', - }, - ], - log, - reportPath: Path.resolve(__dirname, './__fixtures__/karma_report.xml'), - }); - - expect(createPatch('karma.xml', KARMA_REPORT, xml, { context: 0 })).toMatchInlineSnapshot(` - Index: karma.xml - =================================================================== - --- karma.xml [object Object] - +++ karma.xml - @@ -1,5 +1,5 @@ - -‹?xml version="1.0"?› - +‹?xml version="1.0" encoding="utf-8"?› - ‹testsuite name="Chrome 75.0.3770 (Mac OS X 10.14.5)" package="" timestamp="2019-07-02T19:53:21" id="0" hostname="spalger.lan" tests="648" errors="0" failures="4" time="1.759"› - ‹properties› - ‹property name="browser.fullName" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"/› - ‹/properties› - @@ -7,27 +7,31 @@ - ‹testcase name="Vis-Editor-Agg-Params plugin directive should hide custom label parameter" time="0" classname="Browser Unit Tests.Vis-Editor-Agg-Params plugin directive"› - ‹skipped/› - ‹/testcase› - ‹testcase name="CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK" time="0.265" classname="Browser Unit Tests.CoordinateMapsVisualizationTest"› - - ‹failure type=""›Error: expected 7069 to be below 64 - - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11) - - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8) - - at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15) - - at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60) - + ‹failure type=""›‹![CDATA[Error: expected 7069 to be below 64 - + at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11) - + at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8) - + at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15) - + at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60) - at tryCatch (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:62:40) - at Generator.invoke [as _invoke] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:288:22) - - at Generator.prototype.<computed> [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21) - - at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103) - - at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194) - -‹/failure› - + at Generator.prototype.‹computed› [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21) - + at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103) - + at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194) - +]]›‹/failure› - + ‹system-out›Failed Tests Reporter: - + - foo bar - + - +‹/system-out› - ‹/testcase› - ‹testcase name="CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should toggle to Heatmap OK" time="0.055" classname="Browser Unit Tests.CoordinateMapsVisualizationTest"/› - ‹testcase name="VegaParser._parseSchema should warn on vega-lite version too new to be supported" time="0.001" classname="Browser Unit Tests.VegaParser·_parseSchema"/› - ‹system-out› - - ‹![CDATA[Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'ready to load tests for shard 1 of 4' - + Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'ready to load tests for shard 1 of 4' - ,Chrome 75.0.3770 (Mac OS X 10.14.5) WARN: 'Unmatched GET to http://localhost:9876/api/interpreter/fns' - ... - - -]]› - + - ‹/system-out› - ‹system-err/› - -‹/testsuite› - +‹/testsuite› - \\ No newline at end of file - - `); -}); diff --git a/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts b/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts index 23d9805727f32..f570ed36111b3 100644 --- a/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts @@ -19,7 +19,7 @@ import { getFailures } from './get_failures'; import { parseTestReport } from './test_report'; -import { FTR_REPORT, JEST_REPORT, KARMA_REPORT, MOCHA_REPORT } from './__fixtures__'; +import { FTR_REPORT, JEST_REPORT, MOCHA_REPORT } from './__fixtures__'; it('discovers failures in ftr report', async () => { const failures = getFailures(await parseTestReport(FTR_REPORT)); @@ -85,31 +85,6 @@ it('discovers failures in jest report', async () => { `); }); -it('discovers failures in karma report', async () => { - const failures = getFailures(await parseTestReport(KARMA_REPORT)); - expect(failures).toMatchInlineSnapshot(` - Array [ - Object { - "classname": "Browser Unit Tests.CoordinateMapsVisualizationTest", - "failure": "Error: expected 7069 to be below 64 - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11) - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8) - at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15) - at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60) - at tryCatch (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:62:40) - at Generator.invoke [as _invoke] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:288:22) - at Generator.prototype. [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21) - at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103) - at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194) - ", - "likelyIrrelevant": false, - "name": "CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK", - "time": "0.265", - }, - ] - `); -}); - it('discovers failures in mocha report', async () => { const failures = getFailures(await parseTestReport(MOCHA_REPORT)); expect(failures).toMatchInlineSnapshot(` diff --git a/packages/kbn-test/src/failed_tests_reporter/report_metadata.test.ts b/packages/kbn-test/src/failed_tests_reporter/report_metadata.test.ts index 729d80ddfcb44..c079084965609 100644 --- a/packages/kbn-test/src/failed_tests_reporter/report_metadata.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/report_metadata.test.ts @@ -19,7 +19,7 @@ import { getReportMessageIter } from './report_metadata'; import { parseTestReport } from './test_report'; -import { FTR_REPORT, JEST_REPORT, KARMA_REPORT, MOCHA_REPORT } from './__fixtures__'; +import { FTR_REPORT, JEST_REPORT, MOCHA_REPORT } from './__fixtures__'; it('reads messages and screenshots from metadata-json properties', async () => { const ftrReport = await parseTestReport(FTR_REPORT); @@ -43,7 +43,4 @@ it('reads messages and screenshots from metadata-json properties', async () => { const mochaReport = await parseTestReport(MOCHA_REPORT); expect(Array.from(getReportMessageIter(mochaReport))).toMatchInlineSnapshot(`Array []`); - - const karmaReport = await parseTestReport(KARMA_REPORT); - expect(Array.from(getReportMessageIter(karmaReport))).toMatchInlineSnapshot(`Array []`); }); diff --git a/packages/kbn-ui-shared-deps/theme.ts b/packages/kbn-ui-shared-deps/theme.ts index 4b2758516fc26..a810e1de0a21f 100644 --- a/packages/kbn-ui-shared-deps/theme.ts +++ b/packages/kbn-ui-shared-deps/theme.ts @@ -24,7 +24,7 @@ const globals: any = typeof window === 'undefined' ? {} : window; export type Theme = typeof LightTheme; // in the Kibana app we can rely on this global being defined, but in -// some cases (like jest, or karma tests) the global is undefined +// some cases (like jest) the global is undefined export const tag: string = globals.__kbnThemeTag__ || 'v7light'; export const version = tag.startsWith('v7') ? 7 : 8; export const darkMode = tag.endsWith('dark'); diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index f7acff14915a7..72945597758e2 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1620,14 +1620,6 @@ If others are consuming your plugin's new platform contracts via the `ui/new_pla > Note: The `ui/new_platform` mock is only designed for use by old Jest tests. If you are writing new tests, you should structure your code and tests such that you don't need this mock. Instead, you should import the `core` mock directly and instantiate it. -#### What about karma tests? - -While our plan is to only provide first-class mocks for Jest tests, there are many legacy karma tests that cannot be quickly or easily converted to Jest -- particularly those which are still relying on mocking Angular services via `ngMock`. - -For these tests, we are maintaining a separate set of mocks. Files with a `.karma_mock.{js|ts|tsx}` extension will be loaded _globally_ before karma tests are run. - -It is important to note that this behavior is different from `jest.mock('ui/new_platform')`, which only mocks tests on an individual basis. If you encounter any failures in karma tests as a result of new platform migration efforts, you may need to add a `.karma_mock.js` file for the affected services, or add to the existing karma mock we are maintaining in `ui/new_platform`. - ### Provide Legacy Platform API to the New platform plugin #### On the server side diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index d77676b350f93..78a9219f3d694 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -53,7 +53,7 @@ interface BootstrapModule { * The LegacyPlatformService is responsible for initializing * the legacy platform by injecting parts of the new platform * services into the legacy platform modules, like ui/modules, - * and then bootstrapping the ui/chrome or ui/test_harness to + * and then bootstrapping the ui/chrome or ~~ui/test_harness~~ to * setup either the app or browser tests. */ export class LegacyPlatformService { diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 83a2e712b424f..e74f6d32e92b0 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -42,21 +42,7 @@ export const config = { validate: match(validBasePathRegex, "must start with a slash, don't end with one"), }) ), - cors: schema.conditional( - schema.contextRef('dev'), - true, - schema.object( - { - origin: schema.arrayOf(schema.string()), - }, - { - defaultValue: { - origin: ['*://localhost:9876'], // karma test server - }, - } - ), - schema.boolean({ defaultValue: false }) - ), + cors: schema.boolean({ defaultValue: false }), customResponseHeaders: schema.recordOf(schema.string(), schema.any(), { defaultValue: {}, }), diff --git a/src/dev/build/tasks/copy_source_task.ts b/src/dev/build/tasks/copy_source_task.ts index 221c9162bd2a9..c8489673b83af 100644 --- a/src/dev/build/tasks/copy_source_task.ts +++ b/src/dev/build/tasks/copy_source_task.ts @@ -33,7 +33,6 @@ export const CopySource: Task = { '!src/**/{__tests__,__snapshots__,__mocks__}/**', '!src/test_utils/**', '!src/fixtures/**', - '!src/legacy/core_plugins/tests_bundle/**', '!src/legacy/core_plugins/console/public/tests/**', '!src/cli/cluster/**', '!src/cli/repl/**', diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index e11668ab57f55..5249b7d652790 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -52,7 +52,6 @@ export default { '!packages/kbn-ui-framework/src/services/**/*/index.js', 'src/legacy/core_plugins/**/*.{js,mjs,jsx,ts,tsx}', '!src/legacy/core_plugins/**/{__test__,__snapshots__}/**/*', - '!src/legacy/core_plugins/tests_bundle/**', ], moduleNameMapper: { '@elastic/eui$': '/node_modules/@elastic/eui/test-env', diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 1e4f048be8ea4..864bf7515053c 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -124,7 +124,6 @@ export const IGNORE_DIRECTORY_GLOBS = [ export const TEMPORARILY_IGNORED_PATHS = [ 'src/legacy/core_plugins/console/public/src/directives/helpExample.txt', 'src/legacy/core_plugins/console/public/src/sense_editor/theme-sense-dark.js', - 'src/legacy/core_plugins/tests_bundle/webpackShims/angular-mocks.js', 'src/legacy/core_plugins/tile_map/public/__tests__/scaledCircleMarkers.png', 'src/legacy/core_plugins/tile_map/public/__tests__/shadedCircleMarkers.png', 'src/legacy/core_plugins/tile_map/public/__tests__/shadedGeohashGrid.png', diff --git a/src/legacy/core_plugins/tests_bundle/find_source_files.js b/src/legacy/core_plugins/tests_bundle/find_source_files.js deleted file mode 100644 index eed88a5ecb8b0..0000000000000 --- a/src/legacy/core_plugins/tests_bundle/find_source_files.js +++ /dev/null @@ -1,64 +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. - */ - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { fromRoot } from '../../../core/server/utils'; -import { chain } from 'lodash'; -import { resolve } from 'path'; -import { fromNode } from 'bluebird'; -import glob from 'glob-all'; - -const findSourceFiles = async (patterns, cwd = fromRoot('.')) => { - patterns = [].concat(patterns || []); - - const matches = await fromNode((cb) => { - glob( - patterns, - { - cwd: cwd, - ignore: [ - 'node_modules/**/*', - 'bower_components/**/*', - '**/_*.js', - '**/*.test.js', - '**/*.test.mocks.js', - '**/__mocks__/**/*', - ], - symlinks: findSourceFiles.symlinks, - statCache: findSourceFiles.statCache, - realpathCache: findSourceFiles.realpathCache, - cache: findSourceFiles.cache, - }, - cb - ); - }); - - return chain(matches) - .flatten() - .uniq() - .map((match) => resolve(cwd, match)) - .value(); -}; - -findSourceFiles.symlinks = {}; -findSourceFiles.statCache = {}; -findSourceFiles.realpathCache = {}; -findSourceFiles.cache = {}; - -export default findSourceFiles; diff --git a/src/legacy/core_plugins/tests_bundle/index.js b/src/legacy/core_plugins/tests_bundle/index.js deleted file mode 100644 index da7c1c4f4527e..0000000000000 --- a/src/legacy/core_plugins/tests_bundle/index.js +++ /dev/null @@ -1,180 +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 { createReadStream } from 'fs'; -import { resolve } from 'path'; - -import globby from 'globby'; -import MultiStream from 'multistream'; -import webpackMerge from 'webpack-merge'; - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { fromRoot } from '../../../core/server/utils'; -import { replacePlaceholder } from '../../../optimize/public_path_placeholder'; -import findSourceFiles from './find_source_files'; -import { createTestEntryTemplate } from './tests_entry_template'; - -export default (kibana) => { - return new kibana.Plugin({ - config: (Joi) => { - return Joi.object({ - enabled: Joi.boolean().default(true), - instrument: Joi.boolean().default(false), - pluginId: Joi.string(), - }).default(); - }, - - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - async __bundleProvider__(kbnServer) { - const modules = new Set(); - - const { - config, - uiApps, - uiBundles, - plugins, - uiExports: { uiSettingDefaults = {} }, - } = kbnServer; - - const testGlobs = []; - - const testingPluginIds = config.get('tests_bundle.pluginId'); - - if (testingPluginIds) { - testingPluginIds.split(',').forEach((pluginId) => { - const plugin = plugins.find((plugin) => plugin.id === pluginId); - - if (!plugin) { - throw new Error('Invalid testingPluginId :: unknown plugin ' + pluginId); - } - - // add the modules from all of this plugins apps - for (const app of uiApps) { - if (app.getPluginId() === pluginId) { - modules.add(app.getMainModuleId()); - } - } - - testGlobs.push(`${plugin.publicDir}/**/__tests__/**/*.js`); - }); - } else { - // add all since we are not just focused on specific plugins - testGlobs.push('src/legacy/ui/public/**/*.js', '!src/legacy/ui/public/flot-charts/**/*'); - // add the modules from all of the apps - for (const app of uiApps) { - modules.add(app.getMainModuleId()); - } - - for (const plugin of plugins) { - testGlobs.push(`${plugin.publicDir}/**/__tests__/**/*.js`); - } - } - - const testFiles = await findSourceFiles(testGlobs); - for (const f of testFiles) modules.add(f); - - if (config.get('tests_bundle.instrument')) { - uiBundles.addPostLoader({ - test: /\.js$/, - exclude: /[\/\\](__tests__|node_modules|bower_components|webpackShims)[\/\\]/, - loader: 'istanbul-instrumenter-loader', - }); - } - - uiBundles.add({ - id: 'tests', - modules: [...modules], - template: createTestEntryTemplate(uiSettingDefaults), - extendConfig(webpackConfig) { - const mergedConfig = webpackMerge( - { - resolve: { - extensions: ['.karma_mock.js', '.karma_mock.tsx', '.karma_mock.ts'], - }, - node: { - fs: 'empty', - child_process: 'empty', - dns: 'empty', - net: 'empty', - tls: 'empty', - }, - }, - webpackConfig - ); - - /** - * [..] it removes the commons bundle creation from the webpack - * config when we're building the bundle for the browser tests. It - * shouldn't be created, and by default isn't, but something is - * triggering it in webpack which breaks the tests so if we just - * remove the optimization config it will never happen and the tests - * will keep working [..] - * - * TLDR: If you have any questions about this line, ask Spencer. - */ - delete mergedConfig.optimization.splitChunks.cacheGroups.commons; - - return mergedConfig; - }, - }); - - kbnServer.server.route({ - method: 'GET', - path: '/test_bundle/built_css.css', - async handler(_, h) { - const cssFiles = await globby( - testingPluginIds - ? testingPluginIds.split(',').map((id) => `built_assets/css/plugins/${id}/**/*.css`) - : `built_assets/css/**/*.css`, - { cwd: fromRoot('.'), absolute: true } - ); - - const stream = replacePlaceholder( - new MultiStream(cssFiles.map((path) => createReadStream(path))), - '/built_assets/css/' - ); - - return h.response(stream).code(200).type('text/css'); - }, - }); - - // Sets global variables normally set by the bootstrap.js script - kbnServer.server.route({ - path: '/test_bundle/karma/globals.js', - method: 'GET', - async handler(req, h) { - const basePath = config.get('server.basePath'); - - const file = `window.__kbnPublicPath__ = { 'kbn-ui-shared-deps': "${basePath}/bundles/kbn-ui-shared-deps/" };`; - - return h.response(file).header('content-type', 'application/json'); - }, - }); - }, - - __globalImportAliases__: { - ng_mock$: fromRoot('src/test_utils/public/ng_mock'), - 'angular-mocks$': require.resolve('./webpackShims/angular-mocks'), - fixtures: fromRoot('src/fixtures'), - test_utils: fromRoot('src/test_utils/public'), - }, - }, - }); -}; diff --git a/src/legacy/core_plugins/tests_bundle/package.json b/src/legacy/core_plugins/tests_bundle/package.json deleted file mode 100644 index 4d2df048d4164..0000000000000 --- a/src/legacy/core_plugins/tests_bundle/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "tests_bundle", - "version": "kibana" -} diff --git a/src/legacy/core_plugins/tests_bundle/public/index.scss b/src/legacy/core_plugins/tests_bundle/public/index.scss deleted file mode 100644 index d8dbf8d6dc885..0000000000000 --- a/src/legacy/core_plugins/tests_bundle/public/index.scss +++ /dev/null @@ -1,4 +0,0 @@ -// This file pulls some styles of NP plugins into the legacy test stylesheet -// so they are available for karma browser tests. -@import '../../../../plugins/vis_type_vislib/public/index'; -@import '../../../../plugins/visualizations/public/index'; diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js deleted file mode 100644 index 28c26f08621eb..0000000000000 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ /dev/null @@ -1,158 +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 { Type } from '@kbn/config-schema'; -import pkg from '../../../../package.json'; - -export const createTestEntryTemplate = (defaultUiSettings) => (bundle) => ` -/** - * Test entry file - * - * This is programmatically created and updated, do not modify - * - * context: ${bundle.getContext()} - * - */ - -import fetchMock from 'fetch-mock/es5/client'; -import { CoreSystem } from '__kibanaCore__'; - -// Fake uiCapabilities returned to Core in browser tests -const uiCapabilities = { - navLinks: { - myLink: true, - notMyLink: true, - }, - discover: { - showWriteControls: true - }, - visualize: { - save: true - }, - dashboard: { - showWriteControls: true - }, - timelion: { - save: true - }, - management: { - kibana: { - settings: true, - index_patterns: true, - objects: true - } - } -}; - -// Mock fetch for CoreSystem calls. -fetchMock.config.fallbackToNetwork = true; -fetchMock.post(/\\/api\\/core\\/capabilities/, { - status: 200, - body: JSON.stringify(uiCapabilities), - headers: { 'Content-Type': 'application/json' }, -}); - -// render the core system in a element not attached to the document as the -// default children of the body in the browser tests are needed for mocha and -// other test components to work -const rootDomElement = document.createElement('div'); - -const coreSystem = new CoreSystem({ - injectedMetadata: { - version: '1.2.3', - buildNumber: 1234, - legacyMode: true, - legacyMetadata: { - app: { - id: 'karma', - title: 'Karma', - }, - nav: [], - version: '1.2.3', - buildNum: 1234, - devMode: true, - uiSettings: { - defaults: ${JSON.stringify( - defaultUiSettings, - (key, value) => { - if (value instanceof Type) return null; - return value; - }, - 2 - ) - .split('\n') - .join('\n ')}, - user: {} - }, - nav: [] - }, - csp: { - warnLegacyBrowsers: false, - }, - capabilities: uiCapabilities, - uiPlugins: [], - vars: { - kbnIndex: '.kibana', - esShardTimeout: 1500, - esApiVersion: ${JSON.stringify(pkg.branch)}, - esRequestTimeout: '300000', - tilemapsConfig: { - deprecated: { - isOverridden: false, - config: { - options: { - } - } - } - }, - regionmapsConfig: { - layers: [] - }, - mapConfig: { - includeElasticMapsService: true, - emsFileApiUrl: 'https://vector-staging.maps.elastic.co', - emsTileApiUrl: 'https://tiles.maps.elastic.co', - }, - vegaConfig: { - enabled: true, - enableExternalUrls: true - }, - }, - }, - rootDomElement, - requireLegacyBootstrapModule: () => { - // wrapped in NODE_ENV check so the 'ui/test_harness' module - // is not included in the distributable - if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true') { - return require('ui/test_harness'); - } - - throw new Error('tests bundle is not available in the distributable'); - }, - requireNewPlatformShimModule: () => require('ui/new_platform'), - requireLegacyFiles: () => { - ${bundle.getRequires().join('\n ')} - } -}) - -coreSystem - .setup() - .then(() => { - return coreSystem.start(); - }); -`; diff --git a/src/legacy/core_plugins/tests_bundle/webpackShims/angular-mocks.js b/src/legacy/core_plugins/tests_bundle/webpackShims/angular-mocks.js deleted file mode 100644 index 24f794cb32990..0000000000000 --- a/src/legacy/core_plugins/tests_bundle/webpackShims/angular-mocks.js +++ /dev/null @@ -1,21 +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. - */ - -require('angular'); -require('../../../../../node_modules/angular-mocks/angular-mocks.js'); diff --git a/src/legacy/ui/public/test_harness/.eslintrc b/src/legacy/ui/public/test_harness/.eslintrc deleted file mode 100644 index b1b85968796dd..0000000000000 --- a/src/legacy/ui/public/test_harness/.eslintrc +++ /dev/null @@ -1,2 +0,0 @@ -rules: - no-console: 0 diff --git a/src/legacy/ui/public/test_harness/index.js b/src/legacy/ui/public/test_harness/index.js deleted file mode 100644 index d66a4b1d67214..0000000000000 --- a/src/legacy/ui/public/test_harness/index.js +++ /dev/null @@ -1,20 +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 { bootstrap } from './test_harness'; diff --git a/src/legacy/ui/public/test_harness/test_harness.css b/src/legacy/ui/public/test_harness/test_harness.css deleted file mode 100644 index d0a0f50c55b9b..0000000000000 --- a/src/legacy/ui/public/test_harness/test_harness.css +++ /dev/null @@ -1,22 +0,0 @@ -/** - * This file is only for tests so it is it's own CSS - * to be imported directly by and only by this module. - */ - -body#test-harness-body { - /** - now that tests include the kibana styles, we have - to override the body { display: flex; } rule as it - prevents the visualizations from properly testing - their sizing - */ - display: block; -} - -body#test-harness-body #mocha-stats .progress { - /* bootstrap thinks it is the only one who will use ".progress" */ - height: auto; - background-color: transparent; - overflow: auto; - border-radius: 0; -} diff --git a/src/legacy/ui/public/test_harness/test_harness.js b/src/legacy/ui/public/test_harness/test_harness.js deleted file mode 100644 index 22c981fe0cf54..0000000000000 --- a/src/legacy/ui/public/test_harness/test_harness.js +++ /dev/null @@ -1,89 +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. - */ - -// chrome expects to be loaded first, let it get its way -import chrome from '../chrome'; - -import { parse as parseUrl } from 'url'; -import { Subject } from 'rxjs'; -import sinon from 'sinon'; -import { metadata } from '../metadata'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { UiSettingsClient } from '../../../../core/public/ui_settings'; - -import './test_harness.css'; -import 'ng_mock'; -import { setupTestSharding } from './test_sharding'; - -const { query } = parseUrl(window.location.href, true); -if (query && query.mocha) { - try { - window.mocha.setup(JSON.parse(query.mocha)); - } catch (error) { - throw new Error( - `'?mocha=${query.mocha}' query string param provided but it could not be parsed as json` - ); - } -} - -setupTestSharding(); - -before(() => { - // prevent accidental ajax requests - sinon.useFakeXMLHttpRequest(); -}); - -let stubUiSettings; -let done$; -function createStubUiSettings() { - if (stubUiSettings) { - done$.complete(); - } - done$ = new Subject(); - - stubUiSettings = new UiSettingsClient({ - api: { - async batchSet() { - return { settings: stubUiSettings.getAll() }; - }, - }, - onUpdateError: () => {}, - defaults: metadata.uiSettings.defaults, - initialSettings: {}, - done$, - }); -} - -createStubUiSettings(); -sinon.stub(chrome, 'getUiSettingsClient').callsFake(() => stubUiSettings); - -afterEach(function () { - createStubUiSettings(); -}); - -// Kick off mocha, called at the end of test entry files -export function bootstrap(targetDomElement) { - // allows test_harness.less to have higher priority selectors - targetDomElement.setAttribute('id', 'test-harness-body'); - - // load the hacks since we aren't actually bootstrapping the - // chrome, which is where the hacks would normally be loaded - require('uiExports/hacks'); - chrome.setupAngular(); -} diff --git a/src/legacy/ui/public/test_harness/test_sharding/find_test_bundle_url.js b/src/legacy/ui/public/test_harness/test_sharding/find_test_bundle_url.js deleted file mode 100644 index 53800d08ca05b..0000000000000 --- a/src/legacy/ui/public/test_harness/test_sharding/find_test_bundle_url.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * We don't have a lot of options for passing arguments to the page that karma - * creates, so we tack some query string params onto the test bundle script url. - * - * This function finds that url by looking for a script tag that has - * the "/tests.bundle.js" segment - * - * @return {string} url - */ -export function findTestBundleUrl() { - const scriptTags = document.querySelectorAll('script[src]'); - const scriptUrls = [].map.call(scriptTags, (el) => el.getAttribute('src')); - const testBundleUrl = scriptUrls.find((url) => url.includes('/tests.bundle.js')); - - if (!testBundleUrl) { - throw new Error("test bundle url couldn't be found"); - } - - return testBundleUrl; -} diff --git a/src/legacy/ui/public/test_harness/test_sharding/get_shard_num.js b/src/legacy/ui/public/test_harness/test_sharding/get_shard_num.js deleted file mode 100644 index 55a33fc295191..0000000000000 --- a/src/legacy/ui/public/test_harness/test_sharding/get_shard_num.js +++ /dev/null @@ -1,47 +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 murmurHash3 from 'murmurhash3js'; - -// murmur hashes are 32bit unsigned integers -const MAX_HASH = Math.pow(2, 32); - -/** - * Determine the shard number for a suite by hashing - * its name and placing it based on the hash - * - * @param {number} shardTotal - the total number of shards - * @param {string} suiteName - the suite name to hash - * @return {number} shardNum - 1-based shard number - */ -export function getShardNum(shardTotal, suiteName) { - const hashIntsPerShard = MAX_HASH / shardTotal; - - const hashInt = murmurHash3.x86.hash32(suiteName); - - // murmur3 produces 32bit integers, so we devide it by the number of chunks - // to determine which chunk the suite should fall in. +1 because the current - // chunk is 1-based - const shardNum = Math.floor(hashInt / hashIntsPerShard) + 1; - - // It's not clear if hash32 can produce the MAX_HASH or not, - // but this just ensures that shard numbers don't go out of bounds - // and cause tests to be ignored unnecessarily - return Math.max(1, Math.min(shardNum, shardTotal)); -} diff --git a/src/legacy/ui/public/test_harness/test_sharding/get_sharding_params_from_url.js b/src/legacy/ui/public/test_harness/test_sharding/get_sharding_params_from_url.js deleted file mode 100644 index 65e41dbc84b63..0000000000000 --- a/src/legacy/ui/public/test_harness/test_sharding/get_sharding_params_from_url.js +++ /dev/null @@ -1,45 +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 as parseUrl } from 'url'; - -/** - * This function extracts the relevant "shards" and "shard_num" - * params from the url. - * - * @param {string} testBundleUrl - * @return {object} params - * @property {number} params.shards - the total number of shards - * @property {number} params.shard_num - the current shard number, 1 based - */ -export function getShardingParamsFromUrl(url) { - const parsedUrl = parseUrl(url, true); - const parsedQuery = parsedUrl.query || {}; - - const params = {}; - if (parsedQuery.shards) { - params.shards = parseInt(parsedQuery.shards, 10); - } - - if (parsedQuery.shard_num) { - params.shard_num = parseInt(parsedQuery.shard_num, 10); - } - - return params; -} diff --git a/src/legacy/ui/public/test_harness/test_sharding/index.js b/src/legacy/ui/public/test_harness/test_sharding/index.js deleted file mode 100644 index cdca50725a058..0000000000000 --- a/src/legacy/ui/public/test_harness/test_sharding/index.js +++ /dev/null @@ -1,20 +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 { setupTestSharding } from './setup_test_sharding'; diff --git a/src/legacy/ui/public/test_harness/test_sharding/setup_test_sharding.js b/src/legacy/ui/public/test_harness/test_sharding/setup_test_sharding.js deleted file mode 100644 index fce1876162387..0000000000000 --- a/src/legacy/ui/public/test_harness/test_sharding/setup_test_sharding.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uniq, defaults } from 'lodash'; - -import { findTestBundleUrl } from './find_test_bundle_url'; -import { getShardingParamsFromUrl } from './get_sharding_params_from_url'; -import { setupTopLevelDescribeFilter } from './setup_top_level_describe_filter'; -import { getShardNum } from './get_shard_num'; - -const DEFAULT_PARAMS = { - shards: 1, - shard_num: 1, -}; - -export function setupTestSharding() { - const pageUrl = window.location.href; - const bundleUrl = findTestBundleUrl(); - - // supports overriding params via the debug page - // url in dev mode - const params = defaults( - {}, - getShardingParamsFromUrl(pageUrl), - getShardingParamsFromUrl(bundleUrl), - DEFAULT_PARAMS - ); - - const { shards: shardTotal, shard_num: shardNum } = params; - if (shardNum < 1 || shardNum > shardTotal) { - throw new TypeError( - `shard_num param of ${shardNum} must be greater 0 and less than the total, ${shardTotal}` - ); - } - - // track and log the number of ignored describe calls - const ignoredDescribeShards = []; - before(() => { - const ignoredCount = ignoredDescribeShards.length; - const ignoredFrom = uniq(ignoredDescribeShards).join(', '); - console.log(`Ignored ${ignoredCount} top-level suites from ${ignoredFrom}`); - }); - - // Filter top-level describe statements as they come - setupTopLevelDescribeFilter((describeName) => { - const describeShardNum = getShardNum(shardTotal, describeName); - if (describeShardNum === shardNum) return true; - // track shard numbers that we ignore - ignoredDescribeShards.push(describeShardNum); - }); - - console.log(`ready to load tests for shard ${shardNum} of ${shardTotal}`); -} diff --git a/src/legacy/ui/public/test_harness/test_sharding/setup_top_level_describe_filter.js b/src/legacy/ui/public/test_harness/test_sharding/setup_top_level_describe_filter.js deleted file mode 100644 index 726f890077b94..0000000000000 --- a/src/legacy/ui/public/test_harness/test_sharding/setup_top_level_describe_filter.js +++ /dev/null @@ -1,125 +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. - */ - -/** - * Intercept all calls to mocha.describe() and determine - * which calls make it through using a filter function. - * - * The filter function is also only called for top-level - * describe() calls; all describe calls nested within another - * are allowed based on the filter value for the parent - * describe - * - * ## example - * - * assume tests that look like this: - * - * ```js - * describe('section 1', () => { - * describe('item 1', () => { - * - * }) - * }) - * ``` - * - * If the filter function returned true for "section 1" then "item 1" - * would automatically be defined. If it returned false for "section 1" - * then "section 1" would be ignored and "item 1" would never be defined - * - * @param {function} test - a function that takes the first argument - * passed to describe, the sections name, and - * returns true if the describe call should - * be delegated to mocha, any other value causes - * the describe call to be ignored - * @return {undefined} - */ -export function setupTopLevelDescribeFilter(test) { - const originalDescribe = window.describe; - - if (!originalDescribe) { - throw new TypeError( - 'window.describe must be defined by mocha before test sharding can be setup' - ); - } - - /** - * When describe is called it is likely to make additional, nested, - * calls to describe. We track how deeply nested we are at any time - * with a depth counter, `describeCallDepth`. - * - * Before delegating a describe call to mocha we increment - * that counter, and once mocha is done we decrement it. - * - * This way, we can check if `describeCallDepth > 0` at any time - * to know if we are already within a describe call. - * - * ```js - * // +1 - * describe('section 1', () => { - * // describeCallDepth = 1 - * // +1 - * describe('item 1', () => { - * // describeCallDepth = 2 - * }) - * // -1 - * }) - * // -1 - * // describeCallDepth = 0 - * ``` - * - * @type {Number} - */ - let describeCallDepth = 0; - - const describeInterceptor = function (describeName, describeBody) { - const context = this; - - const isTopLevelCall = describeCallDepth === 0; - const shouldIgnore = isTopLevelCall && Boolean(test(describeName)) === false; - if (shouldIgnore) return; - - /** - * we wrap the delegation to mocha in a try/finally block - * to ensure that our describeCallDepth counter stays up - * to date even if the call throws an error. - * - * note that try/finally won't actually catch the error, it - * will continue to propagate up the call stack - */ - let result; - try { - describeCallDepth += 1; - result = originalDescribe.call(context, describeName, describeBody); - } finally { - describeCallDepth -= 1; - } - return result; - }; - - // to allow describe.only calls. we dont need interceptor as it will call describe internally - describeInterceptor.only = originalDescribe.only; - describeInterceptor.skip = originalDescribe.skip; - - // ensure that window.describe isn't messed with by other code - Object.defineProperty(window, 'describe', { - configurable: false, - enumerable: true, - value: describeInterceptor, - }); -} diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index f7ee9aa056762..348f4ee77fab4 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -30,7 +30,6 @@ export const UI_EXPORT_DEFAULTS = { webpackAliases: { ui: resolve(ROOT, 'src/legacy/ui/public'), __kibanaCore__$: resolve(ROOT, 'src/core/public'), - test_harness: resolve(ROOT, 'src/test_harness/public'), }, styleSheetPaths: ['light', 'dark'].map((theme) => ({ diff --git a/tasks/config/karma.js b/tasks/config/karma.js deleted file mode 100644 index 114e09876406c..0000000000000 --- a/tasks/config/karma.js +++ /dev/null @@ -1,207 +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 { dirname } from 'path'; -import { times } from 'lodash'; -import { makeJunitReportPath } from '@kbn/test'; -import * as UiSharedDeps from '@kbn/ui-shared-deps'; - -const TOTAL_CI_SHARDS = 4; -const ROOT = dirname(require.resolve('../../package.json')); -const buildHash = String(Number.MAX_SAFE_INTEGER); - -module.exports = function (grunt) { - function pickBrowser() { - if (grunt.option('browser')) { - return grunt.option('browser'); - } - if (process.env.TEST_BROWSER_HEADLESS === '1') { - return 'Chrome_Headless'; - } - return 'Chrome'; - } - - function pickReporters() { - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - if (process.env.CI && process.env.DISABLE_JUNIT_REPORTER) { - return ['dots']; - } - - if (process.env.CI) { - return ['dots', 'junit']; - } - - return ['progress']; - } - - function getKarmaFiles(shardNum) { - return [ - 'http://localhost:5610/test_bundle/built_css.css', - // Sets global variables normally set by the bootstrap.js script - 'http://localhost:5610/test_bundle/karma/globals.js', - - ...UiSharedDeps.jsDepFilenames.map( - (chunkFilename) => - `http://localhost:5610/${buildHash}/bundles/kbn-ui-shared-deps/${chunkFilename}` - ), - `http://localhost:5610/${buildHash}/bundles/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, - - shardNum === undefined - ? `http://localhost:5610/${buildHash}/bundles/tests.bundle.js` - : `http://localhost:5610/${buildHash}/bundles/tests.bundle.js?shards=${TOTAL_CI_SHARDS}&shard_num=${shardNum}`, - - `http://localhost:5610/${buildHash}/bundles/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, - // this causes tilemap tests to fail, probably because the eui styles haven't been - // included in the karma harness a long some time, if ever - // `http://localhost:5610/bundles/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, - `http://localhost:5610/${buildHash}/bundles/tests.style.css`, - ]; - } - - const config = { - options: { - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '', - - captureTimeout: 30000, - browserNoActivityTimeout: 120000, - frameworks: ['mocha'], - plugins: [ - 'karma-chrome-launcher', - 'karma-coverage', - 'karma-firefox-launcher', - 'karma-ie-launcher', - 'karma-junit-reporter', - 'karma-mocha', - 'karma-safari-launcher', - ], - port: 9876, - colors: true, - logLevel: grunt.option('debug') || grunt.option('verbose') ? 'DEBUG' : 'INFO', - autoWatch: false, - browsers: [pickBrowser()], - customLaunchers: { - Chrome_Headless: { - base: 'Chrome', - flags: ['--headless', '--disable-gpu', '--remote-debugging-port=9222'], - }, - }, - - reporters: pickReporters(), - - junitReporter: { - outputFile: makeJunitReportPath(ROOT, 'karma'), - useBrowserName: false, - nameFormatter: (_, result) => [...result.suite, result.description].join(' '), - classNameFormatter: (_, result) => { - const rootSuite = result.suite[0] || result.description; - return `Browser Unit Tests.${rootSuite.replace(/\./g, '·')}`; - }, - }, - - // list of files / patterns to load in the browser - files: getKarmaFiles(), - - proxies: { - '/tests/': 'http://localhost:5610/tests/', - '/test_bundle/': 'http://localhost:5610/test_bundle/', - [`/${buildHash}/bundles/`]: `http://localhost:5610/${buildHash}/bundles/`, - }, - - client: { - mocha: { - reporter: 'html', // change Karma's debug.html to the mocha web reporter - timeout: 10000, - slow: 5000, - }, - }, - }, - - dev: { singleRun: false }, - unit: { singleRun: true }, - coverage: { - singleRun: true, - reporters: ['coverage'], - coverageReporter: { - reporters: [{ type: 'html', dir: 'coverage' }, { type: 'text-summary' }], - }, - }, - }; - - /** - * ------------------------------------------------------------ - * CI sharding - * ------------------------------------------------------------ - * - * Every test retains nearly all of the memory it causes to be allocated, - * which has started to kill the test browser as the size of the test suite - * increases. This is a deep-rooted problem that will take some serious - * work to fix. - * - * CI sharding is a short-term solution that splits the top-level describe - * calls into different "shards" and instructs karma to only run one shard - * at a time, reloading the browser in between each shard and forcing the - * memory from the previous shard to be released. - * - * ## how - * - * Rather than modify the bundling process to produce multiple testing - * bundles, top-level describe calls are sharded by their first argument, - * the suite name. - * - * The number of shards to create is controlled with the TOTAL_CI_SHARDS - * constant defined at the top of this file. - * - * ## controlling sharding - * - * To control sharding in a specific karma configuration, the total number - * of shards to create (?shards=X), and the current shard number - * (&shard_num=Y), are added to the testing bundle url and read by the - * test_harness/setup_test_sharding[1] module. This allows us to use a - * different number of shards in different scenarios (ie. running - * `yarn test:karma` runs the tests in a single shard, effectively - * disabling sharding) - * - * These same parameters can also be defined in the URL/query string of the - * karma debug page (started when you run `yarn test:karma:debug`). - * - * ## debugging - * - * It is *possible* that some tests will only pass if run after/with certain - * other suites. To debug this, make sure that your tests pass in isolation - * (by clicking the suite name on the karma debug page) and that it runs - * correctly in it's given shard (using the `?shards=X&shard_num=Y` query - * string params on the karma debug page). You can spot the shard number - * a test is running in by searching for the "ready to load tests for shard X" - * log message. - * - * [1]: src/legacy/ui/public/test_harness/test_sharding/setup_test_sharding.js - */ - times(TOTAL_CI_SHARDS, (i) => { - const n = i + 1; - config[`ciShard-${n}`] = { - singleRun: true, - options: { - files: getKarmaFiles(n), - }, - }; - }); - - return config; -}; diff --git a/tasks/config/run.js b/tasks/config/run.js index 98a1226834bc6..9ac8f72d56d4a 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -17,7 +17,6 @@ * under the License. */ -import { resolve } from 'path'; import { getFunctionalTestGroupRunConfigs } from '../function_test_groups'; const { version } = require('../../package.json'); @@ -25,44 +24,7 @@ const KIBANA_INSTALL_DIR = process.env.KIBANA_INSTALL_DIR || `./build/oss/kibana-${version}-SNAPSHOT-${process.platform}-x86_64`; -module.exports = function (grunt) { - function createKbnServerTask({ runBuild, flags = [] }) { - return { - options: { - wait: false, - ready: /http server running/, - quiet: false, - failOnError: false, - }, - cmd: runBuild ? `./build/${runBuild}/bin/kibana` : process.execPath, - args: [ - ...(runBuild ? [] : [require.resolve('../../scripts/kibana'), '--oss']), - - '--logging.json=false', - - ...flags, - - // allow the user to override/inject flags by defining cli args starting with `--kbnServer.` - ...grunt.option.flags().reduce(function (flags, flag) { - if (flag.startsWith('--kbnServer.')) { - flags.push(`--${flag.slice(12)}`); - } - - return flags; - }, []), - ], - }; - } - - const karmaTestServerFlags = [ - '--env.name=development', - '--plugins.initialize=false', - '--optimize.bundleFilter=tests', - '--optimize.validateSyntaxOfNodeModules=false', - '--server.port=5610', - '--migrations.skip=true', - ]; - +module.exports = function () { const NODE = 'node'; const YARN = 'yarn'; const scriptWithGithubChecks = ({ title, options, cmd, args }) => @@ -177,37 +139,6 @@ module.exports = function (grunt) { ], }), - // used by the test:karma task - // runs the kibana server to serve the browser test bundle - karmaTestServer: createKbnServerTask({ - flags: [...karmaTestServerFlags], - }), - browserSCSS: createKbnServerTask({ - flags: [...karmaTestServerFlags, '--optimize', '--optimize.enabled=false'], - }), - - // used by the test:coverage task - // runs the kibana server to serve the instrumented version of the browser test bundle - karmaTestCoverageServer: createKbnServerTask({ - flags: [...karmaTestServerFlags, '--tests_bundle.instrument=true'], - }), - - // used by the test:karma:debug task - // runs the kibana server to serve the browser test bundle, but listens for changes - // to the public/browser code and rebuilds the test bundle on changes - karmaTestDebugServer: createKbnServerTask({ - flags: [ - ...karmaTestServerFlags, - '--dev', - '--no-dev-config', - '--no-watch', - '--no-base-path', - '--optimize.watchPort=5611', - '--optimize.watchPrebuild=true', - '--optimize.bundleDir=' + resolve(__dirname, '../../data/optimize/testdev'), - ], - }), - verifyNotice: scriptWithGithubChecks({ title: 'Verify NOTICE.txt', options: { @@ -325,7 +256,6 @@ module.exports = function (grunt) { 'test:jest_integration' ), test_projects: gruntTaskWithGithubChecks('Project tests', 'test:projects'), - test_karma_ci: gruntTaskWithGithubChecks('Browser tests', 'test:karma-ci'), ...getFunctionalTestGroupRunConfigs({ kibanaInstallDir: KIBANA_INSTALL_DIR, diff --git a/tasks/jenkins.js b/tasks/jenkins.js index eece5df61a7d1..adfb6f0f46868 100644 --- a/tasks/jenkins.js +++ b/tasks/jenkins.js @@ -37,7 +37,6 @@ module.exports = function (grunt) { 'run:test_jest', 'run:test_jest_integration', 'run:test_projects', - 'run:test_karma_ci', 'run:test_hardening', 'run:test_package_safer_lodash_set', 'run:apiIntegrationTests', diff --git a/tasks/test.js b/tasks/test.js index 09821b97fe2e8..f370ea0b948c6 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -17,8 +17,6 @@ * under the License. */ -import _, { keys } from 'lodash'; - import { run } from '../utilities/visual_regression'; module.exports = function (grunt) { @@ -31,25 +29,6 @@ module.exports = function (grunt) { } ); - grunt.registerTask('test:karma', [ - 'checkPlugins', - 'run:browserSCSS', - 'run:karmaTestServer', - 'karma:unit', - ]); - - grunt.registerTask('test:karma-ci', () => { - const ciShardTasks = keys(grunt.config.get('karma')) - .filter((key) => key.startsWith('ciShard-')) - .map((key) => `karma:${key}`); - - grunt.log.ok(`Running UI tests in ${ciShardTasks.length} shards`); - grunt.task.run(['run:browserSCSS']); - grunt.task.run(['run:karmaTestServer', ...ciShardTasks]); - }); - - grunt.registerTask('test:coverage', ['run:karmaTestCoverageServer', 'karma:coverage']); - grunt.registerTask('test:quick', [ 'checkPlugins', 'run:mocha', @@ -57,18 +36,16 @@ module.exports = function (grunt) { 'test:jest', 'test:jest_integration', 'test:projects', - 'test:karma', 'run:apiIntegrationTests', ]); - grunt.registerTask('test:karmaDebug', ['checkPlugins', 'run:karmaTestDebugServer', 'karma:dev']); grunt.registerTask('test:mochaCoverage', ['run:mochaCoverage']); grunt.registerTask('test', (subTask) => { if (subTask) grunt.fail.fatal(`invalid task "test:${subTask}"`); grunt.task.run( - _.compact([ + [ !grunt.option('quick') && 'run:eslint', !grunt.option('quick') && 'run:sasslint', !grunt.option('quick') && 'run:checkTsProjects', @@ -78,7 +55,7 @@ module.exports = function (grunt) { 'run:checkFileCasing', 'run:licenses', 'test:quick', - ]) + ].filter(Boolean) ); }); diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh index bc927b1ed7b4d..77480554f738c 100755 --- a/test/scripts/jenkins_xpack.sh +++ b/test/scripts/jenkins_xpack.sh @@ -3,9 +3,9 @@ source test/scripts/jenkins_test_setup.sh if [[ -z "$CODE_COVERAGE" ]] ; then - echo " -> Running mocha tests" - cd "$XPACK_DIR" - checks-reporter-with-killswitch "X-Pack Karma Tests" yarn test:karma + echo " -> Building legacy styles for x-pack canvas storyshot tests" + cd "$KIBANA_DIR" + node scripts/build_sass echo "" echo "" diff --git a/x-pack/README.md b/x-pack/README.md index 03d2e3287c0f0..0449f1fc1bdab 100644 --- a/x-pack/README.md +++ b/x-pack/README.md @@ -44,14 +44,6 @@ If you want to run tests only for a specific plugin (to save some time), you can yarn test --plugins [,]* # where is "reporting", etc. ``` -#### Debugging browser tests -``` -yarn test:karma:debug -``` -Initializes an environment for debugging the browser tests. Includes an dedicated instance of the kibana server for building the test bundle, and a karma server. When running this task the build is optimized for the first time and then a karma-owned instance of the browser is opened. Click the "debug" button to open a new tab that executes the unit tests. - -Run single tests by appending `grep` parameter to the end of the URL. For example `http://localhost:9876/debug.html?grep=ML%20-%20Explorer%20Controller` will only run tests with 'ML - Explorer Controller' in the describe block. - #### Running server unit tests You can run mocha unit tests by running: diff --git a/x-pack/gulpfile.js b/x-pack/gulpfile.js index 7e5ab9b18f019..78ed2bff8cb01 100644 --- a/x-pack/gulpfile.js +++ b/x-pack/gulpfile.js @@ -8,7 +8,6 @@ require('../src/setup_node_env'); const { buildTask } = require('./tasks/build'); const { devTask } = require('./tasks/dev'); -const { testTask, testKarmaTask, testKarmaDebugTask } = require('./tasks/test'); const { downloadChromium } = require('./tasks/download_chromium'); // export the tasks that are runnable from the CLI @@ -16,7 +15,4 @@ module.exports = { build: buildTask, dev: devTask, downloadChromium, - test: testTask, - 'test:karma': testKarmaTask, - 'test:karma:debug': testKarmaDebugTask, }; diff --git a/x-pack/package.json b/x-pack/package.json index 39bdb76ac7a73..d1f638ccad8d0 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -11,8 +11,6 @@ "build": "gulp build", "testonly": "echo 'Deprecated, use `yarn test`' && gulp test", "test": "gulp test", - "test:karma:debug": "gulp test:karma:debug", - "test:karma": "gulp test:karma", "test:jest": "node scripts/jest", "test:mocha": "node scripts/mocha" }, diff --git a/x-pack/plugins/canvas/scripts/test_browser.js b/x-pack/plugins/canvas/scripts/test_browser.js deleted file mode 100644 index e04fac0615284..0000000000000 --- a/x-pack/plugins/canvas/scripts/test_browser.js +++ /dev/null @@ -1,7 +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. - */ - -require('./_helpers').runGulpTask('canvas:test:karma'); diff --git a/x-pack/plugins/canvas/scripts/test_dev.js b/x-pack/plugins/canvas/scripts/test_dev.js deleted file mode 100644 index 8b03d7930d473..0000000000000 --- a/x-pack/plugins/canvas/scripts/test_dev.js +++ /dev/null @@ -1,7 +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. - */ - -require('./_helpers').runGulpTask('canvas:karma:debug'); diff --git a/x-pack/tasks/test.ts b/x-pack/tasks/test.ts deleted file mode 100644 index 0d990bff9f44e..0000000000000 --- a/x-pack/tasks/test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as pluginHelpers from '@kbn/plugin-helpers'; -import gulp from 'gulp'; - -import { getEnabledPlugins } from './helpers/flags'; - -export const testServerTask = async () => { - throw new Error('server mocha tests are now included in the `node scripts/mocha` script'); -}; - -export const testKarmaTask = async () => { - const plugins = await getEnabledPlugins(); - await pluginHelpers.run('testKarma', { - plugins: plugins.join(','), - }); -}; - -export const testKarmaDebugTask = async () => { - const plugins = await getEnabledPlugins(); - await pluginHelpers.run('testKarma', { - dev: true, - plugins: plugins.join(','), - }); -}; - -export const testTask = gulp.series(testKarmaTask, testServerTask); diff --git a/yarn.lock b/yarn.lock index fd6019750dda5..0638a019a9402 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6544,11 +6544,6 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abbrev@1.0.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" - integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU= - abort-controller@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-2.0.3.tgz#b174827a732efadff81227ed4b8d1cc569baf20a" @@ -6706,11 +6701,6 @@ after-all-results@^2.0.0: resolved "https://registry.yarnpkg.com/after-all-results/-/after-all-results-2.0.0.tgz#6ac2fc202b500f88da8f4f5530cfa100f4c6a2d0" integrity sha1-asL8ICtQD4jaj09VMM+hAPTGotA= -after@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" - integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= - agent-base@4: version "4.2.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" @@ -7661,11 +7651,6 @@ array.prototype.flatmap@^1.2.3: es-abstract "^1.17.0-next.1" function-bind "^1.1.1" -arraybuffer.slice@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" - integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== - arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -7831,11 +7816,6 @@ async-value@^1.2.2: resolved "https://registry.yarnpkg.com/async-value/-/async-value-1.2.2.tgz#84517a1e7cb6b1a5b5e181fa31be10437b7fb125" integrity sha1-hFF6Hny2saW14YH6Mb4QQ3t/sSU= -async@1.x, async@^1.4.2, async@^1.5.2, async@~1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= - async@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/async/-/async-2.4.0.tgz#4990200f18ea5b837c2cc4f8c031a6985c385611" @@ -7843,6 +7823,11 @@ async@2.4.0: dependencies: lodash "^4.14.0" +async@^1.4.2, async@^1.5.2, async@~1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= + async@^2.0.0, async@^2.1.4: version "2.6.0" resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" @@ -7857,7 +7842,7 @@ async@^2.6.0, async@^2.6.1: dependencies: lodash "^4.17.10" -async@^2.6.2, async@^2.6.3: +async@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -8546,11 +8531,6 @@ bach@^1.0.0: async-settle "^1.0.0" now-and-later "^2.0.0" -backo2@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= - backport@5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/backport/-/backport-5.5.1.tgz#2eeddbdc4cfc0530119bdb2b0c3c30bc7ef574dd" @@ -8582,11 +8562,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base64-arraybuffer@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" - integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= - base64-js@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" @@ -8602,11 +8577,6 @@ base64-js@^1.1.2, base64-js@^1.2.1, base64-js@^1.3.0, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== -base64id@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" - integrity sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY= - base64url@^3.0.0, base64url@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" @@ -8654,13 +8624,6 @@ before-after-hook@^1.4.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.4.0.tgz#2b6bf23dca4f32e628fd2747c10a37c74a4b484d" integrity sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg== -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI= - dependencies: - callsite "1.0.0" - big-integer@^1.6.16: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -8739,11 +8702,6 @@ bl@^3.0.0: dependencies: readable-stream "^3.0.1" -blob@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" - integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== - block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -8828,22 +8786,6 @@ body-parser@1.19.0, body-parser@^1.18.1, body-parser@^1.18.3: raw-body "2.4.0" type-is "~1.6.17" -body-parser@^1.16.1: - version "1.18.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" - integrity sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ= - dependencies: - bytes "3.0.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.1" - http-errors "~1.6.2" - iconv-lite "0.4.19" - on-finished "~2.3.0" - qs "6.5.1" - raw-body "2.3.2" - type-is "~1.6.15" - body@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" @@ -8987,7 +8929,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -9491,11 +9433,6 @@ caller-path@^2.0.0: dependencies: caller-callsite "^2.0.0" -callsite@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= - callsites@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" @@ -9962,7 +9899,7 @@ chokidar@^2.0.0, chokidar@^2.1.2, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.0.0, chokidar@^3.2.2: +chokidar@^3.2.2: version "3.3.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== @@ -10493,16 +10430,16 @@ colors@1.0.3: resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= -colors@^1.1.0, colors@^1.2.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.2.tgz#2df8ff573dfbf255af562f8ce7181d6b971a359b" - integrity sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ== - colors@^1.1.2, colors@^1.3.2: version "1.3.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== +colors@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.2.tgz#2df8ff573dfbf255af562f8ce7181d6b971a359b" + integrity sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ== + colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -10619,21 +10556,11 @@ compare-versions@3.5.1: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393" integrity sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg== -component-bind@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" - integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E= - -component-emitter@1.2.1, component-emitter@^1.2.0, component-emitter@^1.2.1: +component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= -component-inherit@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" - integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= - compose-function@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f" @@ -10817,16 +10744,6 @@ connect@^3.4.0: parseurl "~1.3.3" utils-merge "1.0.1" -connect@^3.6.0: - version "3.6.6" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.6.tgz#09eff6c55af7236e137135a72574858b6786f524" - integrity sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ= - dependencies: - debug "2.6.9" - finalhandler "1.1.0" - parseurl "~1.3.2" - utils-merge "1.0.1" - console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" @@ -11559,11 +11476,6 @@ custom-event-polyfill@^0.3.0: resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-0.3.0.tgz#99807839be62edb446b645832e0d80ead6fa1888" integrity sha1-mYB4Ob5i7bRGtkWDLg2A6tb6GIg= -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU= - cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -11882,24 +11794,11 @@ date-fns@^1.27.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw== -date-format@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" - integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== - date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -dateformat@^1.0.6, dateformat@~1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" - integrity sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk= - dependencies: - get-stdin "^4.0.1" - meow "^3.3.0" - dateformat@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" @@ -11910,6 +11809,14 @@ dateformat@^3.0.2: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +dateformat@~1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" + integrity sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk= + dependencies: + get-stdin "^4.0.1" + meow "^3.3.0" + debug-fabulous@1.X: version "1.1.0" resolved "https://registry.yarnpkg.com/debug-fabulous/-/debug-fabulous-1.1.0.tgz#af8a08632465224ef4174a9f06308c3c2a1ebc8e" @@ -11926,7 +11833,7 @@ debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.3. dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0, debug@~3.1.0: +debug@3.1.0, debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -12300,7 +12207,7 @@ depd@1.1.1: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k= -depd@~1.1.1, depd@~1.1.2: +depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -12509,11 +12416,6 @@ dfa@^1.2.0: resolved "https://registry.yarnpkg.com/dfa/-/dfa-1.2.0.tgz#96ac3204e2d29c49ea5b57af8d92c2ae12790657" integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q== -di@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw= - diacritics@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1" @@ -12668,16 +12570,6 @@ dom-helpers@^5.0.0: "@babel/runtime" "^7.6.3" csstype "^2.6.7" -dom-serialize@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - integrity sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs= - dependencies: - custom-event "~1.0.0" - ent "~2.2.0" - extend "^3.0.0" - void-elements "^2.0.0" - dom-serializer@0, dom-serializer@~0.1.0, dom-serializer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" @@ -13130,7 +13022,7 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== -encodeurl@^1.0.2, encodeurl@~1.0.1, encodeurl@~1.0.2: +encodeurl@^1.0.2, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= @@ -13156,46 +13048,6 @@ end-of-stream@^1.4.1, end-of-stream@^1.4.4: dependencies: once "^1.4.0" -engine.io-client@~3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36" - integrity sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw== - dependencies: - component-emitter "1.2.1" - component-inherit "0.0.3" - debug "~3.1.0" - engine.io-parser "~2.1.1" - has-cors "1.1.0" - indexof "0.0.1" - parseqs "0.0.5" - parseuri "0.0.5" - ws "~3.3.1" - xmlhttprequest-ssl "~1.5.4" - yeast "0.1.2" - -engine.io-parser@~2.1.0, engine.io-parser@~2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.3.tgz#757ab970fbf2dfb32c7b74b033216d5739ef79a6" - integrity sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA== - dependencies: - after "0.8.2" - arraybuffer.slice "~0.0.7" - base64-arraybuffer "0.1.5" - blob "0.0.5" - has-binary2 "~1.0.2" - -engine.io@~3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.2.1.tgz#b60281c35484a70ee0351ea0ebff83ec8c9522a2" - integrity sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w== - dependencies: - accepts "~1.3.4" - base64id "1.0.0" - cookie "0.3.1" - debug "~3.1.0" - engine.io-parser "~2.1.0" - ws "~3.3.1" - enhanced-resolve@4.1.0, enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" @@ -13214,11 +13066,6 @@ enhanced-resolve@~0.9.0: memory-fs "^0.2.0" tapable "^0.1.8" -ent@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= - entities@^1.1.1, entities@^1.1.2, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -13570,18 +13417,6 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@1.8.x: - version "1.8.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" - integrity sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg= - dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.2.0" - escodegen@^1.11.0: version "1.14.1" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" @@ -13983,11 +13818,6 @@ espree@^6.1.2: acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" -esprima@2.7.x, esprima@^2.7.1: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" - integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= - esprima@^3.1.3, esprima@~3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -14017,11 +13847,6 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" -estraverse@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" - integrity sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q= - estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" @@ -14075,11 +13900,6 @@ eventemitter2@~0.4.13: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" integrity sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas= -eventemitter3@1.x.x: - version "1.2.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" - integrity sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg= - eventemitter3@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" @@ -14876,19 +14696,6 @@ filter-obj@^1.1.0: resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= -finalhandler@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" - integrity sha1-zgtoVbRYU+eRsvzGgARtiCU91/U= - dependencies: - debug "2.6.9" - encodeurl "~1.0.1" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.2" - statuses "~1.3.1" - unpipe "~1.0.0" - finalhandler@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" @@ -15340,13 +15147,6 @@ front-matter@2.1.2: dependencies: js-yaml "^3.4.6" -fs-access@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" - integrity sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o= - dependencies: - null-check "^1.0.0" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -15875,17 +15675,6 @@ glob@7.1.4, glob@~7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^5.0.15, glob@~5.0.0: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^6.0.1, glob@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -15909,6 +15698,17 @@ glob@^7.0.5, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@~5.0.0: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@~7.0.0: version "7.0.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" @@ -16620,13 +16420,6 @@ grunt-contrib-watch@^1.1.0: lodash "^4.17.10" tiny-lr "^1.1.1" -grunt-karma@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/grunt-karma/-/grunt-karma-3.0.2.tgz#4f14386d43ee45f8f6b98081862e4910f5056764" - integrity sha512-imNhQO1bR1O7X6/3F5vO0o7mKy4xdkpSd40QVfxGO70cBAFcOqjv2Zu5QzsfEsSrppuu3N0vIQPbfBRjeGdpWg== - dependencies: - lodash "^4.17.10" - grunt-known-options@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-1.1.0.tgz#a4274eeb32fa765da5a7a3b1712617ce3b144149" @@ -16917,23 +16710,11 @@ has-ansi@^3.0.0: dependencies: ansi-regex "^3.0.0" -has-binary2@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" - integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw== - dependencies: - isarray "2.0.1" - has-color@~0.1.0: version "0.1.7" resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8= -has-cors@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" - integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= - has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" @@ -17348,16 +17129,6 @@ http-deceiver@^1.2.7: resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= -http-errors@1.6.2, http-errors@~1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" - integrity sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY= - dependencies: - depd "1.1.1" - inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - http-errors@1.6.3, http-errors@~1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" @@ -17379,6 +17150,16 @@ http-errors@1.7.2, http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-errors@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + integrity sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY= + dependencies: + depd "1.1.1" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + http-errors@~1.7.0: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" @@ -17425,14 +17206,6 @@ http-proxy-middleware@0.19.1: lodash "^4.17.11" micromatch "^3.1.10" -http-proxy@^1.13.0: - version "1.16.2" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" - integrity sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I= - dependencies: - eventemitter3 "1.x.x" - requires-port "1.x.x" - http-proxy@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" @@ -18929,11 +18702,6 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" - integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= - isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -18944,11 +18712,6 @@ isbinaryfile@4.0.2: resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.2.tgz#bfc45642da645681c610cca831022e30af426488" integrity sha512-C3FSxJdNrEr2F4z6uFtNzECDM5hXk+46fxaa+cwBe5/XrWSmzdG8DDgyjfX6/NRdBB21q2JXuRAzPCUs+fclnQ== -isbinaryfile@^4.0.2: - version "4.0.6" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" - integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg== - isemail@3.x.x: version "3.1.4" resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.4.tgz#76e2187ff7bee59d57522c6fd1c3f09a331933cf" @@ -19106,26 +18869,6 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -istanbul@^0.4.0: - version "0.4.5" - resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" - integrity sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs= - dependencies: - abbrev "1.0.x" - async "1.x" - escodegen "1.8.x" - esprima "2.7.x" - glob "^5.0.15" - handlebars "^4.0.1" - js-yaml "3.x" - mkdirp "0.5.x" - nopt "3.x" - once "1.x" - resolve "1.1.x" - supports-color "^3.1.0" - which "^1.1.1" - wordwrap "^1.0.0" - istextorbinary@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.2.1.tgz#a5231a08ef6dd22b268d0895084cf8d58b5bec53" @@ -19798,7 +19541,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.13.1, js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0, js-yaml@~3.13.0, js-yaml@~3.13.1: +js-yaml@3.13.1, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0, js-yaml@~3.13.0, js-yaml@~3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -20209,87 +19952,6 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -karma-chrome-launcher@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz#cf1b9d07136cc18fe239327d24654c3dbc368acf" - integrity sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w== - dependencies: - fs-access "^1.0.0" - which "^1.2.1" - -karma-coverage@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-1.1.2.tgz#cc09dceb589a83101aca5fe70c287645ef387689" - integrity sha512-eQawj4Cl3z/CjxslYy9ariU4uDh7cCNFZHNWXWRpl0pNeblY/4wHR7M7boTYXWrn9bY0z2pZmr11eKje/S/hIw== - dependencies: - dateformat "^1.0.6" - istanbul "^0.4.0" - lodash "^4.17.0" - minimatch "^3.0.0" - source-map "^0.5.1" - -karma-firefox-launcher@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz#2c47030452f04531eb7d13d4fc7669630bb93339" - integrity sha512-LbZ5/XlIXLeQ3cqnCbYLn+rOVhuMIK9aZwlP6eOLGzWdo1UVp7t6CN3DP4SafiRLjexKwHeKHDm0c38Mtd3VxA== - -karma-ie-launcher@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz#497986842c490190346cd89f5494ca9830c6d59c" - integrity sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw= - dependencies: - lodash "^4.6.1" - -karma-junit-reporter@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz#4f9c40cedfb1a395f8aef876abf96189917c6396" - integrity sha1-T5xAzt+xo5X4rvh2q/lhiZF8Y5Y= - dependencies: - path-is-absolute "^1.0.0" - xmlbuilder "8.2.2" - -karma-mocha@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.0.tgz#ad6b56b6a72e9b191e4c432dd30f4a44fc2435bc" - integrity sha512-qiZkZDJnn2kb9t2m4LoM4cYJHJVPoxvAYYe0B+go5s+A/3vc/3psUT05zW4yFz4vT0xHf+XzTTery8zdr8GWgA== - dependencies: - minimist "^1.2.3" - -karma-safari-launcher@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz#96982a2cc47d066aae71c553babb28319115a2ce" - integrity sha1-lpgqLMR9BmquccVTursoMZEVos4= - -karma@5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/karma/-/karma-5.0.2.tgz#e404373dac6e3fa08409ae4d9eda7d83adb43ee5" - integrity sha512-RpUuCuGJfN3WnjYPGIH+VBF8023Lfm3TQH6D1kcNL+FxtEPc2UUz/nVjbVAGXH4Pm+Q7FVOAQjdAeFUpXpQ3IA== - dependencies: - body-parser "^1.16.1" - braces "^3.0.2" - chokidar "^3.0.0" - colors "^1.1.0" - connect "^3.6.0" - di "^0.0.1" - dom-serialize "^2.2.0" - flatted "^2.0.0" - glob "^7.1.1" - graceful-fs "^4.1.2" - http-proxy "^1.13.0" - isbinaryfile "^4.0.2" - lodash "^4.17.14" - log4js "^4.0.0" - mime "^2.3.1" - minimatch "^3.0.2" - qjobs "^1.1.4" - range-parser "^1.2.0" - rimraf "^2.6.0" - socket.io "2.1.1" - source-map "^0.6.1" - tmp "0.0.33" - ua-parser-js "0.7.21" - yargs "^15.3.1" - kdbush@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" @@ -21127,7 +20789,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -21200,17 +20862,6 @@ log-update@^1.0.2: ansi-escapes "^1.0.0" cli-cursor "^1.0.2" -log4js@^4.0.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.5.1.tgz#e543625e97d9e6f3e6e7c9fc196dd6ab2cae30b5" - integrity sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw== - dependencies: - date-format "^2.0.0" - debug "^4.1.1" - flatted "^2.0.0" - rfdc "^1.1.4" - streamroller "^1.0.6" - logform@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" @@ -22048,7 +21699,7 @@ minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@1.2.5, minimist@^1.2.3, minimist@^1.2.5: +minimist@1.2.5, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -22970,7 +22621,7 @@ nodemon@^2.0.2: chalk "~0.4.0" underscore "~1.6.0" -"nopt@2 || 3", nopt@3.x, nopt@~3.0.1, nopt@~3.0.6: +"nopt@2 || 3", nopt@~3.0.1, nopt@~3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= @@ -23172,11 +22823,6 @@ nth-check@^1.0.2, nth-check@~1.0.1: dependencies: boolbase "~1.0.0" -null-check@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" - integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0= - null-loader@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-3.0.0.tgz#3e2b6c663c5bda8c73a54357d8fa0708dc61b245" @@ -23262,11 +22908,6 @@ object-assign@^3.0.0: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -23484,7 +23125,7 @@ on-headers@~1.0.2: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -24177,20 +23818,6 @@ parse5@^3.0.1: dependencies: "@types/node" "*" -parseqs@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" - integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0= - dependencies: - better-assert "~1.0.0" - -parseuri@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" - integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo= - dependencies: - better-assert "~1.0.0" - parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -25334,16 +24961,6 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qjobs@^1.1.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" - integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== - -qs@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== - qs@6.5.2, qs@^6.4.0, qs@^6.5.1, qs@~6.5.1, qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -25492,25 +25109,15 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -range-parser@^1.2.0, range-parser@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= - range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" - integrity sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k= - dependencies: - bytes "3.0.0" - http-errors "1.6.2" - iconv-lite "0.4.19" - unpipe "1.0.0" +range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= raw-body@2.3.3: version "2.3.3" @@ -27396,7 +27003,7 @@ requirejs@^2.3.5: resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== -requires-port@1.x.x, requires-port@^1.0.0: +requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= @@ -27515,7 +27122,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.1.7, resolve@1.1.x, resolve@~1.1.0, resolve@~1.1.7: +resolve@1.1.7, resolve@~1.1.0, resolve@~1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= @@ -27633,11 +27240,6 @@ rework@1.0.1: convert-source-map "^0.3.3" css "^2.0.0" -rfdc@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" - integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== - right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -27645,7 +27247,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.0, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@^2.2.0, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== @@ -28716,52 +28318,6 @@ socket-location@^1.0.0: dependencies: await-event "^2.1.0" -socket.io-adapter@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" - integrity sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs= - -socket.io-client@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.1.1.tgz#dcb38103436ab4578ddb026638ae2f21b623671f" - integrity sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ== - dependencies: - backo2 "1.0.2" - base64-arraybuffer "0.1.5" - component-bind "1.0.0" - component-emitter "1.2.1" - debug "~3.1.0" - engine.io-client "~3.2.0" - has-binary2 "~1.0.2" - has-cors "1.1.0" - indexof "0.0.1" - object-component "0.0.3" - parseqs "0.0.5" - parseuri "0.0.5" - socket.io-parser "~3.2.0" - to-array "0.1.4" - -socket.io-parser@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.2.0.tgz#e7c6228b6aa1f814e6148aea325b51aa9499e077" - integrity sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA== - dependencies: - component-emitter "1.2.1" - debug "~3.1.0" - isarray "2.0.1" - -socket.io@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980" - integrity sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA== - dependencies: - debug "~3.1.0" - engine.io "~3.2.0" - has-binary2 "~1.0.2" - socket.io-adapter "~1.1.0" - socket.io-client "2.1.1" - socket.io-parser "~3.2.0" - sockjs-client@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177" @@ -28893,13 +28449,6 @@ source-map@~0.1.30: dependencies: amdefine ">=0.0.4" -source-map@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" - integrity sha1-2rc/vPwrqBm03gO9b26qSBZLP50= - dependencies: - amdefine ">=0.0.4" - sourcemap-codec@^1.4.1: version "1.4.6" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9" @@ -29242,11 +28791,6 @@ stats-lite@^2.2.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -statuses@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" - integrity sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4= - stdout-stream@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" @@ -29320,17 +28864,6 @@ stream-spigot@~2.1.2: dependencies: readable-stream "~1.1.0" -streamroller@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-1.0.6.tgz#8167d8496ed9f19f05ee4b158d9611321b8cacd9" - integrity sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg== - dependencies: - async "^2.6.2" - date-format "^2.0.0" - debug "^3.2.6" - fs-extra "^7.0.1" - lodash "^4.17.14" - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -29834,7 +29367,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^3.1.0, supports-color@^3.1.2: +supports-color@^3.1.2: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= @@ -30531,7 +30064,7 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" -tmp@0.0.33, tmp@0.0.x, tmp@^0.0.33: +tmp@0.0.x, tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== @@ -30565,11 +30098,6 @@ to-absolute-glob@^2.0.0: is-absolute "^1.0.0" is-negated-glob "^1.0.0" -to-array@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= - to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -31463,7 +30991,7 @@ type-fest@^0.8.0, type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.15, type-is@~1.6.16: +type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== @@ -31544,7 +31072,7 @@ typings-tester@^0.3.2: dependencies: commander "^2.12.2" -ua-parser-js@0.7.21, ua-parser-js@^0.7.18, ua-parser-js@^0.7.9: +ua-parser-js@^0.7.18, ua-parser-js@^0.7.9: version "0.7.21" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== @@ -31594,11 +31122,6 @@ uid-safe@2.1.5: dependencies: random-bytes "~1.0.0" -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== - unbzip2-stream@^1.0.9: version "1.2.5" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47" @@ -32825,7 +32348,7 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== -void-elements@^2.0.0, void-elements@^2.0.1: +void-elements@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= @@ -33250,7 +32773,7 @@ which@1, which@1.3.1, which@^1.2.9, which@^1.3.1, which@~1.3.0: dependencies: isexe "^2.0.0" -which@^1.1.1, which@^1.2.1, which@^1.2.14, which@^1.2.8: +which@^1.2.14, which@^1.2.8: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" integrity sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg== @@ -33590,15 +33113,6 @@ ws@^7.0.0: dependencies: async-limiter "^1.0.0" -ws@~3.3.1: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== - dependencies: - async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" - x-is-function@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/x-is-function/-/x-is-function-1.0.4.tgz#5d294dc3d268cbdd062580e0c5df77a391d1fa1e" @@ -33681,11 +33195,6 @@ xmlbuilder@13.0.2: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-13.0.2.tgz#02ae33614b6a047d1c32b5389c1fdacb2bce47a7" integrity sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ== -xmlbuilder@8.2.2: - version "8.2.2" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773" - integrity sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M= - xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" @@ -33706,11 +33215,6 @@ xmldom@0.1.27: resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= - xpath@0.0.27: version "0.0.27" resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" @@ -34010,11 +33514,6 @@ yazl@^2.5.1: dependencies: buffer-crc32 "~0.2.3" -yeast@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" - integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= - yeoman-character@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/yeoman-character/-/yeoman-character-1.1.0.tgz#90d4b5beaf92759086177015b2fdfa2e0684d7c7" From 867a672c7a0be1b1c447c9ceaf84ec4883061b10 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Mon, 27 Jul 2020 14:13:50 -0400 Subject: [PATCH 06/36] [Security Solution] Use docker for endpoint tests (#73092) * Copying api integration tests into their own directory * Removing api integration tests and using ingest docker image * Fixing typo * Fixing type errors and empty string and reenabling tests * Rebuilding docs * Renaming url override variable Co-authored-by: Elastic Machine --- .../architecture/code-exploration.asciidoc | 4 +- x-pack/plugins/security_solution/README.md | 130 ++++++++++++++++++ x-pack/scripts/functional_tests.js | 1 + .../api_integration/apis/endpoint/index.ts | 22 --- x-pack/test/api_integration/apis/index.js | 1 - x-pack/test/api_integration/services/index.ts | 2 - .../ingest_manager_api_integration/config.ts | 9 +- .../apps/endpoint/endpoint_list.ts | 2 +- .../apps/endpoint/index.ts | 18 ++- .../test/security_solution_endpoint/config.ts | 7 + .../apis}/artifacts/index.ts | 8 +- .../apis}/data_stream_helper.ts | 2 +- .../apis/fixtures/package_registry_config.yml | 2 + .../apis/index.ts | 34 +++++ .../apis}/metadata.ts | 2 +- .../apis}/policy.ts | 2 +- .../apis}/resolver.ts | 10 +- .../config.ts | 31 +++++ .../ftr_provider_context.d.ts | 11 ++ .../registry.ts | 77 +++++++++++ .../services/index.ts | 13 ++ .../services/resolver.ts | 0 22 files changed, 341 insertions(+), 47 deletions(-) create mode 100644 x-pack/plugins/security_solution/README.md delete mode 100644 x-pack/test/api_integration/apis/endpoint/index.ts rename x-pack/test/{api_integration/apis/endpoint => security_solution_endpoint_api_int/apis}/artifacts/index.ts (98%) rename x-pack/test/{api_integration/apis/endpoint => security_solution_endpoint_api_int/apis}/data_stream_helper.ts (94%) create mode 100644 x-pack/test/security_solution_endpoint_api_int/apis/fixtures/package_registry_config.yml create mode 100644 x-pack/test/security_solution_endpoint_api_int/apis/index.ts rename x-pack/test/{api_integration/apis/endpoint => security_solution_endpoint_api_int/apis}/metadata.ts (99%) rename x-pack/test/{api_integration/apis/endpoint => security_solution_endpoint_api_int/apis}/policy.ts (96%) rename x-pack/test/{api_integration/apis/endpoint => security_solution_endpoint_api_int/apis}/resolver.ts (98%) create mode 100644 x-pack/test/security_solution_endpoint_api_int/config.ts create mode 100644 x-pack/test/security_solution_endpoint_api_int/ftr_provider_context.d.ts create mode 100644 x-pack/test/security_solution_endpoint_api_int/registry.ts create mode 100644 x-pack/test/security_solution_endpoint_api_int/services/index.ts rename x-pack/test/{api_integration => security_solution_endpoint_api_int}/services/resolver.ts (100%) diff --git a/docs/developer/architecture/code-exploration.asciidoc b/docs/developer/architecture/code-exploration.asciidoc index f18a6c2f14926..4481dea44795c 100644 --- a/docs/developer/architecture/code-exploration.asciidoc +++ b/docs/developer/architecture/code-exploration.asciidoc @@ -524,9 +524,9 @@ WARNING: Missing README. See Configuring security in Kibana. -- {kib-repo}blob/{branch}/x-pack/plugins/security_solution[securitySolution] +- {kib-repo}blob/{branch}/x-pack/plugins/security_solution/README.md[securitySolution] -WARNING: Missing README. +Welcome to the Kibana Security Solution plugin! This README will go over getting started with development and testing. - {kib-repo}blob/{branch}/x-pack/plugins/snapshot_restore/README.md[snapshotRestore] diff --git a/x-pack/plugins/security_solution/README.md b/x-pack/plugins/security_solution/README.md new file mode 100644 index 0000000000000..6680dbf1a149b --- /dev/null +++ b/x-pack/plugins/security_solution/README.md @@ -0,0 +1,130 @@ +# Security Solution + +Welcome to the Kibana Security Solution plugin! This README will go over getting started with development and testing. + +## Development + +## Tests + +The endpoint specific tests leverage the ingest manager to install the endpoint package. Before the api integration +and functional tests are run the ingest manager is initialized. This initialization process includes reaching out to +a package registry service to install the endpoint package. The endpoint tests support three different ways to run +the tests given the constraint on an available package registry. + +1. Using Docker +2. Running your own local package registry +3. Using the default external package registry + +These scenarios will be outlined the sections below. + +### Endpoint API Integration Tests Location + +The endpoint api integration tests are located [here](../../test/security_solution_endpoint_api_int) + +### Endpoint Functional Tests Location + +The endpoint functional tests are located [here](../../test/security_solution_endpoint) + +### Using Docker + +To run the tests using the recommended docker image version you must have `docker` installed. The testing infrastructure +will stand up a docker container using the image defined [here](../../test/ingest_manager_api_integration/config.ts#L15) + +Make sure you're in the Kibana root directory. + +#### Endpoint API Integration Tests + +In one terminal, run: + +```bash +INGEST_MANAGEMENT_PACKAGE_REGISTRY_PORT=12345 yarn test:ftr:server --config x-pack/test/security_solution_endpoint_api_int/config.ts +``` + +In another terminal, run: + +```bash +INGEST_MANAGEMENT_PACKAGE_REGISTRY_PORT=12345 yarn test:ftr:runner --config x-pack/test/security_solution_endpoint_api_int/config.ts +``` + +#### Endpoint Functional Tests + +In one terminal, run: + +```bash +INGEST_MANAGEMENT_PACKAGE_REGISTRY_PORT=12345 yarn test:ftr:server --config x-pack/test/security_solution_endpoint/config.ts +``` + +In another terminal, run: + +```bash +INGEST_MANAGEMENT_PACKAGE_REGISTRY_PORT=12345 yarn test:ftr:runner --config x-pack/test/security_solution_endpoint/config.ts +``` + +### Running your own package registry + +If you are doing endpoint package development it will be useful to run your own package registry to serve the latest package you're building. +To do this use the following commands: + +Make sure you're in the Kibana root directory. + +#### Endpoint API Integration Tests + +In one terminal, run: + +```bash +PACKAGE_REGISTRY_URL_OVERRIDE= yarn test:ftr:server --config x-pack/test/security_solution_endpoint_api_int/config.ts +``` + +In another terminal, run: + +```bash +PACKAGE_REGISTRY_URL_OVERRIDE= yarn test:ftr:runner --config x-pack/test/security_solution_endpoint_api_int/config.ts +``` + +#### Endpoint Functional Tests + +In one terminal, run: + +```bash +PACKAGE_REGISTRY_URL_OVERRIDE= yarn test:ftr:server --config x-pack/test/security_solution_endpoint/config.ts +``` + +In another terminal, run: + +```bash +PACKAGE_REGISTRY_URL_OVERRIDE= yarn test:ftr:runner --config x-pack/test/security_solution_endpoint/config.ts +``` + +### Using the default public registry + +If you don't have docker installed and don't want to run your own registry, you can run the tests using the ingest manager's default public package registry. The actual package registry used is [here](../../plugins/ingest_manager/common/constants/epm.ts#L9) + +Make sure you're in the Kibana root directory. + +#### Endpoint API Integration Tests + +In one terminal, run: + +```bash +yarn test:ftr:server --config x-pack/test/security_solution_endpoint_api_int/config.ts +``` + +In another terminal, run: + +```bash +yarn test:ftr:runner --config x-pack/test/security_solution_endpoint_api_int/config.ts +``` + +#### Endpoint Functional Tests + +In one terminal, run: + +```bash +yarn test:ftr:server --config x-pack/test/security_solution_endpoint/config.ts +``` + +In another terminal, run: + +```bash +yarn test:ftr:runner --config x-pack/test/security_solution_endpoint/config.ts +``` diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index c568b92e85515..eeff81d492d26 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -53,6 +53,7 @@ const onlyNotInCoverageTests = [ require.resolve('../test/licensing_plugin/config.legacy.ts'), require.resolve('../test/endpoint_api_integration_no_ingest/config.ts'), require.resolve('../test/reporting_api_integration/config.js'), + require.resolve('../test/security_solution_endpoint_api_int/config.ts'), require.resolve('../test/ingest_manager_api_integration/config.ts'), ]; diff --git a/x-pack/test/api_integration/apis/endpoint/index.ts b/x-pack/test/api_integration/apis/endpoint/index.ts deleted file mode 100644 index 5ada2bd094d4b..0000000000000 --- a/x-pack/test/api_integration/apis/endpoint/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function endpointAPIIntegrationTests({ - loadTestFile, - getService, -}: FtrProviderContext) { - describe('Endpoint plugin', function () { - const ingestManager = getService('ingestManager'); - this.tags(['endpoint']); - before(async () => { - await ingestManager.setup(); - }); - loadTestFile(require.resolve('./resolver')); - loadTestFile(require.resolve('./metadata')); - loadTestFile(require.resolve('./policy')); - }); -} diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index 05b305ccd833f..23532d1311754 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -28,7 +28,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./lens')); loadTestFile(require.resolve('./ml')); loadTestFile(require.resolve('./transform')); - loadTestFile(require.resolve('./endpoint')); loadTestFile(require.resolve('./lists')); loadTestFile(require.resolve('./upgrade_assistant')); }); diff --git a/x-pack/test/api_integration/services/index.ts b/x-pack/test/api_integration/services/index.ts index 75cc2b451ea2e..7113e117582dd 100644 --- a/x-pack/test/api_integration/services/index.ts +++ b/x-pack/test/api_integration/services/index.ts @@ -27,7 +27,6 @@ import { InfraOpsSourceConfigurationProvider } from './infraops_source_configura import { InfraLogSourceConfigurationProvider } from './infra_log_source_configuration'; import { MachineLearningProvider } from './ml'; import { IngestManagerProvider } from '../../common/services/ingest_manager'; -import { ResolverGeneratorProvider } from './resolver'; import { TransformProvider } from './transform'; export const services = { @@ -48,6 +47,5 @@ export const services = { usageAPI: UsageAPIProvider, ml: MachineLearningProvider, ingestManager: IngestManagerProvider, - resolverGenerator: ResolverGeneratorProvider, transform: TransformProvider, }; diff --git a/x-pack/test/ingest_manager_api_integration/config.ts b/x-pack/test/ingest_manager_api_integration/config.ts index 2aa2e62a4b9e1..ddb49a09a7afa 100644 --- a/x-pack/test/ingest_manager_api_integration/config.ts +++ b/x-pack/test/ingest_manager_api_integration/config.ts @@ -9,6 +9,11 @@ import path from 'path'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { defineDockerServersConfig } from '@kbn/test'; +// Docker image to use for Ingest Manager API integration tests. +// This hash comes from the commit hash here: https://github.com/elastic/package-storage/commit/48f3935a72b0c5aacc6fec8ef36d559b089a238b +export const dockerImage = + 'docker.elastic.co/package-registry/distribution:48f3935a72b0c5aacc6fec8ef36d559b089a238b'; + export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); @@ -29,10 +34,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { )}:/packages/test-packages`, ]; - // Docker image to use for Ingest Manager API integration tests. - const dockerImage = - 'docker.elastic.co/package-registry/distribution:184b85f19e8fd14363e36150173d338ff9659f01'; - return { testFiles: [require.resolve('./apis')], servers: xPackAPITestsConfig.get('servers'), diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 6971d9f523e7e..07667a140d090 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { deleteMetadataStream } from '../../../api_integration/apis/endpoint/data_stream_helper'; +import { deleteMetadataStream } from '../../../security_solution_endpoint_api_int/apis/data_stream_helper'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'endpoint', 'header', 'endpointPageUtils']); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index bd933c9a136f2..eec3da4ce1c5e 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -3,12 +3,28 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { DEFAULT_REGISTRY_URL } from '../../../../plugins/ingest_manager/common'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { + isRegistryEnabled, + getRegistryUrl, +} from '../../../security_solution_endpoint_api_int/registry'; + +export default function (providerContext: FtrProviderContext) { + const { loadTestFile, getService } = providerContext; -export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('endpoint', function () { this.tags('ciGroup7'); const ingestManager = getService('ingestManager'); + const log = getService('log'); + + if (!isRegistryEnabled()) { + log.warning('These tests are being run with an external package registry'); + } + + const registryUrl = getRegistryUrl() ?? DEFAULT_REGISTRY_URL; + log.info(`Package registry URL for tests: ${registryUrl}`); + before(async () => { await ingestManager.setup(); }); diff --git a/x-pack/test/security_solution_endpoint/config.ts b/x-pack/test/security_solution_endpoint/config.ts index 2d94163fa1018..5aa5e42ffd4ee 100644 --- a/x-pack/test/security_solution_endpoint/config.ts +++ b/x-pack/test/security_solution_endpoint/config.ts @@ -8,6 +8,10 @@ import { resolve } from 'path'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { pageObjects } from './page_objects'; import { services } from './services'; +import { + getRegistryUrlAsArray, + createEndpointDockerConfig, +} from '../security_solution_endpoint_api_int/registry'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); @@ -16,6 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xpackFunctionalConfig.getAll(), pageObjects, testFiles: [resolve(__dirname, './apps/endpoint')], + dockerServers: createEndpointDockerConfig(), junit: { reportName: 'X-Pack Endpoint Functional Tests', }, @@ -31,6 +36,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { serverArgs: [ ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), '--xpack.ingestManager.enabled=true', + // if you return an empty string here the kibana server will not start properly but an empty array works + ...getRegistryUrlAsArray(), ], }, }; diff --git a/x-pack/test/api_integration/apis/endpoint/artifacts/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts similarity index 98% rename from x-pack/test/api_integration/apis/endpoint/artifacts/index.ts rename to x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts index b37522ed52b5c..a4a8de418157f 100644 --- a/x-pack/test/api_integration/apis/endpoint/artifacts/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts @@ -8,11 +8,8 @@ import expect from '@kbn/expect'; import { createHash } from 'crypto'; import { inflateSync } from 'zlib'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { - getSupertestWithoutAuth, - setupIngest, -} from '../../../../ingest_manager_api_integration/apis/fleet/agents/services'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { getSupertestWithoutAuth } from '../../../ingest_manager_api_integration/apis/fleet/agents/services'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; @@ -22,7 +19,6 @@ export default function (providerContext: FtrProviderContext) { let agentAccessAPIKey: string; describe('artifact download', () => { - setupIngest(providerContext); before(async () => { await esArchiver.load('endpoint/artifacts/api_feature', { useCreate: true }); diff --git a/x-pack/test/api_integration/apis/endpoint/data_stream_helper.ts b/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts similarity index 94% rename from x-pack/test/api_integration/apis/endpoint/data_stream_helper.ts rename to x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts index b239ab41e41f1..b16da16b3137f 100644 --- a/x-pack/test/api_integration/apis/endpoint/data_stream_helper.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts @@ -10,7 +10,7 @@ import { eventsIndexPattern, alertsIndexPattern, policyIndexPattern, -} from '../../../../plugins/security_solution/common/endpoint/constants'; +} from '../../../plugins/security_solution/common/endpoint/constants'; export async function deleteDataStream(getService: (serviceName: 'es') => Client, index: string) { const client = getService('es'); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/fixtures/package_registry_config.yml b/x-pack/test/security_solution_endpoint_api_int/apis/fixtures/package_registry_config.yml new file mode 100644 index 0000000000000..4d93386b4d4e1 --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/apis/fixtures/package_registry_config.yml @@ -0,0 +1,2 @@ +package_paths: + - /packages/production diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts new file mode 100644 index 0000000000000..fb11a7c52fd35 --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../ftr_provider_context'; +import { isRegistryEnabled, getRegistryUrl } from '../registry'; +import { DEFAULT_REGISTRY_URL } from '../../../plugins/ingest_manager/common'; + +export default function endpointAPIIntegrationTests(providerContext: FtrProviderContext) { + const { loadTestFile, getService } = providerContext; + + describe('Endpoint plugin', function () { + const ingestManager = getService('ingestManager'); + + this.tags('ciGroup7'); + const log = getService('log'); + + if (!isRegistryEnabled()) { + log.warning('These tests are being run with an external package registry'); + } + + const registryUrl = getRegistryUrl() ?? DEFAULT_REGISTRY_URL; + log.info(`Package registry URL for tests: ${registryUrl}`); + + before(async () => { + await ingestManager.setup(); + }); + loadTestFile(require.resolve('./resolver')); + loadTestFile(require.resolve('./metadata')); + loadTestFile(require.resolve('./policy')); + loadTestFile(require.resolve('./artifacts')); + }); +} diff --git a/x-pack/test/api_integration/apis/endpoint/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts similarity index 99% rename from x-pack/test/api_integration/apis/endpoint/metadata.ts rename to x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index 41531269ddeb9..719327e5f9b79 100644 --- a/x-pack/test/api_integration/apis/endpoint/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect/expect.js'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../ftr_provider_context'; import { deleteMetadataStream } from './data_stream_helper'; /** diff --git a/x-pack/test/api_integration/apis/endpoint/policy.ts b/x-pack/test/security_solution_endpoint_api_int/apis/policy.ts similarity index 96% rename from x-pack/test/api_integration/apis/endpoint/policy.ts rename to x-pack/test/security_solution_endpoint_api_int/apis/policy.ts index e33423d172567..66bcc0e759916 100644 --- a/x-pack/test/api_integration/apis/endpoint/policy.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/policy.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect/expect.js'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../ftr_provider_context'; import { deletePolicyStream } from './data_stream_helper'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/api_integration/apis/endpoint/resolver.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver.ts similarity index 98% rename from x-pack/test/api_integration/apis/endpoint/resolver.ts rename to x-pack/test/security_solution_endpoint_api_int/apis/resolver.ts index fa980aed30502..3b515f86c6761 100644 --- a/x-pack/test/api_integration/apis/endpoint/resolver.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver.ts @@ -16,12 +16,12 @@ import { LegacyEndpointEvent, ResolverNodeStats, ResolverRelatedAlerts, -} from '../../../../plugins/security_solution/common/endpoint/types'; +} from '../../../plugins/security_solution/common/endpoint/types'; import { parentEntityId, eventId, -} from '../../../../plugins/security_solution/common/endpoint/models/event'; -import { FtrProviderContext } from '../../ftr_provider_context'; +} from '../../../plugins/security_solution/common/endpoint/models/event'; +import { FtrProviderContext } from '../ftr_provider_context'; import { Event, Tree, @@ -29,8 +29,8 @@ import { RelatedEventCategory, RelatedEventInfo, categoryMapping, -} from '../../../../plugins/security_solution/common/endpoint/generate_data'; -import { Options, GeneratedTrees } from '../../services/resolver'; +} from '../../../plugins/security_solution/common/endpoint/generate_data'; +import { Options, GeneratedTrees } from '../services/resolver'; /** * Check that the given lifecycle is in the resolver tree's corresponding map diff --git a/x-pack/test/security_solution_endpoint_api_int/config.ts b/x-pack/test/security_solution_endpoint_api_int/config.ts new file mode 100644 index 0000000000000..726059a8d73fe --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/config.ts @@ -0,0 +1,31 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { createEndpointDockerConfig, getRegistryUrlAsArray } from './registry'; +import { services } from './services'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); + + return { + ...xPackAPITestsConfig.getAll(), + testFiles: [require.resolve('./apis')], + dockerServers: createEndpointDockerConfig(), + services, + junit: { + reportName: 'X-Pack Endpoint API Integration Tests', + }, + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), + // if you return an empty string here the kibana server will not start properly but an empty array works + ...getRegistryUrlAsArray(), + ], + }, + }; +} diff --git a/x-pack/test/security_solution_endpoint_api_int/ftr_provider_context.d.ts b/x-pack/test/security_solution_endpoint_api_int/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..e3add3748f56d --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/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/security_solution_endpoint_api_int/registry.ts b/x-pack/test/security_solution_endpoint_api_int/registry.ts new file mode 100644 index 0000000000000..cc474cbf29aaf --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/registry.ts @@ -0,0 +1,77 @@ +/* + * 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 path from 'path'; + +import { defineDockerServersConfig } from '@kbn/test'; +import { dockerImage as ingestDockerImage } from '../ingest_manager_api_integration/config'; + +/** + * This is used by CI to set the docker registry port + * you can also define this environment variable locally when running tests which + * will spin up a local docker package registry locally for you + * if this is defined it takes precedence over the `packageRegistryOverride` variable + */ +const dockerRegistryPort: string | undefined = process.env.INGEST_MANAGEMENT_PACKAGE_REGISTRY_PORT; + +/** + * If you don't want to use the docker image version pinned below and instead want to run your own + * registry or use an external registry you can define this environment variable when running + * the tests to use that registry url instead. + * + * This is particularly useful when a developer needs to test a new package against the kibana + * integration or functional tests. Instead of having to publish a whole new docker image we + * can set this environment variable which will point to the location of where your package registry + * is serving the updated package. + * + * This variable will not and should not be used by CI. CI should always use the pinned docker image below. + */ +const packageRegistryOverride: string | undefined = process.env.PACKAGE_REGISTRY_URL_OVERRIDE; + +const defaultRegistryConfigPath = path.join( + __dirname, + './apis/fixtures/package_registry_config.yml' +); + +export function createEndpointDockerConfig( + packageRegistryConfig: string = defaultRegistryConfigPath, + dockerImage: string = ingestDockerImage, + dockerArgs: string[] = [] +) { + const args: string[] = [ + '-v', + `${packageRegistryConfig}:/package-registry/config.yml`, + ...dockerArgs, + ]; + return defineDockerServersConfig({ + registry: { + enabled: !!dockerRegistryPort, + image: dockerImage, + portInContainer: 8080, + port: dockerRegistryPort, + args, + waitForLogLine: 'package manifests loaded', + }, + }); +} + +export function getRegistryUrl(): string | undefined { + let registryUrl: string | undefined; + if (dockerRegistryPort !== undefined) { + registryUrl = `--xpack.ingestManager.registryUrl=http://localhost:${dockerRegistryPort}`; + } else if (packageRegistryOverride !== undefined) { + registryUrl = `--xpack.ingestManager.registryUrl=${packageRegistryOverride}`; + } + return registryUrl; +} + +export function getRegistryUrlAsArray(): string[] { + const registryUrl: string | undefined = getRegistryUrl(); + return registryUrl !== undefined ? [registryUrl] : []; +} + +export function isRegistryEnabled() { + return getRegistryUrl() !== undefined; +} diff --git a/x-pack/test/security_solution_endpoint_api_int/services/index.ts b/x-pack/test/security_solution_endpoint_api_int/services/index.ts new file mode 100644 index 0000000000000..47d45557022d3 --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/services/index.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 { services as xPackAPIServices } from '../../api_integration/services'; +import { ResolverGeneratorProvider } from './resolver'; + +export const services = { + ...xPackAPIServices, + resolverGenerator: ResolverGeneratorProvider, +}; diff --git a/x-pack/test/api_integration/services/resolver.ts b/x-pack/test/security_solution_endpoint_api_int/services/resolver.ts similarity index 100% rename from x-pack/test/api_integration/services/resolver.ts rename to x-pack/test/security_solution_endpoint_api_int/services/resolver.ts From 34fd7b301d3e1b3eec0eac6b2ce33d02e93166b9 Mon Sep 17 00:00:00 2001 From: Marshall Main <55718608+marshallmain@users.noreply.github.com> Date: Mon, 27 Jul 2020 14:17:19 -0400 Subject: [PATCH 07/36] Increase limit on exception items to 10k (#73117) Co-authored-by: Elastic Machine --- .../server/lib/detection_engine/signals/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 1c59a4b7ea5d0..90373ee676121 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -183,7 +183,7 @@ export const getExceptions = async ({ listId: foundList.list_id, namespaceType, page: 1, - perPage: 5000, + perPage: 10000, filter: undefined, sortOrder: undefined, sortField: undefined, From 7c24a61c435099acfe751b0379a8092df4d5b5e3 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 27 Jul 2020 15:19:42 -0400 Subject: [PATCH 08/36] Make ingest node pipelines api tests more robust (#73289) --- .../ingest_pipelines/ingest_pipelines.ts | 90 +++++++++++++------ .../ingest_pipelines/lib/elasticsearch.ts | 21 ++++- 2 files changed, 82 insertions(+), 29 deletions(-) diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts index 6a827298521dd..b3fab42a46114 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts @@ -14,16 +14,26 @@ const API_BASE_PATH = '/api/ingest_pipelines'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const { createPipeline, deletePipeline } = registerEsHelpers(getService); + const { createPipeline, deletePipeline, cleanupPipelines } = registerEsHelpers(getService); + + describe('Pipelines', function () { + after(async () => { + await cleanupPipelines(); + }); - describe.skip('Pipelines', function () { describe('Create', () => { const PIPELINE_ID = 'test_create_pipeline'; const REQUIRED_FIELDS_PIPELINE_ID = 'test_create_required_fields_pipeline'; - after(() => { - deletePipeline(PIPELINE_ID); - deletePipeline(REQUIRED_FIELDS_PIPELINE_ID); + after(async () => { + // Clean up any pipelines created in test cases + await Promise.all([PIPELINE_ID, REQUIRED_FIELDS_PIPELINE_ID].map(deletePipeline)).catch( + (err) => { + // eslint-disable-next-line no-console + console.log(`[Cleanup error] Error deleting pipelines: ${err.message}`); + throw err; + } + ); }); it('should create a pipeline', async () => { @@ -127,8 +137,16 @@ export default function ({ getService }: FtrProviderContext) { ], }; - before(() => createPipeline({ body: PIPELINE, id: PIPELINE_ID })); - after(() => deletePipeline(PIPELINE_ID)); + before(async () => { + // Create pipeline that can be used to test PUT request + try { + await createPipeline({ body: PIPELINE, id: PIPELINE_ID }, true); + } catch (err) { + // eslint-disable-next-line no-console + console.log('[Setup error] Error creating ingest node pipeline'); + throw err; + } + }); it('should allow an existing pipeline to be updated', async () => { const uri = `${API_BASE_PATH}/${PIPELINE_ID}`; @@ -185,7 +203,7 @@ export default function ({ getService }: FtrProviderContext) { }); describe('Get', () => { - const PIPELINE_ID = 'test_pipeline'; + const PIPELINE_ID = 'test_get_pipeline'; const PIPELINE = { description: 'test pipeline description', processors: [ @@ -198,8 +216,16 @@ export default function ({ getService }: FtrProviderContext) { version: 1, }; - before(() => createPipeline({ body: PIPELINE, id: PIPELINE_ID })); - after(() => deletePipeline(PIPELINE_ID)); + before(async () => { + // Create pipeline that can be used to test GET request + try { + await createPipeline({ body: PIPELINE, id: PIPELINE_ID }, true); + } catch (err) { + // eslint-disable-next-line no-console + console.log('[Setup error] Error creating ingest node pipeline'); + throw err; + } + }); describe('all pipelines', () => { it('should return an array of pipelines', async () => { @@ -245,29 +271,40 @@ export default function ({ getService }: FtrProviderContext) { version: 1, }; + const pipelineA = { body: PIPELINE, id: 'test_delete_pipeline_a' }; + const pipelineB = { body: PIPELINE, id: 'test_delete_pipeline_b' }; + const pipelineC = { body: PIPELINE, id: 'test_delete_pipeline_c' }; + const pipelineD = { body: PIPELINE, id: 'test_delete_pipeline_d' }; + + before(async () => { + // Create several pipelines that can be used to test deletion + await Promise.all( + [pipelineA, pipelineB, pipelineC, pipelineD].map((pipeline) => createPipeline(pipeline)) + ).catch((err) => { + // eslint-disable-next-line no-console + console.log(`[Setup error] Error creating pipelines: ${err.message}`); + throw err; + }); + }); + it('should delete a pipeline', async () => { - // Create pipeline to be deleted - const PIPELINE_ID = 'test_delete_pipeline'; - createPipeline({ body: PIPELINE, id: PIPELINE_ID }); + const { id } = pipelineA; - const uri = `${API_BASE_PATH}/${PIPELINE_ID}`; + const uri = `${API_BASE_PATH}/${id}`; const { body } = await supertest.delete(uri).set('kbn-xsrf', 'xxx').expect(200); expect(body).to.eql({ - itemsDeleted: [PIPELINE_ID], + itemsDeleted: [id], errors: [], }); }); it('should delete multiple pipelines', async () => { - // Create pipelines to be deleted - const PIPELINE_ONE_ID = 'test_delete_pipeline_1'; - const PIPELINE_TWO_ID = 'test_delete_pipeline_2'; - createPipeline({ body: PIPELINE, id: PIPELINE_ONE_ID }); - createPipeline({ body: PIPELINE, id: PIPELINE_TWO_ID }); + const { id: pipelineBId } = pipelineB; + const { id: pipelineCId } = pipelineC; - const uri = `${API_BASE_PATH}/${PIPELINE_ONE_ID},${PIPELINE_TWO_ID}`; + const uri = `${API_BASE_PATH}/${pipelineBId},${pipelineCId}`; const { body: { itemsDeleted, errors }, @@ -276,24 +313,21 @@ export default function ({ getService }: FtrProviderContext) { expect(errors).to.eql([]); // The itemsDeleted array order isn't guaranteed, so we assert against each pipeline name instead - [PIPELINE_ONE_ID, PIPELINE_TWO_ID].forEach((pipelineName) => { + [pipelineBId, pipelineCId].forEach((pipelineName) => { expect(itemsDeleted.includes(pipelineName)).to.be(true); }); }); it('should return an error for any pipelines not sucessfully deleted', async () => { const PIPELINE_DOES_NOT_EXIST = 'pipeline_does_not_exist'; + const { id: existingPipelineId } = pipelineD; - // Create pipeline to be deleted - const PIPELINE_ONE_ID = 'test_delete_pipeline_1'; - createPipeline({ body: PIPELINE, id: PIPELINE_ONE_ID }); - - const uri = `${API_BASE_PATH}/${PIPELINE_ONE_ID},${PIPELINE_DOES_NOT_EXIST}`; + const uri = `${API_BASE_PATH}/${existingPipelineId},${PIPELINE_DOES_NOT_EXIST}`; const { body } = await supertest.delete(uri).set('kbn-xsrf', 'xxx').expect(200); expect(body).to.eql({ - itemsDeleted: [PIPELINE_ONE_ID], + itemsDeleted: [existingPipelineId], errors: [ { name: PIPELINE_DOES_NOT_EXIST, diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts index 2f42596a66b54..6de91e1154a85 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts @@ -26,14 +26,33 @@ interface Pipeline { * @param {ElasticsearchClient} es The Elasticsearch client instance */ export const registerEsHelpers = (getService: FtrProviderContext['getService']) => { + let pipelinesCreated: string[] = []; + const es = getService('legacyEs'); - const createPipeline = (pipeline: Pipeline) => es.ingest.putPipeline(pipeline); + const createPipeline = (pipeline: Pipeline, cachePipeline?: boolean) => { + if (cachePipeline) { + pipelinesCreated.push(pipeline.id); + } + + return es.ingest.putPipeline(pipeline); + }; const deletePipeline = (pipelineId: string) => es.ingest.deletePipeline({ id: pipelineId }); + const cleanupPipelines = () => + Promise.all(pipelinesCreated.map(deletePipeline)) + .then(() => { + pipelinesCreated = []; + }) + .catch((err) => { + // eslint-disable-next-line no-console + console.log(`[Cleanup error] Error deleting ES resources: ${err.message}`); + }); + return { createPipeline, deletePipeline, + cleanupPipelines, }; }; From f23359c099ece3807f68b9b8ab24a72ff005d911 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 27 Jul 2020 14:20:26 -0500 Subject: [PATCH 09/36] [APM] Fix license management URL (#73294) The URL to license management has changed, so update our links, both in the prompt on the Service Map, and in the app-wide expired license message. Use `useKibanaUrl` in both places and update that hook to make the `hash` argument optional, since many apps don't use a hash now. I looked for a more reliable way to get the URL for that app but couldn't come up with anything. I'd appreciate any suggestions if there's a better method. --- .../apm/public/components/shared/LicensePrompt/index.tsx | 3 +-- .../context/LicenseContext/InvalidLicenseNotification.tsx | 5 ++--- x-pack/plugins/apm/public/hooks/useKibanaUrl.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx b/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx index 50be268d9ccd0..8409326243614 100644 --- a/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx @@ -16,8 +16,7 @@ interface Props { export function LicensePrompt({ text, showBetaBadge = false }: Props) { const licensePageUrl = useKibanaUrl( - '/app/kibana', - '/management/stack/license_management/home' + '/app/management/stack/license_management' ); const renderLicenseBody = ( diff --git a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx index 481e89e09685e..1195038a6b753 100644 --- a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx +++ b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx @@ -6,11 +6,10 @@ import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; +import { useKibanaUrl } from '../../hooks/useKibanaUrl'; export function InvalidLicenseNotification() { - const { core } = useApmPluginContext(); - const manageLicenseURL = core.http.basePath.prepend( + const manageLicenseURL = useKibanaUrl( '/app/management/stack/license_management' ); diff --git a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts index 186a752f52487..b4a354c231633 100644 --- a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts +++ b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts @@ -9,7 +9,7 @@ import { useApmPluginContext } from './useApmPluginContext'; export function useKibanaUrl( /** The path to the plugin */ path: string, - /** The hash path */ hash: string + /** The hash path */ hash?: string ) { const { core } = useApmPluginContext(); return url.format({ From 1c690c68af6ea05248ca04b259fb1f01970a044c Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 27 Jul 2020 15:39:52 -0400 Subject: [PATCH 10/36] [Uptime] Increase timeout in attempt to fix skipped a11y test (#73078) * Increase timeout in attempt to fix skipped a11y test. * Temporarily only run uptime tests for faster flaky testing. * Uncomment other test suites. * Unskip test and delete comment. Co-authored-by: Elastic Machine --- x-pack/test/accessibility/apps/uptime.ts | 3 +-- x-pack/test/functional/services/uptime/navigation.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/test/accessibility/apps/uptime.ts b/x-pack/test/accessibility/apps/uptime.ts index e6ef1cfe8cfe2..ebd120fa0feea 100644 --- a/x-pack/test/accessibility/apps/uptime.ts +++ b/x-pack/test/accessibility/apps/uptime.ts @@ -17,8 +17,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const es = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/72994 - describe.skip('uptime', () => { + describe('uptime', () => { before(async () => { await esArchiver.load('uptime/blank'); await makeChecks(es, A11Y_TEST_MONITOR_ID, 150, 1, 1000, { diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index f8e0c0cff41f4..ab511abf130a5 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -41,7 +41,7 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv goToSettings: async () => { await goToUptimeRoot(); await testSubjects.click('settings-page-link', 5000); - await testSubjects.existOrFail('uptimeSettingsPage', { timeout: 2000 }); + await testSubjects.existOrFail('uptimeSettingsPage', { timeout: 10000 }); }, checkIfOnMonitorPage: async (monitorId: string) => { From 2ae470e897976abb939c31708bff41fd0d0dcd07 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Mon, 27 Jul 2020 16:04:10 -0500 Subject: [PATCH 11/36] Add Kea.js support to Enterprise Search plugin (#72160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Kea packages - kea and kea-waitfor * Add Kea declarations and types Hopefully TypeScript support coming soon from author * Add Kea to entry point * Add logic for overview * Update components to use Kea * Fix a couple of tests that weren’t getting complete coverage * Remove kea-waitfor Turns out we don’t need it * Remove unused declaration * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts Co-authored-by: Constance * [Opinionated] Remove extra actions defs - they're already being defined in IOverviewActions, so no need to repeat them * DRY out a new reusable/generics IKeaLogic/Listeners interface - Multiple logic files can now do IKeaListeners and not have to declare their own IListenerParams! + bonus IKeaSelectors just for consistency * DRY out Kea reducers definitions to generics interface * [Refactor] Improve KeaReducers generic to actually type-check/check key names - Typescript will now throw an error if you put in a key name that isn't declared in your actions/values interface - default & new states now will be type checked!! :tada: * [Refactor] Update selectors() and listeners() to also check types and keys * [Refactor] Move param defs to bottom of file instead of inline - so that inline definitions mostly focus on type checks, and more boilerplate defs are DRYed out - I played around with 2.1 obj definitions and got terrible results here :( * Update tests and remove selectors per code review * Remove last statsColumns instance * Remove last instance of hideOnboarding Co-authored-by: Constance Co-authored-by: Constance Chen Co-authored-by: Elastic Machine --- x-pack/package.json | 3 +- .../public/applications/kea.d.ts | 13 ++ .../public/applications/shared/types.ts | 56 ++++++ .../components/overview/__mocks__/index.ts | 7 + .../overview/__mocks__/overview_logic.mock.ts | 47 +++++ .../overview/onboarding_steps.test.tsx | 77 ++++---- .../components/overview/onboarding_steps.tsx | 27 +-- .../overview/organization_stats.test.tsx | 8 +- .../overview/organization_stats.tsx | 115 ++++++------ .../components/overview/overview.test.tsx | 45 +++-- .../components/overview/overview.tsx | 97 ++-------- .../overview/overview_logic.test.ts | 141 +++++++++++++++ .../components/overview/overview_logic.ts | 168 ++++++++++++++++++ .../overview/recent_activity.test.tsx | 21 ++- .../components/overview/recent_activity.tsx | 13 +- .../content_section/content_section.test.tsx | 3 +- .../view_content_header.test.tsx | 3 +- .../applications/workplace_search/index.tsx | 11 +- .../applications/workplace_search/types.ts | 11 ++ yarn.lock | 5 + 20 files changed, 634 insertions(+), 237 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/kea.d.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts diff --git a/x-pack/package.json b/x-pack/package.json index d1f638ccad8d0..dee99d6f0ddac 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -282,6 +282,7 @@ "json-stable-stringify": "^1.0.1", "jsonwebtoken": "^8.5.1", "jsts": "^1.6.2", + "kea": "^2.0.1", "lodash": "^4.17.15", "lz-string": "^1.4.4", "mapbox-gl": "^1.10.0", @@ -384,4 +385,4 @@ "cypress-multi-reporters" ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/enterprise_search/public/applications/kea.d.ts b/x-pack/plugins/enterprise_search/public/applications/kea.d.ts new file mode 100644 index 0000000000000..961d93ccc12e6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/kea.d.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. + */ + +declare module 'kea' { + export function useValues(logic?: object): object; + export function useActions(logic?: object): object; + export function getContext(): { store: object }; + export function resetContext(context: object): object; + export function kea(logic: object): object; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index 3f28710d92295..74bb53ef3a954 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -12,3 +12,59 @@ export interface IFlashMessagesProps { isWrapped?: boolean; children?: React.ReactNode; } + +export interface IKeaLogic { + mount(): void; + values: IKeaValues; + actions: IKeaActions; +} + +/** + * This reusable interface mostly saves us a few characters / allows us to skip + * defining params inline. Unfortunately, the return values *do not work* as + * expected (hence the voids). While I can tell selectors to use TKeaSelectors, + * the return value is *not* properly type checked if it's not declared inline. :/ + * + * Also note that if you switch to Kea 2.1's plain object notation - + * `selectors: {}` vs. `selectors: () => ({})` + * - type checking also stops working and type errors become significantly less + * helpful - showing less specific error messages and highlighting. 👎 + */ +export interface IKeaParams { + selectors?(params: { selectors: IKeaValues }): void; + listeners?(params: { actions: IKeaActions; values: IKeaValues }): void; +} + +/** + * This reducers() type checks that: + * + * 1. The value object keys are defined within IKeaValues + * 2. The default state (array[0]) matches the type definition within IKeaValues + * 3. The action object keys (array[1]) are defined within IKeaActions + * 3. The new state returned by the action matches the type definition within IKeaValues + */ +export type TKeaReducers = { + [Value in keyof IKeaValues]?: [ + IKeaValues[Value], + { + [Action in keyof IKeaActions]?: (state: IKeaValues, payload: IKeaValues) => IKeaValues[Value]; + } + ]; +}; + +/** + * This selectors() type checks that: + * + * 1. The object keys are defined within IKeaValues + * 2. The selected values are defined within IKeaValues + * 3. The returned value match the type definition within IKeaValues + * + * The unknown[] and any[] are unfortunately because I have no idea how to + * assert for arbitrary type/values as an array + */ +export type TKeaSelectors = { + [Value in keyof IKeaValues]?: [ + (selectors: IKeaValues) => unknown[], + (...args: any[]) => IKeaValues[Value] // eslint-disable-line @typescript-eslint/no-explicit-any + ]; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/index.ts new file mode 100644 index 0000000000000..e5169a51ce522 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/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 { setMockValues, mockLogicValues, mockLogicActions } from './overview_logic.mock'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts new file mode 100644 index 0000000000000..43cff5de6668d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/__mocks__/overview_logic.mock.ts @@ -0,0 +1,47 @@ +/* + * 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 { IOverviewValues } from '../overview_logic'; +import { IAccount, IOrganization, IUser } from '../../../types'; + +export const mockLogicValues = { + accountsCount: 0, + activityFeed: [], + canCreateContentSources: false, + canCreateInvitations: false, + currentUser: {} as IUser, + fpAccount: {} as IAccount, + hasOrgSources: false, + hasUsers: false, + isFederatedAuth: true, + isOldAccount: false, + organization: {} as IOrganization, + pendingInvitationsCount: 0, + personalSourcesCount: 0, + sourcesCount: 0, + dataLoading: true, + hasErrorConnecting: false, + flashMessages: {}, +} as IOverviewValues; + +export const mockLogicActions = { + initializeOverview: jest.fn(() => ({})), +}; + +jest.mock('kea', () => ({ + ...(jest.requireActual('kea') as object), + useActions: jest.fn(() => ({ ...mockLogicActions })), + useValues: jest.fn(() => ({ ...mockLogicValues })), +})); + +import { useValues } from 'kea'; + +export const setMockValues = (values: object) => { + (useValues as jest.Mock).mockImplementationOnce(() => ({ + ...mockLogicValues, + ...values, + })); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.test.tsx index 6174dc1c795eb..3cf88cf120cc4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.test.tsx @@ -5,6 +5,8 @@ */ import '../../../__mocks__/shallow_usecontext.mock'; +import './__mocks__/overview_logic.mock'; +import { setMockValues } from './__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; @@ -16,7 +18,6 @@ import { sendTelemetry } from '../../../shared/telemetry'; import { OnboardingSteps, OrgNameOnboarding } from './onboarding_steps'; import { OnboardingCard } from './onboarding_card'; -import { defaultServerData } from './overview'; const account = { id: '1', @@ -30,7 +31,8 @@ const account = { describe('OnboardingSteps', () => { describe('Shared Sources', () => { it('renders 0 sources state', () => { - const wrapper = shallow(); + setMockValues({ canCreateContentSources: true }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard)).toHaveLength(1); expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(ORG_SOURCES_PATH); @@ -40,9 +42,8 @@ describe('OnboardingSteps', () => { }); it('renders completed sources state', () => { - const wrapper = shallow( - - ); + setMockValues({ sourcesCount: 2, hasOrgSources: true }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard).prop('description')).toEqual( 'You have added 2 shared sources. Happy searching.' @@ -50,9 +51,8 @@ describe('OnboardingSteps', () => { }); it('disables link when the user cannot create sources', () => { - const wrapper = shallow( - - ); + setMockValues({ canCreateContentSources: false }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(undefined); }); @@ -60,15 +60,14 @@ describe('OnboardingSteps', () => { describe('Users & Invitations', () => { it('renders 0 users when not on federated auth', () => { - const wrapper = shallow( - - ); + setMockValues({ + canCreateInvitations: true, + isFederatedAuth: false, + fpAccount: account, + accountsCount: 0, + hasUsers: false, + }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard)).toHaveLength(2); expect(wrapper.find(OnboardingCard).last().prop('actionPath')).toBe(USERS_PATH); @@ -78,15 +77,13 @@ describe('OnboardingSteps', () => { }); it('renders completed users state', () => { - const wrapper = shallow( - - ); + setMockValues({ + isFederatedAuth: false, + fpAccount: account, + accountsCount: 1, + hasUsers: true, + }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard).last().prop('description')).toEqual( 'Nice, you’ve invited colleagues to search with you.' @@ -94,21 +91,15 @@ describe('OnboardingSteps', () => { }); it('disables link when the user cannot create invitations', () => { - const wrapper = shallow( - - ); - + setMockValues({ isFederatedAuth: false, canCreateInvitations: false }); + const wrapper = shallow(); expect(wrapper.find(OnboardingCard).last().prop('actionPath')).toBe(undefined); }); }); describe('Org Name', () => { it('renders button to change name', () => { - const wrapper = shallow(); + const wrapper = shallow(); const button = wrapper .find(OrgNameOnboarding) @@ -120,15 +111,13 @@ describe('OnboardingSteps', () => { }); it('hides card when name has been changed', () => { - const wrapper = shallow( - - ); + setMockValues({ + organization: { + name: 'foo', + defaultOrgName: 'bar', + }, + }); + const wrapper = shallow(); expect(wrapper.find(OrgNameOnboarding)).toHaveLength(0); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx index 1b00347437338..7fe1eae502329 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx @@ -7,6 +7,7 @@ import React, { useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useValues } from 'kea'; import { EuiSpacer, @@ -28,7 +29,7 @@ import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; import { ContentSection } from '../shared/content_section'; -import { IAppServerData } from './overview'; +import { OverviewLogic, IOverviewValues } from './overview_logic'; import { OnboardingCard } from './onboarding_card'; @@ -57,17 +58,19 @@ const ONBOARDING_USERS_CARD_DESCRIPTION = i18n.translate( { defaultMessage: 'Invite your colleagues into this organization to search with you.' } ); -export const OnboardingSteps: React.FC = ({ - hasUsers, - hasOrgSources, - canCreateContentSources, - canCreateInvitations, - accountsCount, - sourcesCount, - fpAccount: { isCurated }, - organization: { name, defaultOrgName }, - isFederatedAuth, -}) => { +export const OnboardingSteps: React.FC = () => { + const { + hasUsers, + hasOrgSources, + canCreateContentSources, + canCreateInvitations, + accountsCount, + sourcesCount, + fpAccount: { isCurated }, + organization: { name, defaultOrgName }, + isFederatedAuth, + } = useValues(OverviewLogic) as IOverviewValues; + const accountsPath = !isFederatedAuth && (canCreateInvitations || isCurated) ? USERS_PATH : undefined; const sourcesPath = canCreateContentSources || isCurated ? ORG_SOURCES_PATH : undefined; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.test.tsx index 112e9a910667a..d9b05c5da777d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.test.tsx @@ -5,6 +5,8 @@ */ import '../../../__mocks__/shallow_usecontext.mock'; +import './__mocks__/overview_logic.mock'; +import { setMockValues } from './__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; @@ -12,18 +14,18 @@ import { EuiFlexGrid } from '@elastic/eui'; import { OrganizationStats } from './organization_stats'; import { StatisticCard } from './statistic_card'; -import { defaultServerData } from './overview'; describe('OrganizationStats', () => { it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(StatisticCard)).toHaveLength(2); expect(wrapper.find(EuiFlexGrid).prop('columns')).toEqual(2); }); it('renders additional cards for federated auth', () => { - const wrapper = shallow(); + setMockValues({ isFederatedAuth: false }); + const wrapper = shallow(); expect(wrapper.find(StatisticCard)).toHaveLength(4); expect(wrapper.find(EuiFlexGrid).prop('columns')).toEqual(4); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.tsx index aa9be81f32bae..4c5efce9baf12 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/organization_stats.tsx @@ -6,6 +6,7 @@ import React from 'react'; import { EuiFlexGrid } from '@elastic/eui'; +import { useValues } from 'kea'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -13,62 +14,66 @@ import { i18n } from '@kbn/i18n'; import { ContentSection } from '../shared/content_section'; import { ORG_SOURCES_PATH, USERS_PATH } from '../../routes'; -import { IAppServerData } from './overview'; +import { OverviewLogic, IOverviewValues } from './overview_logic'; import { StatisticCard } from './statistic_card'; -export const OrganizationStats: React.FC = ({ - sourcesCount, - pendingInvitationsCount, - accountsCount, - personalSourcesCount, - isFederatedAuth, -}) => ( - - } - headerSpacer="m" - > - - - {!isFederatedAuth && ( - <> - - - - )} - { + const { + sourcesCount, + pendingInvitationsCount, + accountsCount, + personalSourcesCount, + isFederatedAuth, + } = useValues(OverviewLogic) as IOverviewValues; + + return ( + + } + headerSpacer="m" + > + + + {!isFederatedAuth && ( + <> + + + )} - count={personalSourcesCount} - /> - - -); + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.test.tsx index e5e5235c52368..744fd8aeb1951 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.test.tsx @@ -5,11 +5,11 @@ */ import '../../../__mocks__/react_router_history.mock'; +import './__mocks__/overview_logic.mock'; +import { mockLogicActions, setMockValues } from './__mocks__'; import React from 'react'; -import { shallow } from 'enzyme'; - -import { mountWithAsyncContext, mockKibanaContext } from '../../../__mocks__'; +import { shallow, mount } from 'enzyme'; import { ErrorState } from '../error_state'; import { Loading } from '../shared/loading'; @@ -18,11 +18,9 @@ import { ViewContentHeader } from '../shared/view_content_header'; import { OnboardingSteps } from './onboarding_steps'; import { OrganizationStats } from './organization_stats'; import { RecentActivity } from './recent_activity'; -import { Overview, defaultServerData } from './overview'; +import { Overview } from './overview'; describe('Overview', () => { - const mockHttp = mockKibanaContext.http; - describe('non-happy-path states', () => { it('isLoading', () => { const wrapper = shallow(); @@ -30,24 +28,24 @@ describe('Overview', () => { expect(wrapper.find(Loading)).toHaveLength(1); }); - it('hasErrorConnecting', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { - ...mockHttp, - get: () => Promise.reject({ invalidPayload: true }), - }, - }); + it('hasErrorConnecting', () => { + setMockValues({ hasErrorConnecting: true }); + const wrapper = shallow(); expect(wrapper.find(ErrorState)).toHaveLength(1); }); }); describe('happy-path states', () => { - it('renders onboarding state', async () => { - const mockApi = jest.fn(() => defaultServerData); - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - }); + it('calls initialize function', async () => { + mount(); + + expect(mockLogicActions.initializeOverview).toHaveBeenCalled(); + }); + + it('renders onboarding state', () => { + setMockValues({ dataLoading: false }); + const wrapper = shallow(); expect(wrapper.find(ViewContentHeader)).toHaveLength(1); expect(wrapper.find(OnboardingSteps)).toHaveLength(1); @@ -55,9 +53,9 @@ describe('Overview', () => { expect(wrapper.find(RecentActivity)).toHaveLength(1); }); - it('renders when onboarding complete', async () => { - const obCompleteData = { - ...defaultServerData, + it('renders when onboarding complete', () => { + setMockValues({ + dataLoading: false, hasUsers: true, hasOrgSources: true, isOldAccount: true, @@ -65,11 +63,8 @@ describe('Overview', () => { name: 'foo', defaultOrgName: 'bar', }, - }; - const mockApi = jest.fn(() => obCompleteData); - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, }); + const wrapper = shallow(); expect(wrapper.find(OnboardingSteps)).toHaveLength(0); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.tsx index bacd65a2be75f..b75a2841dad9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview.tsx @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; import { EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useActions, useValues } from 'kea'; import { SetWorkplaceSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs'; import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; import { KibanaContext, IKibanaContext } from '../../../index'; -import { IAccount } from '../../types'; +import { OverviewLogic, IOverviewActions, IOverviewValues } from './overview_logic'; import { ErrorState } from '../error_state'; @@ -22,57 +23,7 @@ import { ViewContentHeader } from '../shared/view_content_header'; import { OnboardingSteps } from './onboarding_steps'; import { OrganizationStats } from './organization_stats'; -import { RecentActivity, IFeedActivity } from './recent_activity'; - -export interface IAppServerData { - hasUsers: boolean; - hasOrgSources: boolean; - canCreateContentSources: boolean; - canCreateInvitations: boolean; - isOldAccount: boolean; - sourcesCount: number; - pendingInvitationsCount: number; - accountsCount: number; - personalSourcesCount: number; - activityFeed: IFeedActivity[]; - organization: { - name: string; - defaultOrgName: string; - }; - isFederatedAuth: boolean; - currentUser: { - firstName: string; - email: string; - name: string; - color: string; - }; - fpAccount: IAccount; -} - -export const defaultServerData = { - accountsCount: 1, - activityFeed: [], - canCreateContentSources: true, - canCreateInvitations: true, - currentUser: { - firstName: '', - email: '', - name: '', - color: '', - }, - fpAccount: {} as IAccount, - hasOrgSources: false, - hasUsers: false, - isFederatedAuth: true, - isOldAccount: false, - organization: { - name: '', - defaultOrgName: '', - }, - pendingInvitationsCount: 0, - personalSourcesCount: 0, - sourcesCount: 0, -} as IAppServerData; +import { RecentActivity } from './recent_activity'; const ONBOARDING_HEADER_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.title', @@ -96,34 +47,24 @@ const HEADER_DESCRIPTION = i18n.translate( export const Overview: React.FC = () => { const { http } = useContext(KibanaContext) as IKibanaContext; - const [isLoading, setIsLoading] = useState(true); - const [hasErrorConnecting, setHasErrorConnecting] = useState(false); - const [appData, setAppData] = useState(defaultServerData); - - const getAppData = async () => { - try { - const response = await http.get('/api/workplace_search/overview'); - setAppData(response); - } catch (error) { - setHasErrorConnecting(true); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - getAppData(); - }, []); - - if (hasErrorConnecting) return ; - if (isLoading) return ; + const { initializeOverview } = useActions(OverviewLogic) as IOverviewActions; const { + dataLoading, + hasErrorConnecting, hasUsers, hasOrgSources, isOldAccount, organization: { name: orgName, defaultOrgName }, - } = appData as IAppServerData; + } = useValues(OverviewLogic) as IOverviewValues; + + useEffect(() => { + initializeOverview({ http }); + }, [initializeOverview]); + + if (hasErrorConnecting) return ; + if (dataLoading) return ; + const hideOnboarding = hasUsers && hasOrgSources && isOldAccount && orgName !== defaultOrgName; const headerTitle = hideOnboarding ? HEADER_TITLE : ONBOARDING_HEADER_TITLE; @@ -140,11 +81,11 @@ export const Overview: React.FC = () => { description={headerDescription} action={} /> - {!hideOnboarding && } + {!hideOnboarding && } - + - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts new file mode 100644 index 0000000000000..285ec9b973378 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.test.ts @@ -0,0 +1,141 @@ +/* + * 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 { resetContext } from 'kea'; +import { act } from 'react-dom/test-utils'; + +import { mockKibanaContext } from '../../../__mocks__'; + +import { mockLogicValues } from './__mocks__'; +import { OverviewLogic } from './overview_logic'; + +describe('OverviewLogic', () => { + let unmount: any; + + beforeEach(() => { + resetContext({}); + unmount = OverviewLogic.mount() as any; + jest.clearAllMocks(); + }); + + afterEach(() => { + unmount(); + }); + + it('has expected default values', () => { + expect(OverviewLogic.values).toEqual(mockLogicValues); + }); + + describe('setServerData', () => { + const feed = [{ foo: 'bar' }] as any; + const user = { firstName: 'Joe', email: 'e@e.e', name: 'Joe Jo', color: 'pearl' }; + const account = { + name: 'Jane doe', + id: '1243', + isAdmin: true, + canCreatePersonalSources: true, + groups: [], + supportEligible: true, + }; + const org = { name: 'ACME', defaultOrgName: 'Org' }; + + const data = { + accountsCount: 1, + activityFeed: feed, + canCreateContentSources: true, + canCreateInvitations: true, + currentUser: user, + fpAccount: account, + hasOrgSources: true, + hasUsers: true, + isFederatedAuth: false, + isOldAccount: true, + organization: org, + pendingInvitationsCount: 1, + personalSourcesCount: 1, + sourcesCount: 1, + }; + + beforeEach(() => { + OverviewLogic.actions.setServerData(data); + }); + + it('will set `dataLoading` to false', () => { + expect(OverviewLogic.values.dataLoading).toEqual(false); + }); + + it('will set server values', () => { + expect(OverviewLogic.values.organization).toEqual(org); + expect(OverviewLogic.values.isFederatedAuth).toEqual(false); + expect(OverviewLogic.values.currentUser).toEqual(user); + expect(OverviewLogic.values.fpAccount).toEqual(account); + expect(OverviewLogic.values.canCreateInvitations).toEqual(true); + expect(OverviewLogic.values.hasUsers).toEqual(true); + expect(OverviewLogic.values.hasOrgSources).toEqual(true); + expect(OverviewLogic.values.canCreateContentSources).toEqual(true); + expect(OverviewLogic.values.isOldAccount).toEqual(true); + expect(OverviewLogic.values.sourcesCount).toEqual(1); + expect(OverviewLogic.values.pendingInvitationsCount).toEqual(1); + expect(OverviewLogic.values.accountsCount).toEqual(1); + expect(OverviewLogic.values.personalSourcesCount).toEqual(1); + expect(OverviewLogic.values.activityFeed).toEqual(feed); + }); + }); + + describe('setFlashMessages', () => { + it('will set `flashMessages`', () => { + const flashMessages = { error: ['error'] }; + OverviewLogic.actions.setFlashMessages(flashMessages); + + expect(OverviewLogic.values.flashMessages).toEqual(flashMessages); + }); + }); + + describe('setHasErrorConnecting', () => { + it('will set `hasErrorConnecting`', () => { + OverviewLogic.actions.setHasErrorConnecting(true); + + expect(OverviewLogic.values.hasErrorConnecting).toEqual(true); + expect(OverviewLogic.values.dataLoading).toEqual(false); + }); + }); + + describe('initializeOverview', () => { + it('calls API and sets values', async () => { + const mockHttp = mockKibanaContext.http; + const mockApi = jest.fn(() => mockLogicValues as any); + const setServerDataSpy = jest.spyOn(OverviewLogic.actions, 'setServerData'); + + await act(async () => + OverviewLogic.actions.initializeOverview({ + http: { + ...mockHttp, + get: mockApi, + }, + }) + ); + + expect(mockApi).toHaveBeenCalledWith('/api/workplace_search/overview'); + expect(setServerDataSpy).toHaveBeenCalled(); + }); + + it('handles error state', async () => { + const mockHttp = mockKibanaContext.http; + const setHasErrorConnectingSpy = jest.spyOn(OverviewLogic.actions, 'setHasErrorConnecting'); + + await act(async () => + OverviewLogic.actions.initializeOverview({ + http: { + ...mockHttp, + get: () => Promise.reject(), + }, + }) + ); + + expect(setHasErrorConnectingSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts new file mode 100644 index 0000000000000..f1b4f447f7445 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/overview_logic.ts @@ -0,0 +1,168 @@ +/* + * 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 { HttpSetup } from 'src/core/public'; + +import { kea } from 'kea'; + +import { IAccount, IOrganization, IUser } from '../../types'; +import { IFlashMessagesProps, IKeaLogic, TKeaReducers, IKeaParams } from '../../../shared/types'; + +import { IFeedActivity } from './recent_activity'; + +export interface IOverviewServerData { + hasUsers: boolean; + hasOrgSources: boolean; + canCreateContentSources: boolean; + canCreateInvitations: boolean; + isOldAccount: boolean; + sourcesCount: number; + pendingInvitationsCount: number; + accountsCount: number; + personalSourcesCount: number; + activityFeed: IFeedActivity[]; + organization: IOrganization; + isFederatedAuth: boolean; + currentUser: IUser; + fpAccount: IAccount; +} + +export interface IOverviewActions { + setServerData(serverData: IOverviewServerData): void; + setFlashMessages(flashMessages: IFlashMessagesProps): void; + setHasErrorConnecting(hasErrorConnecting: boolean): void; + initializeOverview({ http }: { http: HttpSetup }): void; +} + +export interface IOverviewValues extends IOverviewServerData { + dataLoading: boolean; + hasErrorConnecting: boolean; + flashMessages: IFlashMessagesProps; +} + +export const OverviewLogic = kea({ + actions: (): IOverviewActions => ({ + setServerData: (serverData) => serverData, + setFlashMessages: (flashMessages) => ({ flashMessages }), + setHasErrorConnecting: (hasErrorConnecting) => ({ hasErrorConnecting }), + initializeOverview: ({ http }) => ({ http }), + }), + reducers: (): TKeaReducers => ({ + organization: [ + {} as IOrganization, + { + setServerData: (_, { organization }) => organization, + }, + ], + isFederatedAuth: [ + true, + { + setServerData: (_, { isFederatedAuth }) => isFederatedAuth, + }, + ], + currentUser: [ + {} as IUser, + { + setServerData: (_, { currentUser }) => currentUser, + }, + ], + fpAccount: [ + {} as IAccount, + { + setServerData: (_, { fpAccount }) => fpAccount, + }, + ], + canCreateInvitations: [ + false, + { + setServerData: (_, { canCreateInvitations }) => canCreateInvitations, + }, + ], + flashMessages: [ + {}, + { + setFlashMessages: (_, { flashMessages }) => flashMessages, + }, + ], + hasUsers: [ + false, + { + setServerData: (_, { hasUsers }) => hasUsers, + }, + ], + hasOrgSources: [ + false, + { + setServerData: (_, { hasOrgSources }) => hasOrgSources, + }, + ], + canCreateContentSources: [ + false, + { + setServerData: (_, { canCreateContentSources }) => canCreateContentSources, + }, + ], + isOldAccount: [ + false, + { + setServerData: (_, { isOldAccount }) => isOldAccount, + }, + ], + sourcesCount: [ + 0, + { + setServerData: (_, { sourcesCount }) => sourcesCount, + }, + ], + pendingInvitationsCount: [ + 0, + { + setServerData: (_, { pendingInvitationsCount }) => pendingInvitationsCount, + }, + ], + accountsCount: [ + 0, + { + setServerData: (_, { accountsCount }) => accountsCount, + }, + ], + personalSourcesCount: [ + 0, + { + setServerData: (_, { personalSourcesCount }) => personalSourcesCount, + }, + ], + activityFeed: [ + [], + { + setServerData: (_, { activityFeed }) => activityFeed, + }, + ], + dataLoading: [ + true, + { + setServerData: () => false, + setHasErrorConnecting: () => false, + }, + ], + hasErrorConnecting: [ + false, + { + setHasErrorConnecting: (_, { hasErrorConnecting }) => hasErrorConnecting, + }, + ], + }), + listeners: ({ actions }): Partial => ({ + initializeOverview: async ({ http }: { http: HttpSetup }) => { + try { + const response = await http.get('/api/workplace_search/overview'); + actions.setServerData(response); + } catch (error) { + actions.setHasErrorConnecting(true); + } + }, + }), +} as IKeaParams) as IKeaLogic; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.test.tsx index e9bdedb199dad..22a82af18527d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.test.tsx @@ -5,6 +5,8 @@ */ import '../../../__mocks__/shallow_usecontext.mock'; +import './__mocks__/overview_logic.mock'; +import { setMockValues } from './__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; @@ -12,14 +14,13 @@ import { shallow } from 'enzyme'; import { EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import { RecentActivity, RecentActivityItem } from './recent_activity'; -import { defaultServerData } from './overview'; jest.mock('../../../shared/telemetry', () => ({ sendTelemetry: jest.fn() })); import { sendTelemetry } from '../../../shared/telemetry'; -const org = { name: 'foo', defaultOrgName: 'bar' }; +const organization = { name: 'foo', defaultOrgName: 'bar' }; -const feed = [ +const activityFeed = [ { id: 'demo', sourceId: 'd2d2d23d', @@ -30,17 +31,19 @@ const feed = [ ]; describe('RecentActivity', () => { - it('renders with no feed data', () => { - const wrapper = shallow(); + it('renders with no activityFeed data', () => { + const wrapper = shallow(); expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); // Branch coverage - renders without error for custom org name - shallow(); + setMockValues({ organization }); + shallow(); }); - it('renders an activity feed with links', () => { - const wrapper = shallow(); + it('renders an activityFeed with links', () => { + setMockValues({ activityFeed }); + const wrapper = shallow(); const activity = wrapper.find(RecentActivityItem).dive(); expect(activity).toHaveLength(1); @@ -51,7 +54,7 @@ describe('RecentActivity', () => { }); it('renders activity item error state', () => { - const props = { ...feed[0], status: 'error' }; + const props = { ...activityFeed[0], status: 'error' }; const wrapper = shallow(); expect(wrapper.find('.activity--error')).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx index 8d69582c93684..2c0fbe1275cbf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx @@ -7,6 +7,7 @@ import React, { useContext } from 'react'; import moment from 'moment'; +import { useValues } from 'kea'; import { EuiEmptyPrompt, EuiLink, EuiPanel, EuiSpacer, EuiLinkProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -17,7 +18,7 @@ import { sendTelemetry } from '../../../shared/telemetry'; import { KibanaContext, IKibanaContext } from '../../../index'; import { getSourcePath } from '../../routes'; -import { IAppServerData } from './overview'; +import { OverviewLogic, IOverviewValues } from './overview_logic'; import './recent_activity.scss'; @@ -29,10 +30,12 @@ export interface IFeedActivity { sourceId: string; } -export const RecentActivity: React.FC = ({ - organization: { name, defaultOrgName }, - activityFeed, -}) => { +export const RecentActivity: React.FC = () => { + const { + organization: { name, defaultOrgName }, + activityFeed, + } = useValues(OverviewLogic) as IOverviewValues; + return ( , testSubj: 'contentSection', - className: 'test', }; describe('ContentSection', () => { it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.prop('data-test-subj')).toEqual('contentSection'); expect(wrapper.prop('className')).toEqual('test'); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/view_content_header/view_content_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/view_content_header/view_content_header.test.tsx index 4680f15771caa..b0b07c46b4ea8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/view_content_header/view_content_header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/view_content_header/view_content_header.test.tsx @@ -26,9 +26,10 @@ describe('ViewContentHeader', () => { }); it('shows description, when present', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('p').text()).toEqual('Hello World'); + expect(wrapper.find(EuiFlexGroup).prop('alignItems')).toEqual('center'); }); it('shows action, when present', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index 36b1a56ecba26..cfa70ea29eca8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -6,6 +6,13 @@ import React, { useContext } from 'react'; import { Route, Redirect } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { Store } from 'redux'; +import { getContext, resetContext } from 'kea'; + +resetContext({ createStore: true }); + +const store = getContext().store as Store; import { KibanaContext, IKibanaContext } from '../index'; @@ -17,13 +24,13 @@ import { Overview } from './components/overview'; export const WorkplaceSearch: React.FC = () => { const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext; return ( - <> + {!enterpriseSearchUrl ? : } - + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts index b448c59c52f3e..77c35adef3300 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts @@ -13,4 +13,15 @@ export interface IAccount { supportEligible: boolean; } +export interface IOrganization { + name: string; + defaultOrgName: string; +} +export interface IUser { + firstName: string; + email: string; + name: string; + color: string; +} + export type TSpacerSize = 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'; diff --git a/yarn.lock b/yarn.lock index 0638a019a9402..899bc45fbe3fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19957,6 +19957,11 @@ kdbush@^3.0.0: resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== +kea@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/kea/-/kea-2.1.1.tgz#6e3c65c4873b67d270a2ec7bf73b0d178937234c" + integrity sha512-W9o4lHLOcEDIu3ASHPrWJJJzL1bMkGyxaHn9kuaDgI96ztBShVrf52R0QPGlQ2k9ca3XnkB/dnVHio1UB8kGWA== + kew@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/kew/-/kew-0.1.7.tgz#0a32a817ff1a9b3b12b8c9bacf4bc4d679af8e72" From 88aebc9fe17fa0583b7c5b9af17520511c9b18ad Mon Sep 17 00:00:00 2001 From: liza-mae Date: Mon, 27 Jul 2020 15:10:33 -0600 Subject: [PATCH 12/36] Remove ca cert path for cloud testing (#73317) --- test/common/services/elasticsearch.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/test/common/services/elasticsearch.ts b/test/common/services/elasticsearch.ts index 0436dc901292d..a01f9ff3c8da5 100644 --- a/test/common/services/elasticsearch.ts +++ b/test/common/services/elasticsearch.ts @@ -27,11 +27,18 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function ElasticsearchProvider({ getService }: FtrProviderContext) { const config = getService('config'); - return new Client({ - ssl: { - ca: fs.readFileSync(CA_CERT_PATH, 'utf-8'), - }, - nodes: [formatUrl(config.get('servers.elasticsearch'))], - requestTimeout: config.get('timeouts.esRequestTimeout'), - }); + if (process.env.TEST_CLOUD) { + return new Client({ + nodes: [formatUrl(config.get('servers.elasticsearch'))], + requestTimeout: config.get('timeouts.esRequestTimeout'), + }); + } else { + return new Client({ + ssl: { + ca: fs.readFileSync(CA_CERT_PATH, 'utf-8'), + }, + nodes: [formatUrl(config.get('servers.elasticsearch'))], + requestTimeout: config.get('timeouts.esRequestTimeout'), + }); + } } From 5a472189715931012096b99b95651ffd5791179c Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 27 Jul 2020 16:24:45 -0500 Subject: [PATCH 13/36] [APM] Fix focus map link on service map (#73338) The link was including `serviceName` from the `urlParams` so it was linking to the wrong service. Overwrite the service name so the link is correct. Fixes #72911. --- .../app/ServiceMap/Popover/Buttons.test.tsx | 32 +++++++++++++++++++ .../app/ServiceMap/Popover/Buttons.tsx | 7 +++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx new file mode 100644 index 0000000000000..4146266b17916 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx @@ -0,0 +1,32 @@ +/* + * 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 { Buttons } from './Buttons'; +import { render } from '@testing-library/react'; + +describe('Popover Buttons', () => { + it('renders', () => { + expect(() => + render() + ).not.toThrowError(); + }); + + it('handles focus click', async () => { + const onFocusClick = jest.fn(); + const result = render( + + ); + const focusButton = await result.findByText('Focus map'); + + focusButton.click(); + + expect(onFocusClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx index d67447e04ef81..cb33fb32f3b0d 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx @@ -22,7 +22,12 @@ export function Buttons({ onFocusClick = () => {}, selectedNodeServiceName, }: ButtonsProps) { - const urlParams = useUrlParams().urlParams as APMQueryParams; + // The params may contain the service name. We want to use the selected node's + // service name in the button URLs, so make a copy and set the + // `serviceName` property. + const urlParams = { ...useUrlParams().urlParams } as APMQueryParams; + urlParams.serviceName = selectedNodeServiceName; + const detailsUrl = getAPMHref( `/services/${selectedNodeServiceName}/transactions`, '', From 157fb097a9aeed8a9e167efa91347617a258ca5b Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 27 Jul 2020 14:31:02 -0700 Subject: [PATCH 14/36] [dev/build/docker_generator] convert to typescript (#73339) Co-authored-by: spalger --- ...e_dockerfiles.js => bundle_dockerfiles.ts} | 28 ++++++++-------- .../os_packages/docker_generator/index.ts | 1 - .../docker_generator/{run.js => run.ts} | 19 ++++++++--- .../docker_generator/template_context.ts | 33 +++++++++++++++++++ ...emplate.js => build_docker_sh.template.ts} | 4 ++- ...ile.template.js => dockerfile.template.ts} | 4 ++- .../templates/{index.js => index.ts} | 0 ...yml.template.js => kibana_yml.template.ts} | 4 ++- 8 files changed, 71 insertions(+), 22 deletions(-) rename src/dev/build/tasks/os_packages/docker_generator/{bundle_dockerfiles.js => bundle_dockerfiles.ts} (80%) rename src/dev/build/tasks/os_packages/docker_generator/{run.js => run.ts} (90%) create mode 100644 src/dev/build/tasks/os_packages/docker_generator/template_context.ts rename src/dev/build/tasks/os_packages/docker_generator/templates/{build_docker_sh.template.js => build_docker_sh.template.ts} (94%) rename src/dev/build/tasks/os_packages/docker_generator/templates/{dockerfile.template.js => dockerfile.template.ts} (98%) rename src/dev/build/tasks/os_packages/docker_generator/templates/{index.js => index.ts} (100%) rename src/dev/build/tasks/os_packages/docker_generator/templates/{kibana_yml.template.js => kibana_yml.template.ts} (91%) diff --git a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts similarity index 80% rename from src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js rename to src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts index 3f34a84057668..7a8f7316913be 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js +++ b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts @@ -18,10 +18,14 @@ */ import { resolve } from 'path'; -import { compressTar, copyAll, mkdirp, write } from '../../../lib'; + +import { ToolingLog } from '@kbn/dev-utils'; + +import { compressTar, copyAll, mkdirp, write, Config } from '../../../lib'; import { dockerfileTemplate } from './templates'; +import { TemplateContext } from './template_context'; -export async function bundleDockerFiles(config, log, build, scope) { +export async function bundleDockerFiles(config: Config, log: ToolingLog, scope: TemplateContext) { log.info( `Generating kibana${scope.imageFlavor}${scope.ubiImageFlavor} docker build context bundle` ); @@ -50,17 +54,15 @@ export async function bundleDockerFiles(config, log, build, scope) { // Compress dockerfiles dir created inside // docker build dir as output it as a target // on targets folder - await compressTar( - { - archiverOptions: { - gzip: true, - gzipOptions: { - level: 9, - }, + await compressTar({ + source: dockerFilesBuildDir, + destination: dockerFilesOutputDir, + archiverOptions: { + gzip: true, + gzipOptions: { + level: 9, }, - createRootDirectory: false, }, - dockerFilesBuildDir, - dockerFilesOutputDir - ); + createRootDirectory: false, + }); } diff --git a/src/dev/build/tasks/os_packages/docker_generator/index.ts b/src/dev/build/tasks/os_packages/docker_generator/index.ts index 78d2b197dc7b2..dff56585fc704 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/index.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/index.ts @@ -17,5 +17,4 @@ * under the License. */ -// @ts-expect-error not ts yet export { runDockerGenerator, runDockerGeneratorForUBI } from './run'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.js b/src/dev/build/tasks/os_packages/docker_generator/run.ts similarity index 90% rename from src/dev/build/tasks/os_packages/docker_generator/run.js rename to src/dev/build/tasks/os_packages/docker_generator/run.ts index b6dab43887f14..0a26729f3502d 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.js +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -20,8 +20,12 @@ import { access, link, unlink, chmod } from 'fs'; import { resolve } from 'path'; import { promisify } from 'util'; -import { write, copyAll, mkdirp, exec } from '../../../lib'; + +import { ToolingLog } from '@kbn/dev-utils'; + +import { write, copyAll, mkdirp, exec, Config, Build } from '../../../lib'; import * as dockerTemplates from './templates'; +import { TemplateContext } from './template_context'; import { bundleDockerFiles } from './bundle_dockerfiles'; const accessAsync = promisify(access); @@ -29,7 +33,12 @@ const linkAsync = promisify(link); const unlinkAsync = promisify(unlink); const chmodAsync = promisify(chmod); -export async function runDockerGenerator(config, log, build, ubi = false) { +export async function runDockerGenerator( + config: Config, + log: ToolingLog, + build: Build, + ubi: boolean = false +) { // UBI var config const baseOSImage = ubi ? 'registry.access.redhat.com/ubi7/ubi-minimal:7.7' : 'centos:7'; const ubiVersionTag = 'ubi7'; @@ -52,7 +61,7 @@ export async function runDockerGenerator(config, log, build, ubi = false) { const dockerOutputDir = config.resolveFromTarget( `kibana${imageFlavor}${ubiImageFlavor}-${versionTag}-docker.tar.gz` ); - const scope = { + const scope: TemplateContext = { artifactTarball, imageFlavor, versionTag, @@ -112,10 +121,10 @@ export async function runDockerGenerator(config, log, build, ubi = false) { }); // Pack Dockerfiles and create a target for them - await bundleDockerFiles(config, log, build, scope); + await bundleDockerFiles(config, log, scope); } -export async function runDockerGeneratorForUBI(config, log, build) { +export async function runDockerGeneratorForUBI(config: Config, log: ToolingLog, build: Build) { // Only run ubi docker image build for default distribution if (build.isOss()) { return; diff --git a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts new file mode 100644 index 0000000000000..115d4c6927c30 --- /dev/null +++ b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts @@ -0,0 +1,33 @@ +/* + * 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 interface TemplateContext { + artifactTarball: string; + imageFlavor: string; + versionTag: string; + license: string; + artifactsDir: string; + imageTag: string; + dockerBuildDir: string; + dockerOutputDir: string; + baseOSImage: string; + ubiImageFlavor: string; + dockerBuildDate: string; + usePublicArtifact?: boolean; +} diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts similarity index 94% rename from src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js rename to src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts index 4e8dfe188b867..ff6fcf7548d9d 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts @@ -19,6 +19,8 @@ import dedent from 'dedent'; +import { TemplateContext } from '../template_context'; + function generator({ imageTag, imageFlavor, @@ -26,7 +28,7 @@ function generator({ dockerOutputDir, baseOSImage, ubiImageFlavor, -}) { +}: TemplateContext) { return dedent(` #!/usr/bin/env bash # diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts similarity index 98% rename from src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js rename to src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts index 5832d00162b20..ea2f881768c8f 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts @@ -19,6 +19,8 @@ import dedent from 'dedent'; +import { TemplateContext } from '../template_context'; + function generator({ artifactTarball, versionTag, @@ -27,7 +29,7 @@ function generator({ baseOSImage, ubiImageFlavor, dockerBuildDate, -}) { +}: TemplateContext) { const copyArtifactTarballInsideDockerOptFolder = () => { if (usePublicArtifact) { return `RUN cd /opt && curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/kibana/${artifactTarball} && cd -`; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/index.js b/src/dev/build/tasks/os_packages/docker_generator/templates/index.ts similarity index 100% rename from src/dev/build/tasks/os_packages/docker_generator/templates/index.js rename to src/dev/build/tasks/os_packages/docker_generator/templates/index.ts diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts similarity index 91% rename from src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js rename to src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts index c80f9334cfaeb..240ec6f4e9326 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts @@ -19,7 +19,9 @@ import dedent from 'dedent'; -function generator({ imageFlavor }) { +import { TemplateContext } from '../template_context'; + +function generator({ imageFlavor }: TemplateContext) { return dedent(` # # ** THIS IS AN AUTO-GENERATED FILE ** From 57997beed8f7eaf7f67cd17d397eb4abcd6abf36 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 27 Jul 2020 15:06:42 -0700 Subject: [PATCH 15/36] [Enterprise Search] Error state UI tweaks to account for current Cloud SSO behavior (#73324) * Do not disable the Launch App Search button on the error page - so users always have access to App Search * Add troubleshooting steps that mention user authentication - more info can be found in setup guide * Tweak styling/spacing on troubleshooting steps * Copyedits (thanks Chris!) --- .../components/empty_states/error_state.tsx | 2 +- .../engine_overview_header.test.tsx | 8 ------- .../engine_overview_header.tsx | 23 +++++------------- .../error_state/error_state_prompt.scss | 12 ++++++++++ .../shared/error_state/error_state_prompt.tsx | 24 ++++++++++++++++++- 5 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.scss diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx index 7ac02082ee75c..346e70d32f7b1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx @@ -21,7 +21,7 @@ export const ErrorState: React.FC = () => { - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx index 2e49540270ef0..7d2106f2a56f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx @@ -30,12 +30,4 @@ describe('EngineOverviewHeader', () => { button.simulate('click'); expect(sendTelemetry).toHaveBeenCalled(); }); - - it('renders a disabled button when isButtonDisabled is true', () => { - const wrapper = shallow(); - const button = wrapper.find('[data-test-subj="launchButton"]'); - - expect(button.prop('isDisabled')).toBe(true); - expect(button.prop('href')).toBeUndefined(); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx index 9aafa8ec0380c..cc480d241ad50 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx @@ -18,34 +18,23 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { sendTelemetry } from '../../../shared/telemetry'; import { KibanaContext, IKibanaContext } from '../../../index'; -interface IEngineOverviewHeaderProps { - isButtonDisabled?: boolean; -} - -export const EngineOverviewHeader: React.FC = ({ - isButtonDisabled, -}) => { +export const EngineOverviewHeader: React.FC = () => { const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext; const buttonProps = { fill: true, iconType: 'popout', 'data-test-subj': 'launchButton', - } as EuiButtonProps & EuiLinkProps; - - if (isButtonDisabled) { - buttonProps.isDisabled = true; - } else { - buttonProps.href = `${enterpriseSearchUrl}/as`; - buttonProps.target = '_blank'; - buttonProps.onClick = () => + href: `${enterpriseSearchUrl}/as`, + target: '_blank', + onClick: () => sendTelemetry({ http, product: 'app_search', action: 'clicked', metric: 'header_launch_button', - }); - } + }), + } as EuiButtonProps & EuiLinkProps; return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.scss b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.scss new file mode 100644 index 0000000000000..0d9926ab147bf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.scss @@ -0,0 +1,12 @@ +.troubleshootingSteps { + text-align: left; + + li { + margin: $euiSizeS auto; + } + + ul, + ol { + margin-bottom: 0; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx index 81455cea0b497..ccd5beff66e70 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx @@ -11,6 +11,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton } from '../react_router_helpers'; import { KibanaContext, IKibanaContext } from '../../index'; +import './error_state_prompt.scss'; + export const ErrorStatePrompt: React.FC = () => { const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext; @@ -38,7 +40,7 @@ export const ErrorStatePrompt: React.FC = () => { }} />

-
    +
    1. { defaultMessage="Confirm that the Enterprise Search server is responsive." />
    2. +
    3. + +
        +
      • + +
      • +
      • + +
      • +
      +
    4. Date: Mon, 27 Jul 2020 18:19:16 -0400 Subject: [PATCH 16/36] [Security Solution][Exceptions] - Update exception item comments to include id (#73129) ## Summary This PR is somewhat of an intermediary step. Comments on exception list items are denormalized. We initially decided that we would not add `uuid` to comments, but found that it is in fact necessary. This is intermediary in the sense that what we ideally want to have is a dedicated `comments` CRUD route. Also just note that I added a callout for when a version conflict occurs (ie: exception item was updated by someone else while a user is editing the same item). With this PR users are able to: - Create comments when creating exception list items - Add new comments on exception item update Users will currently be blocked from: - Deleting comments - Updating comments - Updating exception item if version conflict is found --- x-pack/plugins/lists/common/constants.mock.ts | 1 + .../create_endpoint_list_item_schema.test.ts | 36 +- .../create_exception_list_item_schema.test.ts | 36 +- ...ate_exception_list_item_validation.test.ts | 43 ++ .../update_exception_list_item_validation.ts | 40 ++ .../{comments.mock.ts => comment.mock.ts} | 7 +- .../{comments.test.ts => comment.test.ts} | 109 +++-- .../schemas/types/{comments.ts => comment.ts} | 23 +- ...omments.mock.ts => create_comment.mock.ts} | 4 +- ...omments.test.ts => create_comment.test.ts} | 50 +-- .../{create_comments.ts => create_comment.ts} | 11 +- .../types/default_comments_array.test.ts | 21 +- .../schemas/types/default_comments_array.ts | 6 +- .../default_create_comments_array.test.ts | 30 +- .../types/default_create_comments_array.ts | 6 +- .../default_update_comments_array.test.ts | 23 +- .../types/default_update_comments_array.ts | 2 +- .../lists/common/schemas/types/index.ts | 6 +- ...omments.mock.ts => update_comment.mock.ts} | 15 +- .../schemas/types/update_comment.test.ts | 150 +++++++ .../{update_comments.ts => update_comment.ts} | 20 +- .../schemas/types/update_comments.test.ts | 108 ----- x-pack/plugins/lists/common/shared_exports.ts | 5 +- .../update_exception_list_item_route.ts | 6 + .../server/saved_objects/exception_list.ts | 3 + .../updates/simple_update_item.json | 25 +- .../create_exception_list_item.ts | 5 +- .../services/exception_lists/utils.test.ts | 390 ++---------------- .../server/services/exception_lists/utils.ts | 115 +----- .../common/shared_imports.ts | 5 +- .../exceptions/add_exception_comments.tsx | 4 +- .../exceptions/add_exception_modal/index.tsx | 4 +- .../components/exceptions/builder/index.tsx | 2 +- .../exceptions/edit_exception_modal/index.tsx | 40 +- .../edit_exception_modal/translations.ts | 15 + .../components/exceptions/helpers.test.tsx | 55 ++- .../common/components/exceptions/helpers.tsx | 55 ++- .../exception_item/exception_details.test.tsx | 2 +- .../viewer/exception_item/index.stories.tsx | 2 +- .../viewer/exception_item/index.test.tsx | 2 +- .../components/exceptions/viewer/index.tsx | 3 +- 41 files changed, 702 insertions(+), 783 deletions(-) create mode 100644 x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.test.ts create mode 100644 x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.ts rename x-pack/plugins/lists/common/schemas/types/{comments.mock.ts => comment.mock.ts} (71%) rename x-pack/plugins/lists/common/schemas/types/{comments.test.ts => comment.test.ts} (56%) rename x-pack/plugins/lists/common/schemas/types/{comments.ts => comment.ts} (56%) rename x-pack/plugins/lists/common/schemas/types/{create_comments.mock.ts => create_comment.mock.ts} (73%) rename x-pack/plugins/lists/common/schemas/types/{create_comments.test.ts => create_comment.test.ts} (72%) rename x-pack/plugins/lists/common/schemas/types/{create_comments.ts => create_comment.ts} (64%) rename x-pack/plugins/lists/common/schemas/types/{update_comments.mock.ts => update_comment.mock.ts} (54%) create mode 100644 x-pack/plugins/lists/common/schemas/types/update_comment.test.ts rename x-pack/plugins/lists/common/schemas/types/{update_comments.ts => update_comment.ts} (58%) delete mode 100644 x-pack/plugins/lists/common/schemas/types/update_comments.test.ts diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index 30f219c3ec101..22706890e2020 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -6,6 +6,7 @@ import { EntriesArray } from './schemas/types'; export const DATE_NOW = '2020-04-20T15:25:31.830Z'; +export const OLD_DATE_RELATIVE_TO_DATE_NOW = '2020-04-19T15:25:31.830Z'; export const USER = 'some user'; export const LIST_INDEX = '.lists'; export const LIST_ITEM_INDEX = '.items'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts index 5de9fbb0d5b50..75e0410be610a 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.test.ts @@ -8,8 +8,8 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; -import { getCreateCommentsArrayMock } from '../types/create_comments.mock'; -import { getCommentsMock } from '../types/comments.mock'; +import { getCreateCommentsArrayMock } from '../types/create_comment.mock'; +import { getCommentsMock } from '../types/comment.mock'; import { CommentsArray } from '../types'; import { @@ -19,7 +19,7 @@ import { import { getCreateEndpointListItemSchemaMock } from './create_endpoint_list_item_schema.mock'; describe('create_endpoint_list_item_schema', () => { - test('it should validate a typical list item request not counting the auto generated uuid', () => { + test('it should pass validation when supplied a typical list item request not counting the auto generated uuid', () => { const payload = getCreateEndpointListItemSchemaMock(); const decoded = createEndpointListItemSchema.decode(payload); const checked = exactCheck(payload, decoded); @@ -29,7 +29,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate an undefined for "description"', () => { + test('it should fail validation when supplied an undefined for "description"', () => { const payload = getCreateEndpointListItemSchemaMock(); delete payload.description; const decoded = createEndpointListItemSchema.decode(payload); @@ -41,7 +41,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "name"', () => { + test('it should fail validation when supplied an undefined for "name"', () => { const payload = getCreateEndpointListItemSchemaMock(); delete payload.name; const decoded = createEndpointListItemSchema.decode(payload); @@ -53,7 +53,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "type"', () => { + test('it should fail validation when supplied an undefined for "type"', () => { const payload = getCreateEndpointListItemSchemaMock(); delete payload.type; const decoded = createEndpointListItemSchema.decode(payload); @@ -65,7 +65,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate a "list_id" since it does not required one', () => { + test('it should fail validation when supplied a "list_id" since it does not required one', () => { const inputPayload: CreateEndpointListItemSchema & { list_id: string } = { ...getCreateEndpointListItemSchemaMock(), list_id: 'list-123', @@ -77,7 +77,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate a "namespace_type" since it does not required one', () => { + test('it should fail validation when supplied a "namespace_type" since it does not required one', () => { const inputPayload: CreateEndpointListItemSchema & { namespace_type: string } = { ...getCreateEndpointListItemSchemaMock(), namespace_type: 'single', @@ -89,7 +89,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should validate an undefined for "meta" but strip it out and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "meta" but strip it out and generate a correct body not counting the auto generated uuid', () => { const payload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete payload.meta; @@ -102,7 +102,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "comments" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "comments" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.comments; @@ -115,7 +115,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate "comments" array', () => { + test('it should pass validation when supplied "comments" array', () => { const inputPayload = { ...getCreateEndpointListItemSchemaMock(), comments: getCreateCommentsArrayMock(), @@ -128,7 +128,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(inputPayload); }); - test('it should NOT validate "comments" with "created_at" or "created_by" values', () => { + test('it should fail validation when supplied "comments" with "created_at", "created_by", or "id" values', () => { const inputPayload: Omit & { comments?: CommentsArray; } = { @@ -138,11 +138,11 @@ describe('create_endpoint_list_item_schema', () => { const decoded = createEndpointListItemSchema.decode(inputPayload); const checked = exactCheck(inputPayload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "created_at,created_by"']); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "created_at,created_by,id"']); expect(message.schema).toEqual({}); }); - test('it should NOT validate an undefined for "entries"', () => { + test('it should fail validation when supplied an undefined for "entries"', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.entries; @@ -157,7 +157,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should validate an undefined for "tags" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "tags" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.tags; @@ -170,7 +170,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "_tags" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "_tags" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); const outputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload._tags; @@ -183,7 +183,7 @@ describe('create_endpoint_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "item_id" and auto generate a uuid', () => { + test('it should pass validation when supplied an undefined for "item_id" and auto generate a uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.item_id; const decoded = createEndpointListItemSchema.decode(inputPayload); @@ -195,7 +195,7 @@ describe('create_endpoint_list_item_schema', () => { ); }); - test('it should validate an undefined for "item_id" and generate a correct body not counting the uuid', () => { + test('it should pass validation when supplied an undefined for "item_id" and generate a correct body not counting the uuid', () => { const inputPayload = getCreateEndpointListItemSchemaMock(); delete inputPayload.item_id; const decoded = createEndpointListItemSchema.decode(inputPayload); diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts index 08f3966af08d9..cf4c1fea0306f 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.test.ts @@ -8,8 +8,8 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; -import { getCreateCommentsArrayMock } from '../types/create_comments.mock'; -import { getCommentsMock } from '../types/comments.mock'; +import { getCreateCommentsArrayMock } from '../types/create_comment.mock'; +import { getCommentsMock } from '../types/comment.mock'; import { CommentsArray } from '../types'; import { @@ -19,7 +19,7 @@ import { import { getCreateExceptionListItemSchemaMock } from './create_exception_list_item_schema.mock'; describe('create_exception_list_item_schema', () => { - test('it should validate a typical exception list item request not counting the auto generated uuid', () => { + test('it should pass validation when supplied a typical exception list item request not counting the auto generated uuid', () => { const payload = getCreateExceptionListItemSchemaMock(); const decoded = createExceptionListItemSchema.decode(payload); const checked = exactCheck(payload, decoded); @@ -29,7 +29,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate an undefined for "description"', () => { + test('it should fail validation when supplied an undefined for "description"', () => { const payload = getCreateExceptionListItemSchemaMock(); delete payload.description; const decoded = createExceptionListItemSchema.decode(payload); @@ -41,7 +41,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "name"', () => { + test('it should fail validation when supplied an undefined for "name"', () => { const payload = getCreateExceptionListItemSchemaMock(); delete payload.name; const decoded = createExceptionListItemSchema.decode(payload); @@ -53,7 +53,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "type"', () => { + test('it should fail validation when supplied an undefined for "type"', () => { const payload = getCreateExceptionListItemSchemaMock(); delete payload.type; const decoded = createExceptionListItemSchema.decode(payload); @@ -65,7 +65,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should not validate an undefined for "list_id"', () => { + test('it should fail validation when supplied an undefined for "list_id"', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.list_id; const decoded = createExceptionListItemSchema.decode(inputPayload); @@ -77,7 +77,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should validate an undefined for "meta" but strip it out and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "meta" but strip it out and generate a correct body not counting the auto generated uuid', () => { const payload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete payload.meta; @@ -90,7 +90,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "comments" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "comments" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.comments; @@ -103,7 +103,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate "comments" array', () => { + test('it should pass validation when supplied "comments" array', () => { const inputPayload = { ...getCreateExceptionListItemSchemaMock(), comments: getCreateCommentsArrayMock(), @@ -116,7 +116,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(inputPayload); }); - test('it should NOT validate "comments" with "created_at" or "created_by" values', () => { + test('it should fail validation when supplied "comments" with "created_at" or "created_by" values', () => { const inputPayload: Omit & { comments?: CommentsArray; } = { @@ -126,11 +126,11 @@ describe('create_exception_list_item_schema', () => { const decoded = createExceptionListItemSchema.decode(inputPayload); const checked = exactCheck(inputPayload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "created_at,created_by"']); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "created_at,created_by,id"']); expect(message.schema).toEqual({}); }); - test('it should NOT validate an undefined for "entries"', () => { + test('it should fail validation when supplied an undefined for "entries"', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.entries; @@ -145,7 +145,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual({}); }); - test('it should validate an undefined for "namespace_type" but return enum "single" and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "namespace_type" but return enum "single" and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.namespace_type; @@ -158,7 +158,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "tags" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "tags" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.tags; @@ -171,7 +171,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "_tags" but return an array and generate a correct body not counting the auto generated uuid', () => { + test('it should pass validation when supplied an undefined for "_tags" but return an array and generate a correct body not counting the auto generated uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); const outputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload._tags; @@ -184,7 +184,7 @@ describe('create_exception_list_item_schema', () => { expect(message.schema).toEqual(outputPayload); }); - test('it should validate an undefined for "item_id" and auto generate a uuid', () => { + test('it should pass validation when supplied an undefined for "item_id" and auto generate a uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.item_id; const decoded = createExceptionListItemSchema.decode(inputPayload); @@ -196,7 +196,7 @@ describe('create_exception_list_item_schema', () => { ); }); - test('it should validate an undefined for "item_id" and generate a correct body not counting the uuid', () => { + test('it should pass validation when supplied an undefined for "item_id" and generate a correct body not counting the uuid', () => { const inputPayload = getCreateExceptionListItemSchemaMock(); delete inputPayload.item_id; const decoded = createExceptionListItemSchema.decode(inputPayload); diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.test.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.test.ts new file mode 100644 index 0000000000000..3358582786cc7 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getUpdateExceptionListItemSchemaMock } from './update_exception_list_item_schema.mock'; +import { validateComments } from './update_exception_list_item_validation'; + +describe('update_exception_list_item_validation', () => { + describe('#validateComments', () => { + test('it returns no errors if comments is undefined', () => { + const payload = getUpdateExceptionListItemSchemaMock(); + delete payload.comments; + const output = validateComments(payload); + + expect(output).toEqual([]); + }); + + test('it returns no errors if new comments are append only', () => { + const payload = getUpdateExceptionListItemSchemaMock(); + payload.comments = [ + { comment: 'Im an old comment', id: '1' }, + { comment: 'Im a new comment' }, + ]; + const output = validateComments(payload); + + expect(output).toEqual([]); + }); + + test('it returns error if comments are not append only', () => { + const payload = getUpdateExceptionListItemSchemaMock(); + payload.comments = [ + { comment: 'Im an old comment', id: '1' }, + { comment: 'Im a new comment modifying the order of existing comments' }, + { comment: 'Im an old comment', id: '2' }, + ]; + const output = validateComments(payload); + + expect(output).toEqual(['item "comments" are append only']); + }); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.ts new file mode 100644 index 0000000000000..5e44c4e9f73e7 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_validation.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UpdateExceptionListItemSchema } from './update_exception_list_item_schema'; + +export const validateComments = (item: UpdateExceptionListItemSchema): string[] => { + if (item.comments == null) { + return []; + } + + const [appendOnly] = item.comments.reduce( + (acc, comment) => { + const [, hasNewComments] = acc; + if (comment.id == null) { + return [true, true]; + } + + if (hasNewComments && comment.id != null) { + return [false, true]; + } + + return acc; + }, + [true, false] + ); + if (!appendOnly) { + return ['item "comments" are append only']; + } else { + return []; + } +}; + +export const updateExceptionListItemValidate = ( + schema: UpdateExceptionListItemSchema +): string[] => { + return [...validateComments(schema)]; +}; diff --git a/x-pack/plugins/lists/common/schemas/types/comments.mock.ts b/x-pack/plugins/lists/common/schemas/types/comment.mock.ts similarity index 71% rename from x-pack/plugins/lists/common/schemas/types/comments.mock.ts rename to x-pack/plugins/lists/common/schemas/types/comment.mock.ts index 9e56ac292f8b5..213259b3cce29 100644 --- a/x-pack/plugins/lists/common/schemas/types/comments.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/comment.mock.ts @@ -4,14 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DATE_NOW, USER } from '../../constants.mock'; +import { DATE_NOW, ID, USER } from '../../constants.mock'; -import { Comments, CommentsArray } from './comments'; +import { Comment, CommentsArray } from './comment'; -export const getCommentsMock = (): Comments => ({ +export const getCommentsMock = (): Comment => ({ comment: 'some old comment', created_at: DATE_NOW, created_by: USER, + id: ID, }); export const getCommentsArrayMock = (): CommentsArray => [getCommentsMock(), getCommentsMock()]; diff --git a/x-pack/plugins/lists/common/schemas/types/comments.test.ts b/x-pack/plugins/lists/common/schemas/types/comment.test.ts similarity index 56% rename from x-pack/plugins/lists/common/schemas/types/comments.test.ts rename to x-pack/plugins/lists/common/schemas/types/comment.test.ts index 29bfde03abcc8..c7c945277f756 100644 --- a/x-pack/plugins/lists/common/schemas/types/comments.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/comment.test.ts @@ -10,56 +10,79 @@ import { left } from 'fp-ts/lib/Either'; import { DATE_NOW } from '../../constants.mock'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; -import { getCommentsArrayMock, getCommentsMock } from './comments.mock'; +import { getCommentsArrayMock, getCommentsMock } from './comment.mock'; import { - Comments, + Comment, CommentsArray, CommentsArrayOrUndefined, - comments, + comment, commentsArray, commentsArrayOrUndefined, -} from './comments'; +} from './comment'; -describe('Comments', () => { - describe('comments', () => { - test('it should validate a comments', () => { +describe('Comment', () => { + describe('comment', () => { + test('it fails validation when "id" is undefined', () => { + const payload = { ...getCommentsMock(), id: undefined }; + const decoded = comment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it passes validation with a typical comment', () => { const payload = getCommentsMock(); - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(payload); }); - test('it should validate with "updated_at" and "updated_by"', () => { + test('it passes validation with "updated_at" and "updated_by" fields included', () => { const payload = getCommentsMock(); payload.updated_at = DATE_NOW; payload.updated_by = 'someone'; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(payload); }); - test('it should not validate when undefined', () => { + test('it fails validation when undefined', () => { const payload = undefined; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)"', - 'Invalid value "undefined" supplied to "({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)"', + 'Invalid value "undefined" supplied to "({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)"', + 'Invalid value "undefined" supplied to "({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)"', ]); expect(message.schema).toEqual({}); }); - test('it should not validate when "comment" is not a string', () => { - const payload: Omit & { comment: string[] } = { + test('it fails validation when "comment" is an empty string', () => { + const payload: Omit & { comment: string } = { + ...getCommentsMock(), + comment: '', + }; + const decoded = comment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "comment"']); + expect(message.schema).toEqual({}); + }); + + test('it fails validation when "comment" is not a string', () => { + const payload: Omit & { comment: string[] } = { ...getCommentsMock(), comment: ['some value'], }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -68,12 +91,12 @@ describe('Comments', () => { expect(message.schema).toEqual({}); }); - test('it should not validate when "created_at" is not a string', () => { - const payload: Omit & { created_at: number } = { + test('it fails validation when "created_at" is not a string', () => { + const payload: Omit & { created_at: number } = { ...getCommentsMock(), created_at: 1, }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -82,12 +105,12 @@ describe('Comments', () => { expect(message.schema).toEqual({}); }); - test('it should not validate when "created_by" is not a string', () => { - const payload: Omit & { created_by: number } = { + test('it fails validation when "created_by" is not a string', () => { + const payload: Omit & { created_by: number } = { ...getCommentsMock(), created_by: 1, }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -96,12 +119,12 @@ describe('Comments', () => { expect(message.schema).toEqual({}); }); - test('it should not validate when "updated_at" is not a string', () => { - const payload: Omit & { updated_at: number } = { + test('it fails validation when "updated_at" is not a string', () => { + const payload: Omit & { updated_at: number } = { ...getCommentsMock(), updated_at: 1, }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -110,12 +133,12 @@ describe('Comments', () => { expect(message.schema).toEqual({}); }); - test('it should not validate when "updated_by" is not a string', () => { - const payload: Omit & { updated_by: number } = { + test('it fails validation when "updated_by" is not a string', () => { + const payload: Omit & { updated_by: number } = { ...getCommentsMock(), updated_by: 1, }; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -125,11 +148,11 @@ describe('Comments', () => { }); test('it should strip out extra keys', () => { - const payload: Comments & { + const payload: Comment & { extraKey?: string; } = getCommentsMock(); payload.extraKey = 'some value'; - const decoded = comments.decode(payload); + const decoded = comment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -138,7 +161,7 @@ describe('Comments', () => { }); describe('commentsArray', () => { - test('it should validate an array of comments', () => { + test('it passes validation an array of Comment', () => { const payload = getCommentsArrayMock(); const decoded = commentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -147,7 +170,7 @@ describe('Comments', () => { expect(message.schema).toEqual(payload); }); - test('it should validate when a comments includes "updated_at" and "updated_by"', () => { + test('it passes validation when a Comment includes "updated_at" and "updated_by"', () => { const commentsPayload = getCommentsMock(); commentsPayload.updated_at = DATE_NOW; commentsPayload.updated_by = 'someone'; @@ -159,32 +182,32 @@ describe('Comments', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate when undefined', () => { + test('it fails validation when undefined', () => { const payload = undefined; const decoded = commentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "undefined" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); - test('it should not validate when array includes non comments types', () => { + test('it fails validation when array includes non Comment types', () => { const payload = ([1] as unknown) as CommentsArray; const decoded = commentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); }); describe('commentsArrayOrUndefined', () => { - test('it should validate an array of comments', () => { + test('it passes validation an array of Comment', () => { const payload = getCommentsArrayMock(); const decoded = commentsArrayOrUndefined.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -193,7 +216,7 @@ describe('Comments', () => { expect(message.schema).toEqual(payload); }); - test('it should validate when undefined', () => { + test('it passes validation when undefined', () => { const payload = undefined; const decoded = commentsArrayOrUndefined.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -202,14 +225,14 @@ describe('Comments', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate when array includes non comments types', () => { + test('it fails validation when array includes non Comment types', () => { const payload = ([1] as unknown) as CommentsArrayOrUndefined; const decoded = commentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/comments.ts b/x-pack/plugins/lists/common/schemas/types/comment.ts similarity index 56% rename from x-pack/plugins/lists/common/schemas/types/comments.ts rename to x-pack/plugins/lists/common/schemas/types/comment.ts index 0ee3b05c8102f..6b0b0166b9ee1 100644 --- a/x-pack/plugins/lists/common/schemas/types/comments.ts +++ b/x-pack/plugins/lists/common/schemas/types/comment.ts @@ -3,26 +3,33 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + +/* eslint-disable @typescript-eslint/camelcase */ + import * as t from 'io-ts'; -export const comments = t.intersection([ +import { NonEmptyString } from '../../siem_common_deps'; +import { created_at, created_by, id, updated_at, updated_by } from '../common/schemas'; + +export const comment = t.intersection([ t.exact( t.type({ - comment: t.string, - created_at: t.string, // TODO: Make this into an ISO Date string check, - created_by: t.string, + comment: NonEmptyString, + created_at, + created_by, + id, }) ), t.exact( t.partial({ - updated_at: t.string, - updated_by: t.string, + updated_at, + updated_by, }) ), ]); -export const commentsArray = t.array(comments); +export const commentsArray = t.array(comment); export type CommentsArray = t.TypeOf; -export type Comments = t.TypeOf; +export type Comment = t.TypeOf; export const commentsArrayOrUndefined = t.union([commentsArray, t.undefined]); export type CommentsArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/create_comments.mock.ts b/x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts similarity index 73% rename from x-pack/plugins/lists/common/schemas/types/create_comments.mock.ts rename to x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts index 60a59432275ca..689d4ccdc2c2e 100644 --- a/x-pack/plugins/lists/common/schemas/types/create_comments.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/create_comment.mock.ts @@ -3,9 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CreateComments, CreateCommentsArray } from './create_comments'; +import { CreateComment, CreateCommentsArray } from './create_comment'; -export const getCreateCommentsMock = (): CreateComments => ({ +export const getCreateCommentsMock = (): CreateComment => ({ comment: 'some comments', }); diff --git a/x-pack/plugins/lists/common/schemas/types/create_comments.test.ts b/x-pack/plugins/lists/common/schemas/types/create_comment.test.ts similarity index 72% rename from x-pack/plugins/lists/common/schemas/types/create_comments.test.ts rename to x-pack/plugins/lists/common/schemas/types/create_comment.test.ts index d2680750e05e4..366bf84d48bbf 100644 --- a/x-pack/plugins/lists/common/schemas/types/create_comments.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/create_comment.test.ts @@ -9,44 +9,44 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; -import { getCreateCommentsArrayMock, getCreateCommentsMock } from './create_comments.mock'; +import { getCreateCommentsArrayMock, getCreateCommentsMock } from './create_comment.mock'; import { - CreateComments, + CreateComment, CreateCommentsArray, CreateCommentsArrayOrUndefined, - createComments, + createComment, createCommentsArray, createCommentsArrayOrUndefined, -} from './create_comments'; +} from './create_comment'; -describe('CreateComments', () => { - describe('createComments', () => { - test('it should validate a comments', () => { +describe('CreateComment', () => { + describe('createComment', () => { + test('it passes validation with a default comment', () => { const payload = getCreateCommentsMock(); - const decoded = createComments.decode(payload); + const decoded = createComment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(payload); }); - test('it should not validate when undefined', () => { + test('it fails validation when undefined', () => { const payload = undefined; - const decoded = createComments.decode(payload); + const decoded = createComment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "{| comment: string |}"', + 'Invalid value "undefined" supplied to "{| comment: NonEmptyString |}"', ]); expect(message.schema).toEqual({}); }); - test('it should not validate when "comment" is not a string', () => { - const payload: Omit & { comment: string[] } = { + test('it fails validation when "comment" is not a string', () => { + const payload: Omit & { comment: string[] } = { ...getCreateCommentsMock(), comment: ['some value'], }; - const decoded = createComments.decode(payload); + const decoded = createComment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -56,11 +56,11 @@ describe('CreateComments', () => { }); test('it should strip out extra keys', () => { - const payload: CreateComments & { + const payload: CreateComment & { extraKey?: string; } = getCreateCommentsMock(); payload.extraKey = 'some value'; - const decoded = createComments.decode(payload); + const decoded = createComment.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -69,7 +69,7 @@ describe('CreateComments', () => { }); describe('createCommentsArray', () => { - test('it should validate an array of comments', () => { + test('it passes validation an array of comments', () => { const payload = getCreateCommentsArrayMock(); const decoded = createCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -78,31 +78,31 @@ describe('CreateComments', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate when undefined', () => { + test('it fails validation when undefined', () => { const payload = undefined; const decoded = createCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "Array<{| comment: string |}>"', + 'Invalid value "undefined" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); - test('it should not validate when array includes non comments types', () => { + test('it fails validation when array includes non comments types', () => { const payload = ([1] as unknown) as CreateCommentsArray; const decoded = createCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| comment: string |}>"', + 'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); }); describe('createCommentsArrayOrUndefined', () => { - test('it should validate an array of comments', () => { + test('it passes validation an array of comments', () => { const payload = getCreateCommentsArrayMock(); const decoded = createCommentsArrayOrUndefined.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -111,7 +111,7 @@ describe('CreateComments', () => { expect(message.schema).toEqual(payload); }); - test('it should validate when undefined', () => { + test('it passes validation when undefined', () => { const payload = undefined; const decoded = createCommentsArrayOrUndefined.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -120,13 +120,13 @@ describe('CreateComments', () => { expect(message.schema).toEqual(payload); }); - test('it should not validate when array includes non comments types', () => { + test('it fails validation when array includes non comments types', () => { const payload = ([1] as unknown) as CreateCommentsArrayOrUndefined; const decoded = createCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| comment: string |}>"', + 'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/create_comments.ts b/x-pack/plugins/lists/common/schemas/types/create_comment.ts similarity index 64% rename from x-pack/plugins/lists/common/schemas/types/create_comments.ts rename to x-pack/plugins/lists/common/schemas/types/create_comment.ts index c34419298ef93..fd33313430ce6 100644 --- a/x-pack/plugins/lists/common/schemas/types/create_comments.ts +++ b/x-pack/plugins/lists/common/schemas/types/create_comment.ts @@ -5,14 +5,17 @@ */ import * as t from 'io-ts'; -export const createComments = t.exact( +import { NonEmptyString } from '../../siem_common_deps'; + +export const createComment = t.exact( t.type({ - comment: t.string, + comment: NonEmptyString, }) ); -export const createCommentsArray = t.array(createComments); +export type CreateComment = t.TypeOf; +export const createCommentsArray = t.array(createComment); export type CreateCommentsArray = t.TypeOf; -export type CreateComments = t.TypeOf; +export type CreateComments = t.TypeOf; export const createCommentsArrayOrUndefined = t.union([createCommentsArray, t.undefined]); export type CreateCommentsArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts index 3a4241aaec82d..541b8ab1c799c 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_comments_array.test.ts @@ -10,11 +10,11 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; import { DefaultCommentsArray } from './default_comments_array'; -import { CommentsArray } from './comments'; -import { getCommentsArrayMock } from './comments.mock'; +import { CommentsArray } from './comment'; +import { getCommentsArrayMock } from './comment.mock'; describe('default_comments_array', () => { - test('it should validate an empty array', () => { + test('it should pass validation when supplied an empty array', () => { const payload: CommentsArray = []; const decoded = DefaultCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -23,7 +23,7 @@ describe('default_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should validate an array of comments', () => { + test('it should pass validation when supplied an array of comments', () => { const payload: CommentsArray = getCommentsArrayMock(); const decoded = DefaultCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -32,27 +32,26 @@ describe('default_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should NOT validate an array of numbers', () => { + test('it should fail validation when supplied an array of numbers', () => { const payload = [1]; const decoded = DefaultCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); - // TODO: Known weird error formatting that is on our list to address expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - 'Invalid value "1" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); - test('it should NOT validate an array of strings', () => { + test('it should fail validation when supplied an array of strings', () => { const payload = ['some string']; const decoded = DefaultCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', - 'Invalid value "some string" supplied to "Array<({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', + 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts index 342cf8b0d7091..0d7e28e69cf71 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_comments_array.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { CommentsArray, comments } from './comments'; +import { CommentsArray, comment } from './comment'; /** * Types the DefaultCommentsArray as: @@ -15,8 +15,8 @@ import { CommentsArray, comments } from './comments'; */ export const DefaultCommentsArray = new t.Type( 'DefaultCommentsArray', - t.array(comments).is, + t.array(comment).is, (input): Either => - input == null ? t.success([]) : t.array(comments).decode(input), + input == null ? t.success([]) : t.array(comment).decode(input), t.identity ); diff --git a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts index f5ef7d0ad96bd..eb960b5411904 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.test.ts @@ -10,11 +10,12 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; import { DefaultCreateCommentsArray } from './default_create_comments_array'; -import { CreateCommentsArray } from './create_comments'; -import { getCreateCommentsArrayMock } from './create_comments.mock'; +import { CreateCommentsArray } from './create_comment'; +import { getCreateCommentsArrayMock } from './create_comment.mock'; +import { getCommentsArrayMock } from './comment.mock'; describe('default_create_comments_array', () => { - test('it should validate an empty array', () => { + test('it should pass validation when an empty array', () => { const payload: CreateCommentsArray = []; const decoded = DefaultCreateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -23,7 +24,7 @@ describe('default_create_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should validate an array of comments', () => { + test('it should pass validation when an array of comments', () => { const payload: CreateCommentsArray = getCreateCommentsArrayMock(); const decoded = DefaultCreateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -32,25 +33,38 @@ describe('default_create_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should NOT validate an array of numbers', () => { + test('it should strip out "created_at" and "created_by" if they are passed in', () => { + const payload = getCommentsArrayMock(); + const decoded = DefaultCreateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + // TODO: Known weird error formatting that is on our list to address + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual([ + { comment: 'some old comment' }, + { comment: 'some old comment' }, + ]); + }); + + test('it should not pass validation when an array of numbers', () => { const payload = [1]; const decoded = DefaultCreateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); // TODO: Known weird error formatting that is on our list to address expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| comment: string |}>"', + 'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); - test('it should NOT validate an array of strings', () => { + test('it should not pass validation when an array of strings', () => { const payload = ['some string']; const decoded = DefaultCreateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<{| comment: string |}>"', + 'Invalid value "some string" supplied to "Array<{| comment: NonEmptyString |}>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts index 7fd79782836e3..4df888ba728fb 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_create_comments_array.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { CreateCommentsArray, createComments } from './create_comments'; +import { CreateCommentsArray, createComment } from './create_comment'; /** * Types the DefaultCreateComments as: @@ -19,8 +19,8 @@ export const DefaultCreateCommentsArray = new t.Type< unknown >( 'DefaultCreateComments', - t.array(createComments).is, + t.array(createComment).is, (input): Either => - input == null ? t.success([]) : t.array(createComments).decode(input), + input == null ? t.success([]) : t.array(createComment).decode(input), t.identity ); diff --git a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts index b023e73cb9328..612148dc4ccab 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.test.ts @@ -10,11 +10,11 @@ import { left } from 'fp-ts/lib/Either'; import { foldLeftRight, getPaths } from '../../siem_common_deps'; import { DefaultUpdateCommentsArray } from './default_update_comments_array'; -import { UpdateCommentsArray } from './update_comments'; -import { getUpdateCommentsArrayMock } from './update_comments.mock'; +import { UpdateCommentsArray } from './update_comment'; +import { getUpdateCommentsArrayMock } from './update_comment.mock'; describe('default_update_comments_array', () => { - test('it should validate an empty array', () => { + test('it should pass validation when supplied an empty array', () => { const payload: UpdateCommentsArray = []; const decoded = DefaultUpdateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -23,7 +23,7 @@ describe('default_update_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should validate an array of comments', () => { + test('it should pass validation when supplied an array of comments', () => { const payload: UpdateCommentsArray = getUpdateCommentsArrayMock(); const decoded = DefaultUpdateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); @@ -32,29 +32,26 @@ describe('default_update_comments_array', () => { expect(message.schema).toEqual(payload); }); - test('it should NOT validate an array of numbers', () => { + test('it should fail validation when supplied an array of numbers', () => { const payload = [1]; const decoded = DefaultUpdateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); - // TODO: Known weird error formatting that is on our list to address expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', ]); expect(message.schema).toEqual({}); }); - test('it should NOT validate an array of strings', () => { + test('it should fail validation when supplied an array of strings', () => { const payload = ['some string']; const decoded = DefaultUpdateCommentsArray.decode(payload); const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some string" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "some string" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "some string" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', + 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts index 854b7cf7ada7e..35338dae64387 100644 --- a/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts +++ b/x-pack/plugins/lists/common/schemas/types/default_update_comments_array.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { UpdateCommentsArray, updateCommentsArray } from './update_comments'; +import { UpdateCommentsArray, updateCommentsArray } from './update_comment'; /** * Types the DefaultCommentsUpdate as: diff --git a/x-pack/plugins/lists/common/schemas/types/index.ts b/x-pack/plugins/lists/common/schemas/types/index.ts index 463f7cfe51ce3..6b7e9fd17a1af 100644 --- a/x-pack/plugins/lists/common/schemas/types/index.ts +++ b/x-pack/plugins/lists/common/schemas/types/index.ts @@ -3,9 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -export * from './comments'; -export * from './create_comments'; -export * from './update_comments'; +export * from './comment'; +export * from './create_comment'; +export * from './update_comment'; export * from './default_comments_array'; export * from './default_create_comments_array'; export * from './default_update_comments_array'; diff --git a/x-pack/plugins/lists/common/schemas/types/update_comments.mock.ts b/x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts similarity index 54% rename from x-pack/plugins/lists/common/schemas/types/update_comments.mock.ts rename to x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts index 3e963c2607dc5..9b85a24abe40b 100644 --- a/x-pack/plugins/lists/common/schemas/types/update_comments.mock.ts +++ b/x-pack/plugins/lists/common/schemas/types/update_comment.mock.ts @@ -4,11 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getCommentsMock } from './comments.mock'; -import { getCreateCommentsMock } from './create_comments.mock'; -import { UpdateCommentsArray } from './update_comments'; +import { ID } from '../../constants.mock'; + +import { UpdateComment, UpdateCommentsArray } from './update_comment'; + +export const getUpdateCommentMock = (): UpdateComment => ({ + comment: 'some comment', + id: ID, +}); export const getUpdateCommentsArrayMock = (): UpdateCommentsArray => [ - getCommentsMock(), - getCreateCommentsMock(), + getUpdateCommentMock(), + getUpdateCommentMock(), ]; diff --git a/x-pack/plugins/lists/common/schemas/types/update_comment.test.ts b/x-pack/plugins/lists/common/schemas/types/update_comment.test.ts new file mode 100644 index 0000000000000..ac7716af40966 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/update_comment.test.ts @@ -0,0 +1,150 @@ +/* + * 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 { pipe } from 'fp-ts/lib/pipeable'; +import { left } from 'fp-ts/lib/Either'; + +import { foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getUpdateCommentMock, getUpdateCommentsArrayMock } from './update_comment.mock'; +import { + UpdateComment, + UpdateCommentsArray, + UpdateCommentsArrayOrUndefined, + updateComment, + updateCommentsArray, + updateCommentsArrayOrUndefined, +} from './update_comment'; + +describe('CommentsUpdate', () => { + describe('updateComment', () => { + test('it should pass validation when supplied typical comment update', () => { + const payload = getUpdateCommentMock(); + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should fail validation when supplied an undefined for "comment"', () => { + const payload = getUpdateCommentMock(); + delete payload.comment; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "comment"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when supplied an empty string for "comment"', () => { + const payload = { ...getUpdateCommentMock(), comment: '' }; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "comment"']); + expect(message.schema).toEqual({}); + }); + + test('it should pass validation when supplied an undefined for "id"', () => { + const payload = getUpdateCommentMock(); + delete payload.id; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should fail validation when supplied an empty string for "id"', () => { + const payload = { ...getUpdateCommentMock(), id: '' }; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('it should strip out extra key passed in', () => { + const payload: UpdateComment & { + extraKey?: string; + } = { ...getUpdateCommentMock(), extraKey: 'some new value' }; + const decoded = updateComment.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(getUpdateCommentMock()); + }); + }); + + describe('updateCommentsArray', () => { + test('it should pass validation when supplied an array of comments', () => { + const payload = getUpdateCommentsArrayMock(); + const decoded = updateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should fail validation when undefined', () => { + const payload = undefined; + const decoded = updateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when array includes non comments types', () => { + const payload = ([1] as unknown) as UpdateCommentsArray; + const decoded = updateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + ]); + expect(message.schema).toEqual({}); + }); + }); + + describe('updateCommentsArrayOrUndefined', () => { + test('it should pass validation when supplied an array of comments', () => { + const payload = getUpdateCommentsArrayMock(); + const decoded = updateCommentsArrayOrUndefined.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should pass validation when supplied when undefined', () => { + const payload = undefined; + const decoded = updateCommentsArrayOrUndefined.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should fail validation when array includes non comments types', () => { + const payload = ([1] as unknown) as UpdateCommentsArrayOrUndefined; + const decoded = updateCommentsArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + 'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"', + ]); + expect(message.schema).toEqual({}); + }); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/types/update_comments.ts b/x-pack/plugins/lists/common/schemas/types/update_comment.ts similarity index 58% rename from x-pack/plugins/lists/common/schemas/types/update_comments.ts rename to x-pack/plugins/lists/common/schemas/types/update_comment.ts index 4a21bfa363d45..b95812cb35bf9 100644 --- a/x-pack/plugins/lists/common/schemas/types/update_comments.ts +++ b/x-pack/plugins/lists/common/schemas/types/update_comment.ts @@ -5,10 +5,24 @@ */ import * as t from 'io-ts'; -import { comments } from './comments'; -import { createComments } from './create_comments'; +import { NonEmptyString } from '../../siem_common_deps'; +import { id } from '../common/schemas'; -export const updateCommentsArray = t.array(t.union([comments, createComments])); +export const updateComment = t.intersection([ + t.exact( + t.type({ + comment: NonEmptyString, + }) + ), + t.exact( + t.partial({ + id, + }) + ), +]); + +export type UpdateComment = t.TypeOf; +export const updateCommentsArray = t.array(updateComment); export type UpdateCommentsArray = t.TypeOf; export const updateCommentsArrayOrUndefined = t.union([updateCommentsArray, t.undefined]); export type UpdateCommentsArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/update_comments.test.ts b/x-pack/plugins/lists/common/schemas/types/update_comments.test.ts deleted file mode 100644 index 7668504b031b5..0000000000000 --- a/x-pack/plugins/lists/common/schemas/types/update_comments.test.ts +++ /dev/null @@ -1,108 +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 { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -import { foldLeftRight, getPaths } from '../../siem_common_deps'; - -import { getUpdateCommentsArrayMock } from './update_comments.mock'; -import { - UpdateCommentsArray, - UpdateCommentsArrayOrUndefined, - updateCommentsArray, - updateCommentsArrayOrUndefined, -} from './update_comments'; -import { getCommentsMock } from './comments.mock'; -import { getCreateCommentsMock } from './create_comments.mock'; - -describe('CommentsUpdate', () => { - describe('updateCommentsArray', () => { - test('it should validate an array of comments', () => { - const payload = getUpdateCommentsArrayMock(); - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of existing comments', () => { - const payload = [getCommentsMock()]; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate an array of new comments', () => { - const payload = [getCreateCommentsMock()]; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should not validate when undefined', () => { - const payload = undefined; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should not validate when array includes non comments types', () => { - const payload = ([1] as unknown) as UpdateCommentsArray; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('updateCommentsArrayOrUndefined', () => { - test('it should validate an array of comments', () => { - const payload = getUpdateCommentsArrayMock(); - const decoded = updateCommentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate when undefined', () => { - const payload = undefined; - const decoded = updateCommentsArrayOrUndefined.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should not validate when array includes non comments types', () => { - const payload = ([1] as unknown) as UpdateCommentsArrayOrUndefined; - const decoded = updateCommentsArray.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - 'Invalid value "1" supplied to "Array<(({| comment: string, created_at: string, created_by: string |} & Partial<{| updated_at: string, updated_by: string |}>) | {| comment: string |})>"', - ]); - expect(message.schema).toEqual({}); - }); - }); -}); diff --git a/x-pack/plugins/lists/common/shared_exports.ts b/x-pack/plugins/lists/common/shared_exports.ts index dc0a9aa5926ef..1f6c65919b063 100644 --- a/x-pack/plugins/lists/common/shared_exports.ts +++ b/x-pack/plugins/lists/common/shared_exports.ts @@ -8,8 +8,8 @@ export { ListSchema, CommentsArray, CreateCommentsArray, - Comments, - CreateComments, + Comment, + CreateComment, ExceptionListSchema, ExceptionListItemSchema, CreateExceptionListSchema, @@ -28,6 +28,7 @@ export { OperatorType, OperatorTypeEnum, ExceptionListTypeEnum, + comment, exceptionListItemSchema, exceptionListType, createExceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts index 293435b3f6202..f5e0e7ae75700 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -14,6 +14,7 @@ import { exceptionListItemSchema, updateExceptionListItemSchema, } from '../../common/schemas'; +import { updateExceptionListItemValidate } from '../../common/schemas/request/update_exception_list_item_validation'; import { getExceptionListClient } from '.'; @@ -33,6 +34,11 @@ export const updateExceptionListItemRoute = (router: IRouter): void => { }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); + const validationErrors = updateExceptionListItemValidate(request.body); + if (validationErrors.length) { + return siemResponse.error({ body: validationErrors, statusCode: 400 }); + } + try { const { description, diff --git a/x-pack/plugins/lists/server/saved_objects/exception_list.ts b/x-pack/plugins/lists/server/saved_objects/exception_list.ts index 3bde3545837cf..f9e408833e069 100644 --- a/x-pack/plugins/lists/server/saved_objects/exception_list.ts +++ b/x-pack/plugins/lists/server/saved_objects/exception_list.ts @@ -83,6 +83,9 @@ export const exceptionListItemMapping: SavedObjectsType['mappings'] = { created_by: { type: 'keyword', }, + id: { + type: 'keyword', + }, updated_at: { type: 'keyword', }, diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json index da345fb930c04..81db909277595 100644 --- a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json +++ b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json @@ -1,17 +1,18 @@ { - "item_id": "simple_list_item", - "_tags": ["endpoint", "process", "malware", "os:windows"], - "tags": ["user added string for a tag", "malware"], - "type": "simple", - "description": "This is a sample change here this list", - "name": "Sample Endpoint Exception List update change", - "comments": [{ "comment": "this is a newly added comment" }], + "_tags": ["detection"], + "comments": [], + "description": "Test comments - exception list item", "entries": [ { - "field": "event.category", - "operator": "included", - "type": "match_any", - "value": ["process", "malware"] + "field": "host.name", + "type": "match", + "value": "rock01", + "operator": "included" } - ] + ], + "item_id": "993f43f7-325d-4df3-9338-964e77c37053", + "name": "Test comments - exception list item", + "namespace_type": "single", + "tags": [], + "type": "simple" } diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts index a90ec61aef4af..47c21735b45f4 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts @@ -64,7 +64,10 @@ export const createExceptionListItem = async ({ }: CreateExceptionListItemOptions): Promise => { const savedObjectType = getSavedObjectType({ namespaceType }); const dateNow = new Date().toISOString(); - const transformedComments = transformCreateCommentsToComments({ comments, user }); + const transformedComments = transformCreateCommentsToComments({ + incomingComments: comments, + user, + }); const savedObject = await savedObjectsClient.create(savedObjectType, { _tags, comments: transformedComments, diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts index 6f0c5195f2025..e3d96a9c3f6d0 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts @@ -5,15 +5,11 @@ */ import sinon from 'sinon'; import moment from 'moment'; +import uuid from 'uuid'; -import { USER } from '../../../common/constants.mock'; +import { transformCreateCommentsToComments, transformUpdateCommentsToComments } from './utils'; -import { - isCommentEqual, - transformCreateCommentsToComments, - transformUpdateComments, - transformUpdateCommentsToComments, -} from './utils'; +jest.mock('uuid/v4'); describe('utils', () => { const oldDate = '2020-03-17T20:34:51.337Z'; @@ -22,59 +18,43 @@ describe('utils', () => { let clock: sinon.SinonFakeTimers; beforeEach(() => { + ((uuid.v4 as unknown) as jest.Mock) + .mockImplementationOnce(() => '123') + .mockImplementationOnce(() => '456'); + clock = sinon.useFakeTimers(unix); }); afterEach(() => { clock.restore(); + jest.clearAllMocks(); + jest.restoreAllMocks(); + jest.resetAllMocks(); }); describe('#transformUpdateCommentsToComments', () => { - test('it returns empty array if "comments" is undefined and no comments exist', () => { + test('it formats new comments', () => { const comments = transformUpdateCommentsToComments({ - comments: undefined, + comments: [{ comment: 'Im a new comment' }], existingComments: [], user: 'lily', }); - expect(comments).toEqual([]); - }); - - test('it formats newly added comments', () => { - const comments = transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane' }, - { comment: 'Im a new comment' }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane' }, - ], - user: 'lily', - }); - expect(comments).toEqual([ - { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'bane', - }, { comment: 'Im a new comment', created_at: dateNow, created_by: 'lily', + id: '123', }, ]); }); - test('it formats multiple newly added comments', () => { + test('it formats new comments and preserves existing comments', () => { const comments = transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - { comment: 'Im a new comment' }, - { comment: 'Im another new comment' }, - ], + comments: [{ comment: 'Im an old comment', id: '1' }, { comment: 'Im a new comment' }], existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, + { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane', id: '1' }, ], user: 'lily', }); @@ -83,26 +63,23 @@ describe('utils', () => { { comment: 'Im an old comment', created_at: oldDate, - created_by: 'lily', + created_by: 'bane', + id: '1', }, { comment: 'Im a new comment', created_at: dateNow, created_by: 'lily', - }, - { - comment: 'Im another new comment', - created_at: dateNow, - created_by: 'lily', + id: '123', }, ]); }); - test('it should not throw if comments match existing comments', () => { + test('it returns existing comments if empty array passed for "comments"', () => { const comments = transformUpdateCommentsToComments({ - comments: [{ comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }], + comments: [], existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, + { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane', id: '1' }, ], user: 'lily', }); @@ -111,170 +88,42 @@ describe('utils', () => { { comment: 'Im an old comment', created_at: oldDate, - created_by: 'lily', + created_by: 'bane', + id: '1', }, ]); }); - test('it does not throw if user tries to update one of their own existing comments', () => { + test('it acts as append only, only modifying new comments', () => { const comments = transformUpdateCommentsToComments({ - comments: [ - { - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - }, - ], + comments: [{ comment: 'Im a new comment' }], existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, + { comment: 'Im an old comment', created_at: oldDate, created_by: 'bane', id: '1' }, ], user: 'lily', }); expect(comments).toEqual([ { - comment: 'Im an old comment that is trying to be updated', + comment: 'Im an old comment', created_at: oldDate, + created_by: 'bane', + id: '1', + }, + { + comment: 'Im a new comment', + created_at: dateNow, created_by: 'lily', - updated_at: dateNow, - updated_by: 'lily', + id: '123', }, ]); }); - - test('it throws an error if user tries to update their comment, without passing in the "created_at" and "created_by" properties', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { - comment: 'Im an old comment that is trying to be updated', - }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot( - `"When trying to update a comment, \\"created_at\\" and \\"created_by\\" must be present"` - ); - }); - - test('it throws an error if user tries to delete comments', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot( - `"Comments cannot be deleted, only new comments may be added"` - ); - }); - - test('it throws if user tries to update existing comment timestamp', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [{ comment: 'Im an old comment', created_at: dateNow, created_by: 'lily' }], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'bane', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Not authorized to edit others comments"`); - }); - - test('it throws if user tries to update existing comment author', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [{ comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'me!' }, - ], - user: 'bane', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Not authorized to edit others comments"`); - }); - - test('it throws if user tries to update an existing comment that is not their own', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'bane', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Not authorized to edit others comments"`); - }); - - test('it throws if user tries to update order of comments', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im a new comment' }, - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot( - `"When trying to update a comment, \\"created_at\\" and \\"created_by\\" must be present"` - ); - }); - - test('it throws an error if user tries to add comment formatted as existing comment when none yet exist', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - { comment: 'Im a new comment' }, - ], - existingComments: [], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Only new comments may be added"`); - }); - - test('it throws if empty comment exists', () => { - expect(() => - transformUpdateCommentsToComments({ - comments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - { comment: ' ' }, - ], - existingComments: [ - { comment: 'Im an old comment', created_at: oldDate, created_by: 'lily' }, - ], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Empty comments not allowed"`); - }); }); describe('#transformCreateCommentsToComments', () => { - test('it returns "undefined" if "comments" is "undefined"', () => { - const comments = transformCreateCommentsToComments({ - comments: undefined, - user: 'lily', - }); - - expect(comments).toBeUndefined(); - }); - test('it formats newly added comments', () => { const comments = transformCreateCommentsToComments({ - comments: [{ comment: 'Im a new comment' }, { comment: 'Im another new comment' }], + incomingComments: [{ comment: 'Im a new comment' }, { comment: 'Im another new comment' }], user: 'lily', }); @@ -283,178 +132,15 @@ describe('utils', () => { comment: 'Im a new comment', created_at: dateNow, created_by: 'lily', + id: '123', }, { comment: 'Im another new comment', created_at: dateNow, created_by: 'lily', + id: '456', }, ]); }); - - test('it throws an error if user tries to add an empty comment', () => { - expect(() => - transformCreateCommentsToComments({ - comments: [{ comment: ' ' }], - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Empty comments not allowed"`); - }); - }); - - describe('#transformUpdateComments', () => { - test('it updates comment and adds "updated_at" and "updated_by" if content differs', () => { - const comments = transformUpdateComments({ - comment: { - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - }, - existingComment: { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }, - user: 'lily', - }); - - expect(comments).toEqual({ - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - updated_at: dateNow, - updated_by: 'lily', - }); - }); - - test('it does not update comment and add "updated_at" and "updated_by" if content is the same', () => { - const comments = transformUpdateComments({ - comment: { - comment: 'Im an old comment ', - created_at: oldDate, - created_by: 'lily', - }, - existingComment: { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }, - user: 'lily', - }); - - expect(comments).toEqual({ - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }); - }); - - test('it throws if user tries to update an existing comment that is not their own', () => { - expect(() => - transformUpdateComments({ - comment: { - comment: 'Im an old comment that is trying to be updated', - created_at: oldDate, - created_by: 'lily', - }, - existingComment: { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }, - user: 'bane', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Not authorized to edit others comments"`); - }); - - test('it throws if user tries to update an existing comments timestamp', () => { - expect(() => - transformUpdateComments({ - comment: { - comment: 'Im an old comment', - created_at: dateNow, - created_by: 'lily', - }, - existingComment: { - comment: 'Im an old comment', - created_at: oldDate, - created_by: 'lily', - }, - user: 'lily', - }) - ).toThrowErrorMatchingInlineSnapshot(`"Unable to update comment"`); - }); - }); - - describe('#isCommentEqual', () => { - test('it returns false if "comment" values differ', () => { - const result = isCommentEqual( - { - comment: 'some old comment', - created_at: oldDate, - created_by: USER, - }, - { - comment: 'some older comment', - created_at: oldDate, - created_by: USER, - } - ); - - expect(result).toBeFalsy(); - }); - - test('it returns false if "created_at" values differ', () => { - const result = isCommentEqual( - { - comment: 'some old comment', - created_at: oldDate, - created_by: USER, - }, - { - comment: 'some old comment', - created_at: dateNow, - created_by: USER, - } - ); - - expect(result).toBeFalsy(); - }); - - test('it returns false if "created_by" values differ', () => { - const result = isCommentEqual( - { - comment: 'some old comment', - created_at: oldDate, - created_by: USER, - }, - { - comment: 'some old comment', - created_at: oldDate, - created_by: 'lily', - } - ); - - expect(result).toBeFalsy(); - }); - - test('it returns true if comment values are equivalent', () => { - const result = isCommentEqual( - { - comment: 'some old comment', - created_at: oldDate, - created_by: USER, - }, - { - created_at: oldDate, - created_by: USER, - // Disabling to assure that order doesn't matter - // eslint-disable-next-line sort-keys - comment: 'some old comment', - } - ); - - expect(result).toBeTruthy(); - }); }); }); diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.ts index b168fae741822..836f642899086 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.ts @@ -3,17 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import uuid from 'uuid'; import { SavedObject, SavedObjectsFindResponse, SavedObjectsUpdateResponse } from 'kibana/server'; import { NamespaceTypeArray } from '../../../common/schemas/types/default_namespace_array'; -import { ErrorWithStatusCode } from '../../error_with_status_code'; import { - Comments, CommentsArray, - CommentsArrayOrUndefined, - CreateComments, - CreateCommentsArrayOrUndefined, + CreateComment, + CreateCommentsArray, ExceptionListItemSchema, ExceptionListSchema, ExceptionListSoSchema, @@ -21,7 +18,6 @@ import { FoundExceptionListSchema, NamespaceType, UpdateCommentsArrayOrUndefined, - comments as commentsSchema, exceptionListItemType, exceptionListType, } from '../../../common/schemas'; @@ -296,17 +292,6 @@ export const transformSavedObjectsToFoundExceptionList = ({ }; }; -/* - * Determines whether two comments are equal, this is a very - * naive implementation, not meant to be used for deep equality of complex objects - */ -export const isCommentEqual = (commentA: Comments, commentB: Comments): boolean => { - const a = Object.values(commentA).sort().join(); - const b = Object.values(commentB).sort().join(); - - return a === b; -}; - export const transformUpdateCommentsToComments = ({ comments, existingComments, @@ -316,90 +301,28 @@ export const transformUpdateCommentsToComments = ({ existingComments: CommentsArray; user: string; }): CommentsArray => { - const newComments = comments ?? []; + const incomingComments = comments ?? []; + const newComments = incomingComments.filter((comment) => comment.id == null); + const newCommentsFormatted = transformCreateCommentsToComments({ + incomingComments: newComments, + user, + }); - if (newComments.length < existingComments.length) { - throw new ErrorWithStatusCode( - 'Comments cannot be deleted, only new comments may be added', - 403 - ); - } else { - return newComments.flatMap((c, index) => { - const existingComment = existingComments[index]; - - if (commentsSchema.is(existingComment) && !commentsSchema.is(c)) { - throw new ErrorWithStatusCode( - 'When trying to update a comment, "created_at" and "created_by" must be present', - 403 - ); - } else if (existingComment == null && commentsSchema.is(c)) { - throw new ErrorWithStatusCode('Only new comments may be added', 403); - } else if ( - commentsSchema.is(c) && - existingComment != null && - isCommentEqual(c, existingComment) - ) { - return existingComment; - } else if (commentsSchema.is(c) && existingComment != null) { - return transformUpdateComments({ comment: c, existingComment, user }); - } else { - return transformCreateCommentsToComments({ comments: [c], user }) ?? []; - } - }); - } -}; - -export const transformUpdateComments = ({ - comment, - existingComment, - user, -}: { - comment: Comments; - existingComment: Comments; - user: string; -}): Comments => { - if (comment.created_by !== user) { - // existing comment is being edited, can only be edited by author - throw new ErrorWithStatusCode('Not authorized to edit others comments', 401); - } else if (existingComment.created_at !== comment.created_at) { - throw new ErrorWithStatusCode('Unable to update comment', 403); - } else if (comment.comment.trim().length === 0) { - throw new ErrorWithStatusCode('Empty comments not allowed', 403); - } else if (comment.comment.trim() !== existingComment.comment) { - const dateNow = new Date().toISOString(); - - return { - ...existingComment, - comment: comment.comment, - updated_at: dateNow, - updated_by: user, - }; - } else { - return existingComment; - } + return [...existingComments, ...newCommentsFormatted]; }; export const transformCreateCommentsToComments = ({ - comments, + incomingComments, user, }: { - comments: CreateCommentsArrayOrUndefined; + incomingComments: CreateCommentsArray; user: string; -}): CommentsArrayOrUndefined => { +}): CommentsArray => { const dateNow = new Date().toISOString(); - if (comments != null) { - return comments.map((c: CreateComments) => { - if (c.comment.trim().length === 0) { - throw new ErrorWithStatusCode('Empty comments not allowed', 403); - } else { - return { - comment: c.comment, - created_at: dateNow, - created_by: user, - }; - } - }); - } else { - return comments; - } + return incomingComments.map((comment: CreateComment) => ({ + comment: comment.comment, + created_at: dateNow, + created_by: user, + id: uuid.v4(), + })); }; diff --git a/x-pack/plugins/security_solution/common/shared_imports.ts b/x-pack/plugins/security_solution/common/shared_imports.ts index 7fb94cea7b612..e28d1969b3976 100644 --- a/x-pack/plugins/security_solution/common/shared_imports.ts +++ b/x-pack/plugins/security_solution/common/shared_imports.ts @@ -8,8 +8,8 @@ export { ListSchema, CommentsArray, CreateCommentsArray, - Comments, - CreateComments, + Comment, + CreateComment, ExceptionListSchema, ExceptionListItemSchema, CreateExceptionListSchema, @@ -30,6 +30,7 @@ export { ExceptionListTypeEnum, exceptionListItemSchema, exceptionListType, + comment, createExceptionListItemSchema, listSchema, entry, diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx index db2d0540971de..22d14ec6bedb1 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_comments.tsx @@ -16,13 +16,13 @@ import { EuiCommentProps, EuiText, } from '@elastic/eui'; -import { Comments } from '../../../lists_plugin_deps'; +import { Comment } from '../../../shared_imports'; import * as i18n from './translations'; import { useCurrentUser } from '../../lib/kibana'; import { getFormattedComments } from './helpers'; interface AddExceptionCommentsProps { - exceptionItemComments?: Comments[]; + exceptionItemComments?: Comment[]; newCommentValue: string; newCommentOnChange: (value: string) => void; } diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index a4fe52eaacf4e..0f7e5b24ed8f9 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -38,7 +38,7 @@ import { useSignalIndex } from '../../../../detections/containers/detection_engi import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list'; import { AddExceptionComments } from '../add_exception_comments'; import { - enrichExceptionItemsWithComments, + enrichNewExceptionItemsWithComments, enrichExceptionItemsWithOS, defaultEndpointExceptionItems, entryHasListType, @@ -251,7 +251,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ let enriched: Array = []; enriched = comment !== '' - ? enrichExceptionItemsWithComments(exceptionItemsToAdd, [{ comment }]) + ? enrichNewExceptionItemsWithComments(exceptionItemsToAdd, [{ comment }]) : exceptionItemsToAdd; if (exceptionListType === 'endpoint') { const osTypes = retrieveAlertOsTypes(); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx index 1ec49425ce8fd..734434484fb4c 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx @@ -392,7 +392,7 @@ export const ExceptionBuilder = ({ )} { - addError(error, { title: i18n.EDIT_EXCEPTION_ERROR }); - onCancel(); + if (error.message.includes('Conflict')) { + setHasVersionConflict(true); + } else { + addError(error, { title: i18n.EDIT_EXCEPTION_ERROR }); + onCancel(); + } }, [addError, onCancel] ); @@ -147,8 +153,8 @@ export const EditExceptionModal = memo(function EditExceptionModal({ }, [shouldDisableBulkClose]); const isSubmitButtonDisabled = useMemo( - () => exceptionItemsToAdd.every((item) => item.entries.length === 0), - [exceptionItemsToAdd] + () => exceptionItemsToAdd.every((item) => item.entries.length === 0) || hasVersionConflict, + [exceptionItemsToAdd, hasVersionConflict] ); const handleBuilderOnChange = useCallback( @@ -177,11 +183,15 @@ export const EditExceptionModal = memo(function EditExceptionModal({ ); const enrichExceptionItems = useCallback(() => { - let enriched: Array = []; - enriched = enrichExceptionItemsWithComments(exceptionItemsToAdd, [ - ...(exceptionItem.comments ? exceptionItem.comments : []), - ...(comment !== '' ? [{ comment }] : []), - ]); + const [exceptionItemToEdit] = exceptionItemsToAdd; + let enriched: Array = [ + { + ...enrichExistingExceptionItemWithComments(exceptionItemToEdit, [ + ...exceptionItem.comments, + ...(comment.trim() !== '' ? [{ comment }] : []), + ]), + }, + ]; if (exceptionListType === 'endpoint') { const osTypes = exceptionItem._tags ? getOperatingSystems(exceptionItem._tags) : []; enriched = enrichExceptionItemsWithOS(enriched, osTypes); @@ -222,7 +232,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ listId={exceptionItem.list_id} listNamespaceType={exceptionItem.namespace_type} ruleName={ruleName} - isOrDisabled={false} + isOrDisabled isAndDisabled={false} isNestedDisabled={false} data-test-subj="edit-exception-modal-builder" @@ -263,6 +273,14 @@ export const EditExceptionModal = memo(function EditExceptionModal({ )} + {hasVersionConflict && ( + + +

      {i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}

      +
      +
      + )} + {i18n.CANCEL} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts index 6c5cb733b7a73..d09f0158b2e1d 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts @@ -67,3 +67,18 @@ export const EXCEPTION_BUILDER_INFO = i18n.translate( defaultMessage: "Alerts are generated when the rule's conditions are met, except when:", } ); + +export const VERSION_CONFLICT_ERROR_TITLE = i18n.translate( + 'xpack.securitySolution.exceptions.editException.versionConflictTitle', + { + defaultMessage: 'Sorry, there was an error', + } +); + +export const VERSION_CONFLICT_ERROR_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.exceptions.editException.versionConflictDescription', + { + defaultMessage: + "It appears this exception was updated since you first selected to edit it. Try clicking 'Cancel' and editing the exception again.", + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index 78936d5d0da6f..5cb65ee6db8ff 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -18,7 +18,8 @@ import { formatOperatingSystems, getEntryValue, formatExceptionItemForUpdate, - enrichExceptionItemsWithComments, + enrichNewExceptionItemsWithComments, + enrichExistingExceptionItemWithComments, enrichExceptionItemsWithOS, entryHasListType, entryHasNonEcsType, @@ -35,14 +36,14 @@ import { existsOperator, doesNotExistOperator, } from '../autocomplete/operators'; -import { OperatorTypeEnum, OperatorEnum, EntryNested } from '../../../lists_plugin_deps'; +import { OperatorTypeEnum, OperatorEnum, EntryNested } from '../../../shared_imports'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getEntryMatchMock } from '../../../../../lists/common/schemas/types/entry_match.mock'; import { getEntryMatchAnyMock } from '../../../../../lists/common/schemas/types/entry_match_any.mock'; import { getEntryExistsMock } from '../../../../../lists/common/schemas/types/entry_exists.mock'; import { getEntryListMock } from '../../../../../lists/common/schemas/types/entry_list.mock'; -import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/comments.mock'; -import { ENTRIES } from '../../../../../lists/common/constants.mock'; +import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/comment.mock'; +import { ENTRIES, OLD_DATE_RELATIVE_TO_DATE_NOW } from '../../../../../lists/common/constants.mock'; import { CreateExceptionListItemSchema, ExceptionListItemSchema, @@ -410,12 +411,52 @@ describe('Exception helpers', () => { expect(result).toEqual(expected); }); }); + describe('#enrichExistingExceptionItemWithComments', () => { + test('it should return exception item with comments stripped of "created_by", "created_at", "updated_by", "updated_at" fields', () => { + const payload = getExceptionListItemSchemaMock(); + const comments = [ + { + comment: 'Im an existing comment', + created_at: OLD_DATE_RELATIVE_TO_DATE_NOW, + created_by: 'lily', + id: '1', + }, + { + comment: 'Im another existing comment', + created_at: OLD_DATE_RELATIVE_TO_DATE_NOW, + created_by: 'lily', + id: '2', + }, + { + comment: 'Im a new comment', + }, + ]; + const result = enrichExistingExceptionItemWithComments(payload, comments); + const expected = { + ...getExceptionListItemSchemaMock(), + comments: [ + { + comment: 'Im an existing comment', + id: '1', + }, + { + comment: 'Im another existing comment', + id: '2', + }, + { + comment: 'Im a new comment', + }, + ], + }; + expect(result).toEqual(expected); + }); + }); - describe('#enrichExceptionItemsWithComments', () => { + describe('#enrichNewExceptionItemsWithComments', () => { test('it should add comments to an exception item', () => { const payload = [getExceptionListItemSchemaMock()]; const comments = getCommentsArrayMock(); - const result = enrichExceptionItemsWithComments(payload, comments); + const result = enrichNewExceptionItemsWithComments(payload, comments); const expected = [ { ...getExceptionListItemSchemaMock(), @@ -428,7 +469,7 @@ describe('Exception helpers', () => { test('it should add comments to multiple exception items', () => { const payload = [getExceptionListItemSchemaMock(), getExceptionListItemSchemaMock()]; const comments = getCommentsArrayMock(); - const result = enrichExceptionItemsWithComments(payload, comments); + const result = enrichNewExceptionItemsWithComments(payload, comments); const expected = [ { ...getExceptionListItemSchemaMock(), diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index a54f20f56d56f..ee45f9b5de1fa 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -20,13 +20,14 @@ import { EXCEPTION_OPERATORS, isOperator } from '../autocomplete/operators'; import { OperatorOption } from '../autocomplete/types'; import { CommentsArray, - Comments, - CreateComments, + Comment, + CreateComment, Entry, ExceptionListItemSchema, NamespaceType, OperatorTypeEnum, CreateExceptionListItemSchema, + comment, entry, entriesNested, createExceptionListItemSchema, @@ -34,7 +35,7 @@ import { UpdateExceptionListItemSchema, ExceptionListType, EntryNested, -} from '../../../lists_plugin_deps'; +} from '../../../shared_imports'; import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; import { validate } from '../../../../common/validate'; import { TimelineNonEcsData } from '../../../graphql/types'; @@ -140,16 +141,16 @@ export const getTagsInclude = ({ * @param comments ExceptionItem.comments */ export const getFormattedComments = (comments: CommentsArray): EuiCommentProps[] => - comments.map((comment) => ({ - username: comment.created_by, - timestamp: moment(comment.created_at).format('on MMM Do YYYY @ HH:mm:ss'), + comments.map((commentItem) => ({ + username: commentItem.created_by, + timestamp: moment(commentItem.created_at).format('on MMM Do YYYY @ HH:mm:ss'), event: i18n.COMMENT_EVENT, - timelineIcon: , - children: {comment.comment}, + timelineIcon: , + children: {commentItem.comment}, actions: ( ), @@ -271,11 +272,11 @@ export const prepareExceptionItemsForBulkClose = ( /** * Adds new and existing comments to all new exceptionItems if not present already * @param exceptionItems new or existing ExceptionItem[] - * @param comments new Comments + * @param comments new Comment */ -export const enrichExceptionItemsWithComments = ( +export const enrichNewExceptionItemsWithComments = ( exceptionItems: Array, - comments: Array + comments: Array ): Array => { return exceptionItems.map((item: ExceptionListItemSchema | CreateExceptionListItemSchema) => { return { @@ -285,6 +286,36 @@ export const enrichExceptionItemsWithComments = ( }); }; +/** + * Adds new and existing comments to exceptionItem + * @param exceptionItem existing ExceptionItem + * @param comments array of comments that can include existing + * and new comments + */ +export const enrichExistingExceptionItemWithComments = ( + exceptionItem: ExceptionListItemSchema | CreateExceptionListItemSchema, + comments: Array +): ExceptionListItemSchema | CreateExceptionListItemSchema => { + const formattedComments = comments.map((item) => { + if (comment.is(item)) { + const { id, comment: existingComment } = item; + return { + id, + comment: existingComment, + }; + } else { + return { + comment: item.comment, + }; + } + }); + + return { + ...exceptionItem, + comments: formattedComments, + }; +}; + /** * Adds provided osTypes to all exceptionItems if not present already * @param exceptionItems new or existing ExceptionItem[] diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx index 8df7b51bb9d31..ab6588b67d5ba 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.test.tsx @@ -12,7 +12,7 @@ import moment from 'moment-timezone'; import { ExceptionDetails } from './exception_details'; import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; -import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comments.mock'; +import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comment.mock'; describe('ExceptionDetails', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx index 56b029aaee81e..fec7354855935 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx @@ -11,7 +11,7 @@ import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { ExceptionItem } from './'; import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; -import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comments.mock'; +import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comment.mock'; addDecorator((storyFn) => ( ({ eui: euiLightVars, darkMode: false })}>{storyFn()} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx index 90752f9450e4c..c9def092fda47 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx @@ -11,7 +11,7 @@ import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { ExceptionItem } from './'; import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; -import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comments.mock'; +import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comment.mock'; jest.mock('../../../../lib/kibana'); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx index 34dc47b9cd411..16eaef4136983 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx @@ -190,7 +190,8 @@ const ExceptionsViewerComponent = ({ const handleOnCancelExceptionModal = useCallback((): void => { setCurrentModal(null); - }, [setCurrentModal]); + handleFetchList(); + }, [setCurrentModal, handleFetchList]); const handleOnConfirmExceptionModal = useCallback((): void => { setCurrentModal(null); From ddff1c9ab9b0a36824ac0fdac97a957827cb8496 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 27 Jul 2020 17:50:46 -0600 Subject: [PATCH 17/36] [Security solution] Threat hunting test coverage improvements (#73276) --- .../components/markdown_editor/index.test.tsx | 49 ++++++ .../components/markdown_editor/index.tsx | 1 - .../navigation/breadcrumbs/index.test.ts | 74 +++++++++ .../utils/timeline/use_show_timeline.test.tsx | 33 ++++ .../components/manage_timeline/index.test.tsx | 145 ++++++++++++++++++ .../components/manage_timeline/index.tsx | 12 +- 6 files changed, 308 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/markdown_editor/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.test.tsx new file mode 100644 index 0000000000000..b5e5b01189418 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.test.tsx @@ -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. + */ + +import { mount } from 'enzyme'; +import React from 'react'; + +import { MarkdownEditor } from '.'; +import { TestProviders } from '../../mock'; + +describe('Markdown Editor', () => { + const onChange = jest.fn(); + const onCursorPositionUpdate = jest.fn(); + const defaultProps = { + content: 'hello world', + onChange, + onCursorPositionUpdate, + }; + beforeEach(() => { + jest.clearAllMocks(); + }); + test('it calls onChange with correct value', () => { + const wrapper = mount( + + + + ); + const newValue = 'a new string'; + wrapper + .find(`[data-test-subj="textAreaInput"]`) + .first() + .simulate('change', { target: { value: newValue } }); + expect(onChange).toBeCalledWith(newValue); + }); + test('it calls onCursorPositionUpdate with correct args', () => { + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="textAreaInput"]`).first().simulate('blur'); + expect(onCursorPositionUpdate).toBeCalledWith({ + start: 0, + end: 0, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.tsx index c40b3910ec152..d4ad4a11b60a3 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/index.tsx @@ -103,7 +103,6 @@ export const MarkdownEditor = React.memo<{ end: e!.target!.selectionEnd ?? 0, }); } - return false; }, [onCursorPositionUpdate] ); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 7e508c28c62df..89aa77106933e 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -36,6 +36,13 @@ const getMockObject = ( ): RouteSpyState & TabNavigationProps => ({ detailName, navTabs: { + case: { + disabled: false, + href: '/app/security/cases', + id: 'case', + name: 'Cases', + urlKey: 'case', + }, hosts: { disabled: false, href: '/app/security/hosts', @@ -227,6 +234,73 @@ describe('Navigation Breadcrumbs', () => { { text: 'Flows', href: '' }, ]); }); + + test('should return Alerts breadcrumbs when supplied detection pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('detections', '/', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: '/app/security/overview' }, + { + text: 'Detections', + href: + "securitySolution:detections?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + ]); + }); + test('should return Cases breadcrumbs when supplied case pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('case', '/', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: '/app/security/overview' }, + { + text: 'Cases', + href: + "securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + ]); + }); + test('should return Case details breadcrumbs when supplied case details pathname', () => { + const sampleCase = { + id: 'my-case-id', + name: 'Case name', + }; + const breadcrumbs = getBreadcrumbsForRoute( + { + ...getMockObject('case', `/${sampleCase.id}`, sampleCase.id), + state: { caseTitle: sampleCase.name }, + }, + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: '/app/security/overview' }, + { + text: 'Cases', + href: + "securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + { + text: sampleCase.name, + href: `securitySolution:case/${sampleCase.id}?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + }, + ]); + }); + test('should return Admin breadcrumbs when supplied admin pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('administration', '/', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: '/app/security/overview' }, + { + text: 'Administration', + href: 'securitySolution:administration', + }, + ]); + }); }); describe('setBreadcrumbs()', () => { diff --git a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx new file mode 100644 index 0000000000000..db6e2536ce558 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx @@ -0,0 +1,33 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { useShowTimeline } from './use_show_timeline'; +import { globalNode } from '../../mock'; + +describe('use show timeline', () => { + it('shows timeline for routes on default', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useShowTimeline()); + await waitForNextUpdate(); + const uninitializedTimeline = result.current; + expect(uninitializedTimeline).toEqual([true]); + }); + }); + it('hides timeline for blacklist routes', async () => { + await act(async () => { + Object.defineProperty(globalNode.window, 'location', { + value: { + pathname: `/cases/configure`, + }, + }); + const { result, waitForNextUpdate } = renderHook(() => useShowTimeline()); + await waitForNextUpdate(); + const uninitializedTimeline = result.current; + expect(uninitializedTimeline).toEqual([false]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx new file mode 100644 index 0000000000000..b918e5abc652b --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx @@ -0,0 +1,145 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { getTimelineDefaults, useTimelineManager, UseTimelineManager } from './'; +import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { TimelineRowAction } from '../timeline/body/actions'; + +const isStringifiedComparisonEqual = (a: {}, b: {}): boolean => + JSON.stringify(a) === JSON.stringify(b); + +describe('useTimelineManager', () => { + const setupMock = coreMock.createSetup(); + const testId = 'coolness'; + const timelineDefaults = getTimelineDefaults(testId); + const timelineRowActions = () => []; + const mockFilterManager = new FilterManager(setupMock.uiSettings); + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + it('initilizes an undefined timeline', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + const uninitializedTimeline = result.current.getManageTimelineById(testId); + expect(isStringifiedComparisonEqual(uninitializedTimeline, timelineDefaults)).toBeTruthy(); + }); + }); + it('getIndexToAddById', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + const data = result.current.getIndexToAddById(testId); + expect(data).toEqual(timelineDefaults.indexToAdd); + }); + }); + it('setIndexToAdd', async () => { + await act(async () => { + const indexToAddArgs = { id: testId, indexToAdd: ['example'] }; + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + }); + result.current.setIndexToAdd(indexToAddArgs); + const data = result.current.getIndexToAddById(testId); + expect(data).toEqual(indexToAddArgs.indexToAdd); + }); + }); + it('setIsTimelineLoading', async () => { + await act(async () => { + const isLoadingArgs = { id: testId, isLoading: true }; + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + }); + let timeline = result.current.getManageTimelineById(testId); + expect(timeline.isLoading).toBeFalsy(); + result.current.setIsTimelineLoading(isLoadingArgs); + timeline = result.current.getManageTimelineById(testId); + expect(timeline.isLoading).toBeTruthy(); + }); + }); + it('setTimelineRowActions', async () => { + await act(async () => { + const timelineRowActionsEx = () => [ + { id: 'wow', content: 'hey', displayType: 'icon', onClick: () => {} } as TimelineRowAction, + ]; + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + }); + let timeline = result.current.getManageTimelineById(testId); + expect(timeline.timelineRowActions).toEqual(timelineRowActions); + result.current.setTimelineRowActions({ + id: testId, + timelineRowActions: timelineRowActionsEx, + }); + timeline = result.current.getManageTimelineById(testId); + expect(timeline.timelineRowActions).toEqual(timelineRowActionsEx); + }); + }); + it('getTimelineFilterManager undefined on uninitialized', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + const data = result.current.getTimelineFilterManager(testId); + expect(data).toEqual(undefined); + }); + }); + it('getTimelineFilterManager defined at initialize', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + filterManager: mockFilterManager, + }); + const data = result.current.getTimelineFilterManager(testId); + expect(data).toEqual(mockFilterManager); + }); + }); + it('isManagedTimeline returns false when unset and then true when set', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTimelineManager() + ); + await waitForNextUpdate(); + let data = result.current.isManagedTimeline(testId); + expect(data).toBeFalsy(); + result.current.initializeTimeline({ + id: testId, + timelineRowActions, + filterManager: mockFilterManager, + }); + data = result.current.isManagedTimeline(testId); + expect(data).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx index dba8506add0ad..a425f9b49add0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx @@ -137,7 +137,7 @@ const reducerManageTimeline = ( } }; -interface UseTimelineManager { +export interface UseTimelineManager { getIndexToAddById: (id: string) => string[] | null; getManageTimelineById: (id: string) => ManageTimeline; getTimelineFilterManager: (id: string) => FilterManager | undefined; @@ -152,7 +152,9 @@ interface UseTimelineManager { }) => void; } -const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseTimelineManager => { +export const useTimelineManager = ( + manageTimelineForTesting?: ManageTimelineById +): UseTimelineManager => { const [state, dispatch] = useReducer< (state: ManageTimelineById, action: ActionManageTimeline) => ManageTimelineById >(reducerManageTimeline, manageTimelineForTesting ?? initManageTimeline); @@ -241,12 +243,12 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT }; const init = { - getManageTimelineById: (id: string) => getTimelineDefaults(id), getIndexToAddById: (id: string) => null, + getManageTimelineById: (id: string) => getTimelineDefaults(id), getTimelineFilterManager: () => undefined, - setIndexToAdd: () => undefined, - isManagedTimeline: () => false, initializeTimeline: () => noop, + isManagedTimeline: () => false, + setIndexToAdd: () => undefined, setIsTimelineLoading: () => noop, setTimelineRowActions: () => noop, }; From ef83e772ca0357932c53dedfbb3ce68dc2361f55 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Mon, 27 Jul 2020 20:03:23 -0400 Subject: [PATCH 18/36] [Security Solution][Resolver] Show origin node details in panel on load (#73313) * show origin node details in panel on load * added comment Co-authored-by: Elastic Machine --- .../public/resolver/view/map.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/view/map.tsx b/x-pack/plugins/security_solution/public/resolver/view/map.tsx index 30aa4b63a138d..19c403f1257be 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/map.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/map.tsx @@ -8,7 +8,7 @@ /* eslint-disable react/display-name */ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { useEffectOnce } from 'react-use'; import { EuiLoadingSpinner } from '@elastic/eui'; @@ -68,11 +68,25 @@ export const ResolverMap = React.memo(function ({ const hasError = useSelector(selectors.hasError); const activeDescendantId = useSelector(selectors.ariaActiveDescendant); const { colorMap } = useResolverTheme(); - const { cleanUpQueryParams } = useResolverQueryParams(); + const { + cleanUpQueryParams, + queryParams: { crumbId }, + pushToQueryParams, + } = useResolverQueryParams(); + useEffectOnce(() => { return () => cleanUpQueryParams(); }); + useEffect(() => { + // When you refresh the page after selecting a process in the table view (not the timeline view) + // The old crumbId still exists in the query string even though a resolver is no longer visible + // This just makes sure the activeDescendant and crumbId are in sync on load for that view as well as the timeline + if (activeDescendantId && crumbId !== activeDescendantId) { + pushToQueryParams({ crumbId: activeDescendantId, crumbEvent: '' }); + } + }, [crumbId, activeDescendantId, pushToQueryParams]); + return ( {isLoading ? ( From 8c52d39b9e757471f472a36eea30cdace30fd3ff Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Mon, 27 Jul 2020 20:34:08 -0400 Subject: [PATCH 19/36] [Security Solution] Show proper icon for termination status of all processes (#73235) * Show proper icon for termination status of all processes * Add basic test for isProcessTerminated selector --- .../resolver/store/data/selectors.test.ts | 29 +++++++++ .../public/resolver/store/data/selectors.ts | 13 ++++ .../resolver/store/mocks/endpoint_event.ts | 4 +- .../resolver/store/mocks/resolver_tree.ts | 63 +++++++++++++++++++ .../public/resolver/store/selectors.ts | 8 +++ .../public/resolver/view/panel.tsx | 20 +----- .../panels/panel_content_process_detail.tsx | 17 +++-- .../panels/panel_content_process_list.tsx | 14 ++--- .../view/panels/process_cube_icon.tsx | 4 +- 9 files changed, 131 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts index 9e1c396723a27..0826391a10688 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts @@ -13,6 +13,7 @@ import { mockTreeWithNoAncestorsAnd2Children, mockTreeWith2AncestorsAndNoChildren, mockTreeWith1AncestorAnd2ChildrenAndAllNodesHave2GraphableEvents, + mockTreeWithAllProcessesTerminated, } from '../mocks/resolver_tree'; import { uniquePidForProcess } from '../../models/process_event'; import { EndpointEvent } from '../../../../common/endpoint/types'; @@ -299,6 +300,34 @@ describe('data state', () => { expect(selectors.ariaFlowtoCandidate(state())(secondAncestorID)).toBe(null); }); }); + describe('with a tree with all processes terminated', () => { + const originID = 'c'; + const firstAncestorID = 'b'; + const secondAncestorID = 'a'; + beforeEach(() => { + actions.push({ + type: 'serverReturnedResolverData', + payload: { + result: mockTreeWithAllProcessesTerminated({ + originID, + firstAncestorID, + secondAncestorID, + }), + // this value doesn't matter + databaseDocumentID: '', + }, + }); + }); + it('should have origin as terminated', () => { + expect(selectors.isProcessTerminated(state())(originID)).toBe(true); + }); + it('should have first ancestor as termianted', () => { + expect(selectors.isProcessTerminated(state())(firstAncestorID)).toBe(true); + }); + it('should have second ancestor as terminated', () => { + expect(selectors.isProcessTerminated(state())(secondAncestorID)).toBe(true); + }); + }); describe('with a tree with 2 children and no ancestors', () => { const originID = 'c'; const firstChildID = 'd'; diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index 1d65b406306a3..ea0cb8663d11d 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts @@ -105,6 +105,19 @@ export const terminatedProcesses = createSelector(resolverTreeResponse, function ); }); +/** + * A function that given an entity id returns a boolean indicating if the id is in the set of terminated processes. + */ +export const isProcessTerminated = createSelector(terminatedProcesses, function ( + /* eslint-disable no-shadow */ + terminatedProcesses + /* eslint-enable no-shadow */ +) { + return (entityId: string) => { + return terminatedProcesses.has(entityId); + }; +}); + /** * Process events that will be graphed. */ diff --git a/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts b/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts index b58ea73e1fdc7..8f2e0ad3a6d85 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts @@ -14,16 +14,18 @@ export function mockEndpointEvent({ name, parentEntityId, timestamp, + lifecycleType, }: { entityID: string; name: string; parentEntityId: string | undefined; timestamp: number; + lifecycleType?: string; }): EndpointEvent { return { '@timestamp': timestamp, event: { - type: 'start', + type: lifecycleType ? lifecycleType : 'start', category: 'process', }, process: { diff --git a/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts b/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts index 2860eec5a6ab6..ae43955f4c47c 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts @@ -46,6 +46,69 @@ export function mockTreeWith2AncestorsAndNoChildren({ } as unknown) as ResolverTree; } +export function mockTreeWithAllProcessesTerminated({ + originID, + firstAncestorID, + secondAncestorID, +}: { + secondAncestorID: string; + firstAncestorID: string; + originID: string; +}): ResolverTree { + const secondAncestor: ResolverEvent = mockEndpointEvent({ + entityID: secondAncestorID, + name: 'a', + parentEntityId: 'none', + timestamp: 0, + }); + const firstAncestor: ResolverEvent = mockEndpointEvent({ + entityID: firstAncestorID, + name: 'b', + parentEntityId: secondAncestorID, + timestamp: 1, + }); + const originEvent: ResolverEvent = mockEndpointEvent({ + entityID: originID, + name: 'c', + parentEntityId: firstAncestorID, + timestamp: 2, + }); + const secondAncestorTermination: ResolverEvent = mockEndpointEvent({ + entityID: secondAncestorID, + name: 'a', + parentEntityId: 'none', + timestamp: 0, + lifecycleType: 'end', + }); + const firstAncestorTermination: ResolverEvent = mockEndpointEvent({ + entityID: firstAncestorID, + name: 'b', + parentEntityId: secondAncestorID, + timestamp: 1, + lifecycleType: 'end', + }); + const originEventTermination: ResolverEvent = mockEndpointEvent({ + entityID: originID, + name: 'c', + parentEntityId: firstAncestorID, + timestamp: 2, + lifecycleType: 'end', + }); + return ({ + entityID: originID, + children: { + childNodes: [], + }, + ancestry: { + ancestors: [ + { lifecycle: [secondAncestor, secondAncestorTermination] }, + { lifecycle: [firstAncestor, firstAncestorTermination] }, + ], + }, + lifecycle: [originEvent, originEventTermination], + } as unknown) as ResolverTree; +} + export function mockTreeWithNoAncestorsAnd2Children({ originID, firstChildID, diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index 66d7e04d118ed..87ef8d5d095ef 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -53,6 +53,14 @@ export const userIsPanning = composeSelectors(cameraStateSelector, cameraSelecto */ export const isAnimating = composeSelectors(cameraStateSelector, cameraSelectors.isAnimating); +/** + * Whether or not a given entity id is in the set of termination events. + */ +export const isProcessTerminated = composeSelectors( + dataStateSelector, + dataSelectors.isProcessTerminated +); + /** * Given a nodeID (aka entity_id) get the indexed process event. * Legacy functions take process events instead of nodeID, use this to get diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx index cb0acdc29ceb1..83d3930065da6 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx @@ -162,19 +162,10 @@ const PanelContent = memo(function PanelContent() { return 'processListWithCounts'; }, [uiSelectedEvent, crumbEvent, crumbId, graphableProcessEntityIds]); - const terminatedProcesses = useSelector(selectors.terminatedProcesses); - const processEntityId = uiSelectedEvent ? event.entityId(uiSelectedEvent) : undefined; - const isProcessTerminated = processEntityId ? terminatedProcesses.has(processEntityId) : false; - const panelInstance = useMemo(() => { if (panelToShow === 'processDetails') { return ( - + ); } @@ -213,13 +204,7 @@ const PanelContent = memo(function PanelContent() { ); } // The default 'Event List' / 'List of all processes' view - return ( - - ); + return ; }, [ uiSelectedEvent, crumbEvent, @@ -227,7 +212,6 @@ const PanelContent = memo(function PanelContent() { pushToQueryParams, relatedStatsForIdFromParams, panelToShow, - isProcessTerminated, ]); return <>{panelInstance}; diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx index 5d90cd11d31af..29c7676d2167d 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { memo, useMemo } from 'react'; +import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { htmlIdGenerator, @@ -15,6 +16,7 @@ import { } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from 'react-intl'; +import * as selectors from '../../store/selectors'; import * as event from '../../../../common/endpoint/models/event'; import { CrumbInfo, formatDate, StyledBreadcrumbs } from './panel_content_utilities'; import { @@ -41,16 +43,14 @@ const StyledDescriptionList = styled(EuiDescriptionList)` */ export const ProcessDetails = memo(function ProcessDetails({ processEvent, - isProcessTerminated, - isProcessOrigin, pushToQueryParams, }: { processEvent: ResolverEvent; - isProcessTerminated: boolean; - isProcessOrigin: boolean; pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown; }) { const processName = event.eventName(processEvent); + const entityId = event.entityId(processEvent); + const isProcessTerminated = useSelector(selectors.isProcessTerminated)(entityId); const processInfoEntry = useMemo(() => { const eventTime = event.eventTimestamp(processEvent); const dateTime = eventTime ? formatDate(eventTime) : ''; @@ -151,8 +151,8 @@ export const ProcessDetails = memo(function ProcessDetails({ if (!processEvent) { return { descriptionText: '' }; } - return cubeAssetsForNode(isProcessTerminated, isProcessOrigin); - }, [processEvent, cubeAssetsForNode, isProcessTerminated, isProcessOrigin]); + return cubeAssetsForNode(isProcessTerminated, false); + }, [processEvent, cubeAssetsForNode, isProcessTerminated]); const titleId = useMemo(() => htmlIdGenerator('resolverTable')(), []); return ( @@ -161,10 +161,7 @@ export const ProcessDetails = memo(function ProcessDetails({

      - + {processName}

      diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx index 6f9bfad8c08c2..efb96cde431e5 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx @@ -50,12 +50,8 @@ const StyledLimitWarning = styled(LimitWarning)` */ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ pushToQueryParams, - isProcessTerminated, - isProcessOrigin, }: { pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown; - isProcessTerminated: boolean; - isProcessOrigin: boolean; }) { interface ProcessTableView { name: string; @@ -65,6 +61,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ const dispatch = useResolverDispatch(); const { timestamp } = useContext(SideEffectContext); + const isProcessTerminated = useSelector(selectors.isProcessTerminated); const handleBringIntoViewClick = useCallback( (processTableViewItem) => { dispatch({ @@ -92,6 +89,8 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ sortable: true, truncateText: true, render(name: string, item: ProcessTableView) { + const entityId = event.entityId(item.event); + const isTerminated = isProcessTerminated(entityId); return name === '' ? ( {i18n.translate( @@ -108,10 +107,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ pushToQueryParams({ crumbId: event.entityId(item.event), crumbEvent: '' }); }} > - + {name} ); @@ -143,7 +139,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ }, }, ], - [pushToQueryParams, handleBringIntoViewClick, isProcessOrigin, isProcessTerminated] + [pushToQueryParams, handleBringIntoViewClick, isProcessTerminated] ); const { processNodePositions } = useSelector(selectors.layout); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx index 98eea51a011b6..b073324b27f9b 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx @@ -13,13 +13,11 @@ import { useResolverTheme } from '../assets'; */ export const CubeForProcess = memo(function CubeForProcess({ isProcessTerminated, - isProcessOrigin, }: { isProcessTerminated: boolean; - isProcessOrigin: boolean; }) { const { cubeAssetsForNode } = useResolverTheme(); - const { cubeSymbol, descriptionText } = cubeAssetsForNode(isProcessTerminated, isProcessOrigin); + const { cubeSymbol, descriptionText } = cubeAssetsForNode(isProcessTerminated, false); return ( <> From 765c2d1ad3308a3c3af50f8d67b80579aeb13a9a Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Mon, 27 Jul 2020 19:52:28 -0600 Subject: [PATCH 20/36] [Security Solution][ML] Updates siem group name to security (#73218) ## Summary Resolves https://github.com/elastic/kibana/issues/69319 Updates `siem` grouping to `security`, and enables cloudtrail module, fixing mis-match between the newly updated modules (https://github.com/elastic/kibana/pull/71696).

      Also updates all module icons to be consistent: Auditbeat (Before/After):

      Packetbeat (Before/After):

      Winlogbeat (Before/After):

      - [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [X] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - Working w/ @benskelker on updated ML Jobs & nomenclature --- .../models/data_recognizer/modules/siem_auditbeat/logo.json | 2 +- .../data_recognizer/modules/siem_auditbeat_auth/logo.json | 4 ++-- .../data_recognizer/modules/siem_packetbeat/logo.json | 4 ++-- .../data_recognizer/modules/siem_winlogbeat/logo.json | 2 +- .../data_recognizer/modules/siem_winlogbeat_auth/logo.json | 4 ++-- .../public/common/components/ml_popover/api.tsx | 2 +- .../common/components/ml_popover/hooks/translations.ts | 2 +- .../components/ml_popover/hooks/use_siem_jobs_helpers.tsx | 2 +- .../ml_popover/jobs_table/filters/groups_filter_popover.tsx | 6 +++--- .../public/common/components/ml_popover/ml_modules.tsx | 1 + .../detections/components/rules/ml_job_select/index.tsx | 2 +- .../server/usage/detections/detections_helpers.ts | 4 +++- 12 files changed, 19 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json index 40a5c59677147..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" + "icon": "logoSecurity" } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json index 6b02648ccf287..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" -} \ No newline at end of file + "icon": "logoSecurity" +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json index 6b02648ccf287..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" -} \ No newline at end of file + "icon": "logoSecurity" +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json index 40a5c59677147..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" + "icon": "logoSecurity" } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json index 6b02648ccf287..dfd22f6b1140b 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json @@ -1,3 +1,3 @@ { - "icon": "securityAnalyticsApp" -} \ No newline at end of file + "icon": "logoSecurity" +} diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.tsx index b4da4fa79e035..7c72098209a06 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.tsx @@ -71,7 +71,7 @@ export const setupMlJob = async ({ configTemplate, indexPatternName = 'auditbeat-*', jobIdErrorFilter = [], - groups = ['siem'], + groups = ['security'], prefix = '', }: MlSetupArgs): Promise => { const response = await KibanaServices.get().http.fetch( diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts index 2b37c437866e0..7b29bab2e38f3 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts @@ -9,6 +9,6 @@ import { i18n } from '@kbn/i18n'; export const SIEM_JOB_FETCH_FAILURE = i18n.translate( 'xpack.securitySolution.components.mlPopup.hooks.errors.siemJobFetchFailureTitle', { - defaultMessage: 'SIEM job fetch failure', + defaultMessage: 'Security job fetch failure', } ); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_siem_jobs_helpers.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_siem_jobs_helpers.tsx index 658d2659282ce..adbd712ffeb3e 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_siem_jobs_helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_siem_jobs_helpers.tsx @@ -104,7 +104,7 @@ export const getInstalledJobs = ( compatibleModuleIds: string[] ): SiemJob[] => jobSummaryData - .filter(({ groups }) => groups.includes('siem')) + .filter(({ groups }) => groups.includes('siem') || groups.includes('security')) .map((jobSummary) => ({ ...jobSummary, ...getAugmentedFields(jobSummary.id, moduleJobs, compatibleModuleIds), diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx index 1aa3ad630306e..d879942b8b101 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx @@ -25,8 +25,8 @@ interface GroupsFilterPopoverProps { /** * Popover for selecting which SiemJob groups to filter on. Component extracts unique groups and - * their counts from the provided SiemJobs. The 'siem' group is filtered out as all jobs will be - * siem jobs + * their counts from the provided SiemJobs. The 'siem' & 'security' groups are filtered out as all jobs will be + * siem/security jobs * * @param siemJobs jobs to fetch groups from to display for filtering * @param onSelectedGroupsChanged change listener to be notified when group selection changes @@ -41,7 +41,7 @@ export const GroupsFilterPopoverComponent = ({ const groups = siemJobs .map((j) => j.groups) .flat() - .filter((g) => g !== 'siem'); + .filter((g) => g !== 'siem' && g !== 'security'); const uniqueGroups = Array.from(new Set(groups)); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx index b956cf2c1494c..4dccba08590a4 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx @@ -12,6 +12,7 @@ export const mlModules: string[] = [ 'siem_auditbeat', 'siem_auditbeat_auth', + 'siem_cloudtrail', 'siem_packetbeat', 'siem_winlogbeat', 'siem_winlogbeat_auth', diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx index cb084d4daa782..cdfdf4ca6b66b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx @@ -41,7 +41,7 @@ const HelpText: React.FC<{ href: string; showEnableWarning: boolean }> = ({ <> diff --git a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts b/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts index e9d4f3aa426f4..f9905c373291c 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts @@ -176,7 +176,9 @@ export const getMlJobsUsage = async (ml: MlPluginSetup | undefined): Promise module.jobs); - const jobs = await ml.jobServiceProvider(internalMlClient, fakeRequest).jobsSummary(['siem']); + const jobs = await ml + .jobServiceProvider(internalMlClient, fakeRequest) + .jobsSummary(['siem', 'security']); jobsUsage = jobs.reduce((usage, job) => { const isElastic = moduleJobs.some((moduleJob) => moduleJob.id === job.id); From 5af2c1080a85b247324d7b1fd36428c6d561ac55 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Mon, 27 Jul 2020 19:21:14 -0700 Subject: [PATCH 21/36] Exclude `version` from package config attributes that are copied, add safeguard to package config bulk create (#73128) Co-authored-by: Elastic Machine --- .../ingest_manager/server/services/agent_config.ts | 12 +++++------- .../ingest_manager/server/services/package_config.ts | 5 ++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.ts index 0a9adc1f1c593..3886146e28806 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_config.ts @@ -233,16 +233,14 @@ class AgentConfigService { if (baseAgentConfig.package_configs.length) { const newPackageConfigs = (baseAgentConfig.package_configs as PackageConfig[]).map( (packageConfig: PackageConfig) => { - const { id: packageConfigId, ...newPackageConfig } = packageConfig; + const { id: packageConfigId, version, ...newPackageConfig } = packageConfig; return newPackageConfig; } ); - await packageConfigService.bulkCreate( - soClient, - newPackageConfigs, - newAgentConfig.id, - options - ); + await packageConfigService.bulkCreate(soClient, newPackageConfigs, newAgentConfig.id, { + ...options, + bumpConfigRevision: false, + }); } // Get updated config diff --git a/x-pack/plugins/ingest_manager/server/services/package_config.ts b/x-pack/plugins/ingest_manager/server/services/package_config.ts index c2d465cf7c73f..5d1c5d1717714 100644 --- a/x-pack/plugins/ingest_manager/server/services/package_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/package_config.ts @@ -121,7 +121,7 @@ class PackageConfigService { options?: { user?: AuthenticatedUser; bumpConfigRevision?: boolean } ): Promise { const isoDate = new Date().toISOString(); - const { saved_objects: newSos } = await soClient.bulkCreate( + const { saved_objects } = await soClient.bulkCreate( packageConfigs.map((packageConfig) => ({ type: SAVED_OBJECT_TYPE, attributes: { @@ -136,6 +136,9 @@ class PackageConfigService { })) ); + // Filter out invalid SOs + const newSos = saved_objects.filter((so) => !so.error && so.attributes); + // Assign it to the given agent config await agentConfigService.assignPackageConfigs( soClient, From 82d7e7db699bbe961da5eb8b2218de5d2c2e7e18 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Mon, 27 Jul 2020 19:21:41 -0700 Subject: [PATCH 22/36] [Ingest Manager] Convert select agent config step to use combo box (#73172) * Initial pass at using combo box instead of selectable for agent configs * Hide agent count messaging if fleet isn't set up * Fix types * Fix i18n * Fix i18n again * Add comment explaining styling Co-authored-by: Elastic Machine --- .../step_select_config.tsx | 227 +++++++++++------- .../list_page/components/create_config.tsx | 2 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 4 files changed, 145 insertions(+), 86 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/step_select_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/step_select_config.tsx index 91c80b7eee4c8..6f06530100d71 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/step_select_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_package_config_page/step_select_config.tsx @@ -3,17 +3,19 @@ * 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, { useEffect, useState, Fragment } from 'react'; +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, - EuiSelectable, - EuiSpacer, + EuiComboBox, + EuiComboBoxOptionOption, EuiTextColor, EuiPortal, - EuiButtonEmpty, + EuiFormRow, + EuiLink, } from '@elastic/eui'; import { Error } from '../../../components'; import { AgentConfig, PackageInfo, GetAgentConfigsResponseItem } from '../../../types'; @@ -23,9 +25,30 @@ import { useGetAgentConfigs, sendGetOneAgentConfig, useCapabilities, + useFleetStatus, } from '../../../hooks'; import { CreateAgentConfigFlyout } from '../list_page/components'; +const AgentConfigWrapper = styled(EuiFormRow)` + .euiFormRow__label { + width: 100%; + } +`; + +// Custom styling for drop down list items due to: +// 1) the max-width and overflow properties is added to prevent long config +// names/descriptions from overflowing the flex items +// 2) max-width is built from the grow property on the flex items because the value +// changes based on if Fleet is enabled/setup or not +const AgentConfigNameColumn = styled(EuiFlexItem)` + max-width: ${(props) => `${((props.grow as number) / 9) * 100}%`}; + overflow: hidden; +`; +const AgentConfigDescriptionColumn = styled(EuiFlexItem)` + max-width: ${(props) => `${((props.grow as number) / 9) * 100}%`}; + overflow: hidden; +`; + export const StepSelectConfig: React.FunctionComponent<{ pkgkey: string; updatePackageInfo: (packageInfo: PackageInfo | undefined) => void; @@ -33,6 +56,8 @@ export const StepSelectConfig: React.FunctionComponent<{ updateAgentConfig: (config: AgentConfig | undefined) => void; setIsLoadingSecondStep: (isLoading: boolean) => void; }> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig, setIsLoadingSecondStep }) => { + const { isReady: isFleetReady } = useFleetStatus(); + // Selected config state const [selectedConfigId, setSelectedConfigId] = useState( agentConfig ? agentConfig.id : undefined @@ -106,6 +131,40 @@ export const StepSelectConfig: React.FunctionComponent<{ } }, [selectedConfigId, agentConfig, updateAgentConfig, setIsLoadingSecondStep]); + const agentConfigOptions: Array> = packageInfoData + ? agentConfigs.map((agentConf) => { + const alreadyHasLimitedPackage = + (isLimitedPackage && + doesAgentConfigAlreadyIncludePackage(agentConf, packageInfoData.response.name)) || + false; + return { + label: agentConf.name, + value: agentConf.id, + disabled: alreadyHasLimitedPackage, + 'data-test-subj': 'agentConfigItem', + }; + }) + : []; + + const selectedConfigOption = agentConfigOptions.find( + (option) => option.value === selectedConfigId + ); + + // Try to select default agent config + useEffect(() => { + if (!selectedConfigId && agentConfigs.length && agentConfigOptions.length) { + const defaultAgentConfig = agentConfigs.find((config) => config.is_default); + if (defaultAgentConfig) { + const defaultAgentConfigOption = agentConfigOptions.find( + (option) => option.value === defaultAgentConfig.id + ); + if (defaultAgentConfigOption && !defaultAgentConfigOption.disabled) { + setSelectedConfigId(defaultAgentConfig.id); + } + } + } + }, [agentConfigs, agentConfigOptions, selectedConfigId]); + // Display package error if there is one if (packageInfoError) { return ( @@ -154,77 +213,95 @@ export const StepSelectConfig: React.FunctionComponent<{ ) : null} - { - const alreadyHasLimitedPackage = - (isLimitedPackage && - packageInfoData && - doesAgentConfigAlreadyIncludePackage(agentConf, packageInfoData.response.name)) || - false; - return { - label: agentConf.name, - key: agentConf.id, - checked: selectedConfigId === agentConf.id ? 'on' : undefined, - disabled: alreadyHasLimitedPackage, - 'data-test-subj': 'agentConfigItem', - }; - })} - renderOption={(option) => ( - - {option.label} + - - {agentConfigsById[option.key!].description} - + - - - +
      + setIsCreateAgentConfigFlyoutOpen(true)} + > + + +
      - )} - listProps={{ - bordered: true, - }} - searchProps={{ - placeholder: i18n.translate( - 'xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder', + } + helpText={ + isFleetReady && selectedConfigId ? ( + + ) : null + } + > + { - const selectedOption = options.find((option) => option.checked === 'on'); - if (selectedOption) { - if (selectedOption.key !== selectedConfigId) { - setSelectedConfigId(selectedOption.key); + )} + singleSelection={{ asPlainText: true }} + isClearable={false} + fullWidth={true} + isLoading={isAgentConfigsLoading || isPackageInfoLoading} + options={agentConfigOptions} + renderOption={(option: EuiComboBoxOptionOption) => { + return ( + + + {option.label} + + + + {agentConfigsById[option.value!].description} + + + {isFleetReady ? ( + + + + + + ) : null} + + ); + }} + selectedOptions={selectedConfigOption ? [selectedConfigOption] : []} + onChange={(options) => { + const selectedOption = options[0] || undefined; + if (selectedOption) { + if (selectedOption.value !== selectedConfigId) { + setSelectedConfigId(selectedOption.value); + } + } else { + setSelectedConfigId(undefined); } - } else { - setSelectedConfigId(undefined); - } - }} - > - {(list, search) => ( - - {search} - - {list} - - )} -
      + }} + /> +
      {/* Display selected agent config error if there is one */} {selectedConfigError ? ( @@ -240,22 +317,6 @@ export const StepSelectConfig: React.FunctionComponent<{ />
      ) : null} - -
      - setIsCreateAgentConfigFlyoutOpen(true)} - flush="left" - size="s" - > - - -
      -
      ); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx index fc593705a4e1b..749716b473c85 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx @@ -160,7 +160,7 @@ export const CreateAgentConfigFlyout: React.FunctionComponent = ({ ); return ( - + onClose()} size="l" maxWidth={400} {...restOfProps}> {header} {body} {footer} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cf79f463b35cb..ee7d1e0298d00 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8108,7 +8108,6 @@ "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingAgentConfigsTitle": "エージェント構成の読み込みエラー", "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingPackageTitle": "パッケージ情報の読み込みエラー", "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingSelectedAgentConfigTitle": "選択したエージェント構成の読み込みエラー", - "xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder": "エージェント構成の検索", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingConfigTitle": "エージェント構成情報の読み込みエラー", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingPackagesTitle": "統合の読み込みエラー", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingSelectedPackageTitle": "選択した統合の読み込みエラー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b45fe1baa9e9a..30c932c362a4f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8113,7 +8113,6 @@ "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingAgentConfigsTitle": "加载代理配置时出错", "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingPackageTitle": "加载软件包信息时出错", "xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingSelectedAgentConfigTitle": "加载选定代理配置时出错", - "xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder": "搜索代理配置", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingConfigTitle": "加载代理配置信息时出错", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingPackagesTitle": "加载集成时出错", "xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingSelectedPackageTitle": "加载选定集成时出错", From cc84ee31856c8eb70d5a2d1093b21678d5842f88 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Mon, 27 Jul 2020 21:28:39 -0500 Subject: [PATCH 23/36] [Metrics UI] Saved views bugs (#72518) * Add test for logs and metrics telemetry * wait before you go * Remove kubenetes * Fix type check * Add back kubernetes test * Remove kubernetes * Don't allow deleting default default view. * Fix bug with duplicate loads of data. Because the load data function takes options.source and the source of options can change, we need to remove it from deps * Remove unused variable * Reload when loadData function is changed * Don't send the request immediately Co-authored-by: Elastic Machine --- .../public/components/saved_views/manage_views_flyout.tsx | 4 ++++ .../public/components/saved_views/toolbar_control.tsx | 2 +- .../infra/public/containers/saved_view/saved_view.tsx | 8 +++----- .../pages/metrics/inventory_view/components/layout.tsx | 3 ++- .../infra/public/pages/metrics/metrics_explorer/index.tsx | 3 ++- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx index fa9b45558e491..698034f8154d1 100644 --- a/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/manage_views_flyout.tsx @@ -96,6 +96,10 @@ export function SavedViewManageViewsFlyout({ const renderDeleteAction = useCallback( (item: SavedView) => { + if (item.id === '0') { + return <>; + } + return ( (props: Props) { /> - + { const { data, loading, find, error: errorOnFind, hasView } = useFindSavedObject< SavedViewSavedObject >(viewType); - + const [shouldLoadDefault] = useState(props.shouldLoadDefault); const [currentView, setCurrentView] = useState | null>(null); const [loadingDefaultView, setLoadingDefaultView] = useState(null); const { create, error: errorOnCreate, data: createdViewData, createdId } = useCreateSavedObject( @@ -211,8 +211,6 @@ export const useSavedView = (props: Props) => { }, [setCurrentView, defaultViewId, defaultViewState]); useEffect(() => { - const shouldLoadDefault = props.shouldLoadDefault; - if (loadingDefaultView || currentView || !shouldLoadDefault) { return; } @@ -225,7 +223,7 @@ export const useSavedView = (props: Props) => { } }, [ loadDefaultView, - props.shouldLoadDefault, + shouldLoadDefault, setDefault, loadingDefaultView, currentView, @@ -246,7 +244,7 @@ export const useSavedView = (props: Props) => { errorOnUpdate, errorOnFind, errorOnCreate: createError, - shouldLoadDefault: props.shouldLoadDefault, + shouldLoadDefault, makeDefault, sourceIsLoading, deleteView, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index fddd92128708a..ad92c054ee459 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -55,7 +55,8 @@ export const Layout = () => { sourceId, currentTime, accountId, - region + region, + false ); const options = { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx index cd875ae54071c..20efca79650a1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx @@ -57,7 +57,8 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl // load metrics explorer data after default view loaded, unless we're not loading a view loadData(); } - }, [loadData, currentView, shouldLoadDefault]); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [loadData, shouldLoadDefault]); return ( From 281c76767b21c458a237474d77211f18883d8d68 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 28 Jul 2020 09:23:28 +0200 Subject: [PATCH 24/36] updates cypress to v4.11.0 (#73327) Co-authored-by: Elastic Machine --- x-pack/package.json | 2 +- yarn.lock | 170 +++++++++++++++----------------------------- 2 files changed, 60 insertions(+), 112 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index dee99d6f0ddac..76655f75cadcc 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -131,7 +131,7 @@ "cheerio": "0.22.0", "commander": "3.0.2", "copy-webpack-plugin": "^6.0.2", - "cypress": "4.5.0", + "cypress": "4.11.0", "cypress-multi-reporters": "^1.2.3", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", diff --git a/yarn.lock b/yarn.lock index 899bc45fbe3fb..c1328731db150 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4717,21 +4717,11 @@ resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.2.5.tgz#582b2476169a6cba460a214d476c744441d873d5" integrity sha1-WCskdhaabLpGCiFNR2x0REHYc9U= -"@types/blob-util@1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a" - integrity sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w== - "@types/bluebird@*", "@types/bluebird@^3.1.1": version "3.5.30" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.30.tgz#ee034a0eeea8b84ed868b1aa60d690b08a6cfbc5" integrity sha512-8LhzvcjIoqoi1TghEkRMkbbmM+jhHnBokPGkJWjclMK+Ks0MxEBow3/p2/iFTZ+OIbJHQDSfpgdZEb+af3gfVw== -"@types/bluebird@3.5.29": - version "3.5.29" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.29.tgz#7cd933c902c4fc83046517a1bef973886d00bdb6" - integrity sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw== - "@types/boom@*", "@types/boom@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/boom/-/boom-7.2.0.tgz#19c36cbb5811a7493f0f2e37f31d42b28df1abc1" @@ -4762,15 +4752,7 @@ resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-10.0.1.tgz#266679017749041fe9873fee1131dd2aaa04a07e" integrity sha512-ECuJ+f5gGHiLeiE4RlE/xdqv/0JVDToegPV1aTb10tQStYa0Ycq2OJfQukDv3IFaw3B+CMV46jHc5bXe6QXEQg== -"@types/chai-jquery@1.1.40": - version "1.1.40" - resolved "https://registry.yarnpkg.com/@types/chai-jquery/-/chai-jquery-1.1.40.tgz#445bedcbbb2ae4e3027f46fa2c1733c43481ffa1" - integrity sha512-mCNEZ3GKP7T7kftKeIs7QmfZZQM7hslGSpYzKbOlR2a2HCFf9ph4nlMRA9UnuOETeOQYJVhJQK7MwGqNZVyUtQ== - dependencies: - "@types/chai" "*" - "@types/jquery" "*" - -"@types/chai@*", "@types/chai@4.2.7", "@types/chai@^4.2.11": +"@types/chai@^4.2.11": version "4.2.11" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50" integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== @@ -5260,7 +5242,7 @@ resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" integrity sha512-JxZ0NP8NuB0BJOXi1KvAA6rySLTPmhOy4n2gzSFq/IFM3LNFm0h+2Vn/bPPgEYlWqzS2NPeLgKqfm75baX+Hog== -"@types/jquery@*", "@types/jquery@3.3.31", "@types/jquery@^3.3.31": +"@types/jquery@*", "@types/jquery@^3.3.31": version "3.3.31" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b" integrity sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg== @@ -5346,11 +5328,6 @@ "@types/node" "*" "@types/webpack" "*" -"@types/lodash@4.14.149", "@types/lodash@^4.14.155": - version "4.14.156" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.156.tgz#cbe30909c89a1feeb7c60803e785344ea0ec82d1" - integrity sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ== - "@types/lodash@^3.10.1": version "3.10.3" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-3.10.3.tgz#aaddec6a3c93bf03b402db3acf5d4c77bce8bdff" @@ -5361,6 +5338,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.150.tgz#649fe44684c3f1fcb6164d943c5a61977e8cf0bd" integrity sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w== +"@types/lodash@^4.14.155": + version "4.14.156" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.156.tgz#cbe30909c89a1feeb7c60803e785344ea0ec82d1" + integrity sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ== + "@types/log-symbols@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/log-symbols/-/log-symbols-2.0.0.tgz#7919e2ec3c8d13879bfdcab310dd7a3f7fc9466d" @@ -5419,7 +5401,7 @@ dependencies: "@types/mime-db" "*" -"@types/minimatch@*", "@types/minimatch@3.0.3", "@types/minimatch@^3.0.3": +"@types/minimatch@*", "@types/minimatch@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== @@ -5441,11 +5423,6 @@ dependencies: "@types/node" "*" -"@types/mocha@5.2.7": - version "5.2.7" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" - integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== - "@types/mocha@^7.0.2": version "7.0.2" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" @@ -5859,32 +5836,12 @@ dependencies: "@types/node" "*" -"@types/sinon-chai@3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.3.tgz#afe392303dda95cc8069685d1e537ff434fa506e" - integrity sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ== - dependencies: - "@types/chai" "*" - "@types/sinon" "*" - -"@types/sinon@*": - version "9.0.4" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1" - integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw== - dependencies: - "@types/sinonjs__fake-timers" "*" - -"@types/sinon@7.5.1": - version "7.5.1" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.5.1.tgz#d27b81af0d1cfe1f9b24eebe7a24f74ae40f5b7c" - integrity sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ== - "@types/sinon@^7.0.13": version "7.0.13" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.13.tgz#ca039c23a9e27ebea53e0901ef928ea2a1a6d313" integrity sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung== -"@types/sinonjs__fake-timers@*": +"@types/sinonjs__fake-timers@6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e" integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA== @@ -7378,10 +7335,10 @@ aproba@^1.0.3, aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -arch@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" - integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== +arch@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.2.tgz#0c52bbe7344bb4fa260c443d2cbad9c00ff2f0bf" + integrity sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ== archiver-utils@^2.1.0: version "2.1.0" @@ -7849,7 +7806,7 @@ async@^2.6.3: dependencies: lodash "^4.17.14" -async@^3.1.0: +async@^3.1.0, async@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== @@ -10499,10 +10456,10 @@ commander@3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83" - integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw== +commander@4.1.1, commander@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0: version "2.20.0" @@ -10524,11 +10481,6 @@ commander@^3.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.0.tgz#0641ea00838c7a964627f04cddc336a2deddd60a" integrity sha512-pl3QrGOBa9RZaslQiqnnKX2J068wcQw7j9AIaBQ9/JEp5RY6je4jKTImg0Bd+rpoONSe7GUFSgkxLeo17m3Pow== -commander@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - commander@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.0.0.tgz#dbf1909b49e5044f8fdaf0adc809f0c0722bdfd0" @@ -11489,48 +11441,39 @@ cypress-multi-reporters@^1.2.3: debug "^4.1.1" lodash "^4.17.11" -cypress@4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.5.0.tgz#01940d085f6429cec3c87d290daa47bb976a7c7b" - integrity sha512-2A4g5FW5d2fHzq8HKUGAMVTnW6P8nlWYQALiCoGN4bqBLvgwhYM/oG9oKc2CS6LnvgHFiKivKzpm9sfk3uU3zQ== +cypress@4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.11.0.tgz#054b0b85fd3aea793f186249ee1216126d5f0a7e" + integrity sha512-6Yd598+KPATM+dU1Ig0g2hbA+R/o1MAKt0xIejw4nZBVLSplCouBzqeKve6XsxGU6n4HMSt/+QYsWfFcoQeSEw== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/request" "2.88.5" "@cypress/xvfb" "1.2.4" - "@types/blob-util" "1.3.3" - "@types/bluebird" "3.5.29" - "@types/chai" "4.2.7" - "@types/chai-jquery" "1.1.40" - "@types/jquery" "3.3.31" - "@types/lodash" "4.14.149" - "@types/minimatch" "3.0.3" - "@types/mocha" "5.2.7" - "@types/sinon" "7.5.1" - "@types/sinon-chai" "3.2.3" + "@types/sinonjs__fake-timers" "6.0.1" "@types/sizzle" "2.3.2" - arch "2.1.1" + arch "2.1.2" bluebird "3.7.2" cachedir "2.3.0" chalk "2.4.2" check-more-types "2.24.0" cli-table3 "0.5.1" - commander "4.1.0" + commander "4.1.1" common-tags "1.8.0" debug "4.1.1" - eventemitter2 "4.1.2" + eventemitter2 "6.4.2" execa "1.0.0" executable "4.1.1" extract-zip "1.7.0" fs-extra "8.1.0" - getos "3.1.4" + getos "3.2.1" is-ci "2.0.0" - is-installed-globally "0.1.0" + is-installed-globally "0.3.2" lazy-ass "1.6.0" listr "0.14.3" - lodash "4.17.15" + lodash "4.17.19" log-symbols "3.0.0" minimist "1.2.5" - moment "2.24.0" + moment "2.26.0" ospath "1.2.2" pretty-bytes "5.3.0" ramda "0.26.1" @@ -13890,10 +13833,10 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter2@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-4.1.2.tgz#0e1a8477af821a6ef3995b311bf74c23a5247f15" - integrity sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU= +eventemitter2@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.2.tgz#f31f8b99d45245f0edbc5b00797830ff3b388970" + integrity sha512-r/Pwupa5RIzxIHbEKCkNXqpEQIIT4uQDxmP4G/Lug/NokVUWj0joz/WzWl3OxRpC5kDrH/WdiUJoR+IrwvXJEw== eventemitter2@~0.4.13: version "0.4.14" @@ -15515,12 +15458,12 @@ getopts@^2.2.5: resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b" integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA== -getos@3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.4.tgz#29cdf240ed10a70c049add7b6f8cb08c81876faf" - integrity sha512-UORPzguEB/7UG5hqiZai8f0vQ7hzynMQyJLxStoQ8dPGAcmgsfXOPA4iE/fGtweHYkK+z4zc9V0g+CIFRf5HYw== +getos@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== dependencies: - async "^3.1.0" + async "^3.2.0" getos@^3.1.0: version "3.1.0" @@ -18256,15 +18199,7 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" integrity sha1-bghLvJIGH7sJcexYts5tQE4k2mk= -is-installed-globally@0.1.0, is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= - dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" - -is-installed-globally@^0.3.1: +is-installed-globally@0.3.2, is-installed-globally@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== @@ -18272,6 +18207,14 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" +is-installed-globally@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= + dependencies: + global-dirs "^0.1.0" + is-path-inside "^1.0.0" + is-integer@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/is-integer/-/is-integer-1.0.7.tgz#6bde81aacddf78b659b6629d629cadc51a886d5c" @@ -20799,16 +20742,16 @@ lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@4.17.19, lodash@^4.17.16: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + lodash@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= -lodash@^4.17.16: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== - "lodash@npm:@elastic/lodash@3.10.1-kibana4": version "3.10.1-kibana4" resolved "https://registry.yarnpkg.com/@elastic/lodash/-/lodash-3.10.1-kibana4.tgz#d491228fd659b4a1b0dfa08ba9c67a4979b9746d" @@ -21974,7 +21917,12 @@ moment-timezone@^0.5.27: dependencies: moment ">= 2.9.0" -moment@2.24.0, "moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.24.0: +moment@2.26.0: + version "2.26.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a" + integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw== + +"moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== From 7b29ecf0b51a835394b0c45fe0623cc978455520 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 28 Jul 2020 10:29:33 +0300 Subject: [PATCH 25/36] [Functional Tests] Fix flakiness on TSVB chart on switching index patterns test (#73238) --- test/functional/services/combo_box.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts index 60fea7ea86cf9..ac7a40361d065 100644 --- a/test/functional/services/combo_box.ts +++ b/test/functional/services/combo_box.ts @@ -90,7 +90,7 @@ export function ComboBoxProvider({ getService, getPageObjects }: FtrProviderCont await this.clickOption(options.clickWithMouse, selectOptions[0]); } else { // if it doesn't find the item which text starts with value, it will choose the first option - const firstOption = await find.byCssSelector('.euiFilterSelectItem'); + const firstOption = await find.byCssSelector('.euiFilterSelectItem', 5000); await this.clickOption(options.clickWithMouse, firstOption); } } else { From a696f6c79b3fe20d60712faeb21e31d1f4538de4 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 28 Jul 2020 10:29:47 +0300 Subject: [PATCH 26/36] [Functional Tests] Increase waitTime for timelion to fetch the results (#73255) Co-authored-by: Elastic Machine --- test/functional/page_objects/timelion_page.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/page_objects/timelion_page.ts b/test/functional/page_objects/timelion_page.ts index f025fc946bef1..23a9cc514a444 100644 --- a/test/functional/page_objects/timelion_page.ts +++ b/test/functional/page_objects/timelion_page.ts @@ -47,7 +47,7 @@ export function TimelionPageProvider({ getService, getPageObjects }: FtrProvider public async updateExpression(updates: string) { const input = await testSubjects.find('timelionExpressionTextArea'); await input.type(updates); - await PageObjects.common.sleep(500); + await PageObjects.common.sleep(1000); } public async getExpression() { @@ -60,7 +60,7 @@ export function TimelionPageProvider({ getService, getPageObjects }: FtrProvider return await Promise.all(elements.map(async (element) => await element.getVisibleText())); } - public async clickSuggestion(suggestionIndex = 0, waitTime = 500) { + public async clickSuggestion(suggestionIndex = 0, waitTime = 1000) { const elements = await testSubjects.findAll('timelionSuggestionListItem'); if (suggestionIndex > elements.length) { throw new Error( From 9b570a9bf1262428661695179fee801345017efc Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 28 Jul 2020 09:46:36 +0200 Subject: [PATCH 27/36] fix dashboard index pattern race condition (#72899) * fix dashboard index pattern race condition * improve Co-authored-by: Elastic Machine --- .../application/dashboard_app_controller.tsx | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 8138e1c7f4dfd..2a0e2889575f3 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -25,8 +25,8 @@ import React, { useState, ReactElement } from 'react'; import ReactDOM from 'react-dom'; import angular from 'angular'; -import { Subscription } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { Observable, pipe, Subscription } from 'rxjs'; +import { filter, map, mapTo, startWith, switchMap } from 'rxjs/operators'; import { History } from 'history'; import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public'; import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; @@ -253,11 +253,7 @@ export class DashboardAppController { navActions[TopNavIds.VISUALIZE](); }; - const updateIndexPatterns = (container?: DashboardContainer) => { - if (!container || isErrorEmbeddable(container)) { - return; - } - + function getDashboardIndexPatterns(container: DashboardContainer): IndexPattern[] { let panelIndexPatterns: IndexPattern[] = []; Object.values(container.getChildIds()).forEach((id) => { const embeddableInstance = container.getChild(id); @@ -267,19 +263,34 @@ export class DashboardAppController { panelIndexPatterns.push(...embeddableIndexPatterns); }); panelIndexPatterns = uniqBy(panelIndexPatterns, 'id'); + return panelIndexPatterns; + } - if (panelIndexPatterns && panelIndexPatterns.length > 0) { - $scope.$evalAsync(() => { - $scope.indexPatterns = panelIndexPatterns; - }); - } else { - indexPatterns.getDefault().then((defaultIndexPattern) => { - $scope.$evalAsync(() => { - $scope.indexPatterns = [defaultIndexPattern as IndexPattern]; - }); + const updateIndexPatternsOperator = pipe( + filter((container: DashboardContainer) => !!container && !isErrorEmbeddable(container)), + map(getDashboardIndexPatterns), + // using switchMap for previous task cancellation + switchMap((panelIndexPatterns: IndexPattern[]) => { + return new Observable((observer) => { + if (panelIndexPatterns && panelIndexPatterns.length > 0) { + $scope.$evalAsync(() => { + if (observer.closed) return; + $scope.indexPatterns = panelIndexPatterns; + observer.complete(); + }); + } else { + indexPatterns.getDefault().then((defaultIndexPattern) => { + if (observer.closed) return; + $scope.$evalAsync(() => { + if (observer.closed) return; + $scope.indexPatterns = [defaultIndexPattern as IndexPattern]; + observer.complete(); + }); + }); + } }); - } - }; + }) + ); const getEmptyScreenProps = ( shouldShowEditHelp: boolean, @@ -384,11 +395,17 @@ export class DashboardAppController { ) : null; }; - updateIndexPatterns(dashboardContainer); - - outputSubscription = dashboardContainer.getOutput$().subscribe(() => { - updateIndexPatterns(dashboardContainer); - }); + outputSubscription = new Subscription(); + outputSubscription.add( + dashboardContainer + .getOutput$() + .pipe( + mapTo(dashboardContainer), + startWith(dashboardContainer), // to trigger initial index pattern update + updateIndexPatternsOperator + ) + .subscribe() + ); inputSubscription = dashboardContainer.getInput$().subscribe(() => { let dirty = false; From abfda1f79273111b581a14b1a43fe134c6053e6c Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 28 Jul 2020 09:57:04 +0200 Subject: [PATCH 28/36] Use "Apply_filter_trigger" in dashboard drilldown (#71468) * attach dashboard drilldown to apply filter trigger * fix types Co-authored-by: Elastic Machine --- ...na-plugin-plugins-data-public.esfilters.md | 1 + src/plugins/dashboard/public/index.ts | 6 +- src/plugins/dashboard/public/plugin.tsx | 7 +- src/plugins/dashboard/public/url_generator.ts | 6 +- src/plugins/data/public/index.ts | 2 + src/plugins/data/public/public.api.md | 94 ++++++------- .../data/public/query/timefilter/index.ts | 2 +- .../timefilter/lib/extract_time_filter.ts | 15 ++- x-pack/plugins/dashboard_enhanced/kibana.json | 3 +- .../flyout_create_drilldown.tsx | 11 +- .../constants.ts | 7 + .../drilldown.test.tsx | 54 ++------ .../drilldown.tsx | 125 +++++++----------- .../dashboard_to_dashboard_drilldown/index.ts | 5 +- .../dashboard_to_dashboard_drilldown/types.ts | 10 -- .../embeddable_action_storage.test.ts | 41 ++++++ .../embeddables/embeddable_action_storage.ts | 30 ++++- .../connected_flyout_manage_drilldowns.tsx | 6 +- 18 files changed, 227 insertions(+), 198 deletions(-) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md index 37142cf1794c3..bc34d4113f847 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md @@ -52,5 +52,6 @@ esFilters: { convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString; mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[]; extractTimeFilter: typeof extractTimeFilter; + extractTimeRange: typeof extractTimeRange; } ``` diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index 17968dd0281e6..dcfde67cd9f13 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -32,7 +32,11 @@ export { export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; export { DashboardStart, DashboardUrlGenerator } from './plugin'; -export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator } from './url_generator'; +export { + DASHBOARD_APP_URL_GENERATOR, + createDashboardUrlGenerator, + DashboardUrlGeneratorState, +} from './url_generator'; export { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; export { SavedObjectDashboard } from './saved_dashboards'; export { SavedDashboardPanel } from './types'; diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 041a02a251e8a..f0b57fec169fd 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -65,6 +65,7 @@ import { ACTION_REPLACE_PANEL, ClonePanelAction, ClonePanelActionContext, + createDashboardContainerByValueRenderer, DASHBOARD_CONTAINER_TYPE, DashboardContainerFactory, DashboardContainerFactoryDefinition, @@ -77,17 +78,17 @@ import { import { createDashboardUrlGenerator, DASHBOARD_APP_URL_GENERATOR, - DashboardAppLinkGeneratorState, + DashboardUrlGeneratorState, } from './url_generator'; import { createSavedDashboardLoader } from './saved_dashboards'; import { DashboardConstants } from './dashboard_constants'; import { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; -import { createDashboardContainerByValueRenderer } from './application'; +import { UrlGeneratorState } from '../../share/public'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { - [DASHBOARD_APP_URL_GENERATOR]: DashboardAppLinkGeneratorState; + [DASHBOARD_APP_URL_GENERATOR]: UrlGeneratorState; } } diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts index 188de7fd857be..68a50396e00d6 100644 --- a/src/plugins/dashboard/public/url_generator.ts +++ b/src/plugins/dashboard/public/url_generator.ts @@ -26,7 +26,7 @@ import { RefreshInterval, } from '../../data/public'; import { setStateToKbnUrl } from '../../kibana_utils/public'; -import { UrlGeneratorsDefinition, UrlGeneratorState } from '../../share/public'; +import { UrlGeneratorsDefinition } from '../../share/public'; import { SavedObjectLoader } from '../../saved_objects/public'; import { ViewMode } from '../../embeddable/public'; @@ -35,7 +35,7 @@ export const GLOBAL_STATE_STORAGE_KEY = '_g'; export const DASHBOARD_APP_URL_GENERATOR = 'DASHBOARD_APP_URL_GENERATOR'; -export type DashboardAppLinkGeneratorState = UrlGeneratorState<{ +export interface DashboardUrlGeneratorState { /** * If given, the dashboard saved object with this id will be loaded. If not given, * a new, unsaved dashboard will be loaded up. @@ -79,7 +79,7 @@ export type DashboardAppLinkGeneratorState = UrlGeneratorState<{ * View mode of the dashboard. */ viewMode?: ViewMode; -}>; +} export const createDashboardUrlGenerator = ( getStartServices: () => Promise<{ diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 846471420327f..e95150e8f6f73 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -58,6 +58,7 @@ import { changeTimeFilter, mapAndFlattenFilters, extractTimeFilter, + extractTimeRange, convertRangeFilterToTimeRangeString, } from './query'; @@ -99,6 +100,7 @@ export const esFilters = { convertRangeFilterToTimeRangeString, mapAndFlattenFilters, extractTimeFilter, + extractTimeRange, }; export { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index a8868c07061c3..65670bc1cf83e 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -499,6 +499,7 @@ export const esFilters: { convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString; mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[]; extractTimeFilter: typeof extractTimeFilter; + extractTimeRange: typeof extractTimeRange; }; // Warning: (ae-missing-release-tag) "esKuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1973,52 +1974,53 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/match_all_filter.ts:28:3 - (ae-forgotten-export) The symbol "MatchAllFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:371:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:372:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:381:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "extractTimeRange" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:138:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:373:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:374:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:41:60 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:54:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:55:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/query/timefilter/index.ts b/src/plugins/data/public/query/timefilter/index.ts index 19386c10ab59f..dc9a4ef8c21a6 100644 --- a/src/plugins/data/public/query/timefilter/index.ts +++ b/src/plugins/data/public/query/timefilter/index.ts @@ -23,5 +23,5 @@ export * from './types'; export { Timefilter, TimefilterContract } from './timefilter'; export { TimeHistory, TimeHistoryContract } from './time_history'; export { changeTimeFilter, convertRangeFilterToTimeRangeString } from './lib/change_time_filter'; -export { extractTimeFilter } from './lib/extract_time_filter'; +export { extractTimeFilter, extractTimeRange } from './lib/extract_time_filter'; export { validateTimeRange } from './lib/validate_timerange'; diff --git a/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts index 23dd1547baf10..2f93196e3218b 100644 --- a/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts +++ b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts @@ -18,7 +18,8 @@ */ import { keys, partition } from 'lodash'; -import { Filter, isRangeFilter, RangeFilter } from '../../../../common'; +import { Filter, isRangeFilter, RangeFilter, TimeRange } from '../../../../common'; +import { convertRangeFilterToTimeRangeString } from './change_time_filter'; export function extractTimeFilter(timeFieldName: string, filters: Filter[]) { const [timeRangeFilter, restOfFilters] = partition(filters, (obj: Filter) => { @@ -36,3 +37,15 @@ export function extractTimeFilter(timeFieldName: string, filters: Filter[]) { timeRangeFilter: timeRangeFilter[0] as RangeFilter | undefined, }; } + +export function extractTimeRange( + filters: Filter[], + timeFieldName?: string +): { restOfFilters: Filter[]; timeRange?: TimeRange } { + if (!timeFieldName) return { restOfFilters: filters, timeRange: undefined }; + const { timeRangeFilter, restOfFilters } = extractTimeFilter(timeFieldName, filters); + return { + restOfFilters, + timeRange: timeRangeFilter ? convertRangeFilterToTimeRangeString(timeRangeFilter) : undefined, + }; +} diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index ba5d8052ca787..264fa0438ea11 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -8,6 +8,7 @@ "requiredBundles": [ "kibanaUtils", "embeddableEnhanced", - "kibanaReact" + "kibanaReact", + "uiActions" ] } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index 4804a700c6cff..2de862a6708a8 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -6,7 +6,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; +import { + ActionByType, + APPLY_FILTER_TRIGGER, + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, +} from '../../../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; @@ -42,7 +47,9 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; + return supportedTriggers.some((trigger) => + [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER, APPLY_FILTER_TRIGGER].includes(trigger) + ); } public async isCompatible(context: EmbeddableContext) { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts index e2a530b156da5..daefcf2d68cc5 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts @@ -4,4 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +/** + * note: + * don't change this string without carefull consideration, + * because it is stored in saved objects. + * Also temporary dashboard drilldown migration code inside embeddable plugin relies on it + * x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts + */ export const DASHBOARD_TO_DASHBOARD_DRILLDOWN = 'DASHBOARD_TO_DASHBOARD_DRILLDOWN'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx index 52b232afa9410..40fa469feb34b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx @@ -5,9 +5,8 @@ */ import { DashboardToDashboardDrilldown } from './drilldown'; -import { savedObjectsServiceMock, coreMock } from '../../../../../../../src/core/public/mocks'; -import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; -import { ActionContext, Config } from './types'; +import { Config } from './types'; +import { coreMock, savedObjectsServiceMock } from '../../../../../../../src/core/public/mocks'; import { Filter, FilterStateStore, @@ -15,16 +14,13 @@ import { RangeFilter, TimeRange, } from '../../../../../../../src/plugins/data/common'; -import { esFilters } from '../../../../../../../src/plugins/data/public'; - +import { + ApplyGlobalFilterActionContext, + esFilters, +} from '../../../../../../../src/plugins/data/public'; // convenient to use real implementation here. import { createDashboardUrlGenerator } from '../../../../../../../src/plugins/dashboard/public/url_generator'; import { UrlGeneratorsService } from '../../../../../../../src/plugins/share/public/url_generators'; -import { VisualizeEmbeddableContract } from '../../../../../../../src/plugins/visualizations/public'; -import { - RangeSelectContext, - ValueClickContext, -} from '../../../../../../../src/plugins/embeddable/public'; import { StartDependencies } from '../../../plugin'; import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public'; import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_utils/public/core'; @@ -82,11 +78,10 @@ describe('.execute() & getHref', () => { config: Partial, embeddableInput: { filters?: Filter[]; timeRange?: TimeRange; query?: Query }, filtersFromEvent: Filter[], - useRangeEvent = false + timeFieldName?: string ) { const navigateToApp = jest.fn(); const getUrlForApp = jest.fn((app, opt) => `${app}/${opt.path}`); - const dataPluginActions = dataPluginMock.createStartContract().actions; const savedObjectsClient = savedObjectsServiceMock.createStartContract().client; const drilldown = new DashboardToDashboardDrilldown({ @@ -102,9 +97,6 @@ describe('.execute() & getHref', () => { }, plugins: { uiActionsEnhanced: {}, - data: { - actions: dataPluginActions, - }, }, self: {}, })) as unknown) as StartServicesGetter>, @@ -119,12 +111,6 @@ describe('.execute() & getHref', () => { ) ), }); - const selectRangeFiltersSpy = jest - .spyOn(dataPluginActions, 'createFiltersFromRangeSelectAction') - .mockImplementation(() => Promise.resolve(filtersFromEvent)); - const valueClickFiltersSpy = jest - .spyOn(dataPluginActions, 'createFiltersFromValueClickAction') - .mockImplementation(() => Promise.resolve(filtersFromEvent)); const completeConfig: Config = { dashboardId: 'id', @@ -134,12 +120,7 @@ describe('.execute() & getHref', () => { }; const context = ({ - data: { - ...(useRangeEvent - ? ({ range: {} } as RangeSelectContext['data']) - : ({ data: [] } as ValueClickContext['data'])), - timeFieldName: 'order_date', - }, + filters: filtersFromEvent, embeddable: { getInput: () => ({ filters: [], @@ -148,18 +129,11 @@ describe('.execute() & getHref', () => { ...embeddableInput, }), }, - } as unknown) as ActionContext; + timeFieldName, + } as unknown) as ApplyGlobalFilterActionContext; await drilldown.execute(completeConfig, context); - if (useRangeEvent) { - expect(selectRangeFiltersSpy).toBeCalledTimes(1); - expect(valueClickFiltersSpy).toBeCalledTimes(0); - } else { - expect(selectRangeFiltersSpy).toBeCalledTimes(0); - expect(valueClickFiltersSpy).toBeCalledTimes(1); - } - expect(navigateToApp).toBeCalledTimes(1); expect(navigateToApp.mock.calls[0][0]).toBe('dashboards'); @@ -180,8 +154,7 @@ describe('.execute() & getHref', () => { dashboardId: testDashboardId, }, {}, - [], - false + [] ); expect(href).toEqual(expect.stringContaining(`view/${testDashboardId}`)); @@ -289,8 +262,7 @@ describe('.execute() & getHref', () => { to: 'now', }, }, - [], - false + [] ); expect(href).not.toEqual(expect.stringContaining('now-300m')); @@ -308,7 +280,7 @@ describe('.execute() & getHref', () => { }, }, [getMockTimeRangeFilter()], - true + getMockTimeRangeFilter().meta.key ); expect(href).not.toEqual(expect.stringContaining('now-300m')); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx index 26a69132cffb1..703acbc8d9d59 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -6,20 +6,24 @@ import React from 'react'; import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public'; -import { DashboardUrlGenerator } from '../../../../../../../src/plugins/dashboard/public'; -import { ActionContext, Config } from './types'; +import { + DashboardUrlGenerator, + DashboardUrlGeneratorState, +} from '../../../../../../../src/plugins/dashboard/public'; import { CollectConfigContainer } from './components'; import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../ui_actions_enhanced/public'; import { txtGoToDashboard } from './i18n'; -import { esFilters } from '../../../../../../../src/plugins/data/public'; -import { VisualizeEmbeddableContract } from '../../../../../../../src/plugins/visualizations/public'; import { - isRangeSelectTriggerContext, - isValueClickTriggerContext, -} from '../../../../../../../src/plugins/embeddable/public'; + ApplyGlobalFilterActionContext, + esFilters, + isFilters, + isQuery, + isTimeRange, +} from '../../../../../../../src/plugins/data/public'; import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_utils/public'; import { StartDependencies } from '../../../plugin'; +import { Config } from './types'; export interface Params { start: StartServicesGetter>; @@ -27,7 +31,7 @@ export interface Params { } export class DashboardToDashboardDrilldown - implements Drilldown> { + implements Drilldown { constructor(protected readonly params: Params) {} public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN; @@ -57,15 +61,12 @@ export class DashboardToDashboardDrilldown public readonly getHref = async ( config: Config, - context: ActionContext + context: ApplyGlobalFilterActionContext ): Promise => { return this.getDestinationUrl(config, context); }; - public readonly execute = async ( - config: Config, - context: ActionContext - ) => { + public readonly execute = async (config: Config, context: ApplyGlobalFilterActionContext) => { const dashboardPath = await this.getDestinationUrl(config, context); const dashboardHash = dashboardPath.split('#')[1]; @@ -76,73 +77,43 @@ export class DashboardToDashboardDrilldown private getDestinationUrl = async ( config: Config, - context: ActionContext + context: ApplyGlobalFilterActionContext ): Promise => { + const state: DashboardUrlGeneratorState = { + dashboardId: config.dashboardId, + }; + + if (context.embeddable) { + const input = context.embeddable.getInput(); + if (isQuery(input.query) && config.useCurrentFilters) state.query = input.query; + + // if useCurrentDashboardDataRange is enabled, then preserve current time range + // if undefined is passed, then destination dashboard will figure out time range itself + // for brush event this time range would be overwritten + if (isTimeRange(input.timeRange) && config.useCurrentDateRange) + state.timeRange = input.timeRange; + + // if useCurrentDashboardFilters enabled, then preserve all the filters (pinned and unpinned) + // otherwise preserve only pinned + if (isFilters(input.filters)) + state.filters = config.useCurrentFilters + ? input.filters + : input.filters?.filter((f) => esFilters.isFilterPinned(f)); + } + const { - createFiltersFromRangeSelectAction, - createFiltersFromValueClickAction, - } = this.params.start().plugins.data.actions; - const { - timeRange: currentTimeRange, - query, - filters: currentFilters, - } = context.embeddable!.getInput(); - - // if useCurrentDashboardFilters enabled, then preserve all the filters (pinned and unpinned) - // otherwise preserve only pinned - const existingFilters = - (config.useCurrentFilters - ? currentFilters - : currentFilters?.filter((f) => esFilters.isFilterPinned(f))) ?? []; - - // if useCurrentDashboardDataRange is enabled, then preserve current time range - // if undefined is passed, then destination dashboard will figure out time range itself - // for brush event this time range would be overwritten - let timeRange = config.useCurrentDateRange ? currentTimeRange : undefined; - let filtersFromEvent = await (async () => { - try { - if (isRangeSelectTriggerContext(context)) - return await createFiltersFromRangeSelectAction(context.data); - if (isValueClickTriggerContext(context)) - return await createFiltersFromValueClickAction(context.data); - - // eslint-disable-next-line no-console - console.warn( - ` - DashboardToDashboard drilldown: can't extract filters from action. - Is it not supported action?`, - context - ); - - return []; - } catch (e) { - // eslint-disable-next-line no-console - console.warn( - ` - DashboardToDashboard drilldown: error extracting filters from action. - Continuing without applying filters from event`, - e - ); - return []; - } - })(); - - if (context.data.timeFieldName) { - const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( - context.data.timeFieldName, - filtersFromEvent - ); - filtersFromEvent = restOfFilters; - if (timeRangeFilter) { - timeRange = esFilters.convertRangeFilterToTimeRangeString(timeRangeFilter); - } + restOfFilters: filtersFromEvent, + timeRange: timeRangeFromEvent, + } = esFilters.extractTimeRange(context.filters, context.timeFieldName); + + if (filtersFromEvent) { + state.filters = [...(state.filters ?? []), ...filtersFromEvent]; } - return this.params.getDashboardUrlGenerator().createUrl({ - dashboardId: config.dashboardId, - query: config.useCurrentFilters ? query : undefined, - timeRange, - filters: [...existingFilters, ...filtersFromEvent], - }); + if (timeRangeFromEvent) { + state.timeRange = timeRangeFromEvent; + } + + return this.params.getDashboardUrlGenerator().createUrl(state); }; } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts index 914f34980a272..49065a96b4f7b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts @@ -9,7 +9,4 @@ export { DashboardToDashboardDrilldown, Params as DashboardToDashboardDrilldownParams, } from './drilldown'; -export { - ActionContext as DashboardToDashboardActionContext, - Config as DashboardToDashboardConfig, -} from './types'; +export { Config } from './types'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts index 6be2e2a77269f..426e250499de0 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts @@ -4,16 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ValueClickContext, - RangeSelectContext, - IEmbeddable, -} from '../../../../../../../src/plugins/embeddable/public'; - -export type ActionContext = - | ValueClickContext - | RangeSelectContext; - export interface Config { dashboardId?: string; useCurrentFilters: boolean; diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index 5c5d98d75295d..fffb75451f8ac 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -11,6 +11,9 @@ import { } from './embeddable_action_storage'; import { UiActionsEnhancedSerializedEvent } from '../../../ui_actions_enhanced/public'; import { of } from '../../../../../src/plugins/kibana_utils/public'; +// use real const to make test fail in case someone accidentally changes it +import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from '../../../dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown'; +import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/ui_actions/public'; class TestEmbeddable extends Embeddable { public readonly type = 'test'; @@ -539,4 +542,42 @@ describe('EmbeddableActionStorage', () => { expect(await storage.list()).toEqual([]); }); }); + + describe('migrate', () => { + test('DASHBOARD_TO_DASHBOARD_DRILLDOWN triggers migration', async () => { + const embeddable = new TestEmbeddable(); + const OTHER_TRIGGER = 'OTHER_TRIGGER'; + embeddable.updateInput({ + enhancements: { + dynamicActions: { + events: [ + { + eventId: '1', + triggers: [OTHER_TRIGGER], + action: { + factoryId: DASHBOARD_TO_DASHBOARD_DRILLDOWN, + name: '', + config: {}, + }, + }, + { + eventId: '2', + triggers: [OTHER_TRIGGER], + action: { + factoryId: 'SOME_OTHER', + name: '', + config: {}, + }, + }, + ], + }, + }, + }); + const storage = new EmbeddableActionStorage(embeddable); + + const [event1, event2] = await storage.list(); + expect(event1.triggers).toEqual([APPLY_FILTER_TRIGGER]); + expect(event2.triggers).toEqual([OTHER_TRIGGER]); + }); + }); }); diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts index fdc42585a80ce..8881b2063c8db 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts @@ -46,7 +46,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async create(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = input.enhancements?.dynamicActions?.events || []; + const events = this.getEventsFromEmbeddable(); const exists = !!events.find(({ eventId }) => eventId === event.eventId); if (exists) { @@ -61,7 +61,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async update(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = input.enhancements?.dynamicActions?.events || []; + const events = this.getEventsFromEmbeddable(); const index = events.findIndex(({ eventId }) => eventId === event.eventId); if (index === -1) { @@ -77,7 +77,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async remove(eventId: string) { const input = this.embbeddable.getInput(); - const events = input.enhancements?.dynamicActions?.events || []; + const events = this.getEventsFromEmbeddable(); const index = events.findIndex((event) => eventId === event.eventId); if (index === -1) { @@ -93,7 +93,7 @@ export class EmbeddableActionStorage extends AbstractActionStorage { public async read(eventId: string): Promise { const input = this.embbeddable.getInput(); - const events = input.enhancements?.dynamicActions?.events || []; + const events = this.getEventsFromEmbeddable(); const event = events.find((ev) => eventId === ev.eventId); if (!event) { @@ -107,8 +107,28 @@ export class EmbeddableActionStorage extends AbstractActionStorage { } public async list(): Promise { + return this.getEventsFromEmbeddable(); + } + + private getEventsFromEmbeddable() { const input = this.embbeddable.getInput(); const events = input.enhancements?.dynamicActions?.events || []; - return events; + return this.migrate(events); + } + + // TODO: https://github.com/elastic/kibana/issues/71431 + // Migration implementation should use registry + // Action factories implementations should register own migrations + private migrate(events: SerializedEvent[]): SerializedEvent[] { + return events.map((event) => { + // Initially dashboard drilldown relied on VALUE_CLICK & RANGE_SELECT + if (event.action.factoryId === 'DASHBOARD_TO_DASHBOARD_DRILLDOWN') { + return { + ...event, + triggers: ['FILTER_TRIGGER'], + }; + } + return event; + }); } } diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 20d15b4f4d2bd..283464b137ff9 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -11,9 +11,8 @@ import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldow import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public'; import { - VALUE_CLICK_TRIGGER, - SELECT_RANGE_TRIGGER, TriggerContextMapping, + APPLY_FILTER_TRIGGER, } from '../../../../../../../src/plugins/ui_actions/public'; import { useContainerState } from '../../../../../../../src/plugins/kibana_utils/public'; import { DrilldownListItem } from '../list_manage_drilldowns'; @@ -67,8 +66,9 @@ export function createFlyoutManageDrilldowns({ return (props: ConnectedFlyoutManageDrilldownsProps) => { const isCreateOnly = props.viewMode === 'create'; + // TODO: https://github.com/elastic/kibana/issues/59569 const selectedTriggers: Array = React.useMemo( - () => [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER], + () => [APPLY_FILTER_TRIGGER], [] ); From 5ea28702f6a2aa3e0592a79fa5ea396ff68fd972 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 28 Jul 2020 11:15:58 +0300 Subject: [PATCH 29/36] [Functional Tests] Increase the timeout when locating the tableview] (#73243) --- test/functional/page_objects/visual_builder_page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 0db8cac0f0758..8488eb8cd2749 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -408,7 +408,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro * @memberof VisualBuilderPage */ public async getViewTable(): Promise { - const tableView = await testSubjects.find('tableView'); + const tableView = await testSubjects.find('tableView', 20000); return await tableView.getVisibleText(); } From c0826a32730ba55c0192e81fc23788be5966fdcd Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 28 Jul 2020 11:37:37 +0300 Subject: [PATCH 30/36] Fix App status flaky test (#72853) * wait for link to be updated * await, please! Co-authored-by: Elastic Machine --- .../plugins/core_app_status/public/plugin.tsx | 3 +-- .../core_plugins/application_status.ts | 16 +++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/test/plugin_functional/plugins/core_app_status/public/plugin.tsx b/test/plugin_functional/plugins/core_app_status/public/plugin.tsx index af23bfbe1f8f5..bdc08c03c1912 100644 --- a/test/plugin_functional/plugins/core_app_status/public/plugin.tsx +++ b/test/plugin_functional/plugins/core_app_status/public/plugin.tsx @@ -26,6 +26,7 @@ import { CoreStart, AppMountParameters, } from 'kibana/public'; +import { renderApp } from './application'; import './types'; export class CoreAppStatusPlugin implements Plugin<{}, CoreAppStatusPluginStart> { @@ -36,7 +37,6 @@ export class CoreAppStatusPlugin implements Plugin<{}, CoreAppStatusPluginStart> id: 'app_status_start', title: 'App Status Start Page', async mount(params: AppMountParameters) { - const { renderApp } = await import('./application'); return renderApp('app_status_start', params); }, }); @@ -47,7 +47,6 @@ export class CoreAppStatusPlugin implements Plugin<{}, CoreAppStatusPluginStart> euiIconType: 'snowflake', updater$: this.appUpdater, async mount(params: AppMountParameters) { - const { renderApp } = await import('./application'); return renderApp('app_status', params); }, }); diff --git a/test/plugin_functional/test_suites/core_plugins/application_status.ts b/test/plugin_functional/test_suites/core_plugins/application_status.ts index 31a1c28b50842..a4c2db733b894 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_status.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_status.ts @@ -41,6 +41,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const PageObjects = getPageObjects(['common']); const browser = getService('browser'); const appsMenu = getService('appsMenu'); + const retry = getService('retry'); const testSubjects = getService('testSubjects'); const setAppStatus = async (s: Partial) => { @@ -50,15 +51,14 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }, s); }; - const navigateToApp = async (i: string) => { + const navigateToApp = async (id: string) => { return await browser.executeAsync(async (appId, cb) => { await window.__coreAppStatus.navigateToApp(appId); cb(); - }, i); + }, id); }; - // FLAKY: https://github.com/elastic/kibana/issues/65423 - describe.skip('application status management', () => { + describe('application status management', () => { beforeEach(async () => { await PageObjects.common.navigateToApp('app_status_start'); }); @@ -101,15 +101,17 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); it('allows to change the defaultPath of an application', async () => { - let link = await appsMenu.getLink('App Status'); + const link = await appsMenu.getLink('App Status'); expect(link!.href).to.eql(getKibanaUrl('/app/app_status')); await setAppStatus({ defaultPath: '/arbitrary/path', }); - link = await appsMenu.getLink('App Status'); - expect(link!.href).to.eql(getKibanaUrl('/app/app_status/arbitrary/path')); + await retry.waitFor('link url updated with "defaultPath"', async () => { + const updatedLink = await appsMenu.getLink('App Status'); + return updatedLink?.href === getKibanaUrl('/app/app_status/arbitrary/path'); + }); await navigateToApp('app_status'); expect(await testSubjects.exists('appStatusApp')).to.eql(true); From 1c791f39dac906f3a46b3703a82ba33e8f263a4b Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 28 Jul 2020 10:48:14 +0200 Subject: [PATCH 31/36] [SIEM][Timelines] Updates timeline template callout text (#73334) * updates timeline template callout text * fixes typo in constant Co-authored-by: Elastic Machine --- .../timelines/components/timeline/header/index.test.tsx | 2 +- .../public/timelines/components/timeline/header/index.tsx | 2 +- .../timelines/components/timeline/header/translations.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx index e0043f3b232da..e7b0ce7b7428e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx @@ -177,7 +177,7 @@ describe('Header', () => { expect( wrapper.find('[data-test-subj="timelineImmutableCallOut"]').first().prop('title') ).toEqual( - 'This timeline is immutable, therefore not allowed to save it within the security application, though you may continue to use the timeline to search and filter security events' + 'This prebuilt timeline template cannot be modified. To make changes, please duplicate this template and make modifications to the duplicate template.' ); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.tsx index 75bfb52f2756b..e50a6ed1e45fe 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.tsx @@ -73,7 +73,7 @@ const TimelineHeaderComponent: React.FC = ({ {status === TimelineStatus.immutable && ( Date: Tue, 28 Jul 2020 13:00:16 +0300 Subject: [PATCH 32/36] [Search] add server logs (#72454) * improve test stability * logs and scope search function * uncomment * fix ts * ts Co-authored-by: Elastic Machine --- src/plugins/data/server/plugin.ts | 4 ++-- .../es_search/es_search_strategy.test.ts | 11 +++++---- .../search/es_search/es_search_strategy.ts | 6 +++-- .../data/server/search/search_service.test.ts | 5 +++- .../data/server/search/search_service.ts | 24 +++++++++++++++---- x-pack/plugins/data_enhanced/server/plugin.ts | 12 ++++++++-- .../server/search/es_search_strategy.test.ts | 13 ++++++---- .../server/search/es_search_strategy.ts | 6 ++++- 8 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 8fa32f9bd564f..61d8e566d2d2b 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -62,11 +62,11 @@ export class DataServerPlugin implements Plugin) { - this.searchService = new SearchService(initializerContext); + this.logger = initializerContext.logger.get('data'); + this.searchService = new SearchService(initializerContext, this.logger); this.scriptsService = new ScriptsService(); this.kqlTelemetryService = new KqlTelemetryService(initializerContext); this.autocompleteService = new AutocompleteService(initializerContext); - this.logger = initializerContext.logger.get('data'); } public setup( diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index 1155a5491e8f3..bc59bdee6a40a 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -22,6 +22,9 @@ import { pluginInitializerContextConfigMock } from '../../../../../core/server/m import { esSearchStrategyProvider } from './es_search_strategy'; describe('ES search strategy', () => { + const mockLogger: any = { + info: () => {}, + }; const mockApiCaller = jest.fn().mockResolvedValue({ _shards: { total: 10, @@ -40,14 +43,14 @@ describe('ES search strategy', () => { }); it('returns a strategy with `search`', async () => { - const esSearch = await esSearchStrategyProvider(mockConfig$); + const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); expect(typeof esSearch.search).toBe('function'); }); it('calls the API caller with the params with defaults', async () => { const params = { index: 'logstash-*' }; - const esSearch = await esSearchStrategyProvider(mockConfig$); + const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); @@ -63,7 +66,7 @@ describe('ES search strategy', () => { it('calls the API caller with overridden defaults', async () => { const params = { index: 'logstash-*', ignoreUnavailable: false, timeout: '1000ms' }; - const esSearch = await esSearchStrategyProvider(mockConfig$); + const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); @@ -77,7 +80,7 @@ describe('ES search strategy', () => { it('returns total, loaded, and raw response', async () => { const params = { index: 'logstash-*' }; - const esSearch = await esSearchStrategyProvider(mockConfig$); + const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); const response = await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params, diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index 82f8ef21ebb38..b8010f735c327 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -17,16 +17,18 @@ * under the License. */ import { first } from 'rxjs/operators'; -import { SharedGlobalConfig } from 'kibana/server'; +import { SharedGlobalConfig, Logger } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { Observable } from 'rxjs'; import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded } from '..'; export const esSearchStrategyProvider = ( - config$: Observable + config$: Observable, + logger: Logger ): ISearchStrategy => { return { search: async (context, request, options) => { + logger.info(`search ${JSON.stringify(request.params)}`); const config = await config$.pipe(first()).toPromise(); const defaultParams = getDefaultSearchParams(config); diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts index 8c2ed96503003..be00b7409fe4a 100644 --- a/src/plugins/data/server/search/search_service.test.ts +++ b/src/plugins/data/server/search/search_service.test.ts @@ -28,7 +28,10 @@ describe('Search service', () => { let mockCoreSetup: MockedKeys>; beforeEach(() => { - plugin = new SearchService(coreMock.createPluginInitializerContext({})); + const mockLogger: any = { + info: () => {}, + }; + plugin = new SearchService(coreMock.createPluginInitializerContext({}), mockLogger); mockCoreSetup = coreMock.createSetup(); }); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 5686023e9a667..bbd0671754749 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -22,6 +22,7 @@ import { PluginInitializerContext, CoreSetup, RequestHandlerContext, + Logger, } from '../../../../core/server'; import { ISearchSetup, ISearchStart, ISearchStrategy } from './types'; import { registerSearchRoute } from './routes'; @@ -41,7 +42,10 @@ interface StrategyMap { export class SearchService implements Plugin { private searchStrategies: StrategyMap = {}; - constructor(private initializerContext: PluginInitializerContext) {} + constructor( + private initializerContext: PluginInitializerContext, + private readonly logger: Logger + ) {} public setup( core: CoreSetup, @@ -49,7 +53,7 @@ export class SearchService implements Plugin { ): ISearchSetup { this.registerSearchStrategy( ES_SEARCH_STRATEGY, - esSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$) + esSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$, this.logger) ); core.savedObjects.registerType(searchTelemetry); @@ -65,7 +69,11 @@ export class SearchService implements Plugin { return { registerSearchStrategy: this.registerSearchStrategy, usage }; } - private search(context: RequestHandlerContext, searchRequest: IEsSearchRequest, options: any) { + private search( + context: RequestHandlerContext, + searchRequest: IEsSearchRequest, + options: Record + ) { return this.getSearchStrategy(options.strategy || ES_SEARCH_STRATEGY).search( context, searchRequest, @@ -76,17 +84,25 @@ export class SearchService implements Plugin { public start(): ISearchStart { return { getSearchStrategy: this.getSearchStrategy, - search: this.search, + search: ( + context: RequestHandlerContext, + searchRequest: IEsSearchRequest, + options: Record + ) => { + return this.search(context, searchRequest, options); + }, }; } public stop() {} private registerSearchStrategy = (name: string, strategy: ISearchStrategy) => { + this.logger.info(`Register strategy ${name}`); this.searchStrategies[name] = strategy; }; private getSearchStrategy = (name: string): ISearchStrategy => { + this.logger.info(`Get strategy ${name}`); const strategy = this.searchStrategies[name]; if (!strategy) { throw new Error(`Search strategy ${name} not found`); diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index 4f6756231912c..9c3a0edf7e733 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -9,6 +9,7 @@ import { CoreSetup, CoreStart, Plugin, + Logger, } from '../../../../src/core/server'; import { ES_SEARCH_STRATEGY } from '../../../../src/plugins/data/common'; import { PluginSetup as DataPluginSetup } from '../../../../src/plugins/data/server'; @@ -19,12 +20,19 @@ interface SetupDependencies { } export class EnhancedDataServerPlugin implements Plugin { - constructor(private initializerContext: PluginInitializerContext) {} + private readonly logger: Logger; + + constructor(private initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get('data_enhanced'); + } public setup(core: CoreSetup, deps: SetupDependencies) { deps.data.search.registerSearchStrategy( ES_SEARCH_STRATEGY, - enhancedEsSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$) + enhancedEsSearchStrategyProvider( + this.initializerContext.config.legacy.globalConfig$, + this.logger + ) ); } diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts index 1eec941466b73..faa4f2ee499e5 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -31,6 +31,9 @@ const mockRollupResponse = { describe('ES search strategy', () => { const mockApiCaller = jest.fn(); + const mockLogger: any = { + info: () => {}, + }; const mockContext = { core: { elasticsearch: { legacy: { client: { callAsCurrentUser: mockApiCaller } } } }, }; @@ -41,7 +44,7 @@ describe('ES search strategy', () => { }); it('returns a strategy with `search`', async () => { - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); expect(typeof esSearch.search).toBe('function'); }); @@ -50,7 +53,7 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); @@ -66,7 +69,7 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { id: 'foo', params }); @@ -82,7 +85,7 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'foo-程', body: {} }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); @@ -97,7 +100,7 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockRollupResponse); const params = { index: 'foo-程', body: {} }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { indexType: 'rollup', diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 7b29117495a67..358335a2a4d60 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -12,6 +12,7 @@ import { LegacyAPICaller, SharedGlobalConfig, RequestHandlerContext, + Logger, } from '../../../../../src/core/server'; import { ISearchOptions, @@ -30,13 +31,15 @@ export interface AsyncSearchResponse { } export const enhancedEsSearchStrategyProvider = ( - config$: Observable + config$: Observable, + logger: Logger ): ISearchStrategy => { const search = async ( context: RequestHandlerContext, request: IEnhancedEsSearchRequest, options?: ISearchOptions ) => { + logger.info(`search ${JSON.stringify(request.params) || request.id}`); const config = await config$.pipe(first()).toPromise(); const caller = context.core.elasticsearch.legacy.client.callAsCurrentUser; const defaultParams = getDefaultSearchParams(config); @@ -48,6 +51,7 @@ export const enhancedEsSearchStrategyProvider = ( }; const cancel = async (context: RequestHandlerContext, id: string) => { + logger.info(`cancel ${id}`); const method = 'DELETE'; const path = encodeURI(`/_async_search/${id}`); await context.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', { From 12d5b8d2f95cc085065def98e614590828adfa7e Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 28 Jul 2020 13:13:01 +0200 Subject: [PATCH 33/36] executes cypress tests when there is a change in parts of alerting team code we use (#73256) --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 69c61b5bfa988..818ba748ee165 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -43,7 +43,7 @@ kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true, setCommitStatus: true) 'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'), 'xpack-savedObjectsFieldMetrics': kibanaPipeline.functionalTestProcess('xpack-savedObjectsFieldMetrics', './test/scripts/jenkins_xpack_saved_objects_field_metrics.sh'), 'xpack-securitySolutionCypress': { processNumber -> - whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/']) { + whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/', 'x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/', 'x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx']) { kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')(processNumber) } }, From 46fb8475f382cff56d10783798d6a3c2d1f3dda2 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 28 Jul 2020 14:38:14 +0300 Subject: [PATCH 34/36] [Security Solutions] Show popovers inside modals (#73264) --- .../security_solution/public/common/components/page/index.tsx | 4 ++-- .../public/common/components/with_hover_actions/index.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx index 9a5654ed6475f..8737fa95c94a2 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx @@ -49,8 +49,8 @@ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimar border: none; } - /* hide open popovers when a modal is being displayed to prevent them from covering the modal */ - body.euiBody-hasOverlayMask .euiPopover__panel-isOpen { + /* hide open draggable popovers when a modal is being displayed to prevent them from covering the modal */ + body.euiBody-hasOverlayMask .withHoverActions__popover.euiPopover__panel-isOpen{ visibility: hidden !important; } diff --git a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx index e6577bd040e25..9e28345ffbbcf 100644 --- a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx @@ -90,6 +90,7 @@ export const WithHoverActions = React.memo( hasArrow={false} isOpen={isOpen} panelPaddingSize={!alwaysShow ? 's' : 'none'} + panelClassName="withHoverActions__popover" > {isOpen ? <>{hoverContent} : null} From 09b11b61f0fefb736847f5000713bcf6ebfae0b0 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 28 Jul 2020 07:44:37 -0400 Subject: [PATCH 35/36] Introduce reserved ml privilege for the apm_user role (#72266) Co-authored-by: Elastic Machine --- x-pack/plugins/infra/server/features.ts | 12 ++++----- .../plugins/ml/common/types/capabilities.ts | 16 ++++++++++++ x-pack/plugins/ml/server/plugin.ts | 6 ++++- .../disable_ui_capabilities.test.ts | 8 +++--- .../authorization/disable_ui_capabilities.ts | 9 +++---- .../feature_privilege_builder/navlink.ts | 5 +--- .../privileges/privileges.test.ts | 25 +++---------------- .../capabilities_switcher.test.ts | 2 +- .../capabilities/capabilities_switcher.ts | 3 +-- .../apis/security/privileges.ts | 2 +- .../apis/security/privileges_basic.ts | 2 +- .../infrastructure_security.ts | 24 +++++++++--------- .../feature_controls/infrastructure_spaces.ts | 22 ++++++++-------- .../infra/feature_controls/logs_security.ts | 24 +++++++++--------- .../infra/feature_controls/logs_spaces.ts | 22 ++++++++-------- .../test/ui_capabilities/common/features.ts | 2 +- .../plugins/foo_plugin/server/index.ts | 6 ++--- .../common/nav_links_builder.ts | 13 ++++++---- .../common/services/features.ts | 2 +- .../common/services/ui_capabilities.ts | 2 +- .../security_only/tests/nav_links.ts | 2 +- 21 files changed, 102 insertions(+), 107 deletions(-) diff --git a/x-pack/plugins/infra/server/features.ts b/x-pack/plugins/infra/server/features.ts index 0de431186b151..fdbd1ec894022 100644 --- a/x-pack/plugins/infra/server/features.ts +++ b/x-pack/plugins/infra/server/features.ts @@ -17,12 +17,12 @@ export const METRICS_FEATURE = { order: 700, icon: 'metricsApp', navLinkId: 'metrics', - app: ['infra', 'kibana'], + app: ['infra', 'metrics', 'kibana'], catalogue: ['infraops'], alerting: [METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID], privileges: { all: { - app: ['infra', 'kibana'], + app: ['infra', 'metrics', 'kibana'], catalogue: ['infraops'], api: ['infra'], savedObject: { @@ -35,7 +35,7 @@ export const METRICS_FEATURE = { ui: ['show', 'configureSource', 'save', 'alerting:show'], }, read: { - app: ['infra', 'kibana'], + app: ['infra', 'metrics', 'kibana'], catalogue: ['infraops'], api: ['infra'], savedObject: { @@ -58,12 +58,12 @@ export const LOGS_FEATURE = { order: 800, icon: 'logsApp', navLinkId: 'logs', - app: ['infra', 'kibana'], + app: ['infra', 'logs', 'kibana'], catalogue: ['infralogging'], alerting: [LOG_DOCUMENT_COUNT_ALERT_TYPE_ID], privileges: { all: { - app: ['infra', 'kibana'], + app: ['infra', 'logs', 'kibana'], catalogue: ['infralogging'], api: ['infra'], savedObject: { @@ -76,7 +76,7 @@ export const LOGS_FEATURE = { ui: ['show', 'configureSource', 'save'], }, read: { - app: ['infra', 'kibana'], + app: ['infra', 'logs', 'kibana'], catalogue: ['infralogging'], api: ['infra'], alerting: { diff --git a/x-pack/plugins/ml/common/types/capabilities.ts b/x-pack/plugins/ml/common/types/capabilities.ts index 504cd28b8fa14..58a2043502d27 100644 --- a/x-pack/plugins/ml/common/types/capabilities.ts +++ b/x-pack/plugins/ml/common/types/capabilities.ts @@ -7,6 +7,10 @@ import { KibanaRequest } from 'kibana/server'; import { PLUGIN_ID } from '../constants/app'; +export const apmUserMlCapabilities = { + canGetJobs: false, +}; + export const userMlCapabilities = { canAccessML: false, // Anomaly Detection @@ -68,6 +72,7 @@ export function getDefaultCapabilities(): MlCapabilities { } export function getPluginPrivileges() { + const apmUserMlCapabilitiesKeys = Object.keys(apmUserMlCapabilities); const userMlCapabilitiesKeys = Object.keys(userMlCapabilities); const adminMlCapabilitiesKeys = Object.keys(adminMlCapabilities); const allMlCapabilitiesKeys = [...adminMlCapabilitiesKeys, ...userMlCapabilitiesKeys]; @@ -101,6 +106,17 @@ export function getPluginPrivileges() { read: savedObjects, }, }, + apmUser: { + excludeFromBasePrivileges: true, + app: [], + catalogue: [], + savedObject: { + all: [], + read: [], + }, + api: apmUserMlCapabilitiesKeys.map((k) => `ml:${k}`), + ui: apmUserMlCapabilitiesKeys, + }, }; } diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 812db744d1bda..3c3824a785032 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -75,7 +75,7 @@ export class MlServerPlugin implements Plugin { new Feature({ id: 'fooFeature', name: 'Foo Feature', - app: ['fooApp'], + app: ['fooApp', 'foo'], navLinkId: 'foo', privileges: null, }), @@ -129,7 +129,7 @@ describe('usingPrivileges', () => { new Feature({ id: 'fooFeature', name: 'Foo Feature', - app: [], + app: ['foo'], navLinkId: 'foo', privileges: null, }), @@ -262,7 +262,7 @@ describe('usingPrivileges', () => { id: 'barFeature', name: 'Bar Feature', navLinkId: 'bar', - app: [], + app: ['bar'], privileges: null, }), ], @@ -412,7 +412,7 @@ describe('all', () => { new Feature({ id: 'fooFeature', name: 'Foo Feature', - app: [], + app: ['foo'], navLinkId: 'foo', privileges: null, }), diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts index a9b3fa54d3617..c126be1b07f6e 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts @@ -18,12 +18,11 @@ export function disableUICapabilitiesFactory( logger: Logger, authz: AuthorizationServiceSetup ) { - // nav links are sourced from two places: - // 1) The `navLinkId` property. This is deprecated and will be removed (https://github.com/elastic/kibana/issues/66217) - // 2) The apps property. The Kibana Platform associates nav links to the app which registers it, in a 1:1 relationship. - // This behavior is replacing the `navLinkId` property above. + // nav links are sourced from the apps property. + // The Kibana Platform associates nav links to the app which registers it, in a 1:1 relationship. + // This behavior is replacing the `navLinkId` property. const featureNavLinkIds = features - .flatMap((feature) => [feature.navLinkId, ...feature.app]) + .flatMap((feature) => feature.app) .filter((navLinkId) => navLinkId != null); const shouldDisableFeatureUICapability = ( diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/navlink.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/navlink.ts index f25632407be86..a6e5a01c7dba8 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/navlink.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/navlink.ts @@ -9,9 +9,6 @@ import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeNavlinkBuilder extends BaseFeaturePrivilegeBuilder { public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { - const appNavLinks = feature.app.map((app) => this.actions.ui.get('navLinks', app)); - return feature.navLinkId - ? [this.actions.ui.get('navLinks', feature.navLinkId), ...appNavLinks] - : appNavLinks; + return (privilegeDefinition.app ?? []).map((app) => this.actions.ui.get('navLinks', app)); } } diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts index d8ece8f68d425..89ac73c220756 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts @@ -54,20 +54,8 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty('features.foo-feature', { - all: [ - actions.login, - actions.version, - actions.ui.get('navLinks', 'kibana:foo'), - actions.ui.get('navLinks', 'app-1'), - actions.ui.get('navLinks', 'app-2'), - ], - read: [ - actions.login, - actions.version, - actions.ui.get('navLinks', 'kibana:foo'), - actions.ui.get('navLinks', 'app-1'), - actions.ui.get('navLinks', 'app-2'), - ], + all: [actions.login, actions.version], + read: [actions.login, actions.version], }); }); @@ -275,7 +263,6 @@ describe('features', () => { actions.ui.get('catalogue', 'all-catalogue-2'), actions.ui.get('management', 'all-management', 'all-management-1'), actions.ui.get('management', 'all-management', 'all-management-2'), - actions.ui.get('navLinks', 'kibana:foo'), actions.savedObject.get('all-savedObject-all-1', 'bulk_get'), actions.savedObject.get('all-savedObject-all-1', 'get'), actions.savedObject.get('all-savedObject-all-1', 'find'), @@ -386,7 +373,6 @@ describe('features', () => { actions.ui.get('catalogue', 'read-catalogue-2'), actions.ui.get('management', 'read-management', 'read-management-1'), actions.ui.get('management', 'read-management', 'read-management-2'), - actions.ui.get('navLinks', 'kibana:foo'), actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), actions.savedObject.get('read-savedObject-all-1', 'get'), actions.savedObject.get('read-savedObject-all-1', 'find'), @@ -644,12 +630,7 @@ describe('reserved', () => { const privileges = privilegesFactory(actions, mockXPackMainPlugin as any, mockLicenseService); const actual = privileges.get(); - expect(actual).toHaveProperty('reserved.foo', [ - actions.version, - actions.ui.get('navLinks', 'kibana:foo'), - actions.ui.get('navLinks', 'app-1'), - actions.ui.get('navLinks', 'app-2'), - ]); + expect(actual).toHaveProperty('reserved.foo', [actions.version]); }); test(`actions only specified at the privilege are alright too`, () => { diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts index 797d7fd1bdcc4..c9ea1b44e723d 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts @@ -23,7 +23,7 @@ const features = ([ id: 'feature_2', name: 'Feature 2', navLinkId: 'feature2', - app: [], + app: ['feature2'], catalogue: ['feature2Entry'], management: { kibana: ['somethingElse'], diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts index 00e2419136f48..e8d964b22010c 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts @@ -83,8 +83,7 @@ function toggleDisabledFeatures( for (const feature of disabledFeatures) { // Disable associated navLink, if one exists - const featureNavLinks = feature.navLinkId ? [feature.navLinkId, ...feature.app] : feature.app; - featureNavLinks.forEach((app) => { + feature.app.forEach((app) => { if (navLinks.hasOwnProperty(app) && !enabledAppEntries.has(app)) { navLinks[app] = false; } diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 1ad25a11be879..07233f1685385 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -43,7 +43,7 @@ export default function ({ getService }: FtrProviderContext) { }, global: ['all', 'read'], space: ['all', 'read'], - reserved: ['ml_user', 'ml_admin', 'monitoring'], + reserved: ['ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'], }; await supertest diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index d5263aed26d0b..74d95fa1e4a76 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -41,7 +41,7 @@ export default function ({ getService }: FtrProviderContext) { }, global: ['all', 'read'], space: ['all', 'read'], - reserved: ['ml_user', 'ml_admin', 'monitoring'], + reserved: ['ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'], }; await supertest diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 971826112a3e2..3c471516e9c66 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -423,19 +423,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(navLinks).to.not.contain(['Metrics']); }); - it(`metrics app is inaccessible and Application Not Found message is rendered`, async () => { - await PageObjects.common.navigateToApp('infraOps'); - await testSubjects.existOrFail('~appNotFoundPageContent'); - await PageObjects.common.navigateToUrlWithBrowserHistory( - 'infraOps', - '/inventory', - undefined, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } + it(`metrics app is inaccessible and returns a 404`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + }); + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) ); - await testSubjects.existOrFail('~appNotFoundPageContent'); }); }); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts index 211a9ce718b56..1bf8ded69016b 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts @@ -79,21 +79,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`metrics app is inaccessible and Application Not Found message is rendered`, async () => { - await PageObjects.common.navigateToApp('infraOps', { + await PageObjects.common.navigateToActualUrl('infraOps', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, basePath: '/s/custom_space', }); - await testSubjects.existOrFail('~appNotFoundPageContent'); - await PageObjects.common.navigateToUrlWithBrowserHistory( - 'infraOps', - '/inventory', - undefined, - { - basePath: '/s/custom_space', - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) ); - await testSubjects.existOrFail('~appNotFoundPageContent'); }); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts index c7d94f86ea420..64154ff6cf3f7 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts @@ -187,19 +187,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(navLinks).to.not.contain('Logs'); }); - it(`logs app is inaccessible and Application Not Found message is rendered`, async () => { - await PageObjects.common.navigateToApp('infraLogs'); - await testSubjects.existOrFail('~appNotFoundPageContent'); - await PageObjects.common.navigateToUrlWithBrowserHistory( - 'infraLogs', - '/stream', - undefined, - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } + it(`logs app is inaccessible and returns a 404`, async () => { + await PageObjects.common.navigateToActualUrl('infraLogs', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + }); + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) ); - await testSubjects.existOrFail('~appNotFoundPageContent'); }); }); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts index 4d54539a4d09e..ea08307ccedd3 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts @@ -80,21 +80,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`logs app is inaccessible and Application Not Found message is rendered`, async () => { - await PageObjects.common.navigateToApp('infraLogs', { + await PageObjects.common.navigateToActualUrl('infraLogs', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, basePath: '/s/custom_space', }); - await testSubjects.existOrFail('~appNotFoundPageContent'); - await PageObjects.common.navigateToUrlWithBrowserHistory( - 'infraLogs', - '/stream', - undefined, - { - basePath: '/s/custom_space', - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) ); - await testSubjects.existOrFail('~appNotFoundPageContent'); }); }); }); diff --git a/x-pack/test/ui_capabilities/common/features.ts b/x-pack/test/ui_capabilities/common/features.ts index 3c015bc21e937..e3febc945c299 100644 --- a/x-pack/test/ui_capabilities/common/features.ts +++ b/x-pack/test/ui_capabilities/common/features.ts @@ -5,7 +5,7 @@ */ interface Feature { - navLinkId: string; + app: string[]; } export interface Features { diff --git a/x-pack/test/ui_capabilities/common/fixtures/plugins/foo_plugin/server/index.ts b/x-pack/test/ui_capabilities/common/fixtures/plugins/foo_plugin/server/index.ts index bff794801119a..5c80b4283a69b 100644 --- a/x-pack/test/ui_capabilities/common/fixtures/plugins/foo_plugin/server/index.ts +++ b/x-pack/test/ui_capabilities/common/fixtures/plugins/foo_plugin/server/index.ts @@ -19,11 +19,11 @@ class FooPlugin implements Plugin { name: 'Foo', icon: 'upArrow', navLinkId: 'foo_plugin', - app: ['kibana'], + app: ['foo_plugin', 'kibana'], catalogue: ['foo'], privileges: { all: { - app: ['kibana'], + app: ['foo_plugin', 'kibana'], catalogue: ['foo'], savedObject: { all: ['foo'], @@ -32,7 +32,7 @@ class FooPlugin implements Plugin { ui: ['create', 'edit', 'delete', 'show'], }, read: { - app: ['kibana'], + app: ['foo_plugin', 'kibana'], catalogue: ['foo'], savedObject: { all: [], diff --git a/x-pack/test/ui_capabilities/common/nav_links_builder.ts b/x-pack/test/ui_capabilities/common/nav_links_builder.ts index b20a499ba7e20..04ab08e08a2ba 100644 --- a/x-pack/test/ui_capabilities/common/nav_links_builder.ts +++ b/x-pack/test/ui_capabilities/common/nav_links_builder.ts @@ -13,11 +13,14 @@ export class NavLinksBuilder { ...features, // management isn't a first-class "feature", but it makes our life easier here to pretend like it is management: { - navLinkId: 'kibana:stack_management', + app: ['kibana:stack_management'], }, // TODO: Temp until navLinkIds fix is merged in appSearch: { - navLinkId: 'appSearch', + app: ['appSearch', 'workplaceSearch'], + }, + kibana: { + app: ['kibana'], }, }; } @@ -38,9 +41,9 @@ export class NavLinksBuilder { private build(callback: buildCallback): Record { const navLinks = {} as Record; for (const [featureId, feature] of Object.entries(this.features)) { - if (feature.navLinkId) { - navLinks[feature.navLinkId] = callback(featureId); - } + feature.app.forEach((app) => { + navLinks[app] = callback(featureId); + }); } return navLinks; diff --git a/x-pack/test/ui_capabilities/common/services/features.ts b/x-pack/test/ui_capabilities/common/services/features.ts index 0f796c1d0a0cc..5f6ec0ad050c7 100644 --- a/x-pack/test/ui_capabilities/common/services/features.ts +++ b/x-pack/test/ui_capabilities/common/services/features.ts @@ -40,7 +40,7 @@ export class FeaturesService { (acc: Features, feature: any) => ({ ...acc, [feature.id]: { - navLinkId: feature.navLinkId, + app: feature.app, }, }), {} diff --git a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts index bb1f3b6eefe4a..7f831973aea5c 100644 --- a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts +++ b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts @@ -52,7 +52,7 @@ export class UICapabilitiesService { }): Promise { const features = await this.featureService.get(); const applications = Object.values(features) - .map((feature) => feature.navLinkId) + .flatMap((feature) => feature.app) .filter((link) => !!link); const spaceUrlPrefix = spaceId ? `/s/${spaceId}` : ''; diff --git a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts index 18838e536cf96..d7a0dfa1cf80a 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts @@ -57,7 +57,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) { expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('navLinks'); expect(uiCapabilities.value!.navLinks).to.eql( - navLinksBuilder.only('management', 'foo') + navLinksBuilder.only('management', 'foo', 'kibana') ); break; case 'legacy_all': From b5a920d8c9cf94a1468d9f9cb022f716e17bdfa3 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 28 Jul 2020 13:50:02 +0200 Subject: [PATCH 36/36] [Uptime] Convert kuery bar to ts (#70310) Co-authored-by: Elastic Machine --- .../__tests__/alert_monitor_status.test.tsx | 10 - .../overview/alerts/alert_monitor_status.tsx | 3 - .../alert_monitor_status.tsx | 4 - .../overview/kuery_bar/kuery_bar.tsx | 22 +- .../kuery_bar/typeahead/click_outside.js | 40 --- .../overview/kuery_bar/typeahead/index.d.ts | 46 --- .../overview/kuery_bar/typeahead/index.js | 245 -------------- .../overview/kuery_bar/typeahead/index.ts | 7 + .../kuery_bar/typeahead/suggestion.js | 140 -------- .../kuery_bar/typeahead/suggestion.tsx | 89 +++++ .../kuery_bar/typeahead/suggestions.js | 111 ------ .../kuery_bar/typeahead/suggestions.tsx | 116 +++++++ .../overview/kuery_bar/typeahead/typehead.tsx | 318 ++++++++++++++++++ .../plugins/uptime/public/pages/overview.tsx | 8 - 14 files changed, 543 insertions(+), 616 deletions(-) delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/click_outside.js delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.d.ts delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.js create mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.ts delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.js create mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.tsx delete mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.js create mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.tsx create mode 100644 x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/typehead.tsx diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx index f3f3d583fd938..f26da59238b20 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/__tests__/alert_monitor_status.test.tsx @@ -17,10 +17,6 @@ describe('alert monitor status component', () => { timerangeUnit: 'h', timerangeCount: 21, }, - autocomplete: { - addQuerySuggestionProvider: jest.fn(), - getQuerySuggestions: jest.fn(), - }, enabled: true, hasFilters: false, isOldAlert: true, @@ -45,12 +41,6 @@ describe('alert monitor status component', () => { /> = (p setAlertParams('search', value)} diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx index 4ac0355f5edc8..50b6fe2aa0ef1 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx @@ -7,7 +7,6 @@ import React, { useMemo, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; -import { DataPublicPluginSetup } from 'src/plugins/data/public'; import { isRight } from 'fp-ts/lib/Either'; import { selectMonitorStatusAlert, @@ -32,7 +31,6 @@ import { useUpdateKueryString } from '../../../../hooks'; interface Props { alertParams: { [key: string]: any }; - autocomplete: DataPublicPluginSetup['autocomplete']; enabled: boolean; numTimes: number; setAlertParams: (key: string, value: any) => void; @@ -43,7 +41,6 @@ interface Props { } export const AlertMonitorStatus: React.FC = ({ - autocomplete, enabled, numTimes, setAlertParams, @@ -122,7 +119,6 @@ export const AlertMonitorStatus: React.FC = ({ return ( ({ suggestions: [], isLoadingIndexPattern: true, @@ -80,7 +85,7 @@ export function KueryBar({ const indexPatternMissing = loading && !indexPattern; - async function onChange(inputValue: string, selectionStart: number) { + async function onChange(inputValue: string, selectionStart: number | null) { if (!indexPattern) { return; } @@ -94,7 +99,7 @@ export function KueryBar({ try { const suggestions = ( - (await autocompleteService.getQuerySuggestions({ + (await autocomplete.getQuerySuggestions({ language: 'kuery', indexPatterns: [indexPattern], query: inputValue, @@ -111,8 +116,7 @@ export function KueryBar({ }, ], })) || [] - ).filter((suggestion) => !startsWith(suggestion.text, 'span.')); - + ).filter((suggestion: QuerySuggestion) => !startsWith(suggestion.text, 'span.')); if (currentRequest !== currentRequestCheck) { return; } @@ -155,8 +159,8 @@ export function KueryBar({ return ( { - this.nodeRef = node; - }; - - onClick = (event) => { - if (this.nodeRef && !this.nodeRef.contains(event.target)) { - this.props.onClickOutside(); - } - }; - - render() { - return ( -
      - {this.props.children} -
      - ); - } -} - -ClickOutside.propTypes = { - onClickOutside: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.d.ts b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.d.ts deleted file mode 100644 index 751170f3b1cf7..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.d.ts +++ /dev/null @@ -1,46 +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 React from 'react'; - -interface TypeaheadProps { - onChange: (inputValue: string, selectionStart: number) => void; - onSubmit: (inputValue: string) => void; - loadMore: () => void; - suggestions: unknown[]; - queryExample: string; - initialValue?: string; - isLoading?: boolean; - disabled?: boolean; -} - -export class Typeahead extends React.Component { - incrementIndex(currentIndex: any): void; - - decrementIndex(currentIndex: any): void; - - onKeyUp(event: any): void; - - onKeyDown(event: any): void; - - selectSuggestion(suggestion: any): void; - - onClickOutside(): void; - - onChangeInputValue(event: any): void; - - onClickInput(event: any): void; - - onClickSuggestion(suggestion: any): void; - - onMouseEnterSuggestion(index: any): void; - - onSubmit(): void; - - render(): any; - - loadMore(): void; -} diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.js b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.js deleted file mode 100644 index 17141235d8bf2..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.js +++ /dev/null @@ -1,245 +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 React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import Suggestions from './suggestions'; -import ClickOutside from './click_outside'; -import { EuiFieldSearch, EuiProgress } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -const KEY_CODES = { - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - ENTER: 13, - ESC: 27, - TAB: 9, -}; - -export class Typeahead extends Component { - state = { - isSuggestionsVisible: false, - index: null, - value: '', - inputIsPristine: true, - lastSubmitted: '', - selected: null, - }; - - static getDerivedStateFromProps(props, state) { - if (state.inputIsPristine && props.initialValue) { - return { - value: props.initialValue, - }; - } - - return null; - } - - incrementIndex = (currentIndex) => { - let nextIndex = currentIndex + 1; - if (currentIndex === null || nextIndex >= this.props.suggestions.length) { - nextIndex = 0; - } - this.setState({ index: nextIndex }); - }; - - decrementIndex = (currentIndex) => { - let previousIndex = currentIndex - 1; - if (previousIndex < 0) { - previousIndex = null; - } - this.setState({ index: previousIndex }); - }; - - onKeyUp = (event) => { - const { selectionStart } = event.target; - const { value } = this.state; - switch (event.keyCode) { - case KEY_CODES.LEFT: - this.setState({ isSuggestionsVisible: true }); - this.props.onChange(value, selectionStart); - break; - case KEY_CODES.RIGHT: - this.setState({ isSuggestionsVisible: true }); - this.props.onChange(value, selectionStart); - break; - } - }; - - onKeyDown = (event) => { - const { isSuggestionsVisible, index, value } = this.state; - switch (event.keyCode) { - case KEY_CODES.DOWN: - event.preventDefault(); - if (isSuggestionsVisible) { - this.incrementIndex(index); - } else { - this.setState({ isSuggestionsVisible: true, index: 0 }); - } - break; - case KEY_CODES.UP: - event.preventDefault(); - if (isSuggestionsVisible) { - this.decrementIndex(index); - } - break; - case KEY_CODES.ENTER: - event.preventDefault(); - if (isSuggestionsVisible && this.props.suggestions[index]) { - this.selectSuggestion(this.props.suggestions[index]); - } else { - this.setState({ isSuggestionsVisible: false }); - this.props.onSubmit(value); - } - break; - case KEY_CODES.ESC: - event.preventDefault(); - this.setState({ isSuggestionsVisible: false }); - break; - case KEY_CODES.TAB: - this.setState({ isSuggestionsVisible: false }); - break; - } - }; - - selectSuggestion = (suggestion) => { - const nextInputValue = - this.state.value.substr(0, suggestion.start) + - suggestion.text + - this.state.value.substr(suggestion.end); - - this.setState({ value: nextInputValue, index: null, selected: suggestion }); - this.props.onChange(nextInputValue, nextInputValue.length); - }; - - onClickOutside = () => { - if (this.state.isSuggestionsVisible) { - this.setState({ isSuggestionsVisible: false }); - this.onSubmit(); - } - }; - - onChangeInputValue = (event) => { - const { value, selectionStart } = event.target; - const hasValue = Boolean(value.trim()); - this.setState({ - value, - inputIsPristine: false, - isSuggestionsVisible: hasValue, - index: null, - }); - - if (!hasValue) { - this.props.onSubmit(value); - } - this.props.onChange(value, selectionStart); - }; - - onClickInput = (event) => { - const { selectionStart } = event.target; - this.props.onChange(this.state.value, selectionStart); - }; - - onClickSuggestion = (suggestion) => { - this.selectSuggestion(suggestion); - this.inputRef.focus(); - }; - - onMouseEnterSuggestion = (index) => { - this.setState({ index }); - }; - - onSubmit = () => { - const { value, lastSubmitted, selected } = this.state; - - if ( - lastSubmitted !== value && - selected && - (selected.type === 'value' || selected.text.trim() === ': *') - ) { - this.props.onSubmit(value); - this.setState({ lastSubmitted: value, selected: null }); - } - }; - - onFocus = () => { - this.setState({ isSuggestionsVisible: true }); - }; - - render() { - return ( - -
      - { - if (node) { - this.inputRef = node; - } - }} - disabled={this.props.disabled} - value={this.state.value} - onKeyDown={this.onKeyDown} - onKeyUp={this.onKeyUp} - onFocus={this.onFocus} - onChange={this.onChangeInputValue} - onClick={this.onClickInput} - autoComplete="off" - spellCheck={false} - /> - - {this.props.isLoading && ( - - )} -
      - - -
      - ); - } -} - -Typeahead.propTypes = { - initialValue: PropTypes.string, - isLoading: PropTypes.bool, - disabled: PropTypes.bool, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - loadMore: PropTypes.func.isRequired, - suggestions: PropTypes.array.isRequired, - queryExample: PropTypes.string.isRequired, -}; - -Typeahead.defaultProps = { - isLoading: false, - disabled: false, - suggestions: [], -}; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.ts b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/index.ts new file mode 100644 index 0000000000000..6bf1226131e29 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/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 { Typeahead } from './typehead'; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.js b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.js deleted file mode 100644 index 615a444d23e73..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.js +++ /dev/null @@ -1,140 +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 React from 'react'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import { EuiIcon } from '@elastic/eui'; -import { - fontFamilyCode, - px, - units, - fontSizes, - unit, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../apm/public/style/variables'; -import { tint } from 'polished'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; - -function getIconColor(type) { - switch (type) { - case 'field': - return theme.euiColorVis7; - case 'value': - return theme.euiColorVis0; - case 'operator': - return theme.euiColorVis1; - case 'conjunction': - return theme.euiColorVis3; - case 'recentSearch': - return theme.euiColorMediumShade; - } -} - -const Description = styled.div` - color: ${theme.euiColorDarkShade}; - - p { - display: inline; - - span { - font-family: ${fontFamilyCode}; - color: ${theme.euiColorFullShade}; - padding: 0 ${px(units.quarter)}; - display: inline-block; - } - } -`; - -const ListItem = styled.button` - width: inherit; - font-size: ${fontSizes.small}; - height: ${px(units.double)}; - align-items: center; - display: flex; - background: ${(props) => (props.selected ? theme.euiColorLightestShade : 'initial')}; - cursor: pointer; - border-radius: ${px(units.quarter)}; - - ${Description} { - p span { - background: ${(props) => - props.selected ? theme.euiColorEmptyShade : theme.euiColorLightestShade}; - } - @media only screen and (max-width: ${theme.euiBreakpoints.s}) { - margin-left: auto; - text-align: end; - } - } -`; - -const Icon = styled.div` - flex: 0 0 ${px(units.double)}; - background: ${(props) => tint(0.1, getIconColor(props.type))}; - color: ${(props) => getIconColor(props.type)}; - width: 100%; - height: 100%; - text-align: center; - line-height: ${px(units.double)}; -`; - -const TextValue = styled.div` - text-align: left; - flex: 0 0 ${px(unit * 12)}; - color: ${theme.euiColorDarkestShade}; - padding: 0 ${px(units.half)}; - - @media only screen and (max-width: ${theme.euiBreakpoints.s}) { - flex: 0 0 ${px(unit * 8)}; - } - @media only screen and (min-width: 1300px) { - flex: 0 0 ${px(unit * 16)}; - } -`; - -function getEuiIconType(type) { - switch (type) { - case 'field': - return 'kqlField'; - case 'value': - return 'kqlValue'; - case 'recentSearch': - return 'search'; - case 'conjunction': - return 'kqlSelector'; - case 'operator': - return 'kqlOperand'; - default: - throw new Error('Unknown type', type); - } -} - -function Suggestion(props) { - return ( - props.onClick(props.suggestion)} - onMouseEnter={props.onMouseEnter} - > - - - - {props.suggestion.text} - {props.suggestion.description} - - ); -} - -Suggestion.propTypes = { - onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, - selected: PropTypes.bool, - suggestion: PropTypes.object.isRequired, - innerRef: PropTypes.func.isRequired, -}; - -export default Suggestion; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.tsx new file mode 100644 index 0000000000000..1dc89d2795309 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestion.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 React, { useRef, useEffect, RefObject } from 'react'; +import styled from 'styled-components'; +import { EuiSuggestItem } from '@elastic/eui'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; + +import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public'; + +const SuggestionItem = styled.div<{ selected: boolean }>` + background: ${(props) => (props.selected ? theme.euiColorLightestShade : 'initial')}; +`; + +function getIconColor(type: string) { + switch (type) { + case 'field': + return 'tint5'; + case 'value': + return 'tint0'; + case 'operator': + return 'tint1'; + case 'conjunction': + return 'tint3'; + case 'recentSearch': + return 'tint10'; + default: + return 'tint5'; + } +} + +function getEuiIconType(type: string) { + switch (type) { + case 'field': + return 'kqlField'; + case 'value': + return 'kqlValue'; + case 'recentSearch': + return 'search'; + case 'conjunction': + return 'kqlSelector'; + case 'operator': + return 'kqlOperand'; + default: + throw new Error(`Unknown type ${type}`); + } +} + +interface SuggestionProps { + onClick: (sug: QuerySuggestion) => void; + onMouseEnter: () => void; + selected: boolean; + suggestion: QuerySuggestion; + innerRef: (node: any) => void; +} + +export const Suggestion: React.FC = ({ + innerRef, + selected, + suggestion, + onClick, + onMouseEnter, +}) => { + const childNode: RefObject = useRef(null); + + useEffect(() => { + if (childNode.current) { + innerRef(childNode.current); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [childNode]); + + return ( + + onClick(suggestion)} + onMouseEnter={onMouseEnter} + // @ts-ignore + description={suggestion.description} + /> + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.js b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.js deleted file mode 100644 index 8d614d7ea1aec..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.js +++ /dev/null @@ -1,111 +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 React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import { isEmpty } from 'lodash'; -import Suggestion from './suggestion'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { units, px, unit } from '../../../../../../apm/public/style/variables'; -import { tint } from 'polished'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; - -const List = styled.ul` - width: 100%; - border: 1px solid ${theme.euiColorLightShade}; - border-radius: ${px(units.quarter)}; - box-shadow: 0px ${px(units.quarter)} ${px(units.double)} ${tint(0.1, theme.euiColorFullShade)}; - position: absolute; - background: #fff; - z-index: 10; - left: 0; - max-height: ${px(unit * 20)}; - overflow: scroll; -`; - -class Suggestions extends Component { - childNodes = []; - - scrollIntoView = () => { - const parent = this.parentNode; - const child = this.childNodes[this.props.index]; - - if (this.props.index == null || !parent || !child) { - return; - } - - const scrollTop = Math.max( - Math.min(parent.scrollTop, child.offsetTop), - child.offsetTop + child.offsetHeight - parent.offsetHeight - ); - - parent.scrollTop = scrollTop; - }; - - handleScroll = () => { - const parent = this.parentNode; - - if (!this.props.loadMore || !parent) { - return; - } - - const position = parent.scrollTop + parent.offsetHeight; - const height = parent.scrollHeight; - const remaining = height - position; - const margin = 50; - - if (!height || !position) { - return; - } - if (remaining <= margin) { - this.props.loadMore(); - } - }; - - componentDidUpdate(prevProps) { - if (prevProps.index !== this.props.index) { - this.scrollIntoView(); - } - } - - render() { - if (!this.props.show || isEmpty(this.props.suggestions)) { - return null; - } - - const suggestions = this.props.suggestions.map((suggestion, index) => { - const key = suggestion + '_' + index; - return ( - (this.childNodes[index] = node)} - selected={index === this.props.index} - suggestion={suggestion} - onClick={this.props.onClick} - onMouseEnter={() => this.props.onMouseEnter(index)} - key={key} - /> - ); - }); - - return ( - (this.parentNode = node)} onScroll={this.handleScroll}> - {suggestions} - - ); - } -} - -Suggestions.propTypes = { - index: PropTypes.number, - onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, - show: PropTypes.bool, - suggestions: PropTypes.array.isRequired, - loadMore: PropTypes.func.isRequired, -}; - -export default Suggestions; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.tsx new file mode 100644 index 0000000000000..dcd8df1ba18ef --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/suggestions.tsx @@ -0,0 +1,116 @@ +/* + * 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, { useRef, useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { isEmpty } from 'lodash'; +import { tint } from 'polished'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { Suggestion } from './suggestion'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { units, px, unit } from '../../../../../../apm/public/style/variables'; +import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public'; + +const List = styled.ul` + width: 100%; + border: 1px solid ${theme.euiColorLightShade}; + border-radius: ${px(units.quarter)}; + box-shadow: 0px ${px(units.quarter)} ${px(units.double)} ${tint(0.1, theme.euiColorFullShade)}; + background: #fff; + z-index: 10; + max-height: ${px(unit * 20)}; + overflow: scroll; + position: absolute; +`; + +interface SuggestionsProps { + index: number; + onClick: (sug: QuerySuggestion) => void; + onMouseEnter: (index: number) => void; + show?: boolean; + suggestions: QuerySuggestion[]; + loadMore: () => void; +} + +export const Suggestions: React.FC = ({ + show, + index, + onClick, + suggestions, + onMouseEnter, + loadMore, +}) => { + const [childNodes, setChildNodes] = useState([]); + + const parentNode = useRef(null); + + useEffect(() => { + const scrollIntoView = () => { + const parent = parentNode.current; + const child = childNodes[index]; + + if (index == null || !parent || !child) { + return; + } + + const scrollTop = Math.max( + Math.min(parent.scrollTop, child.offsetTop), + child.offsetTop + child.offsetHeight - parent.offsetHeight + ); + + parent.scrollTop = scrollTop; + }; + scrollIntoView(); + }, [index, childNodes]); + + if (!show || isEmpty(suggestions)) { + return null; + } + + const handleScroll = () => { + const parent = parentNode.current; + + if (!loadMore || !parent) { + return; + } + + const position = parent.scrollTop + parent.offsetHeight; + const height = parent.scrollHeight; + const remaining = height - position; + const margin = 50; + + if (!height || !position) { + return; + } + if (remaining <= margin) { + loadMore(); + } + }; + + const suggestionsNodes = suggestions.map((suggestion, currIndex) => { + const key = suggestion + '_' + currIndex; + return ( + { + const nodes = childNodes; + nodes[currIndex] = node; + setChildNodes([...nodes]); + }} + selected={currIndex === index} + suggestion={suggestion} + onClick={onClick} + onMouseEnter={() => onMouseEnter(currIndex)} + key={key} + /> + ); + }); + + return ( + + {suggestionsNodes} + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/typehead.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/typehead.tsx new file mode 100644 index 0000000000000..5582818b6f09b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/typeahead/typehead.tsx @@ -0,0 +1,318 @@ +/* + * 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, { KeyboardEvent, ChangeEvent, MouseEvent, useState, useRef, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFieldSearch, EuiProgress, EuiOutsideClickDetector } from '@elastic/eui'; +import { Suggestions } from './suggestions'; +import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public'; + +const KEY_CODES = { + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + ENTER: 13, + ESC: 27, + TAB: 9, +}; + +interface TypeaheadState { + isSuggestionsVisible: boolean; + index: number | null; + value: string; + inputIsPristine: boolean; + lastSubmitted: string; + selected: QuerySuggestion | null; +} + +interface TypeaheadProps { + onChange: (inputValue: string, selectionStart: number | null) => void; + onSubmit: (inputValue: string) => void; + suggestions: QuerySuggestion[]; + queryExample: string; + initialValue?: string; + isLoading?: boolean; + disabled?: boolean; + dataTestSubj: string; + ariaLabel: string; + loadMore: () => void; +} + +export const Typeahead: React.FC = ({ + initialValue, + suggestions, + onChange, + onSubmit, + dataTestSubj, + ariaLabel, + disabled, + isLoading, + loadMore, +}) => { + const [state, setState] = useState({ + isSuggestionsVisible: false, + index: null, + value: '', + inputIsPristine: true, + lastSubmitted: '', + selected: null, + }); + + const inputRef = useRef(); + + useEffect(() => { + if (state.inputIsPristine && initialValue) { + setState((prevState) => ({ + ...prevState, + value: initialValue, + })); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialValue]); + + const incrementIndex = (currentIndex: number) => { + let nextIndex = currentIndex + 1; + if (currentIndex === null || nextIndex >= suggestions.length) { + nextIndex = 0; + } + + setState((prevState) => ({ + ...prevState, + index: nextIndex, + })); + }; + + const decrementIndex = (currentIndex: number) => { + let previousIndex: number | null = currentIndex - 1; + if (previousIndex < 0) { + previousIndex = null; + } + + setState((prevState) => ({ + ...prevState, + index: previousIndex, + })); + }; + + const onKeyUp = (event: KeyboardEvent & ChangeEvent) => { + const { selectionStart } = event.target; + const { value } = state; + switch (event.keyCode) { + case KEY_CODES.LEFT: + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: true, + })); + onChange(value, selectionStart); + break; + case KEY_CODES.RIGHT: + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: true, + })); + onChange(value, selectionStart); + break; + } + }; + + const onKeyDown = (event: KeyboardEvent) => { + const { isSuggestionsVisible, index, value } = state; + switch (event.keyCode) { + case KEY_CODES.DOWN: + event.preventDefault(); + if (isSuggestionsVisible) { + incrementIndex(index!); + } else { + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: true, + index: 0, + })); + } + break; + case KEY_CODES.UP: + event.preventDefault(); + if (isSuggestionsVisible) { + decrementIndex(index!); + } + break; + case KEY_CODES.ENTER: + event.preventDefault(); + if (isSuggestionsVisible && suggestions[index!]) { + selectSuggestion(suggestions[index!]); + } else { + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: false, + })); + + onSubmit(value); + } + break; + case KEY_CODES.ESC: + event.preventDefault(); + + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: false, + })); + + break; + case KEY_CODES.TAB: + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: false, + })); + break; + } + }; + + const selectSuggestion = (suggestion: QuerySuggestion) => { + const nextInputValue = + state.value.substr(0, suggestion.start) + + suggestion.text + + state.value.substr(suggestion.end); + + setState((prevState) => ({ + ...prevState, + value: nextInputValue, + index: null, + selected: suggestion, + })); + + onChange(nextInputValue, nextInputValue.length); + }; + + const onClickOutside = () => { + if (state.isSuggestionsVisible) { + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: false, + })); + + onSuggestionSubmit(); + } + }; + + const onChangeInputValue = (event: ChangeEvent) => { + const { value, selectionStart } = event.target; + const hasValue = Boolean(value.trim()); + + setState((prevState) => ({ + ...prevState, + value, + inputIsPristine: false, + isSuggestionsVisible: hasValue, + index: null, + })); + + if (!hasValue) { + onSubmit(value); + } + onChange(value, selectionStart!); + }; + + const onClickInput = (event: MouseEvent & ChangeEvent) => { + event.stopPropagation(); + const { selectionStart } = event.target; + onChange(state.value, selectionStart!); + }; + + const onFocus = () => { + setState((prevState) => ({ + ...prevState, + isSuggestionsVisible: true, + })); + }; + + const onClickSuggestion = (suggestion: QuerySuggestion) => { + selectSuggestion(suggestion); + if (inputRef.current) inputRef.current.focus(); + }; + + const onMouseEnterSuggestion = (index: number) => { + setState({ ...state, index }); + + setState((prevState) => ({ + ...prevState, + index, + })); + }; + + const onSuggestionSubmit = () => { + const { value, lastSubmitted, selected } = state; + + if ( + lastSubmitted !== value && + selected && + (selected.type === 'value' || selected.text.trim() === ': *') + ) { + onSubmit(value); + + setState((prevState) => ({ + ...prevState, + lastSubmitted: value, + selected: null, + })); + } + }; + + return ( + + +
      + { + if (node) { + inputRef.current = node; + } + }} + disabled={disabled} + value={state.value} + onKeyDown={onKeyDown} + onKeyUp={onKeyUp} + onFocus={onFocus} + onChange={onChangeInputValue} + onClick={onClickInput} + autoComplete="off" + spellCheck={false} + /> + + {isLoading && ( + + )} +
      + + +
      +
      + ); +}; diff --git a/x-pack/plugins/uptime/public/pages/overview.tsx b/x-pack/plugins/uptime/public/pages/overview.tsx index 32c86435913f7..3b58ea1e5cf84 100644 --- a/x-pack/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/plugins/uptime/public/pages/overview.tsx @@ -18,7 +18,6 @@ import { useTrackPageview } from '../../../observability/public'; import { MonitorList } from '../components/overview/monitor_list/monitor_list_container'; import { EmptyState, FilterGroup, KueryBar, ParsingErrorCallout } from '../components/overview'; import { StatusPanel } from '../components/overview/status_panel'; -import { useKibana } from '../../../../../src/plugins/kibana_react/public'; interface Props { loading: boolean; @@ -43,12 +42,6 @@ export const OverviewPageComponent = React.memo( const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams(); const { search, filters: urlFilters } = params; - const { - services: { - data: { autocomplete }, - }, - } = useKibana(); - useTrackPageview({ app: 'uptime', path: 'overview' }); useTrackPageview({ app: 'uptime', path: 'overview', delay: 15000 }); @@ -77,7 +70,6 @@ export const OverviewPageComponent = React.memo( aria-label={i18n.translate('xpack.uptime.filterBar.ariaLabel', { defaultMessage: 'Input filter criteria for the overview page', })} - autocomplete={autocomplete} data-test-subj="xpack.uptime.filterBar" />