From d662cb5f4525e3a808900403f54f3c4f2c2c7097 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Fri, 10 Apr 2020 12:58:18 +0200 Subject: [PATCH 01/21] [ESUI] Move XJson Mode to src/plugins/es_ui_shared (#62758) * Move XJson to x-pack/es_ui_shared Also convert files to TS and clean up use of @ts-ignore * Move console_lang to public * Proper use of mocks * Globally mock out .ace.worker.js modules Having worker code exported in `public` of es_ui_shared poisoned a lot of Jest tests with the need to mock out the ace worker. Instead of adding mocks everywhere we handle the importing in Jest by adding an entry to module mapper. * Remove manual imports of mocks * Delete es_ui_shared/public/mocks for now * Put useXJson code in single place * Import and instantiate xJsonMode * Remove language mocks imports Besides the fact that these paths are wrong these are no longer needed because we mock at use Jest module mapper Co-authored-by: Elastic Machine --- src/dev/jest/config.js | 1 + .../jest/mocks/ace_worker_module_mock.js} | 2 +- .../legacy/console_editor/editor_output.tsx | 2 +- .../send_request_to_es.ts | 2 +- .../__tests__/output_tokenization.test.js | 1 + .../mode/input_highlight_rules.js | 2 +- .../mode/output_highlight_rules.js | 2 +- .../models/legacy_core_editor/mode/script.js | 2 +- .../__tests__/sense_editor.test.js | 2 +- .../models/sense_editor/sense_editor.ts | 2 +- src/plugins/console/public/lib/utils/index.ts | 5 +-- .../console_lang/ace/modes/index.ts} | 2 +- .../elasticsearch_sql_highlight_rules.ts | 0 .../ace/modes/lexer_rules/index.ts} | 0 .../lexer_rules/script_highlight_rules.ts} | 4 +-- .../lexer_rules/x_json_highlight_rules.ts} | 12 +++---- .../console_lang/ace/modes/x_json/index.ts | 19 ++++++++++ .../ace/modes/x_json/worker/index.ts | 26 ++++++++++++++ .../ace/modes/x_json/worker/worker.d.ts | 25 +++++++++++++ .../modes/x_json/worker/x_json.ace.worker.js | 0 .../console_lang/ace/modes/x_json/x_json.ts} | 35 ++++++++++++++++--- .../{ => public}/console_lang/index.ts | 3 ++ .../{ => public}/console_lang/lib/index.ts | 0 .../json_xjson_translation_tools.test.ts | 0 .../__tests__/utils_string_collapsing.txt | 0 .../__tests__/utils_string_expanding.txt | 0 .../lib/json_xjson_translation_tools/index.ts | 0 src/plugins/es_ui_shared/public/index.ts | 11 ++++++ .../static/ace_x_json/hooks/index.ts | 20 +++++++++++ .../ace_x_json/hooks/use_x_json.ts} | 26 ++++++++------ x-pack/dev-tools/jest/create_jest_config.js | 1 + .../console_lang/ace/modes/index.ts | 7 ---- .../console_lang/ace/modes/x_json/index.ts | 7 ---- .../ace/modes/x_json/worker/index.ts | 13 ------- .../ace/modes/x_json/worker/worker.d.ts | 12 ------- .../console_lang/ace/modes/x_json/x_json.ts | 34 ------------------ .../es_ui_shared/console_lang/index.ts | 7 ---- .../es_ui_shared/console_lang/mocks.ts | 9 ----- .../components/custom_hooks/index.ts | 1 - .../custom_hooks/use_x_json_mode.ts | 26 -------------- .../create_analytics_advanced_editor.tsx | 5 ++- .../use_create_analytics_form/reducer.ts | 2 +- .../ml_job_editor/ml_job_editor.tsx | 5 ++- x-pack/plugins/ml/shared_imports.ts | 5 ++- .../public/application/editor/editor.test.tsx | 2 -- .../public/application/editor/init_editor.ts | 2 +- .../utils/check_for_json_errors.ts | 2 +- .../public/app/hooks/use_x_json_mode.ts | 20 ----------- .../step_define/step_define_form.tsx | 4 ++- .../transform/public/shared_imports.ts | 4 +-- .../builtin_action_types/es_index.tsx | 2 +- .../public/application/lib/use_x_json_mode.ts | 25 ------------- .../watch_create_json.test.ts | 2 -- .../watch_create_threshold.test.tsx | 2 -- .../client_integration/watch_edit.test.ts | 1 - .../client_integration/watch_list.test.ts | 1 - .../client_integration/watch_status.test.ts | 1 - .../json_watch_edit/json_watch_edit_form.tsx | 3 +- .../json_watch_edit_simulate.tsx | 2 +- .../json_watch_edit/use_x_json_mode.ts | 24 ------------- .../public/application/shared_imports.ts | 2 ++ 61 files changed, 193 insertions(+), 246 deletions(-) rename src/{plugins/es_ui_shared/console_lang/ace/modes/x_json/index.ts => dev/jest/mocks/ace_worker_module_mock.js} (94%) rename src/plugins/es_ui_shared/{console_lang/ace/modes/index.js => public/console_lang/ace/modes/index.ts} (94%) rename src/plugins/es_ui_shared/{ => public}/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts (100%) rename src/plugins/es_ui_shared/{console_lang/ace/modes/lexer_rules/index.js => public/console_lang/ace/modes/lexer_rules/index.ts} (100%) rename src/plugins/es_ui_shared/{console_lang/ace/modes/lexer_rules/script_highlight_rules.js => public/console_lang/ace/modes/lexer_rules/script_highlight_rules.ts} (97%) rename src/plugins/es_ui_shared/{console_lang/ace/modes/lexer_rules/x_json_highlight_rules.js => public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts} (94%) create mode 100644 src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/index.ts create mode 100644 src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/index.ts create mode 100644 src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/worker.d.ts rename x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/worker.js => src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/x_json.ace.worker.js (100%) rename src/plugins/es_ui_shared/{console_lang/ace/modes/x_json/x_json_mode.ts => public/console_lang/ace/modes/x_json/x_json.ts} (61%) rename src/plugins/es_ui_shared/{ => public}/console_lang/index.ts (92%) rename src/plugins/es_ui_shared/{ => public}/console_lang/lib/index.ts (100%) rename src/plugins/es_ui_shared/{ => public}/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts (100%) rename src/plugins/es_ui_shared/{ => public}/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_collapsing.txt (100%) rename src/plugins/es_ui_shared/{ => public}/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt (100%) rename src/plugins/es_ui_shared/{ => public}/console_lang/lib/json_xjson_translation_tools/index.ts (100%) create mode 100644 src/plugins/es_ui_shared/static/ace_x_json/hooks/index.ts rename src/plugins/es_ui_shared/{console_lang/ace/modes/index.d.ts => static/ace_x_json/hooks/use_x_json.ts} (60%) delete mode 100644 x-pack/plugins/es_ui_shared/console_lang/ace/modes/index.ts delete mode 100644 x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/index.ts delete mode 100644 x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/index.ts delete mode 100644 x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/worker.d.ts delete mode 100644 x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/x_json.ts delete mode 100644 x-pack/plugins/es_ui_shared/console_lang/index.ts delete mode 100644 x-pack/plugins/es_ui_shared/console_lang/mocks.ts delete mode 100644 x-pack/plugins/ml/public/application/components/custom_hooks/use_x_json_mode.ts delete mode 100644 x-pack/plugins/transform/public/app/hooks/use_x_json_mode.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/lib/use_x_json_mode.ts delete mode 100644 x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/use_x_json_mode.ts diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index a941735c7840e..7da14e0dfe51b 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -62,6 +62,7 @@ export default { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/dev/jest/mocks/file_mock.js', '\\.(css|less|scss)$': '/src/dev/jest/mocks/style_mock.js', + '\\.ace\\.worker.js$': '/src/dev/jest/mocks/ace_worker_module_mock.js', }, setupFiles: [ '/src/dev/jest/setup/babel_polyfill.js', diff --git a/src/plugins/es_ui_shared/console_lang/ace/modes/x_json/index.ts b/src/dev/jest/mocks/ace_worker_module_mock.js similarity index 94% rename from src/plugins/es_ui_shared/console_lang/ace/modes/x_json/index.ts rename to src/dev/jest/mocks/ace_worker_module_mock.js index caa7b518b8b66..9d267f494f8bf 100644 --- a/src/plugins/es_ui_shared/console_lang/ace/modes/x_json/index.ts +++ b/src/dev/jest/mocks/ace_worker_module_mock.js @@ -17,4 +17,4 @@ * under the License. */ -export { XJsonMode } from './x_json_mode'; +module.exports = ''; diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx index 36d90bb6bff1a..8510aaebfaa1e 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor_output.tsx @@ -20,7 +20,7 @@ import { EuiScreenReaderOnly } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useRef } from 'react'; -import { expandLiteralStrings } from '../../../../../../../es_ui_shared/console_lang/lib'; +import { expandLiteralStrings } from '../../../../../../../es_ui_shared/public'; import { useEditorReadContext, useRequestReadContext, diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts index 102f90a9feb6f..cfbd5691bc22b 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts @@ -18,7 +18,7 @@ */ import { extractDeprecationMessages } from '../../../lib/utils'; -import { collapseLiteralStrings } from '../../../../../es_ui_shared/console_lang/lib'; +import { collapseLiteralStrings } from '../../../../../es_ui_shared/public'; // @ts-ignore import * as es from '../../../lib/es/es'; import { BaseResponseType } from '../../../types'; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/__tests__/output_tokenization.test.js b/src/plugins/console/public/application/models/legacy_core_editor/__tests__/output_tokenization.test.js index 6a93d007a6cdc..5c86b0a1d2092 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/__tests__/output_tokenization.test.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/__tests__/output_tokenization.test.js @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import '../legacy_core_editor.test.mocks'; const $ = require('jquery'); import RowParser from '../../../../lib/row_parser'; import ace from 'brace'; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/input_highlight_rules.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/input_highlight_rules.js index 2c1b30f806f95..29f192f4ea858 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/input_highlight_rules.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/input_highlight_rules.js @@ -18,7 +18,7 @@ */ const ace = require('brace'); -import { addXJsonToRules } from '../../../../../../es_ui_shared/console_lang'; +import { addXJsonToRules } from '../../../../../../es_ui_shared/public'; export function addEOL(tokens, reg, nextIfEOL, normalNext) { if (typeof reg === 'object') { diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.js index e27222ebd65e9..c9d538ab6ceb1 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.js @@ -19,7 +19,7 @@ const ace = require('brace'); import 'brace/mode/json'; -import { addXJsonToRules } from '../../../../../../es_ui_shared/console_lang'; +import { addXJsonToRules } from '../../../../../../es_ui_shared/public'; const oop = ace.acequire('ace/lib/oop'); const JsonHighlightRules = ace.acequire('ace/mode/json_highlight_rules').JsonHighlightRules; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js index 13ae329380221..89adea8921c07 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/script.js @@ -18,7 +18,7 @@ */ import ace from 'brace'; -import { ScriptHighlightRules } from '../../../../../../es_ui_shared/console_lang'; +import { ScriptHighlightRules } from '../../../../../../es_ui_shared/public'; const oop = ace.acequire('ace/lib/oop'); const TextMode = ace.acequire('ace/mode/text').Mode; diff --git a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js index 63f97345bc9ff..6afc03df13b4c 100644 --- a/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js +++ b/src/plugins/console/public/application/models/sense_editor/__tests__/sense_editor.test.js @@ -22,7 +22,7 @@ import $ from 'jquery'; import _ from 'lodash'; import { create } from '../create'; -import { collapseLiteralStrings } from '../../../../../../es_ui_shared/console_lang/lib'; +import { collapseLiteralStrings } from '../../../../../../es_ui_shared/public'; const editorInput1 = require('./editor_input1.txt'); describe('Editor', () => { diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts index b1444bdf2bbab..9bcd3a6872196 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; import RowParser from '../../../lib/row_parser'; -import { collapseLiteralStrings } from '../../../../../es_ui_shared/console_lang/lib'; +import { collapseLiteralStrings } from '../../../../../es_ui_shared/public'; import * as utils from '../../../lib/utils'; // @ts-ignore diff --git a/src/plugins/console/public/lib/utils/index.ts b/src/plugins/console/public/lib/utils/index.ts index f66c952dd3af7..0ebea0f9d4055 100644 --- a/src/plugins/console/public/lib/utils/index.ts +++ b/src/plugins/console/public/lib/utils/index.ts @@ -18,10 +18,7 @@ */ import _ from 'lodash'; -import { - expandLiteralStrings, - collapseLiteralStrings, -} from '../../../../es_ui_shared/console_lang/lib'; +import { expandLiteralStrings, collapseLiteralStrings } from '../../../../es_ui_shared/public'; export function textFromRequest(request: any) { let data = request.data; diff --git a/src/plugins/es_ui_shared/console_lang/ace/modes/index.js b/src/plugins/es_ui_shared/public/console_lang/ace/modes/index.ts similarity index 94% rename from src/plugins/es_ui_shared/console_lang/ace/modes/index.js rename to src/plugins/es_ui_shared/public/console_lang/ace/modes/index.ts index 955f23116f659..f9e016c5078af 100644 --- a/src/plugins/es_ui_shared/console_lang/ace/modes/index.js +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/index.ts @@ -24,4 +24,4 @@ export { addXJsonToRules, } from './lexer_rules'; -export { XJsonMode, installXJsonMode } from './x_json'; +export { installXJsonMode, XJsonMode } from './x_json'; diff --git a/src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts similarity index 100% rename from src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts rename to src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts diff --git a/src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/index.js b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/index.ts similarity index 100% rename from src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/index.js rename to src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/index.ts diff --git a/src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/script_highlight_rules.js b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/script_highlight_rules.ts similarity index 97% rename from src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/script_highlight_rules.js rename to src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/script_highlight_rules.ts index b3999c76493c0..4b2e965276a3c 100644 --- a/src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/script_highlight_rules.js +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/script_highlight_rules.ts @@ -17,13 +17,13 @@ * under the License. */ -const ace = require('brace'); +import ace from 'brace'; const oop = ace.acequire('ace/lib/oop'); const { TextHighlightRules } = ace.acequire('ace/mode/text_highlight_rules'); const painlessKeywords = 'def|int|long|byte|String|float|double|char|null|if|else|while|do|for|continue|break|new|try|catch|throw|this|instanceof|return|ctx'; -export function ScriptHighlightRules() { +export function ScriptHighlightRules(this: any) { this.name = 'ScriptHighlightRules'; this.$rules = { start: [ diff --git a/src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.js b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts similarity index 94% rename from src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.js rename to src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts index 14323b9320330..3663b92d92a6f 100644 --- a/src/plugins/es_ui_shared/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.js +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/lexer_rules/x_json_highlight_rules.ts @@ -22,14 +22,14 @@ import ace from 'brace'; import 'brace/mode/json'; import { ElasticsearchSqlHighlightRules } from './elasticsearch_sql_highlight_rules'; -const { ScriptHighlightRules } = require('./script_highlight_rules'); +import { ScriptHighlightRules } from './script_highlight_rules'; const { JsonHighlightRules } = ace.acequire('ace/mode/json_highlight_rules'); const oop = ace.acequire('ace/lib/oop'); -const jsonRules = function(root) { +const jsonRules = function(root: any) { root = root ? root : 'json'; - const rules = {}; + const rules: any = {}; const xJsonRules = [ { token: [ @@ -135,7 +135,7 @@ const jsonRules = function(root) { merge: false, push: true, }, - ].concat(xJsonRules); + ].concat(xJsonRules as any); rules.string_literal = [ { @@ -151,7 +151,7 @@ const jsonRules = function(root) { return rules; }; -export function XJsonHighlightRules() { +export function XJsonHighlightRules(this: any) { this.$rules = { ...jsonRules('start'), }; @@ -175,7 +175,7 @@ export function XJsonHighlightRules() { oop.inherits(XJsonHighlightRules, JsonHighlightRules); -export function addToRules(otherRules, embedUnder) { +export function addToRules(otherRules: any, embedUnder: any) { otherRules.$rules = _.defaultsDeep(otherRules.$rules, jsonRules(embedUnder)); otherRules.embedRules(ScriptHighlightRules, 'script-', [ { diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/index.ts b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/index.ts new file mode 100644 index 0000000000000..6fd819bb4e9eb --- /dev/null +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { installXJsonMode, XJsonMode } from './x_json'; diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/index.ts b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/index.ts new file mode 100644 index 0000000000000..0f884765be05e --- /dev/null +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/index.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +// @ts-ignore +import src from '!!raw-loader!./x_json.ace.worker.js'; + +export const workerModule = { + id: 'ace/mode/json_worker', + src, +}; diff --git a/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/worker.d.ts b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/worker.d.ts new file mode 100644 index 0000000000000..d2363a2610689 --- /dev/null +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/worker.d.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +// Satisfy TS's requirements that the module be declared per './index.ts'. +declare module '!!raw-loader!./worker.js' { + const content: string; + // eslint-disable-next-line import/no-default-export + export default content; +} diff --git a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/worker.js b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/x_json.ace.worker.js similarity index 100% rename from x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/worker.js rename to src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/worker/x_json.ace.worker.js diff --git a/src/plugins/es_ui_shared/console_lang/ace/modes/x_json/x_json_mode.ts b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/x_json.ts similarity index 61% rename from src/plugins/es_ui_shared/console_lang/ace/modes/x_json/x_json_mode.ts rename to src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/x_json.ts index 9f804c29a5d27..9fbfedba1d23f 100644 --- a/src/plugins/es_ui_shared/console_lang/ace/modes/x_json/x_json_mode.ts +++ b/src/plugins/es_ui_shared/public/console_lang/ace/modes/x_json/x_json.ts @@ -18,23 +18,50 @@ */ import ace from 'brace'; - import { XJsonHighlightRules } from '../index'; +import { workerModule } from './worker'; + +const { WorkerClient } = ace.acequire('ace/worker/worker_client'); const oop = ace.acequire('ace/lib/oop'); + const { Mode: JSONMode } = ace.acequire('ace/mode/json'); const { Tokenizer: AceTokenizer } = ace.acequire('ace/tokenizer'); const { MatchingBraceOutdent } = ace.acequire('ace/mode/matching_brace_outdent'); const { CstyleBehaviour } = ace.acequire('ace/mode/behaviour/cstyle'); const { FoldMode: CStyleFoldMode } = ace.acequire('ace/mode/folding/cstyle'); -export function XJsonMode(this: any) { - const ruleset: any = new XJsonHighlightRules(); +const XJsonMode: any = function XJsonMode(this: any) { + const ruleset: any = new (XJsonHighlightRules as any)(); ruleset.normalizeRules(); this.$tokenizer = new AceTokenizer(ruleset.getRules()); this.$outdent = new MatchingBraceOutdent(); this.$behaviour = new CstyleBehaviour(); this.foldingRules = new CStyleFoldMode(); -} +}; oop.inherits(XJsonMode, JSONMode); + +// Then clobber `createWorker` method to install our worker source. Per ace's wiki: https://github.com/ajaxorg/ace/wiki/Syntax-validation +(XJsonMode.prototype as any).createWorker = function(session: ace.IEditSession) { + const xJsonWorker = new WorkerClient(['ace'], workerModule, 'JsonWorker'); + + xJsonWorker.attachToDocument(session.getDocument()); + + xJsonWorker.on('annotate', function(e: { data: any }) { + session.setAnnotations(e.data); + }); + + xJsonWorker.on('terminate', function() { + session.clearAnnotations(); + }); + + return xJsonWorker; +}; + +export { XJsonMode }; + +export function installXJsonMode(editor: ace.Editor) { + const session = editor.getSession(); + session.setMode(new XJsonMode()); +} diff --git a/src/plugins/es_ui_shared/console_lang/index.ts b/src/plugins/es_ui_shared/public/console_lang/index.ts similarity index 92% rename from src/plugins/es_ui_shared/console_lang/index.ts rename to src/plugins/es_ui_shared/public/console_lang/index.ts index a4958911af412..7d83191569622 100644 --- a/src/plugins/es_ui_shared/console_lang/index.ts +++ b/src/plugins/es_ui_shared/public/console_lang/index.ts @@ -26,4 +26,7 @@ export { XJsonHighlightRules, addXJsonToRules, XJsonMode, + installXJsonMode, } from './ace/modes'; + +export { expandLiteralStrings, collapseLiteralStrings } from './lib'; diff --git a/src/plugins/es_ui_shared/console_lang/lib/index.ts b/src/plugins/es_ui_shared/public/console_lang/lib/index.ts similarity index 100% rename from src/plugins/es_ui_shared/console_lang/lib/index.ts rename to src/plugins/es_ui_shared/public/console_lang/lib/index.ts diff --git a/src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts similarity index 100% rename from src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts rename to src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts diff --git a/src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_collapsing.txt b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_collapsing.txt similarity index 100% rename from src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_collapsing.txt rename to src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_collapsing.txt diff --git a/src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt similarity index 100% rename from src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt rename to src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt diff --git a/src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools/index.ts b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts similarity index 100% rename from src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools/index.ts rename to src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index 6db6248f4c68f..a0371bf351193 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -36,6 +36,17 @@ export { indices } from './indices'; export { useUIAceKeyboardMode } from './use_ui_ace_keyboard_mode'; +export { + installXJsonMode, + XJsonMode, + ElasticsearchSqlHighlightRules, + addXJsonToRules, + ScriptHighlightRules, + XJsonHighlightRules, + collapseLiteralStrings, + expandLiteralStrings, +} from './console_lang'; + /** dummy plugin, we just want esUiShared to have its own bundle */ export function plugin() { return new (class EsUiSharedPlugin { diff --git a/src/plugins/es_ui_shared/static/ace_x_json/hooks/index.ts b/src/plugins/es_ui_shared/static/ace_x_json/hooks/index.ts new file mode 100644 index 0000000000000..1d2c33a9f0f47 --- /dev/null +++ b/src/plugins/es_ui_shared/static/ace_x_json/hooks/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { useXJsonMode } from './use_x_json'; diff --git a/src/plugins/es_ui_shared/console_lang/ace/modes/index.d.ts b/src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts similarity index 60% rename from src/plugins/es_ui_shared/console_lang/ace/modes/index.d.ts rename to src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts index 06c9f9a51ea68..2b0bf0c8a3a7c 100644 --- a/src/plugins/es_ui_shared/console_lang/ace/modes/index.d.ts +++ b/src/plugins/es_ui_shared/static/ace_x_json/hooks/use_x_json.ts @@ -17,16 +17,22 @@ * under the License. */ -import { Editor } from 'brace'; +import { useState } from 'react'; +import { XJsonMode, collapseLiteralStrings, expandLiteralStrings } from '../../../public'; -export declare const ElasticsearchSqlHighlightRules: FunctionConstructor; -export declare const ScriptHighlightRules: FunctionConstructor; -export declare const XJsonHighlightRules: FunctionConstructor; +const xJsonMode = new XJsonMode(); -export declare const XJsonMode: FunctionConstructor; +export const useXJsonMode = (json: Record | string | null) => { + const [xJson, setXJson] = useState(() => + json === null + ? '' + : expandLiteralStrings(typeof json === 'string' ? json : JSON.stringify(json, null, 2)) + ); -/** - * @param otherRules Another Ace ruleset - * @param embedUnder The state name under which the rules will be embedded. Defaults to "json". - */ -export declare const addXJsonToRules: (otherRules: any, embedUnder?: string) => void; + return { + xJson, + setXJson, + xJsonMode, + convertToJson: collapseLiteralStrings, + }; +}; diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index 1f4311ae9bcf6..3068cdd0daa5b 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -28,6 +28,7 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath, '\\.module.(css|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/css_module_mock.js`, '\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`, + '\\.ace\\.worker.js$': `${kibanaDirectory}/src/dev/jest/mocks/ace_worker_module_mock.js`, '^test_utils/enzyme_helpers': `${xPackKibanaDirectory}/test_utils/enzyme_helpers.tsx`, '^test_utils/find_test_subject': `${xPackKibanaDirectory}/test_utils/find_test_subject.ts`, '^test_utils/stub_web_worker': `${xPackKibanaDirectory}/test_utils/stub_web_worker.ts`, diff --git a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/index.ts b/x-pack/plugins/es_ui_shared/console_lang/ace/modes/index.ts deleted file mode 100644 index ae3c9962ecadb..0000000000000 --- a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/index.ts +++ /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. - */ - -export { installXJsonMode, XJsonMode } from './x_json'; diff --git a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/index.ts b/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/index.ts deleted file mode 100644 index ae3c9962ecadb..0000000000000 --- a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/index.ts +++ /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. - */ - -export { installXJsonMode, XJsonMode } from './x_json'; diff --git a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/index.ts b/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/index.ts deleted file mode 100644 index 0e40fd335dd31..0000000000000 --- a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/index.ts +++ /dev/null @@ -1,13 +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. - */ - -// @ts-ignore -import src from '!!raw-loader!./worker.js'; - -export const workerModule = { - id: 'ace/mode/json_worker', - src, -}; diff --git a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/worker.d.ts b/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/worker.d.ts deleted file mode 100644 index 4ebad4f2ef91c..0000000000000 --- a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/worker.d.ts +++ /dev/null @@ -1,12 +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. - */ - -// Satisfy TS's requirements that the module be declared per './index.ts'. -declare module '!!raw-loader!./worker.js' { - const content: string; - // eslint-disable-next-line import/no-default-export - export default content; -} diff --git a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/x_json.ts b/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/x_json.ts deleted file mode 100644 index bfeca045bea02..0000000000000 --- a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/x_json.ts +++ /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 ace from 'brace'; -import { XJsonMode } from '../../../../../../../src/plugins/es_ui_shared/console_lang'; -import { workerModule } from './worker'; -const { WorkerClient } = ace.acequire('ace/worker/worker_client'); - -// Then clobber `createWorker` method to install our worker source. Per ace's wiki: https://github.com/ajaxorg/ace/wiki/Syntax-validation -(XJsonMode.prototype as any).createWorker = function(session: ace.IEditSession) { - const xJsonWorker = new WorkerClient(['ace'], workerModule, 'JsonWorker'); - - xJsonWorker.attachToDocument(session.getDocument()); - - xJsonWorker.on('annotate', function(e: { data: any }) { - session.setAnnotations(e.data); - }); - - xJsonWorker.on('terminate', function() { - session.clearAnnotations(); - }); - - return xJsonWorker; -}; - -export { XJsonMode }; - -export function installXJsonMode(editor: ace.Editor) { - const session = editor.getSession(); - session.setMode(new (XJsonMode as any)()); -} diff --git a/x-pack/plugins/es_ui_shared/console_lang/index.ts b/x-pack/plugins/es_ui_shared/console_lang/index.ts deleted file mode 100644 index b5fe3a554e34d..0000000000000 --- a/x-pack/plugins/es_ui_shared/console_lang/index.ts +++ /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. - */ - -export { XJsonMode, installXJsonMode } from './ace/modes'; diff --git a/x-pack/plugins/es_ui_shared/console_lang/mocks.ts b/x-pack/plugins/es_ui_shared/console_lang/mocks.ts deleted file mode 100644 index 68480282ddc03..0000000000000 --- a/x-pack/plugins/es_ui_shared/console_lang/mocks.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -jest.mock('./ace/modes/x_json/worker', () => ({ - workerModule: { id: 'ace/mode/json_worker', src: '' }, -})); diff --git a/x-pack/plugins/ml/public/application/components/custom_hooks/index.ts b/x-pack/plugins/ml/public/application/components/custom_hooks/index.ts index dfd74d8970cb4..ffead802bd6f9 100644 --- a/x-pack/plugins/ml/public/application/components/custom_hooks/index.ts +++ b/x-pack/plugins/ml/public/application/components/custom_hooks/index.ts @@ -5,4 +5,3 @@ */ export { usePartialState } from './use_partial_state'; -export { useXJsonMode, xJsonMode } from './use_x_json_mode'; diff --git a/x-pack/plugins/ml/public/application/components/custom_hooks/use_x_json_mode.ts b/x-pack/plugins/ml/public/application/components/custom_hooks/use_x_json_mode.ts deleted file mode 100644 index c979632db54d6..0000000000000 --- a/x-pack/plugins/ml/public/application/components/custom_hooks/use_x_json_mode.ts +++ /dev/null @@ -1,26 +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 { useState } from 'react'; -import { - collapseLiteralStrings, - expandLiteralStrings, - XJsonMode, -} from '../../../../shared_imports'; - -// @ts-ignore -export const xJsonMode = new XJsonMode(); - -export const useXJsonMode = (json: string) => { - const [xJson, setXJson] = useState(expandLiteralStrings(json)); - - return { - xJson, - setXJson, - xJsonMode, - convertToJson: collapseLiteralStrings, - }; -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx index a3e5da5e2d039..cef03cc0d0c76 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx @@ -18,8 +18,11 @@ import { import { i18n } from '@kbn/i18n'; +import { XJsonMode } from '../../../../../../../shared_imports'; + +const xJsonMode = new XJsonMode(); + import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; -import { xJsonMode } from '../../../../../components/custom_hooks'; export const CreateAnalyticsAdvancedEditor: FC = ({ actions, state }) => { const { setAdvancedEditorRawString, setFormState } = actions; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index ded6e50947035..d55eb14a20e29 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -11,7 +11,7 @@ import numeral from '@elastic/numeral'; import { isEmpty } from 'lodash'; import { isValidIndexName } from '../../../../../../../common/util/es_utils'; -import { collapseLiteralStrings } from '../../../../../../../../../../src/plugins/es_ui_shared/console_lang/lib/json_xjson_translation_tools'; +import { collapseLiteralStrings } from '../../../../../../../../../../src/plugins/es_ui_shared/public'; import { Action, ACTION } from './actions'; import { getInitialState, getJobConfigFromFormState, State } from './state'; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/ml_job_editor/ml_job_editor.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/ml_job_editor/ml_job_editor.tsx index 0633c62f754e0..c10212ee31564 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/ml_job_editor/ml_job_editor.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/ml_job_editor/ml_job_editor.tsx @@ -7,10 +7,9 @@ import React, { FC } from 'react'; import { EuiCodeEditor, EuiCodeEditorProps } from '@elastic/eui'; -import { expandLiteralStrings } from '../../../../../../shared_imports'; -import { xJsonMode } from '../../../../components/custom_hooks'; +import { expandLiteralStrings, XJsonMode } from '../../../../../../shared_imports'; -export const ML_EDITOR_MODE = { TEXT: 'text', JSON: 'json', XJSON: xJsonMode }; +export const ML_EDITOR_MODE = { TEXT: 'text', JSON: 'json', XJSON: new XJsonMode() }; interface MlJobEditorProps { value: string; diff --git a/x-pack/plugins/ml/shared_imports.ts b/x-pack/plugins/ml/shared_imports.ts index 94ce8c82f1d95..a82ed5387818d 100644 --- a/x-pack/plugins/ml/shared_imports.ts +++ b/x-pack/plugins/ml/shared_imports.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { XJsonMode } from '../../../src/plugins/es_ui_shared/console_lang/ace/modes/x_json'; - export { + XJsonMode, collapseLiteralStrings, expandLiteralStrings, -} from '../../../src/plugins/es_ui_shared/console_lang/lib'; +} from '../../../src/plugins/es_ui_shared/public'; diff --git a/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx b/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx index ad56b3957eb74..e56fc79156666 100644 --- a/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/editor/editor.test.tsx @@ -6,8 +6,6 @@ import 'brace'; import 'brace/mode/json'; -import '../../../../es_ui_shared/console_lang/mocks'; - import { registerTestBed } from '../../../../../test_utils'; import { Editor, Props } from '.'; diff --git a/x-pack/plugins/searchprofiler/public/application/editor/init_editor.ts b/x-pack/plugins/searchprofiler/public/application/editor/init_editor.ts index 6f19ce12eb639..3ad92531e4367 100644 --- a/x-pack/plugins/searchprofiler/public/application/editor/init_editor.ts +++ b/x-pack/plugins/searchprofiler/public/application/editor/init_editor.ts @@ -5,7 +5,7 @@ */ import ace from 'brace'; -import { installXJsonMode } from '../../../../es_ui_shared/console_lang'; +import { installXJsonMode } from '../../../../../../src/plugins/es_ui_shared/public'; export function initializeEditor({ el, diff --git a/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.ts b/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.ts index 99687de0f1440..58a62c4636c25 100644 --- a/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.ts +++ b/x-pack/plugins/searchprofiler/public/application/utils/check_for_json_errors.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { collapseLiteralStrings } from '../../../../../../src/plugins/es_ui_shared/console_lang/lib'; +import { collapseLiteralStrings } from '../../../../../../src/plugins/es_ui_shared/public'; export function checkForParseErrors(json: string) { const sanitizedJson = collapseLiteralStrings(json); diff --git a/x-pack/plugins/transform/public/app/hooks/use_x_json_mode.ts b/x-pack/plugins/transform/public/app/hooks/use_x_json_mode.ts deleted file mode 100644 index 1017ce198ff29..0000000000000 --- a/x-pack/plugins/transform/public/app/hooks/use_x_json_mode.ts +++ /dev/null @@ -1,20 +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 { useState } from 'react'; -import { collapseLiteralStrings, expandLiteralStrings, XJsonMode } from '../../shared_imports'; - -export const xJsonMode = new XJsonMode(); - -export const useXJsonMode = (json: string) => { - const [xJson, setXJson] = useState(expandLiteralStrings(json)); - - return { - xJson, - setXJson, - xJsonMode, - convertToJson: collapseLiteralStrings, - }; -}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 5e0eb7ee08361..320e405b5d437 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -33,11 +33,12 @@ import { QueryStringInput, } from '../../../../../../../../../src/plugins/data/public'; +import { useXJsonMode } from '../../../../../../../../../src/plugins/es_ui_shared/static/ace_x_json/hooks'; + import { PivotPreview } from '../../../../components/pivot_preview'; import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; import { SavedSearchQuery, SearchItems } from '../../../../hooks/use_search_items'; -import { useXJsonMode, xJsonMode } from '../../../../hooks/use_x_json_mode'; import { useToastNotifications } from '../../../../app_dependencies'; import { TransformPivotConfig } from '../../../../common'; import { dictionaryToArray, Dictionary } from '../../../../../../common/types/common'; @@ -432,6 +433,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, convertToJson, setXJson: setAdvancedEditorConfig, xJson: advancedEditorConfig, + xJsonMode, } = useXJsonMode(stringifiedPivotConfig); useEffect(() => { diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index 8eb42ad677c0f..494b6db6aafe0 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -5,11 +5,11 @@ */ export { createSavedSearchesLoader } from '../../../../src/plugins/discover/public'; -export { XJsonMode } from '../../es_ui_shared/console_lang/ace/modes/x_json'; export { + XJsonMode, collapseLiteralStrings, expandLiteralStrings, -} from '../../../../src/plugins/es_ui_shared/console_lang/lib'; +} from '../../../../src/plugins/es_ui_shared/public'; export { UseRequestConfig, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx index 9bd6a39d216e3..15f68e6a9f441 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx @@ -17,6 +17,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { useXJsonMode } from '../../../../../../../src/plugins/es_ui_shared/static/ace_x_json/hooks'; import { ActionTypeModel, ActionConnectorFieldsProps, @@ -31,7 +32,6 @@ import { getIndexOptions, getIndexPatterns, } from '../../../common/index_controls'; -import { useXJsonMode } from '../../lib/use_x_json_mode'; import { AddMessageVariables } from '../add_message_variables'; export function getActionType(): ActionTypeModel { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/use_x_json_mode.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/use_x_json_mode.ts deleted file mode 100644 index b677924724ffe..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/use_x_json_mode.ts +++ /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 { useState } from 'react'; -import { XJsonMode } from '../../../../../../src/plugins/es_ui_shared/console_lang/ace/modes/x_json'; -import { - collapseLiteralStrings, - expandLiteralStrings, -} from '../../../../../../src/plugins/es_ui_shared/console_lang/lib'; -// @ts-ignore -export const xJsonMode = new XJsonMode(); -export const useXJsonMode = (json: Record | null) => { - const [xJson, setXJson] = useState( - json === null ? '' : expandLiteralStrings(JSON.stringify(json, null, 2)) - ); - - return { - xJson, - setXJson, - xJsonMode, - convertToJson: collapseLiteralStrings, - }; -}; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts index 47de4548898cf..a35dd1346dc90 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../es_ui_shared/console_lang/mocks'; - import { act } from 'react-dom/test-utils'; import { setupEnvironment, pageHelpers, nextTick, wrapBodyResponse } from './helpers'; import { WatchCreateJsonTestBed } from './helpers/watch_create_json.helpers'; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx index e5bdcbbfb82cf..89cd5207fe250 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../../es_ui_shared/console_lang/mocks'; - import React from 'react'; import { act } from 'react-dom/test-utils'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts index ced06562b1d20..18acf7339580c 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import '../../../es_ui_shared/console_lang/mocks'; import { act } from 'react-dom/test-utils'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts index 3370e42b2417f..a0327c6dfa1db 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import '../../../es_ui_shared/console_lang/mocks'; import { act } from 'react-dom/test-utils'; import * as fixtures from '../../test/fixtures'; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_status.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_status.test.ts index 20b7b526705c0..e9cbb2c92491a 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_status.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_status.test.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import '../../../es_ui_shared/console_lang/mocks'; import { act } from 'react-dom/test-utils'; import { setupEnvironment, pageHelpers, nextTick } from './helpers'; diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx index b3a09d3bc0e65..b5835d862000f 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx @@ -23,14 +23,13 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { serializeJsonWatch } from '../../../../../../common/lib/serialization'; import { ErrableFormRow, SectionError, Error as ServerError } from '../../../../components'; +import { useXJsonMode } from '../../../../shared_imports'; import { onWatchSave } from '../../watch_edit_actions'; import { WatchContext } from '../../watch_context'; import { goToWatchList } from '../../../../lib/navigation'; import { RequestFlyout } from '../request_flyout'; import { useAppContext } from '../../../../app_context'; -import { useXJsonMode } from './use_x_json_mode'; - export const JsonWatchEditForm = () => { const { links: { putWatchApiUrl }, diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx index b9fce52b480ef..fa0b9b0a2566f 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx @@ -40,7 +40,7 @@ import { JsonWatchEditSimulateResults } from './json_watch_edit_simulate_results import { getTimeUnitLabel } from '../../../../lib/get_time_unit_label'; import { useAppContext } from '../../../../app_context'; -import { useXJsonMode } from './use_x_json_mode'; +import { useXJsonMode } from '../../../../shared_imports'; const actionModeOptions = Object.keys(ACTION_MODES).map(mode => ({ text: ACTION_MODES[mode], diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/use_x_json_mode.ts b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/use_x_json_mode.ts deleted file mode 100644 index 7aefb0554e0e8..0000000000000 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/use_x_json_mode.ts +++ /dev/null @@ -1,24 +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 { useState } from 'react'; -import { XJsonMode } from '../../../../../../../es_ui_shared/console_lang'; -import { - collapseLiteralStrings, - expandLiteralStrings, -} from '../../../../../../../../../src/plugins/es_ui_shared/console_lang/lib'; - -export const xJsonMode = new XJsonMode(); - -export const useXJsonMode = (json: string) => { - const [xJson, setXJson] = useState(expandLiteralStrings(json)); - - return { - xJson, - setXJson, - xJsonMode, - convertToJson: collapseLiteralStrings, - }; -}; diff --git a/x-pack/plugins/watcher/public/application/shared_imports.ts b/x-pack/plugins/watcher/public/application/shared_imports.ts index cbc4dde7448ff..94ef7af1c28d1 100644 --- a/x-pack/plugins/watcher/public/application/shared_imports.ts +++ b/x-pack/plugins/watcher/public/application/shared_imports.ts @@ -11,3 +11,5 @@ export { sendRequest, useRequest, } from '../../../../../src/plugins/es_ui_shared/public/request/np_ready_request'; + +export { useXJsonMode } from '../../../../../src/plugins/es_ui_shared/static/ace_x_json/hooks'; From 77d22f55d9d952ed0d3b6ed6da25def23fcea4f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Fri, 10 Apr 2020 13:54:48 +0100 Subject: [PATCH 02/21] [APM] Agent config select box doesn't work on IE (#63236) * adding value property to select options * fixing test --- .../SettingsPage/SettingFormRow.tsx | 7 +++++-- .../setting_definitions/__snapshots__/index.test.ts.snap | 4 ++++ .../setting_definitions/general_settings.ts | 8 ++++---- .../agent_configuration/setting_definitions/types.d.ts | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx index 30c772bf5f634..baab600145b81 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx @@ -73,7 +73,10 @@ function FormRow({ return ( onChange(setting.key, e.target.value)} /> @@ -105,7 +108,7 @@ function FormRow({ defaultMessage: 'Select unit' })} value={unit} - options={setting.units?.map(text => ({ text }))} + options={setting.units?.map(text => ({ text, value: text }))} onChange={e => onChange( setting.key, diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap index 49840d2157af7..ea706be9f584a 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap @@ -29,15 +29,19 @@ Array [ "options": Array [ Object { "text": "off", + "value": "off", }, Object { "text": "errors", + "value": "errors", }, Object { "text": "transactions", + "value": "transactions", }, Object { "text": "all", + "value": "all", }, ], "type": "select", diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index e73aed35e87f9..7477238ba79ae 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -67,10 +67,10 @@ export const generalSettings: RawSettingDefinition[] = [ } ), options: [ - { text: 'off' }, - { text: 'errors' }, - { text: 'transactions' }, - { text: 'all' } + { text: 'off', value: 'off' }, + { text: 'errors', value: 'errors' }, + { text: 'transactions', value: 'transactions' }, + { text: 'all', value: 'all' } ], excludeAgents: ['js-base', 'rum-js'] }, diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/types.d.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/types.d.ts index 282ced346dda0..815b8cb3d4e83 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/types.d.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/types.d.ts @@ -76,7 +76,7 @@ interface FloatSetting extends BaseSetting { interface SelectSetting extends BaseSetting { type: 'select'; - options: Array<{ text: string }>; + options: Array<{ text: string; value: string }>; } interface BooleanSetting extends BaseSetting { From e8491adbab145590e877e5af9b1efa009400bf46 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Fri, 10 Apr 2020 09:04:17 -0500 Subject: [PATCH 03/21] Use globe icon for "ext" span type on service map (#63205) Both "external" and "ext" can be returned and should have the same icon. --- .../apm/public/components/app/ServiceMap/Cytoscape.stories.tsx | 1 + .../legacy/plugins/apm/public/components/app/ServiceMap/icons.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx index a8d3b843a1f3d..31c227d8bbcab 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx @@ -85,6 +85,7 @@ storiesOf('app/ServiceMap/Cytoscape', module) } }, { data: { id: 'external', 'span.type': 'external' } }, + { data: { id: 'ext', 'span.type': 'ext' } }, { data: { id: 'messaging', 'span.type': 'messaging' } }, { data: { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts index dd9b48d312725..095c2d9250e27 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts @@ -32,6 +32,7 @@ export const defaultIcon = defaultIconImport; const icons: { [key: string]: string } = { cache: databaseIcon, db: databaseIcon, + ext: globeIcon, external: globeIcon, messaging: documentsIcon, resource: globeIcon From ed975d0169e6728db9d8c5cf98ca655293217192 Mon Sep 17 00:00:00 2001 From: DianaDerevyankina <54894989+DianaDerevyankina@users.noreply.github.com> Date: Fri, 10 Apr 2020 17:59:50 +0300 Subject: [PATCH 04/21] Move shared vislib components into Charts plugin (#62957) * Closes #56310 Move shared vislib components into Charts plugin * Fixed imports in tests * Changed i18n IDs to match charts namespace * Renamed ColorSchemaVislibParams to ColorSchemaParams, added enums and got rid of useValidation function * Renamed ColorSchemaVislibParams to ColorSchemaParams and got rid of useValidation function * Fixed merge conflict * Replaced enums with objects again --- .../public/components/region_map_options.tsx | 6 ++- .../public/components/tile_map_options.tsx | 2 +- .../components/wms_internal_options.tsx | 2 +- .../public/components/wms_options.tsx | 2 +- .../public/settings_options.tsx | 2 +- .../public/components/metric_vis_options.tsx | 4 +- .../vis_type_metric/public/metric_vis_fn.ts | 3 +- .../vis_type_metric/public/metric_vis_type.ts | 3 +- .../vis_type_metric/public/types.ts | 3 +- .../public/components/table_vis_options.tsx | 6 ++- .../public/components/tag_cloud_options.tsx | 2 +- .../vis_type_vislib/public/area.ts | 2 +- .../public/components/common/index.ts | 8 ---- .../components/options/gauge/labels_panel.tsx | 2 +- .../components/options/gauge/ranges_panel.tsx | 14 +++--- .../components/options/gauge/style_panel.tsx | 2 +- .../components/options/heatmap/index.tsx | 6 +-- .../options/heatmap/labels_panel.tsx | 2 +- .../metrics_axes/category_axis_panel.tsx | 2 +- .../options/metrics_axes/chart_options.tsx | 2 +- .../metrics_axes/custom_extents_options.tsx | 2 +- .../options/metrics_axes/label_options.tsx | 3 +- .../metrics_axes/line_options.test.tsx | 2 +- .../options/metrics_axes/line_options.tsx | 6 ++- .../components/options/metrics_axes/mocks.ts | 3 +- .../metrics_axes/value_axis_options.test.tsx | 2 +- .../metrics_axes/value_axis_options.tsx | 6 ++- .../options/metrics_axes/y_extents.test.tsx | 2 +- .../options/metrics_axes/y_extents.tsx | 2 +- .../public/components/options/pie.tsx | 3 +- .../options/point_series/grid_panel.tsx | 2 +- .../options/point_series/point_series.tsx | 3 +- .../options/point_series/threshold_panel.tsx | 12 ++++-- .../vis_type_vislib/public/gauge.ts | 13 ++++-- .../vis_type_vislib/public/goal.ts | 4 +- .../vis_type_vislib/public/heatmap.ts | 6 +-- .../vis_type_vislib/public/histogram.ts | 2 +- .../vis_type_vislib/public/horizontal_bar.ts | 2 +- .../vis_type_vislib/public/index.ts | 14 ------ .../vis_type_vislib/public/line.ts | 2 +- .../vis_type_vislib/public/types.ts | 25 +---------- .../public/utils/collections.ts | 16 +------ .../static/components}/basic_options.tsx | 4 +- .../public/static/components/collections.ts} | 26 +++++------ .../static/components}/color_ranges.tsx | 7 +-- .../static/components}/color_schema.tsx | 18 ++++---- .../charts/public/static/components/index.ts | 30 +++++++++++++ .../static/components}/number_input.tsx | 0 .../public/static/components}/range.tsx | 2 +- .../components}/required_number_input.tsx | 14 +++--- .../public/static/components}/select.tsx | 0 .../public/static/components}/switch.tsx | 0 .../public/static/components}/text_input.tsx | 0 .../charts/public/static/components/types.ts | 43 +++++++++++++++++++ src/plugins/charts/public/static/index.ts | 1 + .../translations/translations/ja-JP.json | 16 +++---- .../translations/translations/zh-CN.json | 16 +++---- 57 files changed, 215 insertions(+), 169 deletions(-) rename src/{legacy/core_plugins/vis_type_vislib/public/components/common => plugins/charts/public/static/components}/basic_options.tsx (90%) rename src/{legacy/core_plugins/vis_type_vislib/public/components/common/utils.ts => plugins/charts/public/static/components/collections.ts} (67%) rename src/{legacy/core_plugins/vis_type_vislib/public/components/common => plugins/charts/public/static/components}/color_ranges.tsx (93%) rename src/{legacy/core_plugins/vis_type_vislib/public/components/common => plugins/charts/public/static/components}/color_schema.tsx (82%) create mode 100644 src/plugins/charts/public/static/components/index.ts rename src/{legacy/core_plugins/vis_type_vislib/public/components/common => plugins/charts/public/static/components}/number_input.tsx (100%) rename src/{legacy/core_plugins/vis_type_vislib/public/components/common => plugins/charts/public/static/components}/range.tsx (96%) rename src/{legacy/core_plugins/vis_type_vislib/public/components/common => plugins/charts/public/static/components}/required_number_input.tsx (87%) rename src/{legacy/core_plugins/vis_type_vislib/public/components/common => plugins/charts/public/static/components}/select.tsx (100%) rename src/{legacy/core_plugins/vis_type_vislib/public/components/common => plugins/charts/public/static/components}/switch.tsx (100%) rename src/{legacy/core_plugins/vis_type_vislib/public/components/common => plugins/charts/public/static/components}/text_input.tsx (100%) create mode 100644 src/plugins/charts/public/static/components/types.ts diff --git a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx index 61cfbf00ded9e..187b27953830d 100644 --- a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx +++ b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx @@ -24,7 +24,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { FileLayerField, VectorLayer, ServiceSettings } from 'ui/vis/map/service_settings'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { NumberInputOption, SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; +import { + NumberInputOption, + SelectOption, + SwitchOption, +} from '../../../../../plugins/charts/public'; import { WmsOptions } from '../../../tile_map/public/components/wms_options'; import { RegionMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx index 9169647aa2aef..9ca42fe3e4074 100644 --- a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx @@ -27,7 +27,7 @@ import { RangeOption, SelectOption, SwitchOption, -} from '../../../vis_type_vislib/public'; +} from '../../../../../plugins/charts/public'; import { WmsOptions } from './wms_options'; import { TileMapVisParams } from '../types'; import { MapTypes } from '../map_types'; diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx index b81667400303d..47f5b8f31e62b 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { TextInputOption } from '../../../vis_type_vislib/public'; +import { TextInputOption } from '../../../../../plugins/charts/public'; import { WMSOptions } from '../types'; interface WmsInternalOptions { diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx index 27127b781cd4d..b8535e72e8818 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { TmsLayer } from 'ui/vis/map/service_settings'; import { Vis } from '../../../../../plugins/visualizations/public'; import { RegionMapVisParams } from '../../../region_map/public/types'; -import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; +import { SelectOption, SwitchOption } from '../../../../../plugins/charts/public'; import { WmsInternalOptions } from './wms_internal_options'; import { WMSOptions, TileMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx index 552fd63373554..16b2749c34e10 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx +++ b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx @@ -22,7 +22,7 @@ import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { RangeOption, SwitchOption } from '../../vis_type_vislib/public'; +import { RangeOption, SwitchOption } from '../../../../plugins/charts/public'; import { MarkdownVisParams } from './types'; function SettingsOptions({ stateParams, setValue }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx index 5c3032511f09a..56af0ee91878f 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -37,9 +37,9 @@ import { SwitchOption, RangeOption, SetColorSchemaOptionsValue, -} from '../../../vis_type_vislib/public'; + SetColorRangeValue, +} from '../../../../../plugins/charts/public'; import { MetricVisParam, VisParams } from '../types'; -import { SetColorRangeValue } from '../../../vis_type_vislib/public/components/common/color_ranges'; function MetricVisOptions({ stateParams, diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index 03b412c6fff15..52784da1bd73b 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -26,9 +26,8 @@ import { Render, Style, } from '../../../../plugins/expressions/public'; -import { ColorModes } from '../../vis_type_vislib/public'; import { visType, DimensionsVisParam, VisParams } from './types'; -import { ColorSchemas, vislibColorMaps } from '../../../../plugins/charts/public'; +import { ColorSchemas, vislibColorMaps, ColorModes } from '../../../../plugins/charts/public'; export type Input = KibanaDatatable; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index 3bbb8964122e5..ab31c56921412 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -21,8 +21,7 @@ import { i18n } from '@kbn/i18n'; import { MetricVisComponent } from './components/metric_vis_component'; import { MetricVisOptions } from './components/metric_vis_options'; -import { ColorModes } from '../../vis_type_vislib/public'; -import { ColorSchemas, colorSchemas } from '../../../../plugins/charts/public'; +import { ColorSchemas, colorSchemas, ColorModes } from '../../../../plugins/charts/public'; import { AggGroupNames } from '../../../../plugins/data/public'; import { Schemas } from '../../../../plugins/vis_default_editor/public'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/types.ts b/src/legacy/core_plugins/vis_type_metric/public/types.ts index cae18dd8a2ab1..cdf62cba934d7 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/types.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/types.ts @@ -19,8 +19,7 @@ import { Range } from '../../../../plugins/expressions/public'; import { SchemaConfig } from '../../../../plugins/visualizations/public'; -import { ColorModes, Labels, Style } from '../../vis_type_vislib/public'; -import { ColorSchemas } from '../../../../plugins/charts/public'; +import { ColorModes, ColorSchemas, Labels, Style } from '../../../../plugins/charts/public'; export const visType = 'metric'; diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx index d01ab31e0a843..265528f33f9cd 100644 --- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx @@ -25,7 +25,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { search } from '../../../../../plugins/data/public'; -import { NumberInputOption, SwitchOption, SelectOption } from '../../../vis_type_vislib/public'; +import { + SwitchOption, + SelectOption, + NumberInputOption, +} from '../../../../../plugins/charts/public'; import { TableVisParams } from '../types'; import { totalAggregations } from './utils'; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx index 80e4e1de7ddab..7a64549edd747 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx @@ -22,7 +22,7 @@ import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { ValidatedDualRange } from '../../../../../../src/plugins/kibana_react/public'; -import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; +import { SelectOption, SwitchOption } from '../../../../../plugins/charts/public'; import { TagCloudVisParams } from '../types'; function TagCloudOptions({ stateParams, setValue, vis }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/area.ts b/src/legacy/core_plugins/vis_type_vislib/public/area.ts index 68decacaaa040..8a196da64fc4b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/area.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/area.ts @@ -33,13 +33,13 @@ import { AxisTypes, ScaleTypes, AxisModes, - Rotates, ThresholdLineStyles, getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; import { createVislibVisController } from './vis_controller'; import { VisTypeVislibDependencies } from './plugin'; +import { Rotates } from '../../../../plugins/charts/public'; export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ name: 'area', diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/common/index.ts index 05972d101f576..f0bec3167cb7c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/index.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/index.ts @@ -17,13 +17,5 @@ * under the License. */ -export { BasicOptions } from './basic_options'; -export { ColorRanges } from './color_ranges'; -export { ColorSchemaOptions, SetColorSchemaOptionsValue } from './color_schema'; -export { NumberInputOption } from './number_input'; -export { RangeOption } from './range'; -export { SelectOption } from './select'; -export { SwitchOption } from './switch'; -export { TextInputOption } from './text_input'; export { TruncateLabelsOption } from './truncate_labels'; export { ValidationWrapper, ValidationVisOptionsProps } from './validation_wrapper'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx index b9bae1cad35e7..3fca9dc8adc08 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx @@ -22,7 +22,7 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SwitchOption, TextInputOption } from '../../common'; +import { SwitchOption, TextInputOption } from '../../../../../../../plugins/charts/public'; import { GaugeOptionsInternalProps } from '.'; function LabelsPanel({ stateParams, setValue, setGaugeValue }: GaugeOptionsInternalProps) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx index 7de64e5888096..433cc4edeb47b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx @@ -22,12 +22,16 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ColorRanges, ColorSchemaOptions, SwitchOption } from '../../common'; +import { + ColorRanges, + ColorSchemaOptions, + ColorSchemaParams, + SetColorRangeValue, + SwitchOption, + ColorSchemas, +} from '../../../../../../../plugins/charts/public'; import { GaugeOptionsInternalProps } from '.'; -import { ColorSchemaVislibParams } from '../../../types'; import { Gauge } from '../../../gauge'; -import { SetColorRangeValue } from '../../common/color_ranges'; -import { ColorSchemas } from '../../../../../../../plugins/charts/public'; function RangesPanel({ setGaugeValue, @@ -39,7 +43,7 @@ function RangesPanel({ vis, }: GaugeOptionsInternalProps) { const setColorSchemaOptions = useCallback( - (paramName: T, value: ColorSchemaVislibParams[T]) => { + (paramName: T, value: ColorSchemaParams[T]) => { setGaugeValue(paramName, value as Gauge[T]); // set outline if color schema is changed to greys // if outline wasn't set explicitly yet diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx index 9254c3c18347c..48711de7d171a 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx @@ -22,7 +22,7 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SelectOption } from '../../common'; +import { SelectOption } from '../../../../../../../plugins/charts/public'; import { GaugeOptionsInternalProps } from '.'; import { AggGroupNames } from '../../../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx index 715b5902b69da..dc207ad89286f 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx @@ -31,12 +31,12 @@ import { NumberInputOption, SelectOption, SwitchOption, -} from '../../common'; -import { SetColorSchemaOptionsValue } from '../../common/color_schema'; + SetColorSchemaOptionsValue, + SetColorRangeValue, +} from '../../../../../../../plugins/charts/public'; import { HeatmapVisParams } from '../../../heatmap'; import { ValueAxis } from '../../../types'; import { LabelsPanel } from './labels_panel'; -import { SetColorRangeValue } from '../../common/color_ranges'; function HeatmapOptions(props: VisOptionsProps) { const { stateParams, vis, uiState, setValue, setValidity, setTouched } = props; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx index 38811bd836459..3d1629740df2c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx @@ -26,7 +26,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { ValueAxis } from '../../../types'; import { HeatmapVisParams } from '../../../heatmap'; -import { SwitchOption } from '../../common'; +import { SwitchOption } from '../../../../../../../plugins/charts/public'; const VERTICAL_ROTATION = 270; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx index 915885388640c..246c20a14807c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { Axis } from '../../../types'; -import { SelectOption, SwitchOption } from '../../common'; +import { SelectOption, SwitchOption } from '../../../../../../../plugins/charts/public'; import { LabelOptions, SetAxisLabel } from './label_options'; import { Positions } from '../../../utils/collections'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx index ec7a325ba43d1..89aab3a19c589 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx @@ -25,7 +25,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { Vis } from '../../../../../../../plugins/visualizations/public'; import { SeriesParam, ValueAxis } from '../../../types'; import { ChartTypes } from '../../../utils/collections'; -import { SelectOption } from '../../common'; +import { SelectOption } from '../../../../../../../plugins/charts/public'; import { LineOptions } from './line_options'; import { SetParamByIndex, ChangeValueAxis } from './'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx index 53b2ffa55a941..a3a97df9e35ae 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx @@ -21,7 +21,7 @@ import React, { useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { ValueAxis } from '../../../types'; -import { NumberInputOption, SwitchOption } from '../../common'; +import { NumberInputOption, SwitchOption } from '../../../../../../../plugins/charts/public'; import { YExtents } from './y_extents'; import { SetScale } from './value_axis_options'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx index b6b54193e9f4a..bc687e56646f6 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx @@ -24,8 +24,9 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Axis } from '../../../types'; -import { SelectOption, SwitchOption, TruncateLabelsOption } from '../../common'; +import { TruncateLabelsOption } from '../../common'; import { getRotateOptions } from '../../../utils/collections'; +import { SelectOption, SwitchOption } from '../../../../../../../plugins/charts/public'; export type SetAxisLabel = ( paramName: T, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx index 1d29d39bfcb7f..5354bc9c033e6 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { LineOptions, LineOptionsParams } from './line_options'; -import { NumberInputOption } from '../../common'; +import { NumberInputOption } from '../../../../../../../plugins/charts/public'; import { seriesParam, vis } from './mocks'; jest.mock('ui/new_platform'); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx index 01a69a6fac70b..76f95bd93caf8 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx @@ -24,7 +24,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { Vis } from '../../../../../../../plugins/visualizations/public'; import { SeriesParam } from '../../../types'; -import { NumberInputOption, SelectOption, SwitchOption } from '../../common'; +import { + NumberInputOption, + SelectOption, + SwitchOption, +} from '../../../../../../../plugins/charts/public'; import { SetChart } from './chart_options'; export interface LineOptionsParams { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts index 0d9fa8c25a4f7..a296281375dac 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts @@ -18,7 +18,7 @@ */ import { Vis } from '../../../../../../../plugins/visualizations/public'; -import { Axis, ValueAxis, SeriesParam, Style } from '../../../types'; +import { Axis, ValueAxis, SeriesParam } from '../../../types'; import { ChartTypes, ChartModes, @@ -31,6 +31,7 @@ import { getPositions, getInterpolationModes, } from '../../../utils/collections'; +import { Style } from '../../../../../../../plugins/charts/public'; const defaultValueAxisId = 'ValueAxis-1'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx index 955867e66d09f..876a6917ee0b4 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ValueAxisOptions, ValueAxisOptionsParams } from './value_axis_options'; import { ValueAxis } from '../../../types'; -import { TextInputOption } from '../../common'; +import { TextInputOption } from '../../../../../../../plugins/charts/public'; import { LabelOptions } from './label_options'; import { ScaleTypes, Positions } from '../../../utils/collections'; import { valueAxis, vis } from './mocks'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx index 8f0327e78c7ab..1b89a766d0591 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx @@ -24,7 +24,11 @@ import { EuiSpacer, EuiAccordion, EuiHorizontalRule } from '@elastic/eui'; import { Vis } from '../../../../../../../plugins/visualizations/public'; import { ValueAxis } from '../../../types'; import { Positions } from '../../../utils/collections'; -import { SelectOption, SwitchOption, TextInputOption } from '../../common'; +import { + SelectOption, + SwitchOption, + TextInputOption, +} from '../../../../../../../plugins/charts/public'; import { LabelOptions, SetAxisLabel } from './label_options'; import { CustomExtentsOptions } from './custom_extents_options'; import { isAxisHorizontal } from './utils'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx index 17c47b35b20dc..b5ed475ca8e31 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import { YExtents, YExtentsProps } from './y_extents'; import { ScaleTypes } from '../../../utils/collections'; -import { NumberInputOption } from '../../common'; +import { NumberInputOption } from '../../../../../../../plugins/charts/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx index c0db58a612e71..faeb6069b5126 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { Scale } from '../../../types'; import { ScaleTypes } from '../../../utils/collections'; -import { NumberInputOption } from '../../common'; +import { NumberInputOption } from '../../../../../../../plugins/charts/public'; import { SetScale } from './value_axis_options'; const rangeError = i18n.translate('visTypeVislib.controls.pointSeries.valueAxes.minErrorMessage', { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx index 4c0be456aad64..f6be9cd0bd8fe 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx @@ -23,7 +23,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { BasicOptions, TruncateLabelsOption, SwitchOption } from '../common'; +import { TruncateLabelsOption } from '../common'; +import { BasicOptions, SwitchOption } from '../../../../../../plugins/charts/public'; import { PieVisParams } from '../../pie'; function PieOptions(props: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx index bb2b3f8fddb49..392d180d2c5f2 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { SelectOption, SwitchOption } from '../../common'; +import { SelectOption, SwitchOption } from '../../../../../../../plugins/charts/public'; import { BasicVislibParams, ValueAxis } from '../../../types'; function GridPanel({ stateParams, setValue, hasHistogramAgg }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx index b9872ab94bd0b..903c1917751d9 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx @@ -21,7 +21,8 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { BasicOptions, SwitchOption, ValidationVisOptionsProps } from '../../common'; +import { ValidationVisOptionsProps } from '../../common'; +import { BasicOptions, SwitchOption } from '../../../../../../../plugins/charts/public'; import { GridPanel } from './grid_panel'; import { ThresholdPanel } from './threshold_panel'; import { BasicVislibParams } from '../../../types'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx index 7866ad74ede7f..12f058ec7dd1f 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx @@ -21,8 +21,12 @@ import { EuiPanel, EuiTitle, EuiColorPicker, EuiFormRow, EuiSpacer } from '@elas import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SelectOption, SwitchOption, ValidationVisOptionsProps } from '../../common'; -import { NumberInputOption } from '../../common/required_number_input'; +import { ValidationVisOptionsProps } from '../../common'; +import { + SelectOption, + SwitchOption, + RequiredNumberInputOption, +} from '../../../../../../../plugins/charts/public'; import { BasicVislibParams } from '../../../types'; function ThresholdPanel({ @@ -73,7 +77,7 @@ function ThresholdPanel({ {stateParams.thresholdLine.show && ( <> - - ({ name: 'histogram', diff --git a/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts index dc47252ccd44f..6f73271726660 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts @@ -32,13 +32,13 @@ import { AxisTypes, ScaleTypes, AxisModes, - Rotates, ThresholdLineStyles, getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; import { createVislibVisController } from './vis_controller'; import { VisTypeVislibDependencies } from './plugin'; +import { Rotates } from '../../../../plugins/charts/public'; export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ name: 'horizontal_bar', diff --git a/src/legacy/core_plugins/vis_type_vislib/public/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/index.ts index 1f773c4efcb02..4d7091ffb204b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/index.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/index.ts @@ -24,18 +24,4 @@ export function plugin(initializerContext: PluginInitializerContext) { return new Plugin(initializerContext); } -export { - BasicOptions, - RangeOption, - ColorRanges, - SelectOption, - SetColorSchemaOptionsValue, - ColorSchemaOptions, - NumberInputOption, - SwitchOption, - TextInputOption, -} from './components'; - -export { ColorModes } from './utils/collections'; - export * from './types'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/line.ts b/src/legacy/core_plugins/vis_type_vislib/public/line.ts index 885ab295d11e1..1f9a8d77398e6 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/line.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/line.ts @@ -32,7 +32,6 @@ import { AxisTypes, ScaleTypes, AxisModes, - Rotates, ThresholdLineStyles, InterpolationModes, getConfigCollections, @@ -40,6 +39,7 @@ import { import { getAreaOptionTabs, countLabel } from './utils/common_config'; import { createVislibVisController } from './vis_controller'; import { VisTypeVislibDependencies } from './plugin'; +import { Rotates } from '../../../../plugins/charts/public'; export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ name: 'line', diff --git a/src/legacy/core_plugins/vis_type_vislib/public/types.ts b/src/legacy/core_plugins/vis_type_vislib/public/types.ts index f33b42483c53e..25c6ae5439fe8 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/types.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/types.ts @@ -25,39 +25,16 @@ import { AxisModes, AxisTypes, InterpolationModes, - Rotates, ScaleTypes, ThresholdLineStyles, } from './utils/collections'; -import { ColorSchemas } from '../../../../plugins/charts/public'; +import { Labels, Style } from '../../../../plugins/charts/public'; export interface CommonVislibParams { addTooltip: boolean; legendPosition: Positions; } -export interface ColorSchemaVislibParams { - colorSchema: ColorSchemas; - invertColors: boolean; -} - -export interface Labels { - color?: string; - filter?: boolean; - overwriteColor?: boolean; - rotate?: Rotates; - show: boolean; - truncate?: number | null; -} - -export interface Style { - bgFill: string; - bgColor: boolean; - labelColor: boolean; - subText: string; - fontSize: number; -} - export interface Scale { boundsMargin?: number | ''; defaultYExtents?: boolean; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts index f32b765cd6e57..2024c43dd1c8b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { $Values } from '@kbn/utility-types'; -import { colorSchemas } from '../../../../../plugins/charts/public'; +import { colorSchemas, Rotates } from '../../../../../plugins/charts/public'; export const Positions = Object.freeze({ RIGHT: 'right' as 'right', @@ -203,13 +203,6 @@ const getAxisModes = () => [ }, ]; -export const Rotates = Object.freeze({ - HORIZONTAL: 0, - VERTICAL: 90, - ANGLED: 75, -}); -export type Rotates = $Values; - export const ThresholdLineStyles = Object.freeze({ FULL: 'full' as 'full', DASHED: 'dashed' as 'dashed', @@ -265,13 +258,6 @@ export const GaugeTypes = Object.freeze({ }); export type GaugeTypes = $Values; -export const ColorModes = Object.freeze({ - BACKGROUND: 'Background' as 'Background', - LABELS: 'Labels' as 'Labels', - NONE: 'None' as 'None', -}); -export type ColorModes = $Values; - const getGaugeTypes = () => [ { text: i18n.translate('visTypeVislib.gauge.gaugeTypes.arcText', { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx b/src/plugins/charts/public/static/components/basic_options.tsx similarity index 90% rename from src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx rename to src/plugins/charts/public/static/components/basic_options.tsx index baf3e8ecd1b28..cac4c8d70d796 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx +++ b/src/plugins/charts/public/static/components/basic_options.tsx @@ -37,7 +37,7 @@ function BasicOptions({ return ( <> ({ setValue={setValue} /> ( - setValidity: (paramName: ParamName, isValid: boolean) => void, - paramName: ParamName, - isValid: boolean -) { - useEffect(() => { - setValidity(paramName, isValid); +export const ColorModes = Object.freeze({ + BACKGROUND: 'Background' as 'Background', + LABELS: 'Labels' as 'Labels', + NONE: 'None' as 'None', +}); +export type ColorModes = $Values; - return () => setValidity(paramName, true); - }, [isValid, paramName, setValidity]); -} - -export { useValidation }; +export const Rotates = Object.freeze({ + HORIZONTAL: 0, + VERTICAL: 90, + ANGLED: 75, +}); +export type Rotates = $Values; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx b/src/plugins/charts/public/static/components/color_ranges.tsx similarity index 93% rename from src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx rename to src/plugins/charts/public/static/components/color_ranges.tsx index 84c70f10b12da..a9b05d7d91c7c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx +++ b/src/plugins/charts/public/static/components/color_ranges.tsx @@ -22,10 +22,7 @@ import { last } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - RangeValues, - RangesParamEditor, -} from '../../../../../../plugins/vis_default_editor/public'; +import { RangeValues, RangesParamEditor } from '../../../../vis_default_editor/public'; export type SetColorRangeValue = (paramName: string, value: RangeValues[]) => void; @@ -74,7 +71,7 @@ function ColorRanges({ return ( ( +export type SetColorSchemaOptionsValue = ( paramName: T, - value: ColorSchemaVislibParams[T] + value: ColorSchemaParams[T] ) => void; -interface ColorSchemaOptionsProps extends ColorSchemaVislibParams { +interface ColorSchemaOptionsProps extends ColorSchemaParams { disabled?: boolean; colorSchemas: ColorSchema[]; uiState: VisOptionsProps['uiState']; @@ -67,7 +67,7 @@ function ColorSchemaOptions({ }} > @@ -80,11 +80,11 @@ function ColorSchemaOptions({ disabled={disabled} helpText={ showHelpText && - i18n.translate('visTypeVislib.controls.colorSchema.howToChangeColorsDescription', { + i18n.translate('charts.controls.colorSchema.howToChangeColorsDescription', { defaultMessage: 'Individual colors can be changed in the legend.', }) } - label={i18n.translate('visTypeVislib.controls.colorSchema.colorSchemaLabel', { + label={i18n.translate('charts.controls.colorSchema.colorSchemaLabel', { defaultMessage: 'Color schema', })} labelAppend={isCustomColors && resetColorsButton} @@ -96,7 +96,7 @@ function ColorSchemaOptions({ ({ const [stateValue, setStateValue] = useState(value); const [isValidState, setIsValidState] = useState(true); - const error = i18n.translate('visTypeVislib.controls.rangeErrorMessage', { + const error = i18n.translate('charts.controls.rangeErrorMessage', { defaultMessage: 'Values must be on or between {min} and {max}', values: { min, max }, }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/required_number_input.tsx b/src/plugins/charts/public/static/components/required_number_input.tsx similarity index 87% rename from src/legacy/core_plugins/vis_type_vislib/public/components/common/required_number_input.tsx rename to src/plugins/charts/public/static/components/required_number_input.tsx index 7b62016c4e502..7594c775b07ad 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/required_number_input.tsx +++ b/src/plugins/charts/public/static/components/required_number_input.tsx @@ -17,9 +17,8 @@ * under the License. */ -import React, { ReactNode, useCallback, ChangeEvent } from 'react'; +import React, { ReactNode, useCallback, ChangeEvent, useEffect } from 'react'; import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; -import { useValidation } from './utils'; interface NumberInputOptionProps { disabled?: boolean; @@ -42,7 +41,7 @@ interface NumberInputOptionProps { * * @param {number} props.value Should be numeric only */ -function NumberInputOption({ +function RequiredNumberInputOption({ disabled, error, isInvalid, @@ -57,7 +56,12 @@ function NumberInputOption({ 'data-test-subj': dataTestSubj, }: NumberInputOptionProps) { const isValid = value !== null; - useValidation(setValidity, paramName, isValid); + + useEffect(() => { + setValidity(paramName, isValid); + + return () => setValidity(paramName, true); + }, [isValid, paramName, setValidity]); const onChange = useCallback( (ev: ChangeEvent) => @@ -84,4 +88,4 @@ function NumberInputOption({ ); } -export { NumberInputOption }; +export { RequiredNumberInputOption }; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx b/src/plugins/charts/public/static/components/select.tsx similarity index 100% rename from src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx rename to src/plugins/charts/public/static/components/select.tsx diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/switch.tsx b/src/plugins/charts/public/static/components/switch.tsx similarity index 100% rename from src/legacy/core_plugins/vis_type_vislib/public/components/common/switch.tsx rename to src/plugins/charts/public/static/components/switch.tsx diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/text_input.tsx b/src/plugins/charts/public/static/components/text_input.tsx similarity index 100% rename from src/legacy/core_plugins/vis_type_vislib/public/components/common/text_input.tsx rename to src/plugins/charts/public/static/components/text_input.tsx diff --git a/src/plugins/charts/public/static/components/types.ts b/src/plugins/charts/public/static/components/types.ts new file mode 100644 index 0000000000000..196eb60b06aec --- /dev/null +++ b/src/plugins/charts/public/static/components/types.ts @@ -0,0 +1,43 @@ +/* + * 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 { ColorSchemas } from '../color_maps'; +import { Rotates } from './collections'; + +export interface ColorSchemaParams { + colorSchema: ColorSchemas; + invertColors: boolean; +} + +export interface Labels { + color?: string; + filter?: boolean; + overwriteColor?: boolean; + rotate?: Rotates; + show: boolean; + truncate?: number | null; +} + +export interface Style { + bgFill: string; + bgColor: boolean; + labelColor: boolean; + subText: string; + fontSize: number; +} diff --git a/src/plugins/charts/public/static/index.ts b/src/plugins/charts/public/static/index.ts index bee58e4f1e3e1..6fc097d05467f 100644 --- a/src/plugins/charts/public/static/index.ts +++ b/src/plugins/charts/public/static/index.ts @@ -18,3 +18,4 @@ */ export * from './color_maps'; +export * from './components'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c07ec68e99b4f..09903c34e2e5e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -130,6 +130,14 @@ "charts.colormaps.greysText": "グレー", "charts.colormaps.redsText": "赤", "charts.colormaps.yellowToRedText": "黄色から赤", + "charts.controls.colorRanges.errorText": "各範囲は前の範囲よりも大きくなければなりません。", + "charts.controls.colorSchema.colorSchemaLabel": "配色", + "charts.controls.colorSchema.howToChangeColorsDescription": "それぞれの色は凡例で変更できます。", + "charts.controls.colorSchema.resetColorsButtonLabel": "色をリセット", + "charts.controls.colorSchema.reverseColorSchemaLabel": "図表を反転", + "charts.controls.rangeErrorMessage": "値は {min} と {max} の間でなければなりません", + "charts.controls.vislibBasicOptions.legendPositionLabel": "凡例位置", + "charts.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示", "common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "エラー", "common.ui.errorAutoCreateIndex.errorDescription": "Elasticsearch クラスターの {autoCreateIndexActionConfig} 設定が原因で、Kibana が保存されたオブジェクトを格納するインデックスを自動的に作成できないようです。Kibana は、保存されたオブジェクトインデックスが適切なマッピング/スキーマを使用し Kibana から Elasticsearch へのポーリングの回数を減らすための最適な手段であるため、この Elasticsearch の機能を使用します。", "common.ui.errorAutoCreateIndex.errorDisclaimer": "申し訳ございませんが、この問題が解決されるまで Kibana で何も保存することができません。", @@ -3831,11 +3839,6 @@ "visTypeVislib.chartTypes.areaText": "エリア", "visTypeVislib.chartTypes.barText": "バー", "visTypeVislib.chartTypes.lineText": "折れ線", - "visTypeVislib.controls.colorRanges.errorText": "各範囲は前の範囲よりも大きくなければなりません。", - "visTypeVislib.controls.colorSchema.colorSchemaLabel": "配色", - "visTypeVislib.controls.colorSchema.howToChangeColorsDescription": "それぞれの色は凡例で変更できます。", - "visTypeVislib.controls.colorSchema.resetColorsButtonLabel": "色をリセット", - "visTypeVislib.controls.colorSchema.reverseColorSchemaLabel": "図表を反転", "visTypeVislib.controls.gaugeOptions.alignmentLabel": "アラインメント", "visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "範囲を自動拡張", "visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "警告を表示", @@ -3902,10 +3905,7 @@ "visTypeVislib.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "カスタム範囲を切り替える", "visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "{axisName} オプションを切り替える", "visTypeVislib.controls.pointSeries.valueAxes.yAxisTitle": "Y 軸", - "visTypeVislib.controls.rangeErrorMessage": "値は {min} と {max} の間でなければなりません", "visTypeVislib.controls.truncateLabel": "切り捨て", - "visTypeVislib.controls.vislibBasicOptions.legendPositionLabel": "凡例位置", - "visTypeVislib.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示", "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本設定", "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "ヒートマップ設定", "visTypeVislib.editors.heatmap.highlightLabel": "ハイライト範囲", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index de8aaa75632ee..cc1b7d7980a0b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -130,6 +130,14 @@ "charts.colormaps.greysText": "灰色", "charts.colormaps.redsText": "红色", "charts.colormaps.yellowToRedText": "黄到红", + "charts.controls.colorRanges.errorText": "每个范围应大于前一范围。", + "charts.controls.colorSchema.colorSchemaLabel": "颜色模式", + "charts.controls.colorSchema.howToChangeColorsDescription": "可以更改图例中的各个颜色。", + "charts.controls.colorSchema.resetColorsButtonLabel": "重置颜色", + "charts.controls.colorSchema.reverseColorSchemaLabel": "反转模式", + "charts.controls.rangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内", + "charts.controls.vislibBasicOptions.legendPositionLabel": "图例位置", + "charts.controls.vislibBasicOptions.showTooltipLabel": "显示工具提示", "common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "错误", "common.ui.errorAutoCreateIndex.errorDescription": "似乎 Elasticsearch 集群的 {autoCreateIndexActionConfig} 设置使 Kibana 无法自动创建用于存储已保存对象的索引。Kibana 将使用此 Elasticsearch 功能,因为这是确保已保存对象索引使用正确映射/架构的最好方式,而且其允许 Kibana 较少地轮询 Elasticsearch。", "common.ui.errorAutoCreateIndex.errorDisclaimer": "但是,只有解决了此问题后,您才能在 Kibana 保存内容。", @@ -3832,11 +3840,6 @@ "visTypeVislib.chartTypes.areaText": "面积图", "visTypeVislib.chartTypes.barText": "条形图", "visTypeVislib.chartTypes.lineText": "折线图", - "visTypeVislib.controls.colorRanges.errorText": "每个范围应大于前一范围。", - "visTypeVislib.controls.colorSchema.colorSchemaLabel": "颜色模式", - "visTypeVislib.controls.colorSchema.howToChangeColorsDescription": "可以更改图例中的各个颜色。", - "visTypeVislib.controls.colorSchema.resetColorsButtonLabel": "重置颜色", - "visTypeVislib.controls.colorSchema.reverseColorSchemaLabel": "反转模式", "visTypeVislib.controls.gaugeOptions.alignmentLabel": "对齐方式", "visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "自动扩展范围", "visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "显示警告", @@ -3903,10 +3906,7 @@ "visTypeVislib.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "切换定制范围", "visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "切换 {axisName} 选项", "visTypeVislib.controls.pointSeries.valueAxes.yAxisTitle": "Y 轴", - "visTypeVislib.controls.rangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内", "visTypeVislib.controls.truncateLabel": "截断", - "visTypeVislib.controls.vislibBasicOptions.legendPositionLabel": "图例位置", - "visTypeVislib.controls.vislibBasicOptions.showTooltipLabel": "显示工具提示", "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本设置", "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "热图设置", "visTypeVislib.editors.heatmap.highlightLabel": "高亮范围", From 36acb373876f3c6ea5077caae81d3a1cad239dfd Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Fri, 10 Apr 2020 11:18:12 -0400 Subject: [PATCH 05/21] Make uptime alert flyout test a little more resilient (#62702) --- .../apps/uptime/alert_flyout.ts | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts index 2a0358160da51..3e5a8c57c4c7e 100644 --- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts @@ -33,7 +33,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // put the fetch code in a retry block with a timeout. let alert: any; await retry.tryForTime(15000, async () => { - const apiResponse = await supertest.get('/api/alert/_find'); + const apiResponse = await supertest.get('/api/alert/_find?search=uptime-test'); const alertsFromThisTest = apiResponse.body.data.filter( ({ name }: { name: string }) => name === 'uptime-test' ); @@ -54,25 +54,27 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { tags, } = alert; - // we're not testing the flyout's ability to associate alerts with action connectors - expect(actions).to.eql([]); + try { + // we're not testing the flyout's ability to associate alerts with action connectors + expect(actions).to.eql([]); - expect(alertTypeId).to.eql('xpack.uptime.alerts.monitorStatus'); - expect(consumer).to.eql('uptime'); - expect(interval).to.eql('11m'); - expect(tags).to.eql(['uptime', 'another']); - expect(numTimes).to.be(3); - expect(timerange.from).to.be('now-1h'); - expect(timerange.to).to.be('now'); - expect(locations).to.eql(['mpls']); - expect(filters).to.eql( - '{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],"minimum_should_match":1}}' - ); - - await supertest - .delete(`/api/alert/${id}`) - .set('kbn-xsrf', 'true') - .expect(204); + expect(alertTypeId).to.eql('xpack.uptime.alerts.monitorStatus'); + expect(consumer).to.eql('uptime'); + expect(interval).to.eql('11m'); + expect(tags).to.eql(['uptime', 'another']); + expect(numTimes).to.be(3); + expect(timerange.from).to.be('now-1h'); + expect(timerange.to).to.be('now'); + expect(locations).to.eql(['mpls']); + expect(filters).to.eql( + '{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],"minimum_should_match":1}}' + ); + } finally { + await supertest + .delete(`/api/alert/${id}`) + .set('kbn-xsrf', 'true') + .expect(204); + } }); }); }; From 55a3cc45835ccf267c06c2d0d62000027d2bf006 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Fri, 10 Apr 2020 09:55:38 -0600 Subject: [PATCH 06/21] [SIEM] [Cases] Unit tests for case UI components (#63005) --- .../components/filter_popover/index.tsx | 1 + .../components/header_page/editable_title.tsx | 9 +- .../siem/public/containers/case/api.ts | 4 +- .../case/configure/use_configure.tsx | 4 +- .../plugins/siem/public/pages/case/case.tsx | 9 +- .../siem/public/pages/case/case_details.tsx | 6 +- .../case/components/__mock__/case_data.tsx | 226 ++++++++++++ .../pages/case/components/__mock__/form.ts | 37 ++ .../pages/case/components/__mock__/router.ts | 39 +++ .../components/add_comment/index.test.tsx | 144 ++++++++ .../case/components/add_comment/index.tsx | 6 +- .../components/all_cases/__mock__/index.tsx | 115 ------ .../components/all_cases/columns.test.tsx | 48 +++ .../case/components/all_cases/columns.tsx | 14 +- .../case/components/all_cases/index.test.tsx | 66 +++- .../all_cases/table_filters.test.tsx | 121 +++++++ .../components/all_cases/table_filters.tsx | 7 +- .../case/components/all_cases/translations.ts | 4 + .../pages/case/components/callout/helpers.tsx | 4 +- .../case/components/callout/index.test.tsx | 71 ++++ .../pages/case/components/callout/index.tsx | 10 +- .../case/components/case_status/index.tsx | 2 +- .../components/case_view/__mock__/index.tsx | 93 ----- .../components/case_view/actions.test.tsx | 10 +- .../case/components/case_view/actions.tsx | 1 - .../case/components/case_view/index.test.tsx | 320 +++++++++++++---- .../pages/case/components/case_view/index.tsx | 8 +- .../case/components/create/index.test.tsx | 121 +++++++ .../pages/case/components/create/index.tsx | 7 +- .../case/components/tag_list/index.test.tsx | 138 ++++++++ .../pages/case/components/tag_list/index.tsx | 17 +- .../use_push_to_service/index.test.tsx | 192 ++++++++++ .../components/use_push_to_service/index.tsx | 5 +- .../user_action_tree/helpers.test.tsx | 143 ++++++++ .../components/user_action_tree/helpers.tsx | 12 +- .../user_action_tree/index.test.tsx | 331 ++++++++++++++++++ .../components/user_action_tree/index.tsx | 10 +- .../user_action_tree/user_action_item.tsx | 15 +- .../user_action_tree/user_action_markdown.tsx | 20 +- .../user_action_title.test.tsx | 57 +++ .../user_action_tree/user_action_title.tsx | 16 +- .../siem/public/pages/case/translations.ts | 4 + .../scripts/generate_case_and_comment_data.sh | 6 +- .../case/server/scripts/generate_case_data.sh | 4 +- 44 files changed, 2117 insertions(+), 360 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/form.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/router.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.test.tsx delete mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.test.tsx delete mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/create/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx diff --git a/x-pack/legacy/plugins/siem/public/components/filter_popover/index.tsx b/x-pack/legacy/plugins/siem/public/components/filter_popover/index.tsx index 3c01ec18a879f..fca6396a53745 100644 --- a/x-pack/legacy/plugins/siem/public/components/filter_popover/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/filter_popover/index.tsx @@ -89,6 +89,7 @@ export const FilterPopoverComponent = ({ {options.map((option, index) => ( diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx index 165be00384779..0c6f7258d09dc 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -60,12 +60,9 @@ const EditableTitleComponent: React.FC = ({ }, [changedTitle, title]); const handleOnChange = useCallback( - (e: ChangeEvent) => { - onTitleChange(e.target.value); - }, - [onTitleChange] + (e: ChangeEvent) => onTitleChange(e.target.value), + [] ); - return editMode ? ( @@ -107,7 +104,7 @@ const EditableTitleComponent: React.FC = ({ </EuiFlexItem> <EuiFlexItem grow={false}> - {isLoading && <MySpinner />} + {isLoading && <MySpinner data-test-subj="editable-title-loading" />} {!isLoading && ( <MyEuiButtonIcon isDisabled={disabled} diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/api.ts index 69e1602b3d981..12b4c80a2dd89 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/api.ts @@ -204,13 +204,13 @@ export const patchComment = async ( return convertToCamelCase<CaseResponse, Case>(decodeCaseResponse(response)); }; -export const deleteCases = async (caseIds: string[], signal: AbortSignal): Promise<boolean> => { +export const deleteCases = async (caseIds: string[], signal: AbortSignal): Promise<string> => { const response = await KibanaServices.get().http.fetch<string>(CASES_URL, { method: 'DELETE', query: { ids: JSON.stringify(caseIds) }, signal, }); - return response === 'true' ? true : false; + return response; }; export const pushCase = async ( diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx index 7f57149d4e56d..1c03a09a8c2ea 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx @@ -55,7 +55,6 @@ export const useCaseConfigure = ({ setLoading(true); const res = await getCaseConfigure({ signal: abortCtrl.signal }); if (!didCancel) { - setLoading(false); if (res != null) { setConnector(res.connectorId, res.connectorName); if (setClosureType != null) { @@ -73,6 +72,7 @@ export const useCaseConfigure = ({ } } } + setLoading(false); } } catch (error) { if (!didCancel) { @@ -117,7 +117,6 @@ export const useCaseConfigure = ({ abortCtrl.signal ); if (!didCancel) { - setPersistLoading(false); setConnector(res.connectorId); if (setClosureType) { setClosureType(res.closureType); @@ -131,6 +130,7 @@ export const useCaseConfigure = ({ } displaySuccessToast(i18n.SUCCESS_CONFIGURE, dispatchToaster); + setPersistLoading(false); } } catch (error) { if (!didCancel) { diff --git a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx index 2ae35796387b8..aefb0a93366b8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx @@ -11,11 +11,9 @@ import { useGetUserSavedObjectPermissions } from '../../lib/kibana'; import { SpyRoute } from '../../utils/route/spy_routes'; import { AllCases } from './components/all_cases'; -import { getSavedObjectReadOnly, CaseCallOut } from './components/callout'; +import { savedObjectReadOnly, CaseCallOut } from './components/callout'; import { CaseSavedObjectNoPermissions } from './saved_object_no_permissions'; -const infoReadSavedObject = getSavedObjectReadOnly(); - export const CasesPage = React.memo(() => { const userPermissions = useGetUserSavedObjectPermissions(); @@ -24,10 +22,11 @@ export const CasesPage = React.memo(() => { <WrapperPage> {userPermissions != null && !userPermissions?.crud && userPermissions?.read && ( <CaseCallOut - title={infoReadSavedObject.title} - message={infoReadSavedObject.description} + title={savedObjectReadOnly.title} + message={savedObjectReadOnly.description} /> )} + <CaseCallOut title={savedObjectReadOnly.title} message={savedObjectReadOnly.description} /> <AllCases userCanCrud={userPermissions?.crud ?? false} /> </WrapperPage> <SpyRoute /> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx b/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx index cbc7bbc62fbf9..4bb8afa7f8d42 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx @@ -13,9 +13,7 @@ import { SpyRoute } from '../../utils/route/spy_routes'; import { getCaseUrl } from '../../components/link_to'; import { navTabs } from '../home/home_navigations'; import { CaseView } from './components/case_view'; -import { getSavedObjectReadOnly, CaseCallOut } from './components/callout'; - -const infoReadSavedObject = getSavedObjectReadOnly(); +import { savedObjectReadOnly, CaseCallOut } from './components/callout'; export const CaseDetailsPage = React.memo(() => { const userPermissions = useGetUserSavedObjectPermissions(); @@ -29,7 +27,7 @@ export const CaseDetailsPage = React.memo(() => { return caseId != null ? ( <> {userPermissions != null && !userPermissions?.crud && userPermissions?.read && ( - <CaseCallOut title={infoReadSavedObject.title} message={infoReadSavedObject.description} /> + <CaseCallOut title={savedObjectReadOnly.title} message={savedObjectReadOnly.description} /> )} <CaseView caseId={caseId} userCanCrud={userPermissions?.crud ?? false} /> <SpyRoute /> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx new file mode 100644 index 0000000000000..64c6276fc1be2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx @@ -0,0 +1,226 @@ +/* + * 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 { CaseProps } from '../case_view'; +import { Case, Comment, SortFieldCase } from '../../../../containers/case/types'; +import { UseGetCasesState } from '../../../../containers/case/use_get_cases'; +import { UserAction, UserActionField } from '../../../../../../../../plugins/case/common/api/cases'; + +const updateCase = jest.fn(); +const fetchCase = jest.fn(); + +const basicCaseId = 'basic-case-id'; +const basicCommentId = 'basic-comment-id'; +const basicCreatedAt = '2020-02-20T23:06:33.798Z'; +const elasticUser = { + fullName: 'Leslie Knope', + username: 'lknope', + email: 'leslie.knope@elastic.co', +}; + +export const basicComment: Comment = { + comment: 'Solve this fast!', + id: basicCommentId, + createdAt: basicCreatedAt, + createdBy: elasticUser, + pushedAt: null, + pushedBy: null, + updatedAt: '2020-02-20T23:06:33.798Z', + updatedBy: { + username: 'elastic', + }, + version: 'WzQ3LDFc', +}; + +export const basicCase: Case = { + closedAt: null, + closedBy: null, + id: basicCaseId, + comments: [basicComment], + createdAt: '2020-02-13T19:44:23.627Z', + createdBy: elasticUser, + description: 'Security banana Issue', + externalService: null, + status: 'open', + tags: ['defacement'], + title: 'Another horrible breach!!', + totalComment: 1, + updatedAt: '2020-02-19T15:02:57.995Z', + updatedBy: { + username: 'elastic', + }, + version: 'WzQ3LDFd', +}; + +export const caseProps: CaseProps = { + caseId: basicCaseId, + userCanCrud: true, + caseData: basicCase, + fetchCase, + updateCase, +}; + +export const caseClosedProps: CaseProps = { + ...caseProps, + caseData: { + ...caseProps.caseData, + closedAt: '2020-02-20T23:06:33.798Z', + closedBy: { + username: 'elastic', + }, + status: 'closed', + }, +}; + +export const basicCaseClosed: Case = { + ...caseClosedProps.caseData, +}; + +const basicAction = { + actionAt: basicCreatedAt, + actionBy: elasticUser, + oldValue: null, + newValue: 'what a cool value', + caseId: basicCaseId, + commentId: null, +}; +export const caseUserActions = [ + { + ...basicAction, + actionBy: elasticUser, + actionField: ['comment'], + action: 'create', + actionId: 'tt', + }, +]; + +export const useGetCasesMockState: UseGetCasesState = { + data: { + countClosedCases: 0, + countOpenCases: 5, + cases: [ + basicCase, + { + closedAt: null, + closedBy: null, + id: '362a5c10-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:13.328Z', + createdBy: { username: 'elastic' }, + comments: [], + description: 'Security banana Issue', + externalService: { + pushedAt: '2020-02-13T19:45:01.901Z', + pushedBy: 'elastic', + connectorId: 'string', + connectorName: 'string', + externalId: 'string', + externalTitle: 'string', + externalUrl: 'string', + }, + status: 'open', + tags: ['phishing'], + title: 'Bad email', + totalComment: 0, + updatedAt: '2020-02-13T15:45:01.901Z', + updatedBy: { username: 'elastic' }, + version: 'WzQ3LDFd', + }, + { + closedAt: null, + closedBy: null, + id: '34f8b9e0-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:11.328Z', + createdBy: { username: 'elastic' }, + comments: [], + description: 'Security banana Issue', + externalService: { + pushedAt: '2020-02-13T19:45:01.901Z', + pushedBy: 'elastic', + connectorId: 'string', + connectorName: 'string', + externalId: 'string', + externalTitle: 'string', + externalUrl: 'string', + }, + status: 'open', + tags: ['phishing'], + title: 'Bad email', + totalComment: 0, + updatedAt: '2020-02-14T19:45:01.901Z', + updatedBy: { username: 'elastic' }, + version: 'WzQ3LDFd', + }, + { + closedAt: '2020-02-13T19:44:13.328Z', + closedBy: { username: 'elastic' }, + id: '31890e90-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:05.563Z', + createdBy: { username: 'elastic' }, + comments: [], + description: 'Security banana Issue', + externalService: null, + status: 'closed', + tags: ['phishing'], + title: 'Uh oh', + totalComment: 0, + updatedAt: null, + updatedBy: null, + version: 'WzQ3LDFd', + }, + { + closedAt: null, + closedBy: null, + id: '2f5b3210-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:01.901Z', + createdBy: { username: 'elastic' }, + comments: [], + description: 'Security banana Issue', + externalService: null, + status: 'open', + tags: ['phishing'], + title: 'Uh oh', + totalComment: 0, + updatedAt: null, + updatedBy: null, + version: 'WzQ3LDFd', + }, + ], + page: 1, + perPage: 5, + total: 10, + }, + loading: [], + selectedCases: [], + isError: false, + queryParams: { + page: 1, + perPage: 5, + sortField: SortFieldCase.createdAt, + sortOrder: 'desc', + }, + filterOptions: { search: '', reporters: [], tags: [], status: 'open' }, +}; + +const basicPush = { + connector_id: 'connector_id', + connector_name: 'connector name', + external_id: 'external_id', + external_title: 'external title', + external_url: 'basicPush.com', + pushed_at: basicCreatedAt, + pushed_by: elasticUser, +}; +export const getUserAction = (af: UserActionField, a: UserAction) => ({ + ...basicAction, + actionId: `${af[0]}-${a}`, + actionField: af, + action: a, + commentId: af[0] === 'comment' ? basicCommentId : null, + newValue: + a === 'push-to-service' && af[0] === 'pushed' + ? JSON.stringify(basicPush) + : basicAction.newValue, +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/form.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/form.ts new file mode 100644 index 0000000000000..9d2ac29bc47d7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/form.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export const mockFormHook = { + isSubmitted: false, + isSubmitting: false, + isValid: true, + submit: jest.fn(), + subscribe: jest.fn(), + setFieldValue: jest.fn(), + setFieldErrors: jest.fn(), + getFields: jest.fn(), + getFormData: jest.fn(), + getFieldDefaultValue: jest.fn(), + /* Returns a list of all errors in the form */ + getErrors: jest.fn(), + reset: jest.fn(), + __options: {}, + __formData$: {}, + __addField: jest.fn(), + __removeField: jest.fn(), + __validateFields: jest.fn(), + __updateFormDataAt: jest.fn(), + __readFieldConfigFromSchema: jest.fn(), +}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const getFormMock = (sampleData: any) => ({ + ...mockFormHook, + submit: () => + Promise.resolve({ + data: sampleData, + isValid: true, + }), + getFormData: () => sampleData, +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/router.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/router.ts new file mode 100644 index 0000000000000..a20ab00852a36 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/router.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Router } from 'react-router-dom'; +// eslint-disable-next-line @kbn/eslint/module_migration +import routeData from 'react-router'; +type Action = 'PUSH' | 'POP' | 'REPLACE'; +const pop: Action = 'POP'; +const location = { + pathname: '/network', + search: '', + state: '', + hash: '', +}; +export const mockHistory = { + length: 2, + location, + action: pop, + push: jest.fn(), + replace: jest.fn(), + go: jest.fn(), + goBack: jest.fn(), + goForward: jest.fn(), + block: jest.fn(), + createHref: jest.fn(), + listen: jest.fn(), +}; + +export const mockLocation = { + pathname: '/welcome', + hash: '', + search: '', + state: '', +}; + +export { Router, routeData }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.test.tsx new file mode 100644 index 0000000000000..74f6411f17fa0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.test.tsx @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { AddComment } from './'; +import { TestProviders } from '../../../../mock'; +import { getFormMock } from '../__mock__/form'; +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; + +import { useInsertTimeline } from '../../../../components/timeline/insert_timeline_popover/use_insert_timeline'; +import { usePostComment } from '../../../../containers/case/use_post_comment'; +import { useForm } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; +import { wait } from '../../../../lib/helpers'; +jest.mock( + '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' +); +jest.mock('../../../../components/timeline/insert_timeline_popover/use_insert_timeline'); +jest.mock('../../../../containers/case/use_post_comment'); + +export const useFormMock = useForm as jest.Mock; + +const useInsertTimelineMock = useInsertTimeline as jest.Mock; +const usePostCommentMock = usePostComment as jest.Mock; + +const onCommentSaving = jest.fn(); +const onCommentPosted = jest.fn(); +const postComment = jest.fn(); +const handleCursorChange = jest.fn(); +const handleOnTimelineChange = jest.fn(); + +const addCommentProps = { + caseId: '1234', + disabled: false, + insertQuote: null, + onCommentSaving, + onCommentPosted, + showLoading: false, +}; + +const defaultInsertTimeline = { + cursorPosition: { + start: 0, + end: 0, + }, + handleCursorChange, + handleOnTimelineChange, +}; + +const defaultPostCommment = { + isLoading: false, + isError: false, + postComment, +}; +const sampleData = { + comment: 'what a cool comment', +}; +describe('AddComment ', () => { + const formHookMock = getFormMock(sampleData); + + beforeEach(() => { + jest.resetAllMocks(); + useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); + usePostCommentMock.mockImplementation(() => defaultPostCommment); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); + }); + + it('should post comment on submit click', async () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <AddComment {...addCommentProps} /> + </Router> + </TestProviders> + ); + expect(wrapper.find(`[data-test-subj="add-comment"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeFalsy(); + + wrapper + .find(`[data-test-subj="submit-comment"]`) + .first() + .simulate('click'); + await wait(); + expect(onCommentSaving).toBeCalled(); + expect(postComment).toBeCalledWith(sampleData, onCommentPosted); + expect(formHookMock.reset).toBeCalled(); + }); + + it('should render spinner and disable submit when loading', () => { + usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true })); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <AddComment {...{ ...addCommentProps, showLoading: true }} /> + </Router> + </TestProviders> + ); + expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeTruthy(); + expect( + wrapper + .find(`[data-test-subj="submit-comment"]`) + .first() + .prop('isDisabled') + ).toBeTruthy(); + }); + + it('should disable submit button when disabled prop passed', () => { + usePostCommentMock.mockImplementation(() => ({ ...defaultPostCommment, isLoading: true })); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <AddComment {...{ ...addCommentProps, disabled: true }} /> + </Router> + </TestProviders> + ); + expect( + wrapper + .find(`[data-test-subj="submit-comment"]`) + .first() + .prop('isDisabled') + ).toBeTruthy(); + }); + + it('should insert a quote if one is available', () => { + const sampleQuote = 'what a cool quote'; + mount( + <TestProviders> + <Router history={mockHistory}> + <AddComment {...{ ...addCommentProps, insertQuote: sampleQuote }} /> + </Router> + </TestProviders> + ); + + expect(formHookMock.setFieldValue).toBeCalledWith( + 'comment', + `${sampleData.comment}\n\n${sampleQuote}` + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx index ecc57c50e28eb..eaba708948a99 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx @@ -71,10 +71,9 @@ export const AddComment = React.memo<AddCommentProps>( form.reset(); } }, [form, onCommentPosted, onCommentSaving]); - return ( <span id="add-comment-permLink"> - {isLoading && showLoading && <MySpinner size="xl" />} + {isLoading && showLoading && <MySpinner data-test-subj="loading-spinner" size="xl" />} <Form form={form}> <UseField path="comment" @@ -82,11 +81,12 @@ export const AddComment = React.memo<AddCommentProps>( componentProps={{ idAria: 'caseComment', isDisabled: isLoading, - dataTestSubj: 'caseComment', + dataTestSubj: 'add-comment', placeholder: i18n.ADD_COMMENT_HELP_TEXT, onCursorPositionUpdate: handleCursorChange, bottomRightContent: ( <EuiButton + data-test-subj="submit-comment" iconType="plusInCircle" isDisabled={isLoading || disabled} isLoading={isLoading} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx deleted file mode 100644 index d4ec32dfd070b..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx +++ /dev/null @@ -1,115 +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 { SortFieldCase } from '../../../../../containers/case/types'; -import { UseGetCasesState } from '../../../../../containers/case/use_get_cases'; - -export const useGetCasesMockState: UseGetCasesState = { - data: { - countClosedCases: 0, - countOpenCases: 5, - cases: [ - { - closedAt: null, - closedBy: null, - id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:23.627Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: null, - status: 'open', - tags: ['defacement'], - title: 'Another horrible breach', - totalComment: 0, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFd', - }, - { - closedAt: null, - closedBy: null, - id: '362a5c10-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:13.328Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: null, - status: 'open', - tags: ['phishing'], - title: 'Bad email', - totalComment: 0, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFd', - }, - { - closedAt: null, - closedBy: null, - id: '34f8b9e0-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:11.328Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: null, - status: 'open', - tags: ['phishing'], - title: 'Bad email', - totalComment: 0, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFd', - }, - { - closedAt: '2020-02-13T19:44:13.328Z', - closedBy: { username: 'elastic' }, - id: '31890e90-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:05.563Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: null, - status: 'closed', - tags: ['phishing'], - title: 'Uh oh', - totalComment: 0, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFd', - }, - { - closedAt: null, - closedBy: null, - id: '2f5b3210-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:01.901Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: null, - status: 'open', - tags: ['phishing'], - title: 'Uh oh', - totalComment: 0, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFd', - }, - ], - page: 1, - perPage: 5, - total: 10, - }, - loading: [], - selectedCases: [], - isError: false, - queryParams: { - page: 1, - perPage: 5, - sortField: SortFieldCase.createdAt, - sortOrder: 'desc', - }, - filterOptions: { search: '', reporters: [], tags: [], status: 'open' }, -}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx new file mode 100644 index 0000000000000..e008b94ab9e16 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { ServiceNowColumn } from './columns'; + +import { useGetCasesMockState } from '../__mock__/case_data'; + +describe('ServiceNowColumn ', () => { + it('Not pushed render', () => { + const wrapper = mount( + <ServiceNowColumn {...{ theCase: useGetCasesMockState.data.cases[0] }} /> + ); + expect( + wrapper + .find(`[data-test-subj="case-table-column-external-notPushed"]`) + .last() + .exists() + ).toBeTruthy(); + }); + it('Up to date', () => { + const wrapper = mount( + <ServiceNowColumn {...{ theCase: useGetCasesMockState.data.cases[1] }} /> + ); + expect( + wrapper + .find(`[data-test-subj="case-table-column-external-upToDate"]`) + .last() + .exists() + ).toBeTruthy(); + }); + it('Needs update', () => { + const wrapper = mount( + <ServiceNowColumn {...{ theCase: useGetCasesMockState.data.cases[2] }} /> + ); + expect( + wrapper + .find(`[data-test-subj="case-table-column-external-requiresUpdate"]`) + .last() + .exists() + ).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx index 0e12f78e29bc2..e48e5cb0c5959 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx @@ -114,7 +114,9 @@ export const getCasesColumns = ( name: i18n.COMMENTS, sortable: true, render: (totalComment: Case['totalComment']) => - renderStringField(`${totalComment}`, `case-table-column-commentCount`), + totalComment != null + ? renderStringField(`${totalComment}`, `case-table-column-commentCount`) + : getEmptyTagValue(), }, filterStatus === 'open' ? { @@ -150,7 +152,7 @@ export const getCasesColumns = ( }, }, { - name: 'ServiceNow Incident', + name: i18n.SERVICENOW_INCIDENT, render: (theCase: Case) => { if (theCase.id != null) { return <ServiceNowColumn theCase={theCase} />; @@ -159,7 +161,7 @@ export const getCasesColumns = ( }, }, { - name: 'Actions', + name: i18n.ACTIONS, actions, }, ]; @@ -168,7 +170,7 @@ interface Props { theCase: Case; } -const ServiceNowColumn: React.FC<Props> = ({ theCase }) => { +export const ServiceNowColumn: React.FC<Props> = ({ theCase }) => { const handleRenderDataToPush = useCallback(() => { const lastCaseUpdate = theCase.updatedAt != null ? new Date(theCase.updatedAt) : null; const lastCasePush = @@ -190,7 +192,9 @@ const ServiceNowColumn: React.FC<Props> = ({ theCase }) => { > {theCase.externalService?.externalTitle} </EuiLink> - {hasDataToPush ? i18n.REQUIRES_UPDATE : i18n.UP_TO_DATE} + {hasDataToPush + ? renderStringField(i18n.REQUIRES_UPDATE, `case-table-column-external-requiresUpdate`) + : renderStringField(i18n.UP_TO_DATE, `case-table-column-external-upToDate`)} </p> ); }, [theCase]); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx index a6da45a8c5bb1..f65736e7cd109 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx @@ -9,11 +9,15 @@ import { mount } from 'enzyme'; import moment from 'moment-timezone'; import { AllCases } from './'; import { TestProviders } from '../../../../mock'; -import { useGetCasesMockState } from './__mock__'; +import { useGetCasesMockState } from '../__mock__/case_data'; +import * as i18n from './translations'; + +import { getEmptyTagValue } from '../../../../components/empty_value'; import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; import { useGetCases } from '../../../../containers/case/use_get_cases'; import { useGetCasesStatus } from '../../../../containers/case/use_get_cases_status'; import { useUpdateCases } from '../../../../containers/case/use_bulk_update_case'; +import { getCasesColumns } from './columns'; jest.mock('../../../../containers/case/use_bulk_update_case'); jest.mock('../../../../containers/case/use_delete_cases'); jest.mock('../../../../containers/case/use_get_cases'); @@ -35,6 +39,7 @@ describe('AllCases', () => { const setSelectedCases = jest.fn(); const updateBulkStatus = jest.fn(); const fetchCasesStatus = jest.fn(); + const emptyTag = getEmptyTagValue().props.children; const defaultGetCases = { ...useGetCasesMockState, @@ -115,7 +120,7 @@ describe('AllCases', () => { .find(`[data-test-subj="case-table-column-createdBy"]`) .first() .text() - ).toEqual(useGetCasesMockState.data.cases[0].createdBy.username); + ).toEqual(useGetCasesMockState.data.cases[0].createdBy.fullName); expect( wrapper .find(`[data-test-subj="case-table-column-createdAt"]`) @@ -129,6 +134,39 @@ describe('AllCases', () => { .text() ).toEqual('Showing 10 cases'); }); + it('should render empty fields', () => { + useGetCasesMock.mockImplementation(() => ({ + ...defaultGetCases, + data: { + ...defaultGetCases.data, + cases: [ + { + ...defaultGetCases.data.cases[0], + id: null, + createdAt: null, + createdBy: null, + tags: null, + title: null, + totalComment: null, + }, + ], + }, + })); + const wrapper = mount( + <TestProviders> + <AllCases userCanCrud={true} /> + </TestProviders> + ); + const checkIt = (columnName: string, key: number) => { + const column = wrapper.find('[data-test-subj="cases-table"] tbody .euiTableRowCell').at(key); + if (columnName === i18n.ACTIONS) { + return; + } + expect(column.find('.euiTableRowCell--hideForDesktop').text()).toEqual(columnName); + expect(column.find('span').text()).toEqual(emptyTag); + }; + getCasesColumns([], 'open').map((i, key) => i.name != null && checkIt(`${i.name}`, key)); + }); it('should tableHeaderSortButton AllCases', () => { const wrapper = mount( <TestProviders> @@ -165,6 +203,30 @@ describe('AllCases', () => { version: firstCase.version, }); }); + it('opens case when row action icon clicked', () => { + useGetCasesMock.mockImplementation(() => ({ + ...defaultGetCases, + filterOptions: { ...defaultGetCases.filterOptions, status: 'closed' }, + })); + + const wrapper = mount( + <TestProviders> + <AllCases userCanCrud={true} /> + </TestProviders> + ); + wrapper + .find('[data-test-subj="action-open"]') + .first() + .simulate('click'); + const firstCase = useGetCasesMockState.data.cases[0]; + expect(dispatchUpdateCaseProperty).toBeCalledWith({ + caseId: firstCase.id, + updateKey: 'status', + updateValue: 'open', + refetchCasesStatus: fetchCasesStatus, + version: firstCase.version, + }); + }); it('Bulk delete', () => { useGetCasesMock.mockImplementation(() => ({ ...defaultGetCases, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.test.tsx new file mode 100644 index 0000000000000..615d052347203 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.test.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { CasesTableFilters } from './table_filters'; +import { TestProviders } from '../../../../mock'; + +import { useGetTags } from '../../../../containers/case/use_get_tags'; +import { useGetReporters } from '../../../../containers/case/use_get_reporters'; +import { DEFAULT_FILTER_OPTIONS } from '../../../../containers/case/use_get_cases'; +jest.mock('../../../../components/timeline/insert_timeline_popover/use_insert_timeline'); +jest.mock('../../../../containers/case/use_get_reporters'); +jest.mock('../../../../containers/case/use_get_tags'); + +const onFilterChanged = jest.fn(); +const fetchReporters = jest.fn(); + +const props = { + countClosedCases: 1234, + countOpenCases: 99, + onFilterChanged, + initial: DEFAULT_FILTER_OPTIONS, +}; +describe('CasesTableFilters ', () => { + beforeEach(() => { + jest.resetAllMocks(); + (useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'] }); + (useGetReporters as jest.Mock).mockReturnValue({ + reporters: ['casetester'], + respReporters: [{ username: 'casetester' }], + isLoading: true, + isError: false, + fetchReporters, + }); + }); + it('should render the initial case count', () => { + const wrapper = mount( + <TestProviders> + <CasesTableFilters {...props} /> + </TestProviders> + ); + expect( + wrapper + .find(`[data-test-subj="open-case-count"]`) + .last() + .text() + ).toEqual('Open cases (99)'); + expect( + wrapper + .find(`[data-test-subj="closed-case-count"]`) + .last() + .text() + ).toEqual('Closed cases (1234)'); + }); + it('should call onFilterChange when tags change', () => { + const wrapper = mount( + <TestProviders> + <CasesTableFilters {...props} /> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="options-filter-popover-button-Tags"]`) + .last() + .simulate('click'); + wrapper + .find(`[data-test-subj="options-filter-popover-item-0"]`) + .last() + .simulate('click'); + + expect(onFilterChanged).toBeCalledWith({ tags: ['coke'] }); + }); + it('should call onFilterChange when reporters change', () => { + const wrapper = mount( + <TestProviders> + <CasesTableFilters {...props} /> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="options-filter-popover-button-Reporter"]`) + .last() + .simulate('click'); + + wrapper + .find(`[data-test-subj="options-filter-popover-item-0"]`) + .last() + .simulate('click'); + + expect(onFilterChanged).toBeCalledWith({ reporters: [{ username: 'casetester' }] }); + }); + it('should call onFilterChange when search changes', () => { + const wrapper = mount( + <TestProviders> + <CasesTableFilters {...props} /> + </TestProviders> + ); + + wrapper + .find(`[data-test-subj="search-cases"]`) + .last() + .simulate('keyup', { keyCode: 13, target: { value: 'My search' } }); + expect(onFilterChanged).toBeCalledWith({ search: 'My search' }); + }); + it('should call onFilterChange when status toggled', () => { + const wrapper = mount( + <TestProviders> + <CasesTableFilters {...props} /> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="closed-case-count"]`) + .last() + .simulate('click'); + + expect(onFilterChanged).toBeCalledWith({ status: 'closed' }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx index a344dd7891010..da477a56c0a22 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/table_filters.tsx @@ -42,7 +42,7 @@ const CasesTableFiltersComponent = ({ onFilterChanged, initial = defaultInitial, }: CasesTableFiltersProps) => { - const [selectedReporters, setselectedReporters] = useState( + const [selectedReporters, setSelectedReporters] = useState( initial.reporters.map(r => r.full_name ?? r.username ?? '') ); const [search, setSearch] = useState(initial.search); @@ -54,7 +54,7 @@ const CasesTableFiltersComponent = ({ const handleSelectedReporters = useCallback( newReporters => { if (!isEqual(newReporters, selectedReporters)) { - setselectedReporters(newReporters); + setSelectedReporters(newReporters); const reportersObj = respReporters.filter( r => newReporters.includes(r.username) || newReporters.includes(r.full_name) ); @@ -97,6 +97,7 @@ const CasesTableFiltersComponent = ({ <EuiFlexItem grow={true}> <EuiFieldSearch aria-label={i18n.SEARCH_CASES} + data-test-subj="search-cases" fullWidth incremental={false} placeholder={i18n.SEARCH_PLACEHOLDER} @@ -107,6 +108,7 @@ const CasesTableFiltersComponent = ({ <EuiFlexItem grow={false}> <EuiFilterGroup> <EuiFilterButton + data-test-subj="open-case-count" withNext hasActiveFilters={showOpenCases} onClick={handleToggleFilter.bind(null, true)} @@ -115,6 +117,7 @@ const CasesTableFiltersComponent = ({ {countOpenCases != null ? ` (${countOpenCases})` : ''} </EuiFilterButton> <EuiFilterButton + data-test-subj="closed-case-count" hasActiveFilters={!showOpenCases} onClick={handleToggleFilter.bind(null, false)} > diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/translations.ts index 1bee96bc23fff..d3dcfa50ecfa5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/translations.ts @@ -46,6 +46,10 @@ export const BULK_ACTIONS = i18n.translate('xpack.siem.case.caseTable.bulkAction defaultMessage: 'Bulk actions', }); +export const SERVICENOW_INCIDENT = i18n.translate('xpack.siem.case.caseTable.snIncident', { + defaultMessage: 'ServiceNow Incident', +}); + export const SEARCH_PLACEHOLDER = i18n.translate('xpack.siem.case.caseTable.searchPlaceholder', { defaultMessage: 'e.g. case name', }); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/callout/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/helpers.tsx index 929e8640dceb6..3237104274473 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/callout/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/helpers.tsx @@ -6,7 +6,7 @@ import * as i18n from './translations'; -export const getSavedObjectReadOnly = () => ({ +export const savedObjectReadOnly = { title: i18n.READ_ONLY_SAVED_OBJECT_TITLE, description: i18n.READ_ONLY_SAVED_OBJECT_MSG, -}); +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.test.tsx new file mode 100644 index 0000000000000..126ea13e96af6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.test.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { CaseCallOut } from './'; + +const defaultProps = { + title: 'hey title', +}; + +describe('CaseCallOut ', () => { + it('Renders single message callout', () => { + const props = { + ...defaultProps, + message: 'we have one message', + }; + const wrapper = mount(<CaseCallOut {...props} />); + expect( + wrapper + .find(`[data-test-subj="callout-message"]`) + .last() + .exists() + ).toBeTruthy(); + expect( + wrapper + .find(`[data-test-subj="callout-messages"]`) + .last() + .exists() + ).toBeFalsy(); + }); + it('Renders multi message callout', () => { + const props = { + ...defaultProps, + messages: [ + { ...defaultProps, description: <p>{'we have two messages'}</p> }, + { ...defaultProps, description: <p>{'for real'}</p> }, + ], + }; + const wrapper = mount(<CaseCallOut {...props} />); + expect( + wrapper + .find(`[data-test-subj="callout-message"]`) + .last() + .exists() + ).toBeFalsy(); + expect( + wrapper + .find(`[data-test-subj="callout-messages"]`) + .last() + .exists() + ).toBeTruthy(); + }); + it('Dismisses callout', () => { + const props = { + ...defaultProps, + message: 'we have one message', + }; + const wrapper = mount(<CaseCallOut {...props} />); + expect(wrapper.find(`[data-test-subj="case-call-out"]`).exists()).toBeTruthy(); + wrapper + .find(`[data-test-subj="callout-dismiss"]`) + .last() + .simulate('click'); + expect(wrapper.find(`[data-test-subj="case-call-out"]`).exists()).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.tsx index 30a95db2d82a5..0fc93af7f318d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/callout/index.tsx @@ -24,10 +24,12 @@ const CaseCallOutComponent = ({ title, message, messages }: CaseCallOutProps) => return showCallOut ? ( <> - <EuiCallOut title={title} color="primary" iconType="gear"> - {!isEmpty(messages) && <EuiDescriptionList listItems={messages} />} - {!isEmpty(message) && <p>{message}</p>} - <EuiButton color="primary" onClick={handleCallOut}> + <EuiCallOut title={title} color="primary" iconType="gear" data-test-subj="case-call-out"> + {!isEmpty(messages) && ( + <EuiDescriptionList data-test-subj="callout-messages" listItems={messages} /> + )} + {!isEmpty(message) && <p data-test-subj="callout-message">{message}</p>} + <EuiButton data-test-subj="callout-dismiss" color="primary" onClick={handleCallOut}> {i18n.DISMISS_CALLOUT} </EuiButton> </EuiCallOut> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx index 2b16dfa150d61..718eb95767f2e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx @@ -84,7 +84,7 @@ const CaseStatusComp: React.FC<CaseStatusProps> = ({ <EuiFlexItem grow={false}> <EuiFlexGroup gutterSize="l" alignItems="center"> <EuiFlexItem> - <EuiButtonEmpty iconType="refresh" onClick={onRefresh}> + <EuiButtonEmpty data-test-subj="case-refresh" iconType="refresh" onClick={onRefresh}> {i18n.CASE_REFRESH} </EuiButtonEmpty> </EuiFlexItem> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx deleted file mode 100644 index 0e57326707e97..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx +++ /dev/null @@ -1,93 +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 { CaseProps } from '../index'; -import { Case } from '../../../../../containers/case/types'; - -const updateCase = jest.fn(); -const fetchCase = jest.fn(); - -export const caseProps: CaseProps = { - caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', - userCanCrud: true, - caseData: { - closedAt: null, - closedBy: null, - id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', - comments: [ - { - comment: 'Solve this fast!', - id: 'a357c6a0-5435-11ea-b427-fb51a1fcb7b8', - createdAt: '2020-02-20T23:06:33.798Z', - createdBy: { - fullName: 'Steph Milovic', - username: 'smilovic', - email: 'notmyrealemailfool@elastic.co', - }, - pushedAt: null, - pushedBy: null, - updatedAt: '2020-02-20T23:06:33.798Z', - updatedBy: { - username: 'elastic', - }, - version: 'WzQ3LDFd', - }, - ], - createdAt: '2020-02-13T19:44:23.627Z', - createdBy: { fullName: null, email: 'testemail@elastic.co', username: 'elastic' }, - description: 'Security banana Issue', - externalService: null, - status: 'open', - tags: ['defacement'], - title: 'Another horrible breach!!', - totalComment: 1, - updatedAt: '2020-02-19T15:02:57.995Z', - updatedBy: { - username: 'elastic', - }, - version: 'WzQ3LDFd', - }, - fetchCase, - updateCase, -}; - -export const caseClosedProps: CaseProps = { - ...caseProps, - caseData: { - ...caseProps.caseData, - closedAt: '2020-02-20T23:06:33.798Z', - closedBy: { - username: 'elastic', - }, - status: 'closed', - }, -}; - -export const data: Case = { - ...caseProps.caseData, -}; - -export const dataClosed: Case = { - ...caseClosedProps.caseData, -}; - -export const caseUserActions = [ - { - actionField: ['comment'], - action: 'create', - actionAt: '2020-03-20T17:10:09.814Z', - actionBy: { - fullName: 'Steph Milovic', - username: 'smilovic', - email: 'notmyrealemailfool@elastic.co', - }, - newValue: 'Solve this fast!', - oldValue: null, - actionId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', - caseId: '9b833a50-6acd-11ea-8fad-af86b1071bd9', - commentId: 'a357c6a0-5435-11ea-b427-fb51a1fcb7b8', - }, -]; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx index 49f5f44cba271..8a25a2121104d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx @@ -9,7 +9,7 @@ import { mount } from 'enzyme'; import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; import { TestProviders } from '../../../../mock'; -import { data } from './__mock__'; +import { basicCase } from '../__mock__/case_data'; import { CaseViewActions } from './actions'; jest.mock('../../../../containers/case/use_delete_cases'); const useDeleteCasesMock = useDeleteCases as jest.Mock; @@ -34,7 +34,7 @@ describe('CaseView actions', () => { it('clicking trash toggles modal', () => { const wrapper = mount( <TestProviders> - <CaseViewActions caseData={data} /> + <CaseViewActions caseData={basicCase} /> </TestProviders> ); @@ -54,12 +54,14 @@ describe('CaseView actions', () => { })); const wrapper = mount( <TestProviders> - <CaseViewActions caseData={data} /> + <CaseViewActions caseData={basicCase} /> </TestProviders> ); expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeTruthy(); wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click'); - expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([{ id: data.id, title: data.title }]); + expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([ + { id: basicCase.id, title: basicCase.title }, + ]); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx index 0b08b866df964..216180eb2cf0a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx @@ -40,7 +40,6 @@ const CaseViewActionsComponent: React.FC<CaseViewActions> = ({ caseData, disable ), [isDisplayConfirmDeleteModal, caseData] ); - // TO DO refactor each of these const's into their own components const propertyActions = useMemo( () => [ { diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx index 3f5b3a3127177..3721a5a727ca5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx @@ -5,56 +5,43 @@ */ import React from 'react'; -import { Router } from 'react-router-dom'; import { mount } from 'enzyme'; -/* eslint-disable @kbn/eslint/module_migration */ -import routeData from 'react-router'; -/* eslint-enable @kbn/eslint/module_migration */ -import { CaseComponent } from './'; -import { caseProps, caseClosedProps, data, dataClosed, caseUserActions } from './__mock__'; + +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; +import { CaseComponent, CaseView } from './'; +import { + basicCaseClosed, + caseClosedProps, + caseProps, + caseUserActions, +} from '../__mock__/case_data'; import { TestProviders } from '../../../../mock'; import { useUpdateCase } from '../../../../containers/case/use_update_case'; +import { useGetCase } from '../../../../containers/case/use_get_case'; import { useGetCaseUserActions } from '../../../../containers/case/use_get_case_user_actions'; import { wait } from '../../../../lib/helpers'; import { usePushToService } from '../use_push_to_service'; jest.mock('../../../../containers/case/use_update_case'); jest.mock('../../../../containers/case/use_get_case_user_actions'); +jest.mock('../../../../containers/case/use_get_case'); jest.mock('../use_push_to_service'); const useUpdateCaseMock = useUpdateCase as jest.Mock; const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock; const usePushToServiceMock = usePushToService as jest.Mock; -type Action = 'PUSH' | 'POP' | 'REPLACE'; -const pop: Action = 'POP'; -const location = { - pathname: '/network', - search: '', - state: '', - hash: '', -}; -const mockHistory = { - length: 2, - location, - action: pop, - push: jest.fn(), - replace: jest.fn(), - go: jest.fn(), - goBack: jest.fn(), - goForward: jest.fn(), - block: jest.fn(), - createHref: jest.fn(), - listen: jest.fn(), -}; - -const mockLocation = { - pathname: '/welcome', - hash: '', - search: '', - state: '', -}; describe('CaseView ', () => { const updateCaseProperty = jest.fn(); const fetchCaseUserActions = jest.fn(); + const fetchCase = jest.fn(); + const updateCase = jest.fn(); + const data = caseProps.caseData; + const defaultGetCase = { + isLoading: false, + isError: false, + data, + updateCase, + fetchCase, + }; /* eslint-disable no-console */ // Silence until enzyme fixed to use ReactTestUtils.act() const originalError = console.error; @@ -84,17 +71,23 @@ describe('CaseView ', () => { participants: [data.createdBy], }; - const defaultUsePushToServiceMock = { - pushButton: <>{'Hello Button'}</>, - pushCallouts: null, - }; - beforeEach(() => { jest.resetAllMocks(); useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState); jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); useGetCaseUserActionsMock.mockImplementation(() => defaultUseGetCaseUserActions); - usePushToServiceMock.mockImplementation(() => defaultUsePushToServiceMock); + usePushToServiceMock.mockImplementation(({ updateCase: updateCaseMockCall }) => ({ + pushButton: ( + <button + data-test-subj="mock-button" + onClick={() => updateCaseMockCall(caseProps.caseData)} + type="button" + > + {'Hello Button'} + </button> + ), + pushCallouts: null, + })); }); it('should render CaseComponent', async () => { @@ -120,7 +113,7 @@ describe('CaseView ', () => { ).toEqual(data.status); expect( wrapper - .find(`[data-test-subj="case-view-tag-list"] .euiBadge__text`) + .find(`[data-test-subj="case-view-tag-list"] [data-test-subj="case-tag"]`) .first() .text() ).toEqual(data.tags[0]); @@ -139,7 +132,7 @@ describe('CaseView ', () => { ).toEqual(data.createdAt); expect( wrapper - .find(`[data-test-subj="case-view-description"]`) + .find(`[data-test-subj="description-action"] [data-test-subj="user-action-markdown"]`) .first() .prop('raw') ).toEqual(data.description); @@ -148,7 +141,7 @@ describe('CaseView ', () => { it('should show closed indicators in header when case is closed', async () => { useUpdateCaseMock.mockImplementation(() => ({ ...defaultUpdateCaseState, - caseData: dataClosed, + caseData: basicCaseClosed, })); const wrapper = mount( <TestProviders> @@ -164,13 +157,13 @@ describe('CaseView ', () => { .find(`[data-test-subj="case-view-closedAt"]`) .first() .prop('value') - ).toEqual(dataClosed.closedAt); + ).toEqual(basicCaseClosed.closedAt); expect( wrapper .find(`[data-test-subj="case-view-status"]`) .first() .text() - ).toEqual(dataClosed.status); + ).toEqual(basicCaseClosed.status); }); it('should dispatch update state when button is toggled', async () => { @@ -188,7 +181,12 @@ describe('CaseView ', () => { expect(updateCaseProperty).toHaveBeenCalled(); }); - it('should render comments', async () => { + it('should display EditableTitle isLoading', () => { + useUpdateCaseMock.mockImplementation(() => ({ + ...defaultUpdateCaseState, + isLoading: true, + updateKey: 'title', + })); const wrapper = mount( <TestProviders> <Router history={mockHistory}> @@ -196,32 +194,230 @@ describe('CaseView ', () => { </Router> </TestProviders> ); - await wait(); expect( wrapper - .find( - `div[data-test-subj="user-action-${data.comments[0].id}-avatar"] [data-test-subj="user-action-avatar"]` - ) + .find('[data-test-subj="editable-title-loading"]') + .first() + .exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="editable-title-edit-icon"]') .first() - .prop('name') - ).toEqual(data.comments[0].createdBy.fullName); + .exists() + ).toBeFalsy(); + }); + it('should display Toggle Status isLoading', () => { + useUpdateCaseMock.mockImplementation(() => ({ + ...defaultUpdateCaseState, + isLoading: true, + updateKey: 'status', + })); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <CaseComponent {...caseProps} /> + </Router> + </TestProviders> + ); expect( wrapper - .find( - `div[data-test-subj="user-action-${data.comments[0].id}"] [data-test-subj="user-action-title"] strong` - ) + .find('[data-test-subj="toggle-case-status"]') .first() - .text() - ).toEqual(data.comments[0].createdBy.username); + .prop('isLoading') + ).toBeTruthy(); + }); + it('should display description isLoading', () => { + useUpdateCaseMock.mockImplementation(() => ({ + ...defaultUpdateCaseState, + isLoading: true, + updateKey: 'description', + })); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <CaseComponent {...caseProps} /> + </Router> + </TestProviders> + ); + expect( + wrapper + .find('[data-test-subj="description-action"] [data-test-subj="user-action-title-loading"]') + .first() + .exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="description-action"] [data-test-subj="property-actions"]') + .first() + .exists() + ).toBeFalsy(); + }); + + it('should display tags isLoading', () => { + useUpdateCaseMock.mockImplementation(() => ({ + ...defaultUpdateCaseState, + isLoading: true, + updateKey: 'tags', + })); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <CaseComponent {...caseProps} /> + </Router> + </TestProviders> + ); + expect( + wrapper + .find('[data-test-subj="case-view-tag-list"] [data-test-subj="tag-list-loading"]') + .first() + .exists() + ).toBeTruthy(); expect( wrapper - .find( - `div[data-test-subj="user-action-${data.comments[0].id}"] [data-test-subj="markdown"]` - ) + .find('[data-test-subj="tag-list-edit"]') .first() - .prop('source') - ).toEqual(data.comments[0].comment); + .exists() + ).toBeFalsy(); + }); + + it('should update title', () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <CaseComponent {...caseProps} /> + </Router> + </TestProviders> + ); + const newTitle = 'The new title'; + wrapper + .find(`[data-test-subj="editable-title-edit-icon"]`) + .first() + .simulate('click'); + wrapper.update(); + wrapper + .find(`[data-test-subj="editable-title-input-field"]`) + .last() + .simulate('change', { target: { value: newTitle } }); + + wrapper.update(); + wrapper + .find(`[data-test-subj="editable-title-submit-btn"]`) + .first() + .simulate('click'); + + wrapper.update(); + const updateObject = updateCaseProperty.mock.calls[0][0]; + expect(updateObject.updateKey).toEqual('title'); + expect(updateObject.updateValue).toEqual(newTitle); + }); + + it('should push updates on button click', async () => { + useGetCaseUserActionsMock.mockImplementation(() => ({ + ...defaultUseGetCaseUserActions, + hasDataToPush: true, + })); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <CaseComponent {...{ ...caseProps, updateCase }} /> + </Router> + </TestProviders> + ); + expect( + wrapper + .find('[data-test-subj="has-data-to-push-button"]') + .first() + .exists() + ).toBeTruthy(); + wrapper + .find('[data-test-subj="mock-button"]') + .first() + .simulate('click'); + wrapper.update(); + await wait(); + expect(updateCase).toBeCalledWith(caseProps.caseData); + expect(fetchCaseUserActions).toBeCalledWith(caseProps.caseData.id); + }); + + it('should return null if error', () => { + (useGetCase as jest.Mock).mockImplementation(() => ({ + ...defaultGetCase, + isError: true, + })); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <CaseView + {...{ + caseId: '1234', + userCanCrud: true, + }} + /> + </Router> + </TestProviders> + ); + expect(wrapper).toEqual({}); + }); + + it('should return spinner if loading', () => { + (useGetCase as jest.Mock).mockImplementation(() => ({ + ...defaultGetCase, + isLoading: true, + })); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <CaseView + {...{ + caseId: '1234', + userCanCrud: true, + }} + /> + </Router> + </TestProviders> + ); + expect(wrapper.find('[data-test-subj="case-view-loading"]').exists()).toBeTruthy(); + }); + + it('should return case view when data is there', () => { + (useGetCase as jest.Mock).mockImplementation(() => defaultGetCase); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <CaseView + {...{ + caseId: '1234', + userCanCrud: true, + }} + /> + </Router> + </TestProviders> + ); + expect(wrapper.find('[data-test-subj="case-view-title"]').exists()).toBeTruthy(); + }); + + it('should refresh data on refresh', () => { + (useGetCase as jest.Mock).mockImplementation(() => defaultGetCase); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <CaseView + {...{ + caseId: '1234', + userCanCrud: true, + }} + /> + </Router> + </TestProviders> + ); + wrapper + .find('[data-test-subj="case-refresh"]') + .first() + .simulate('click'); + expect(fetchCaseUserActions).toBeCalledWith(caseProps.caseData.id); + expect(fetchCase).toBeCalled(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 947da51365d66..3cf0405f40637 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -271,7 +271,11 @@ export const CaseComponent = React.memo<CaseProps>( onChange={toggleStatusCase} /> </EuiFlexItem> - {hasDataToPush && <EuiFlexItem grow={false}>{pushButton}</EuiFlexItem>} + {hasDataToPush && ( + <EuiFlexItem data-test-subj="has-data-to-push-button" grow={false}> + {pushButton} + </EuiFlexItem> + )} </EuiFlexGroup> </> )} @@ -316,7 +320,7 @@ export const CaseView = React.memo(({ caseId, userCanCrud }: Props) => { return ( <MyEuiFlexGroup justifyContent="center" alignItems="center"> <EuiFlexItem grow={false}> - <EuiLoadingSpinner size="xl" /> + <EuiLoadingSpinner data-test-subj="case-view-loading" size="xl" /> </EuiFlexItem> </MyEuiFlexGroup> ); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.test.tsx new file mode 100644 index 0000000000000..d480744fc932a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.test.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { Create } from './'; +import { TestProviders } from '../../../../mock'; +import { getFormMock } from '../__mock__/form'; +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; + +import { useInsertTimeline } from '../../../../components/timeline/insert_timeline_popover/use_insert_timeline'; +import { usePostCase } from '../../../../containers/case/use_post_case'; +jest.mock('../../../../components/timeline/insert_timeline_popover/use_insert_timeline'); +jest.mock('../../../../containers/case/use_post_case'); +import { useForm } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks'; +import { wait } from '../../../../lib/helpers'; +import { SiemPageName } from '../../../home/types'; +jest.mock( + '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' +); + +export const useFormMock = useForm as jest.Mock; + +const useInsertTimelineMock = useInsertTimeline as jest.Mock; +const usePostCaseMock = usePostCase as jest.Mock; + +const postCase = jest.fn(); +const handleCursorChange = jest.fn(); +const handleOnTimelineChange = jest.fn(); + +const defaultInsertTimeline = { + cursorPosition: { + start: 0, + end: 0, + }, + handleCursorChange, + handleOnTimelineChange, +}; +const sampleData = { + description: 'what a great description', + tags: ['coke', 'pepsi'], + title: 'what a cool title', +}; +const defaultPostCase = { + isLoading: false, + isError: false, + caseData: null, + postCase, +}; +describe('Create case', () => { + const formHookMock = getFormMock(sampleData); + + beforeEach(() => { + jest.resetAllMocks(); + useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); + usePostCaseMock.mockImplementation(() => defaultPostCase); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); + }); + + it('should post case on submit click', async () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="create-case-submit"]`) + .first() + .simulate('click'); + await wait(); + expect(postCase).toBeCalledWith(sampleData); + }); + + it('should redirect to all cases on cancel click', () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="create-case-cancel"]`) + .first() + .simulate('click'); + expect(mockHistory.replace.mock.calls[0][0].pathname).toEqual(`/${SiemPageName.case}`); + }); + it('should redirect to new case when caseData is there', () => { + const sampleId = '777777'; + usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, caseData: { id: sampleId } })); + mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + expect(mockHistory.replace.mock.calls[0][0].pathname).toEqual( + `/${SiemPageName.case}/${sampleId}` + ); + }); + + it('should render spinner when loading', () => { + usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, isLoading: true })); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + expect(wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx index 740909db408ec..53b792bb9b5eb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx @@ -73,7 +73,7 @@ export const Create = React.memo(() => { const handleSetIsCancel = useCallback(() => { setIsCancel(true); - }, [isCancel]); + }, []); if (caseData != null && caseData.id) { return <Redirect to={`/${SiemPageName.case}/${caseData.id}`} />; @@ -85,7 +85,7 @@ export const Create = React.memo(() => { return ( <EuiPanel> - {isLoading && <MySpinner size="xl" />} + {isLoading && <MySpinner data-test-subj="create-case-loading-spinner" size="xl" />} <Form form={form}> <CommonUseField path="title" @@ -107,7 +107,7 @@ export const Create = React.memo(() => { euiFieldProps: { fullWidth: true, placeholder: '', - isDisabled: isLoading, + disabled: isLoading, }, }} /> @@ -151,6 +151,7 @@ export const Create = React.memo(() => { </EuiFlexItem> <EuiFlexItem grow={false}> <EuiButton + data-test-subj="create-case-submit" fill iconType="plusInCircle" isDisabled={isLoading} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.test.tsx new file mode 100644 index 0000000000000..8ad2f8f8cb737 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.test.tsx @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { TagList } from './'; +import { getFormMock } from '../__mock__/form'; +import { TestProviders } from '../../../../mock'; +import { wait } from '../../../../lib/helpers'; +import { useForm } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks'; +import { act } from 'react-dom/test-utils'; + +jest.mock( + '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' +); +const onSubmit = jest.fn(); +const defaultProps = { + disabled: false, + isLoading: false, + onSubmit, + tags: [], +}; + +describe('TagList ', () => { + const sampleTags = ['coke', 'pepsi']; + const formHookMock = getFormMock({ tags: sampleTags }); + beforeEach(() => { + jest.resetAllMocks(); + (useForm as jest.Mock).mockImplementation(() => ({ form: formHookMock })); + }); + it('Renders no tags, and then edit', () => { + const wrapper = mount( + <TestProviders> + <TagList {...defaultProps} /> + </TestProviders> + ); + expect( + wrapper + .find(`[data-test-subj="no-tags"]`) + .last() + .exists() + ).toBeTruthy(); + wrapper + .find(`[data-test-subj="tag-list-edit-button"]`) + .last() + .simulate('click'); + expect( + wrapper + .find(`[data-test-subj="no-tags"]`) + .last() + .exists() + ).toBeFalsy(); + expect( + wrapper + .find(`[data-test-subj="edit-tags"]`) + .last() + .exists() + ).toBeTruthy(); + }); + it('Edit tag on submit', async () => { + const wrapper = mount( + <TestProviders> + <TagList {...defaultProps} /> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="tag-list-edit-button"]`) + .last() + .simulate('click'); + await act(async () => { + wrapper + .find(`[data-test-subj="edit-tags-submit"]`) + .last() + .simulate('click'); + await wait(); + expect(onSubmit).toBeCalledWith(sampleTags); + }); + }); + it('Cancels on cancel', async () => { + const props = { + ...defaultProps, + tags: ['pepsi'], + }; + const wrapper = mount( + <TestProviders> + <TagList {...props} /> + </TestProviders> + ); + expect( + wrapper + .find(`[data-test-subj="case-tag"]`) + .last() + .exists() + ).toBeTruthy(); + wrapper + .find(`[data-test-subj="tag-list-edit-button"]`) + .last() + .simulate('click'); + await act(async () => { + expect( + wrapper + .find(`[data-test-subj="case-tag"]`) + .last() + .exists() + ).toBeFalsy(); + wrapper + .find(`[data-test-subj="edit-tags-cancel"]`) + .last() + .simulate('click'); + await wait(); + wrapper.update(); + expect( + wrapper + .find(`[data-test-subj="case-tag"]`) + .last() + .exists() + ).toBeTruthy(); + }); + }); + it('Renders disabled button', () => { + const props = { ...defaultProps, disabled: true }; + const wrapper = mount( + <TestProviders> + <TagList {...props} /> + </TestProviders> + ); + expect( + wrapper + .find(`[data-test-subj="tag-list-edit-button"]`) + .last() + .prop('disabled') + ).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx index f7d890ca60b16..9bac000b93235 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx @@ -61,10 +61,11 @@ export const TagList = React.memo( <EuiFlexItem grow={false}> <h4>{i18n.TAGS}</h4> </EuiFlexItem> - {isLoading && <EuiLoadingSpinner />} + {isLoading && <EuiLoadingSpinner data-test-subj="tag-list-loading" />} {!isLoading && ( - <EuiFlexItem grow={false}> + <EuiFlexItem data-test-subj="tag-list-edit" grow={false}> <EuiButtonIcon + data-test-subj="tag-list-edit-button" isDisabled={disabled} aria-label={i18n.EDIT_TAGS_ARIA} iconType={'pencil'} @@ -74,17 +75,19 @@ export const TagList = React.memo( )} </EuiFlexGroup> <EuiHorizontalRule margin="xs" /> - <MyFlexGroup gutterSize="xs"> - {tags.length === 0 && !isEditTags && <p>{i18n.NO_TAGS}</p>} + <MyFlexGroup gutterSize="xs" data-test-subj="grr"> + {tags.length === 0 && !isEditTags && <p data-test-subj="no-tags">{i18n.NO_TAGS}</p>} {tags.length > 0 && !isEditTags && tags.map((tag, key) => ( <EuiFlexItem grow={false} key={`${tag}${key}`}> - <EuiBadge color="hollow">{tag}</EuiBadge> + <EuiBadge data-test-subj="case-tag" color="hollow"> + {tag} + </EuiBadge> </EuiFlexItem> ))} {isEditTags && ( - <EuiFlexGroup direction="column"> + <EuiFlexGroup data-test-subj="edit-tags" direction="column"> <EuiFlexItem> <Form form={form}> <CommonUseField @@ -105,6 +108,7 @@ export const TagList = React.memo( <EuiFlexItem grow={false}> <EuiButton color="secondary" + data-test-subj="edit-tags-submit" fill iconType="save" onClick={onSubmitTags} @@ -115,6 +119,7 @@ export const TagList = React.memo( </EuiFlexItem> <EuiFlexItem grow={false}> <EuiButtonEmpty + data-test-subj="edit-tags-cancel" iconType="cross" onClick={setIsEditTags.bind(null, false)} size="s" diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.test.tsx new file mode 100644 index 0000000000000..77215e2318ded --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.test.tsx @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* eslint-disable react/display-name */ +import React from 'react'; +import { renderHook, act } from '@testing-library/react-hooks'; +import { usePushToService, ReturnUsePushToService, UsePushToService } from './'; +import { TestProviders } from '../../../../mock'; +import { usePostPushToService } from '../../../../containers/case/use_post_push_to_service'; +import { ClosureType } from '../../../../../../../../plugins/case/common/api/cases'; +import * as i18n from './translations'; +import { useGetActionLicense } from '../../../../containers/case/use_get_action_license'; +import { getKibanaConfigError, getLicenseError } from './helpers'; +import * as api from '../../../../containers/case/configure/api'; +jest.mock('../../../../containers/case/use_get_action_license'); +jest.mock('../../../../containers/case/use_post_push_to_service'); +jest.mock('../../../../containers/case/configure/api'); + +describe('usePushToService', () => { + const caseId = '12345'; + const updateCase = jest.fn(); + const postPushToService = jest.fn(); + const mockPostPush = { + isLoading: false, + postPushToService, + }; + const closureType: ClosureType = 'close-by-user'; + const mockConnector = { + connectorId: 'c00l', + connectorName: 'name', + }; + const mockCaseConfigure = { + ...mockConnector, + createdAt: 'string', + createdBy: {}, + closureType, + updatedAt: 'string', + updatedBy: {}, + version: 'string', + }; + const getConfigureMock = jest.spyOn(api, 'getCaseConfigure'); + const actionLicense = { + id: '.servicenow', + name: 'ServiceNow', + minimumLicenseRequired: 'platinum', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + }; + beforeEach(() => { + jest.resetAllMocks(); + (usePostPushToService as jest.Mock).mockImplementation(() => mockPostPush); + (useGetActionLicense as jest.Mock).mockImplementation(() => ({ + isLoading: false, + actionLicense, + })); + getConfigureMock.mockImplementation(() => Promise.resolve(mockCaseConfigure)); + }); + it('push case button posts the push with correct args', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( + () => + usePushToService({ + caseId, + caseStatus: 'open', + isNew: false, + updateCase, + userCanCrud: true, + }), + { + wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + } + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(getConfigureMock).toBeCalled(); + result.current.pushButton.props.children.props.onClick(); + expect(postPushToService).toBeCalledWith({ ...mockConnector, caseId, updateCase }); + expect(result.current.pushCallouts).toBeNull(); + }); + }); + it('Displays message when user does not have premium license', async () => { + (useGetActionLicense as jest.Mock).mockImplementation(() => ({ + isLoading: false, + actionLicense: { + ...actionLicense, + enabledInLicense: false, + }, + })); + await act(async () => { + const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( + () => + usePushToService({ + caseId, + caseStatus: 'open', + isNew: false, + updateCase, + userCanCrud: true, + }), + { + wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + } + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(getLicenseError().title); + }); + }); + it('Displays message when user does not have case enabled in config', async () => { + (useGetActionLicense as jest.Mock).mockImplementation(() => ({ + isLoading: false, + actionLicense: { + ...actionLicense, + enabledInConfig: false, + }, + })); + await act(async () => { + const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( + () => + usePushToService({ + caseId, + caseStatus: 'open', + isNew: false, + updateCase, + userCanCrud: true, + }), + { + wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + } + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(getKibanaConfigError().title); + }); + }); + it('Displays message when user does not have a connector configured', async () => { + getConfigureMock.mockImplementation(() => + Promise.resolve({ + ...mockCaseConfigure, + connectorId: 'none', + }) + ); + await act(async () => { + const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( + () => + usePushToService({ + caseId, + caseStatus: 'open', + isNew: false, + updateCase, + userCanCrud: true, + }), + { + wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + } + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE); + }); + }); + it('Displays message when case is closed', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( + () => + usePushToService({ + caseId, + caseStatus: 'closed', + isNew: false, + updateCase, + userCanCrud: true, + }), + { + wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + } + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + const errorsMsg = result.current.pushCallouts?.props.messages; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].title).toEqual(i18n.PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.tsx index 4f370ec978906..5092cba6872e3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/use_push_to_service/index.tsx @@ -19,7 +19,7 @@ import { CaseCallOut } from '../callout'; import { getLicenseError, getKibanaConfigError } from './helpers'; import * as i18n from './translations'; -interface UsePushToService { +export interface UsePushToService { caseId: string; caseStatus: string; isNew: boolean; @@ -32,7 +32,7 @@ interface Connector { connectorName: string; } -interface ReturnUsePushToService { +export interface ReturnUsePushToService { pushButton: JSX.Element; pushCallouts: JSX.Element | null; } @@ -122,6 +122,7 @@ export const usePushToService = ({ const pushToServiceButton = useMemo( () => ( <EuiButton + data-test-subj="push-to-service-now" fill iconType="importAction" onClick={handlePushToService} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx new file mode 100644 index 0000000000000..5c342538f0feb --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx @@ -0,0 +1,143 @@ +/* + * 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 { getUserAction } from '../__mock__/case_data'; +import { getLabelTitle } from './helpers'; +import * as i18n from '../case_view/translations'; +import { mount } from 'enzyme'; + +describe('User action tree helpers', () => { + it('label title generated for update tags', () => { + const action = getUserAction(['title'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'tags', + firstIndexPushToService: 0, + index: 0, + }); + + const wrapper = mount(<>{result}</>); + expect( + wrapper + .find(`[data-test-subj="ua-tags-label"]`) + .first() + .text() + ).toEqual(` ${i18n.TAGS.toLowerCase()}`); + + expect( + wrapper + .find(`[data-test-subj="ua-tag"]`) + .first() + .text() + ).toEqual(action.newValue); + }); + it('label title generated for update title', () => { + const action = getUserAction(['title'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'title', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual( + `${i18n.CHANGED_FIELD.toLowerCase()} ${i18n.CASE_NAME.toLowerCase()} ${i18n.TO} "${ + action.newValue + }"` + ); + }); + it('label title generated for update description', () => { + const action = getUserAction(['description'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'description', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual(`${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`); + }); + it('label title generated for update status to open', () => { + const action = { ...getUserAction(['status'], 'update'), newValue: 'open' }; + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'status', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual(`${i18n.REOPENED_CASE.toLowerCase()} ${i18n.CASE}`); + }); + it('label title generated for update status to closed', () => { + const action = { ...getUserAction(['status'], 'update'), newValue: 'closed' }; + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'status', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual(`${i18n.CLOSED_CASE.toLowerCase()} ${i18n.CASE}`); + }); + it('label title generated for update comment', () => { + const action = getUserAction(['comment'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'comment', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual(`${i18n.EDITED_FIELD} ${i18n.COMMENT.toLowerCase()}`); + }); + it('label title generated for pushed incident', () => { + const action = getUserAction(['pushed'], 'push-to-service'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'pushed', + firstIndexPushToService: 0, + index: 0, + }); + + const wrapper = mount(<>{result}</>); + expect( + wrapper + .find(`[data-test-subj="pushed-label"]`) + .first() + .text() + ).toEqual(i18n.PUSHED_NEW_INCIDENT); + expect( + wrapper + .find(`[data-test-subj="pushed-value"]`) + .first() + .prop('href') + ).toEqual(JSON.parse(action.newValue).external_url); + }); + it('label title generated for needs update incident', () => { + const action = getUserAction(['pushed'], 'push-to-service'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'pushed', + firstIndexPushToService: 0, + index: 1, + }); + + const wrapper = mount(<>{result}</>); + expect( + wrapper + .find(`[data-test-subj="pushed-label"]`) + .first() + .text() + ).toEqual(i18n.UPDATE_INCIDENT); + expect( + wrapper + .find(`[data-test-subj="pushed-value"]`) + .first() + .prop('href') + ).toEqual(JSON.parse(action.newValue).external_url); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx index 008f4d7048f56..d6016e540bdc0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx @@ -41,14 +41,16 @@ export const getLabelTitle = ({ action, field, firstIndexPushToService, index }: const getTagsLabelTitle = (action: CaseUserActions) => ( <EuiFlexGroup alignItems="baseline" gutterSize="xs" component="span"> - <EuiFlexItem> + <EuiFlexItem data-test-subj="ua-tags-label"> {action.action === 'add' && i18n.ADDED_FIELD} {action.action === 'delete' && i18n.REMOVED_FIELD} {i18n.TAGS.toLowerCase()} </EuiFlexItem> {action.newValue != null && action.newValue.split(',').map(tag => ( <EuiFlexItem grow={false} key={tag}> - <EuiBadge color="default">{tag}</EuiBadge> + <EuiBadge data-test-subj={`ua-tag`} color="default"> + {tag} + </EuiBadge> </EuiFlexItem> ))} </EuiFlexGroup> @@ -61,12 +63,12 @@ const getPushedServiceLabelTitle = ( ) => { const pushedVal = JSON.parse(action.newValue ?? '') as CaseFullExternalService; return ( - <EuiFlexGroup alignItems="baseline" gutterSize="xs"> - <EuiFlexItem> + <EuiFlexGroup alignItems="baseline" gutterSize="xs" data-test-subj="pushed-service-label-title"> + <EuiFlexItem data-test-subj="pushed-label"> {firstIndexPushToService === index ? i18n.PUSHED_NEW_INCIDENT : i18n.UPDATE_INCIDENT} </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiLink href={pushedVal?.external_url} target="_blank"> + <EuiLink data-test-subj="pushed-value" href={pushedVal?.external_url} target="_blank"> {pushedVal?.connector_name} {pushedVal?.external_title} </EuiLink> </EuiFlexItem> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx new file mode 100644 index 0000000000000..0d8cd729b4a1d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx @@ -0,0 +1,331 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; +import { getFormMock } from '../__mock__/form'; +import { useUpdateComment } from '../../../../containers/case/use_update_comment'; +import { basicCase, getUserAction } from '../__mock__/case_data'; +import { UserActionTree } from './'; +import { TestProviders } from '../../../../mock'; +import { useFormMock } from '../create/index.test'; +import { wait } from '../../../../lib/helpers'; +import { act } from 'react-dom/test-utils'; +jest.mock( + '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' +); + +const fetchUserActions = jest.fn(); +const onUpdateField = jest.fn(); +const updateCase = jest.fn(); +const defaultProps = { + data: basicCase, + caseUserActions: [], + firstIndexPushToService: -1, + isLoadingDescription: false, + isLoadingUserActions: false, + lastIndexPushToService: -1, + userCanCrud: true, + fetchUserActions, + onUpdateField, + updateCase, +}; +const useUpdateCommentMock = useUpdateComment as jest.Mock; +jest.mock('../../../../containers/case/use_update_comment'); + +const patchComment = jest.fn(); +describe('UserActionTree ', () => { + const sampleData = { + content: 'what a great comment update', + }; + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + useUpdateCommentMock.mockImplementation(() => ({ + isLoadingIds: [], + patchComment, + })); + const formHookMock = getFormMock(sampleData); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); + }); + + it('Loading spinner when user actions loading and displays fullName/username', () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTree {...{ ...defaultProps, isLoadingUserActions: true }} /> + </Router> + </TestProviders> + ); + expect(wrapper.find(`[data-test-subj="user-actions-loading"]`).exists()).toBeTruthy(); + + expect( + wrapper + .find(`[data-test-subj="user-action-avatar"]`) + .first() + .prop('name') + ).toEqual(defaultProps.data.createdBy.fullName); + expect( + wrapper + .find(`[data-test-subj="user-action-title"] strong`) + .first() + .text() + ).toEqual(defaultProps.data.createdBy.username); + }); + it('Renders service now update line with top and bottom when push is required', () => { + const ourActions = [ + getUserAction(['comment'], 'push-to-service'), + getUserAction(['comment'], 'update'), + ]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + lastIndexPushToService: 0, + }; + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTree {...props} /> + </Router> + </TestProviders> + ); + expect(wrapper.find(`[data-test-subj="show-top-footer"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="show-bottom-footer"]`).exists()).toBeTruthy(); + }); + it('Renders service now update line with top only when push is up to date', () => { + const ourActions = [getUserAction(['comment'], 'push-to-service')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + lastIndexPushToService: 0, + }; + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTree {...props} /> + </Router> + </TestProviders> + ); + expect(wrapper.find(`[data-test-subj="show-top-footer"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="show-bottom-footer"]`).exists()).toBeFalsy(); + }); + + it('Outlines comment when update move to link is clicked', () => { + const ourActions = [getUserAction(['comment'], 'create'), getUserAction(['comment'], 'update')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTree {...props} /> + </Router> + </TestProviders> + ); + expect( + wrapper + .find(`[data-test-subj="comment-create-action"]`) + .first() + .prop('idToOutline') + ).toEqual(''); + wrapper + .find(`[data-test-subj="comment-update-action"] [data-test-subj="move-to-link"]`) + .first() + .simulate('click'); + expect( + wrapper + .find(`[data-test-subj="comment-create-action"]`) + .first() + .prop('idToOutline') + ).toEqual(ourActions[0].commentId); + }); + + it('Switches to markdown when edit is clicked and back to panel when canceled', () => { + const ourActions = [getUserAction(['comment'], 'create')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTree {...props} /> + </Router> + </TestProviders> + ); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-pencil"]`) + .first() + .simulate('click'); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(true); + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-cancel-markdown"]` + ) + .first() + .simulate('click'); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + }); + + it('calls update comment when comment markdown is saved', async () => { + const ourActions = [getUserAction(['comment'], 'create')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTree {...props} /> + </Router> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-pencil"]`) + .first() + .simulate('click'); + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-save-markdown"]` + ) + .first() + .simulate('click'); + await act(async () => { + await wait(); + wrapper.update(); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + expect(patchComment).toBeCalledWith({ + commentUpdate: sampleData.content, + caseId: props.data.id, + commentId: props.data.comments[0].id, + fetchUserActions, + updateCase, + version: props.data.comments[0].version, + }); + }); + }); + + it('calls update description when description markdown is saved', async () => { + const props = defaultProps; + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTree {...props} /> + </Router> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-pencil"]`) + .first() + .simulate('click'); + wrapper + .find( + `[data-test-subj="user-action-description"] [data-test-subj="user-action-save-markdown"]` + ) + .first() + .simulate('click'); + await act(async () => { + await wait(); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + expect(onUpdateField).toBeCalledWith('description', sampleData.content); + }); + }); + + it('quotes', async () => { + const commentData = { + comment: '', + }; + const formHookMock = getFormMock(commentData); + const setFieldValue = jest.fn(); + useFormMock.mockImplementation(() => ({ form: { ...formHookMock, setFieldValue } })); + const props = defaultProps; + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTree {...props} /> + </Router> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-quote"]`) + .first() + .simulate('click'); + expect(setFieldValue).toBeCalledWith('comment', `> ${props.data.description} \n`); + }); + it('Outlines comment when url param is provided', () => { + const commentId = 'neat-comment-id'; + const ourActions = [getUserAction(['comment'], 'create')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + jest.spyOn(routeData, 'useParams').mockReturnValue({ commentId }); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTree {...props} /> + </Router> + </TestProviders> + ); + expect( + wrapper + .find(`[data-test-subj="comment-create-action"]`) + .first() + .prop('idToOutline') + ).toEqual(commentId); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx index 0892d5dcb3ee7..f8f3f0651fa3c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx @@ -60,7 +60,6 @@ export const UserActionTree = React.memo( const currentUser = useCurrentUser(); const [manageMarkdownEditIds, setManangeMardownEditIds] = useState<string[]>([]); const [insertQuote, setInsertQuote] = useState<string | null>(null); - const handleManageMarkdownEditId = useCallback( (id: string) => { if (!manageMarkdownEditIds.includes(id)) { @@ -74,7 +73,6 @@ export const UserActionTree = React.memo( const handleSaveComment = useCallback( ({ id, version }: { id: string; version: string }, content: string) => { - handleManageMarkdownEditId(id); patchComment({ caseId: caseData.id, commentId: id, @@ -135,7 +133,6 @@ export const UserActionTree = React.memo( content={caseData.description} isEditable={manageMarkdownEditIds.includes(DESCRIPTION_ID)} onSaveContent={(content: string) => { - handleManageMarkdownEditId(DESCRIPTION_ID); onUpdateField(DESCRIPTION_ID, content); }} onChangeEditable={handleManageMarkdownEditId} @@ -166,11 +163,11 @@ export const UserActionTree = React.memo( } } }, [commentId, initLoading, isLoadingUserActions, isLoadingIds]); - return ( <> <UserActionItem createdAt={caseData.createdAt} + data-test-subj="description-action" disabled={!userCanCrud} id={DESCRIPTION_ID} isEditable={manageMarkdownEditIds.includes(DESCRIPTION_ID)} @@ -193,6 +190,7 @@ export const UserActionTree = React.memo( <UserActionItem key={action.actionId} createdAt={comment.createdAt} + data-test-subj={`comment-create-action`} disabled={!userCanCrud} id={comment.id} idToOutline={selectedOutlineCommentId} @@ -236,6 +234,7 @@ export const UserActionTree = React.memo( <UserActionItem key={action.actionId} createdAt={action.actionAt} + data-test-subj={`${action.actionField[0]}-${action.action}-action`} disabled={!userCanCrud} id={action.actionId} isEditable={false} @@ -263,11 +262,12 @@ export const UserActionTree = React.memo( {(isLoadingUserActions || isLoadingIds.includes(NEW_ID)) && ( <MyEuiFlexGroup justifyContent="center" alignItems="center"> <EuiFlexItem grow={false}> - <EuiLoadingSpinner size="l" /> + <EuiLoadingSpinner data-test-subj="user-actions-loading" size="l" /> </EuiFlexItem> </MyEuiFlexGroup> )} <UserActionItem + data-test-subj={`add-comment`} createdAt={new Date().toISOString()} disabled={!userCanCrud} id={NEW_ID} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx index bcb4edd6129a6..0acd0623f9413 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_item.tsx @@ -21,6 +21,7 @@ import * as i18n from './translations'; interface UserActionItemProps { createdAt: string; + 'data-test-subj'?: string; disabled: boolean; id: string; isEditable: boolean; @@ -112,6 +113,7 @@ const PushedInfoContainer = styled.div` export const UserActionItem = ({ createdAt, disabled, + 'data-test-subj': dataTestSubj, id, idToOutline, isEditable, @@ -130,7 +132,7 @@ export const UserActionItem = ({ username, updatedAt, }: UserActionItemProps) => ( - <UserActionItemContainer gutterSize={'none'} direction="column"> + <UserActionItemContainer data-test-subj={dataTestSubj} gutterSize={'none'} direction="column"> <EuiFlexItem> <EuiFlexGroup gutterSize={'none'}> <EuiFlexItem data-test-subj={`user-action-${id}-avatar`} grow={false}> @@ -145,24 +147,25 @@ export const UserActionItem = ({ {!isEditable && ( <MyEuiPanel className="userAction__panel" + data-test-subj={`user-action-panel`} paddingSize="none" showoutline={id === idToOutline ? 'true' : 'false'} > <UserActionTitle createdAt={createdAt} disabled={disabled} + fullName={fullName} id={id} isLoading={isLoading} labelEditAction={labelEditAction} labelQuoteAction={labelQuoteAction} labelTitle={labelTitle ?? <></>} linkId={linkId} - fullName={fullName} - username={username} - updatedAt={updatedAt} onEdit={onEdit} onQuote={onQuote} outlineComment={outlineComment} + updatedAt={updatedAt} + username={username} /> {markdown} </MyEuiPanel> @@ -171,7 +174,7 @@ export const UserActionItem = ({ </EuiFlexGroup> </EuiFlexItem> {showTopFooter && ( - <PushedContainer> + <PushedContainer data-test-subj="show-top-footer"> <PushedInfoContainer> <EuiText size="xs" color="subdued"> {i18n.ALREADY_PUSHED_TO_SERVICE} @@ -179,7 +182,7 @@ export const UserActionItem = ({ </PushedInfoContainer> <EuiHorizontalRule /> {showBottomFooter && ( - <PushedInfoContainer> + <PushedInfoContainer data-test-subj="show-bottom-footer"> <EuiText size="xs" color="subdued"> {i18n.REQUIRED_UPDATE_TO_SERVICE} </EuiText> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_markdown.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_markdown.tsx index e8503bf43375c..827fe2df120ab 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_markdown.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_markdown.tsx @@ -62,12 +62,24 @@ export const UserActionMarkdown = ({ return ( <EuiFlexGroup gutterSize="s" alignItems="center"> <EuiFlexItem grow={false}> - <EuiButtonEmpty size="s" onClick={cancelAction} iconType="cross"> + <EuiButtonEmpty + data-test-subj="user-action-cancel-markdown" + size="s" + onClick={cancelAction} + iconType="cross" + > {i18n.CANCEL} </EuiButtonEmpty> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButton color="secondary" fill iconType="save" onClick={saveAction} size="s"> + <EuiButton + data-test-subj="user-action-save-markdown" + color="secondary" + fill + iconType="save" + onClick={saveAction} + size="s" + > {i18n.SAVE} </EuiButton> </EuiFlexItem> @@ -77,7 +89,7 @@ export const UserActionMarkdown = ({ [handleCancelAction, handleSaveAction] ); return isEditable ? ( - <Form form={form}> + <Form form={form} data-test-subj="user-action-markdown-form"> <UseField path="content" component={MarkdownEditorForm} @@ -99,7 +111,7 @@ export const UserActionMarkdown = ({ </Form> ) : ( <ContentWrapper> - <Markdown raw={content} data-test-subj="case-view-description" /> + <Markdown raw={content} data-test-subj="user-action-markdown" /> </ContentWrapper> ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx new file mode 100644 index 0000000000000..e2189367068ca --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import copy from 'copy-to-clipboard'; +import { Router, routeData, mockHistory } from '../__mock__/router'; +import { caseUserActions as basicUserActions } from '../__mock__/case_data'; +import { UserActionTitle } from './user_action_title'; +import { TestProviders } from '../../../../mock'; + +const outlineComment = jest.fn(); +const onEdit = jest.fn(); +const onQuote = jest.fn(); + +jest.mock('copy-to-clipboard'); +const defaultProps = { + createdAt: basicUserActions[0].actionAt, + disabled: false, + fullName: basicUserActions[0].actionBy.fullName, + id: basicUserActions[0].actionId, + isLoading: false, + labelEditAction: 'labelEditAction', + labelQuoteAction: 'labelQuoteAction', + labelTitle: <>{'cool'}</>, + linkId: basicUserActions[0].commentId, + onEdit, + onQuote, + outlineComment, + updatedAt: basicUserActions[0].actionAt, + username: basicUserActions[0].actionBy.username, +}; + +describe('UserActionTitle ', () => { + beforeEach(() => { + jest.resetAllMocks(); + jest.spyOn(routeData, 'useParams').mockReturnValue({ commentId: '123' }); + }); + + it('Calls copy when copy link is clicked', async () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionTitle {...defaultProps} /> + </Router> + </TestProviders> + ); + wrapper + .find(`[data-test-subj="copy-link"]`) + .first() + .simulate('click'); + expect(copy).toBeCalledTimes(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx index 9ccf921c87602..a1edbab7e1fa2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx @@ -52,18 +52,18 @@ interface UserActionTitleProps { export const UserActionTitle = ({ createdAt, disabled, + fullName, id, isLoading, labelEditAction, labelQuoteAction, labelTitle, linkId, - fullName, - username, - updatedAt, onEdit, onQuote, outlineComment, + updatedAt, + username, }: UserActionTitleProps) => { const { detailName: caseId } = useParams(); const urlSearch = useGetUrlSearch(navTabs.case); @@ -94,10 +94,7 @@ export const UserActionTitle = ({ const handleAnchorLink = useCallback(() => { copy( - `${window.location.origin}${window.location.pathname}#${SiemPageName.case}/${caseId}/${id}${urlSearch}`, - { - debug: true, - } + `${window.location.origin}${window.location.pathname}#${SiemPageName.case}/${caseId}/${id}${urlSearch}` ); }, [caseId, id, urlSearch]); @@ -106,7 +103,6 @@ export const UserActionTitle = ({ outlineComment(linkId); } }, [linkId, outlineComment]); - return ( <EuiText size="s" className="userAction__title" data-test-subj={`user-action-title`}> <EuiFlexGroup @@ -155,6 +151,7 @@ export const UserActionTitle = ({ <EuiToolTip position="top" content={<p>{i18n.MOVE_TO_ORIGINAL_COMMENT}</p>}> <EuiButtonIcon aria-label={i18n.MOVE_TO_ORIGINAL_COMMENT} + data-test-subj={`move-to-link`} onClick={handleMoveToLink} iconType="arrowUp" /> @@ -165,6 +162,7 @@ export const UserActionTitle = ({ <EuiToolTip position="top" content={<p>{i18n.COPY_REFERENCE_LINK}</p>}> <EuiButtonIcon aria-label={i18n.COPY_REFERENCE_LINK} + data-test-subj={`copy-link`} onClick={handleAnchorLink} iconType="link" id={`${id}-permLink`} @@ -173,7 +171,7 @@ export const UserActionTitle = ({ </EuiFlexItem> {propertyActions.length > 0 && ( <EuiFlexItem grow={false}> - {isLoading && <MySpinner />} + {isLoading && <MySpinner data-test-subj="user-action-title-loading" />} {!isLoading && <PropertyActions propertyActions={propertyActions} />} </EuiFlexItem> )} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts index 0d1e6d1435ca3..097b8220156e2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts @@ -131,6 +131,10 @@ export const TAGS = i18n.translate('xpack.siem.case.caseView.tags', { defaultMessage: 'Tags', }); +export const ACTIONS = i18n.translate('xpack.siem.case.allCases.actions', { + defaultMessage: 'Actions', +}); + export const NO_TAGS_AVAILABLE = i18n.translate('xpack.siem.case.allCases.noTagsAvailable', { defaultMessage: 'No tags available', }); diff --git a/x-pack/plugins/case/server/scripts/generate_case_and_comment_data.sh b/x-pack/plugins/case/server/scripts/generate_case_and_comment_data.sh index 9b6f472d798e0..7ec7dc5a70e92 100755 --- a/x-pack/plugins/case/server/scripts/generate_case_and_comment_data.sh +++ b/x-pack/plugins/case/server/scripts/generate_case_and_comment_data.sh @@ -22,9 +22,9 @@ POSTED_COMMENT="$(curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ -X POST "${KIBANA_URL}${SPACE_URL}/api/cases/$CASE_ID/comments" \ -d @${COMMENT} \ - | jq '{ commentId: .id, commentVersion: .version }' -)" + | jq '{ commentId: .comments[0].id, commentVersion: .comments[0].version }' \ +-j)" POSTED_CASE=$(./get_case.sh $CASE_ID | jq '{ caseId: .id, caseVersion: .version }' -j) echo ${POSTED_COMMENT} ${POSTED_CASE} \ - | jq -s add; \ No newline at end of file +| jq -s add; diff --git a/x-pack/plugins/case/server/scripts/generate_case_data.sh b/x-pack/plugins/case/server/scripts/generate_case_data.sh index f8f6142a5d733..d3a4d3833ad2e 100755 --- a/x-pack/plugins/case/server/scripts/generate_case_data.sh +++ b/x-pack/plugins/case/server/scripts/generate_case_data.sh @@ -11,6 +11,6 @@ # ./generate_case_data.sh set -e -./check_env_variables.sh -./post_case.sh | jq '{ id: .id, version: .version }' -j; + ./check_env_variables.sh + ./post_case.sh | jq '{ id: .id, version: .version }'; From d5d610f168895ba373dec0db3d0caa64bf6a4b47 Mon Sep 17 00:00:00 2001 From: Robert Austin <robert.austin@elastic.co> Date: Fri, 10 Apr 2020 12:19:29 -0400 Subject: [PATCH 07/21] Endpoint: Remove unused `lib` module (#63248) --- .../public/applications/endpoint/lib/index.ts | 7 - .../applications/endpoint/lib/saga.test.ts | 114 ------------- .../public/applications/endpoint/lib/saga.ts | 159 ------------------ 3 files changed, 280 deletions(-) delete mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/lib/index.ts delete mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts delete mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/lib/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/lib/index.ts deleted file mode 100644 index ba2e1ce8f9fe6..0000000000000 --- a/x-pack/plugins/endpoint/public/applications/endpoint/lib/index.ts +++ /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. - */ - -export * from './saga'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts deleted file mode 100644 index 7c06681184085..0000000000000 --- a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts +++ /dev/null @@ -1,114 +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 { createSagaMiddleware, SagaContext, SagaMiddleware } from './index'; -import { applyMiddleware, createStore, Reducer, Store } from 'redux'; - -describe('saga', () => { - const INCREMENT_COUNTER = 'INCREMENT'; - const DELAYED_INCREMENT_COUNTER = 'DELAYED INCREMENT COUNTER'; - const STOP_SAGA_PROCESSING = 'BREAK ASYNC ITERATOR'; - - const sleep = (ms = 100) => new Promise(resolve => setTimeout(resolve, ms)); - let store: Store; - let reducerA: Reducer; - let sideAffect: (a: unknown, s: unknown) => void; - let sagaExe: (sagaContext: SagaContext) => Promise<void>; - let sagaExeReduxMiddleware: SagaMiddleware; - - beforeEach(() => { - reducerA = jest.fn((prevState = { count: 0 }, { type }) => { - switch (type) { - case INCREMENT_COUNTER: - return { ...prevState, count: prevState.count + 1 }; - default: - return prevState; - } - }); - - sideAffect = jest.fn(); - - sagaExe = jest.fn(async ({ actionsAndState, dispatch }: SagaContext) => { - for await (const { action, state } of actionsAndState()) { - expect(action).toBeDefined(); - expect(state).toBeDefined(); - - if (action.type === STOP_SAGA_PROCESSING) { - break; - } - - sideAffect(action, state); - - if (action.type === DELAYED_INCREMENT_COUNTER) { - await sleep(1); - dispatch({ - type: INCREMENT_COUNTER, - }); - } - } - }); - - sagaExeReduxMiddleware = createSagaMiddleware(sagaExe); - store = createStore(reducerA, applyMiddleware(sagaExeReduxMiddleware)); - }); - - afterEach(() => { - sagaExeReduxMiddleware.stop(); - }); - - test('it does nothing if saga is not started', () => { - expect(sagaExe).not.toHaveBeenCalled(); - }); - - test('it can dispatch store actions once running', async () => { - sagaExeReduxMiddleware.start(); - expect(store.getState()).toEqual({ count: 0 }); - expect(sagaExe).toHaveBeenCalled(); - - store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); - expect(store.getState()).toEqual({ count: 0 }); - - await sleep(); - - expect(sideAffect).toHaveBeenCalled(); - expect(store.getState()).toEqual({ count: 1 }); - }); - - test('it stops processing if break out of loop', async () => { - sagaExeReduxMiddleware.start(); - store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); - await sleep(); - - expect(store.getState()).toEqual({ count: 1 }); - expect(sideAffect).toHaveBeenCalledTimes(2); - - store.dispatch({ type: STOP_SAGA_PROCESSING }); - await sleep(); - - store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); - await sleep(); - - expect(store.getState()).toEqual({ count: 1 }); - expect(sideAffect).toHaveBeenCalledTimes(2); - }); - - test('it stops saga middleware when stop() is called', async () => { - sagaExeReduxMiddleware.start(); - store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); - await sleep(); - - expect(store.getState()).toEqual({ count: 1 }); - expect(sideAffect).toHaveBeenCalledTimes(2); - - sagaExeReduxMiddleware.stop(); - - store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); - await sleep(); - - expect(store.getState()).toEqual({ count: 1 }); - expect(sideAffect).toHaveBeenCalledTimes(2); - }); -}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts deleted file mode 100644 index 2a79827847f2e..0000000000000 --- a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts +++ /dev/null @@ -1,159 +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 { AnyAction, Dispatch, Middleware, MiddlewareAPI } from 'redux'; -import { GlobalState } from '../types'; - -interface QueuedAction<TAction = AnyAction> { - /** - * The Redux action that was dispatched - */ - action: TAction; - /** - * The Global state at the time the action was dispatched - */ - state: GlobalState; -} - -interface IteratorInstance { - queue: QueuedAction[]; - nextResolve: null | ((inst: QueuedAction) => void); -} - -type Saga = (storeContext: SagaContext) => Promise<void>; - -type StoreActionsAndState<TAction = AnyAction> = AsyncIterableIterator<QueuedAction<TAction>>; - -export interface SagaContext<TAction extends AnyAction = AnyAction> { - /** - * A generator function that will `yield` `Promise`s that resolve with a `QueuedAction` - */ - actionsAndState: () => StoreActionsAndState<TAction>; - dispatch: Dispatch<TAction>; -} - -export interface SagaMiddleware extends Middleware { - /** - * Start the saga. Should be called after the `store` has been created - */ - start: () => void; - - /** - * Stop the saga by exiting the internal generator `for await...of` loop. - */ - stop: () => void; -} - -const noop = () => {}; -const STOP = Symbol('STOP'); - -/** - * Creates Saga Middleware for use with Redux. - * - * @param {Saga} saga The `saga` should initialize a long-running `for await...of` loop against - * the return value of the `actionsAndState()` method provided by the `SagaContext`. - * - * @return {SagaMiddleware} - * - * @example - * - * type TPossibleActions = { type: 'add', payload: any[] }; - * //... - * const endpointsSaga = async ({ actionsAndState, dispatch }: SagaContext<TPossibleActions>) => { - * for await (const { action, state } of actionsAndState()) { - * if (action.type === "userRequestedResource") { - * const resourceData = await doApiFetch('of/some/resource'); - * dispatch({ - * type: 'add', - * payload: [ resourceData ] - * }); - * } - * } - * } - * const endpointsSagaMiddleware = createSagaMiddleware(endpointsSaga); - * //.... - * const store = createStore(reducers, [ endpointsSagaMiddleware ]); - */ -export function createSagaMiddleware(saga: Saga): SagaMiddleware { - const iteratorInstances = new Set<IteratorInstance>(); - let runSaga: () => void = noop; - let stopSaga: () => void = noop; - let runningPromise: Promise<symbol>; - - async function* getActionsAndStateIterator(): StoreActionsAndState { - const instance: IteratorInstance = { queue: [], nextResolve: null }; - iteratorInstances.add(instance); - - try { - while (true) { - const actionAndState = await Promise.race([nextActionAndState(), runningPromise]); - - if (actionAndState === STOP) { - break; - } - - yield actionAndState as QueuedAction; - } - } finally { - // If the consumer stops consuming this (e.g. `break` or `return` is called in the `for await` - // then this `finally` block will run and unregister this instance and reset `runSaga` - iteratorInstances.delete(instance); - runSaga = stopSaga = noop; - } - - function nextActionAndState() { - if (instance.queue.length) { - return Promise.resolve(instance.queue.shift() as QueuedAction); - } else { - return new Promise<QueuedAction>(function(resolve) { - instance.nextResolve = resolve; - }); - } - } - } - - function enqueue(value: QueuedAction) { - for (const iteratorInstance of iteratorInstances) { - iteratorInstance.queue.push(value); - if (iteratorInstance.nextResolve !== null) { - iteratorInstance.nextResolve(iteratorInstance.queue.shift() as QueuedAction); - iteratorInstance.nextResolve = null; - } - } - } - - function middleware({ getState, dispatch }: MiddlewareAPI) { - if (runSaga === noop) { - runSaga = saga.bind<null, SagaContext, any[], Promise<void>>(null, { - actionsAndState: getActionsAndStateIterator, - dispatch, - }); - } - return (next: Dispatch<AnyAction>) => (action: AnyAction) => { - // Call the next dispatch method in the middleware chain. - const returnValue = next(action); - - enqueue({ - action, - state: getState(), - }); - - // This will likely be the action itself, unless a middleware further in chain changed it. - return returnValue; - }; - } - - middleware.start = () => { - runningPromise = new Promise(resolve => (stopSaga = () => resolve(STOP))); - runSaga(); - }; - - middleware.stop = () => { - stopSaga(); - }; - - return middleware; -} From f96f928e6990d64cd7abdb4bedadc1d84557d39c Mon Sep 17 00:00:00 2001 From: Wylie Conlon <william.conlon@elastic.co> Date: Fri, 10 Apr 2020 12:29:26 -0400 Subject: [PATCH 08/21] [Lens] Fix error in query from generated suggestion (#63018) * [Lens] Fix error in query from generated suggestion * Update from review comments * Fix test Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../indexpattern_suggestions.test.tsx | 4 ++-- .../indexpattern_suggestions.ts | 15 +++++++-------- .../operations/definitions/terms.test.tsx | 2 +- .../operations/definitions/terms.tsx | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index e36622f876acd..fe14e5de5c1e3 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -824,10 +824,10 @@ describe('IndexPattern Data Source suggestions', () => { state: expect.objectContaining({ layers: expect.objectContaining({ currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'id1'], + columnOrder: ['cola', 'colb'], columns: { cola: initialState.layers.currentLayer.columns.cola, - id1: expect.objectContaining({ + colb: expect.objectContaining({ operationType: 'avg', sourceField: 'memory', }), diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index 96127caa67bb4..d339171a5ae1f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -15,8 +15,8 @@ import { operationDefinitionMap, IndexPatternColumn, } from './operations'; -import { hasField } from './utils'; import { operationDefinitions } from './operations/definitions'; +import { hasField } from './utils'; import { IndexPattern, IndexPatternPrivateState, @@ -196,7 +196,7 @@ function addFieldAsMetricOperation( suggestedPriority: undefined, field, }); - const newColumnId = generateId(); + const addedColumnId = generateId(); const [, metrics] = separateBucketColumns(layer); @@ -206,20 +206,19 @@ function addFieldAsMetricOperation( indexPatternId: indexPattern.id, columns: { ...layer.columns, - [newColumnId]: newColumn, + [addedColumnId]: newColumn, }, - columnOrder: [...layer.columnOrder, newColumnId], + columnOrder: [...layer.columnOrder, addedColumnId], }; } - // If only one metric, replace instead of add - const newColumns = { ...layer.columns, [newColumnId]: newColumn }; - delete newColumns[metrics[0]]; + // Replacing old column with new column, keeping the old ID + const newColumns = { ...layer.columns, [metrics[0]]: newColumn }; return { indexPatternId: indexPattern.id, columns: newColumns, - columnOrder: [...layer.columnOrder.filter(c => c !== metrics[0]), newColumnId], + columnOrder: layer.columnOrder, // Order is kept by replacing }; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx index 226246714f18d..fc0c9746b2f98 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.test.tsx @@ -274,7 +274,7 @@ describe('terms', () => { expect(updatedColumn).toBe(initialColumn); }); - it('should switch to alphabetical ordering if the order column is removed', () => { + it('should switch to alphabetical ordering if there are no columns to order by', () => { const termsColumn = termsOperation.onOtherColumnChanged!( { label: 'Top value of category', diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx index cd0dcc0b7e9ce..387b197c9235c 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx @@ -135,7 +135,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn> = { } return currentColumn; }, - paramEditor: ({ state, setState, currentColumn, columnId: currentColumnId, layerId }) => { + paramEditor: ({ state, setState, currentColumn, layerId }) => { const SEPARATOR = '$$$'; function toValue(orderBy: TermsIndexPatternColumn['params']['orderBy']) { if (orderBy.type === 'alphabetical') { From 9d2ecc7c06d16ece27449fe2907801024dd438ec Mon Sep 17 00:00:00 2001 From: Brent Kimmel <bkimmel@users.noreply.github.com> Date: Fri, 10 Apr 2020 12:38:26 -0400 Subject: [PATCH 09/21] Resolver/node svg 2 html (#62958) * Remove some SVG in Resolver nodes and replace with HTML --- .../public/embeddables/resolver/view/defs.tsx | 44 +--- .../resolver/view/process_event_dot.tsx | 199 ++++++++++-------- 2 files changed, 113 insertions(+), 130 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/defs.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/defs.tsx index 8ee9bfafc630e..de9c3c7e8f8f3 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/defs.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/defs.tsx @@ -5,7 +5,7 @@ */ import React, { memo } from 'react'; -import { saturate, lighten } from 'polished'; +import { saturate } from 'polished'; import { htmlIdGenerator, @@ -79,8 +79,6 @@ const idGenerator = htmlIdGenerator(); * Ids of paint servers to be referenced by fill and stroke attributes */ export const PaintServerIds = { - runningProcess: idGenerator('psRunningProcess'), - runningTrigger: idGenerator('psRunningTrigger'), runningProcessCube: idGenerator('psRunningProcessCube'), runningTriggerCube: idGenerator('psRunningTriggerCube'), terminatedProcessCube: idGenerator('psTerminatedProcessCube'), @@ -93,46 +91,6 @@ export const PaintServerIds = { */ const PaintServers = memo(() => ( <> - <linearGradient - id={PaintServerIds.runningProcess} - x1="0" - y1="0" - x2="1" - y2="0" - spreadMethod="reflect" - gradientUnits="objectBoundingBox" - > - <stop - offset="0%" - stopColor={saturate(0.7, lighten(0.05, NamedColors.runningProcessStart))} - stopOpacity="1" - /> - <stop - offset="100%" - stopColor={saturate(0.7, lighten(0.05, NamedColors.runningProcessEnd))} - stopOpacity="1" - /> - </linearGradient> - <linearGradient - id={PaintServerIds.runningTrigger} - x1="0" - y1="0" - x2="1" - y2="0" - spreadMethod="reflect" - gradientUnits="objectBoundingBox" - > - <stop - offset="0%" - stopColor={saturate(0.7, lighten(0.05, NamedColors.runningTriggerStart))} - stopOpacity="1" - /> - <stop - offset="100%" - stopColor={saturate(0.7, lighten(0.05, NamedColors.runningTriggerEnd))} - stopOpacity="1" - /> - </linearGradient> <linearGradient id={PaintServerIds.terminatedProcessCube} x1="-381.23752" diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 2e3981de74d34..10e331ffff02d 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -11,7 +11,7 @@ import { htmlIdGenerator, EuiKeyboardAccessible } from '@elastic/eui'; import { useSelector } from 'react-redux'; import { applyMatrix3 } from '../lib/vector2'; import { Vector2, Matrix3, AdjacentProcessMap, ResolverProcessType } from '../types'; -import { SymbolIds, NamedColors, PaintServerIds } from './defs'; +import { SymbolIds, NamedColors } from './defs'; import { ResolverEvent } from '../../../../common/types'; import { useResolverDispatch } from './use_resolver_dispatch'; import * as eventModel from '../../../../common/models/event'; @@ -21,7 +21,7 @@ import * as selectors from '../store/selectors'; const nodeAssets = { runningProcessCube: { cubeSymbol: `#${SymbolIds.runningProcessCube}`, - labelFill: `url(#${PaintServerIds.runningProcess})`, + labelBackground: NamedColors.fullLabelBackground, descriptionFill: NamedColors.empty, descriptionText: i18n.translate('xpack.endpoint.resolver.runningProcess', { defaultMessage: 'Running Process', @@ -29,7 +29,7 @@ const nodeAssets = { }, runningTriggerCube: { cubeSymbol: `#${SymbolIds.runningTriggerCube}`, - labelFill: `url(#${PaintServerIds.runningTrigger})`, + labelBackground: NamedColors.fullLabelBackground, descriptionFill: NamedColors.empty, descriptionText: i18n.translate('xpack.endpoint.resolver.runningTrigger', { defaultMessage: 'Running Trigger', @@ -37,7 +37,7 @@ const nodeAssets = { }, terminatedProcessCube: { cubeSymbol: `#${SymbolIds.terminatedProcessCube}`, - labelFill: NamedColors.fullLabelBackground, + labelBackground: NamedColors.fullLabelBackground, descriptionFill: NamedColors.empty, descriptionText: i18n.translate('xpack.endpoint.resolver.terminatedProcess', { defaultMessage: 'Terminated Process', @@ -45,7 +45,7 @@ const nodeAssets = { }, terminatedTriggerCube: { cubeSymbol: `#${SymbolIds.terminatedTriggerCube}`, - labelFill: NamedColors.fullLabelBackground, + labelBackground: NamedColors.fullLabelBackground, descriptionFill: NamedColors.empty, descriptionText: i18n.translate('xpack.endpoint.resolver.terminatedTrigger', { defaultMessage: 'Terminated Trigger', @@ -114,14 +114,21 @@ export const ProcessEventDot = styled( [left, magFactorX, top] ); + /** + * Type in non-SVG components scales as follows: + * (These values were adjusted to match the proportions in the comps provided by UX/Design) + * 18.75 : The smallest readable font size at which labels/descriptions can be read. Font size will not scale below this. + * 12.5 : A 'slope' at which the font size will scale w.r.t. to zoom level otherwise + */ + const minimumFontSize = 18.75; + const slopeOfFontScale = 12.5; + const fontSizeAdjustmentForScale = magFactorX > 1 ? slopeOfFontScale * (magFactorX - 1) : 0; + const scaledTypeSize = minimumFontSize + fontSizeAdjustmentForScale; + const markerBaseSize = 15; const markerSize = markerBaseSize; const markerPositionOffset = -markerBaseSize / 2; - const labelYOffset = markerPositionOffset + 0.25 * markerSize - 0.5; - - const labelYHeight = markerSize / 1.7647; - /** * An element that should be animated when the node is clicked. */ @@ -136,9 +143,7 @@ export const ProcessEventDot = styled( }) | null; } = React.createRef(); - const { cubeSymbol, labelFill, descriptionFill, descriptionText } = nodeAssets[ - nodeType(event) - ]; + const { cubeSymbol, labelBackground, descriptionText } = nodeAssets[nodeType(event)]; const resolverNodeIdGenerator = useMemo(() => htmlIdGenerator('resolverNode'), []); const nodeId = useMemo(() => resolverNodeIdGenerator(selfId), [ @@ -154,7 +159,7 @@ export const ProcessEventDot = styled( const dispatch = useResolverDispatch(); const handleFocus = useCallback( - (focusEvent: React.FocusEvent<SVGSVGElement>) => { + (focusEvent: React.FocusEvent<HTMLDivElement>) => { dispatch({ type: 'userFocusedOnResolverNode', payload: { @@ -166,7 +171,7 @@ export const ProcessEventDot = styled( ); const handleClick = useCallback( - (clickEvent: React.MouseEvent<SVGSVGElement, MouseEvent>) => { + (clickEvent: React.MouseEvent<HTMLDivElement, MouseEvent>) => { if (animationTarget.current !== null) { (animationTarget.current as any).beginElement(); } @@ -179,14 +184,15 @@ export const ProcessEventDot = styled( }, [animationTarget, dispatch, nodeId] ); - + /* eslint-disable jsx-a11y/click-events-have-key-events */ + /** + * Key event handling (e.g. 'Enter'/'Space') is provisioned by the `EuiKeyboardAccessible` component + */ return ( <EuiKeyboardAccessible> - <svg + <div data-test-subj={'resolverNode'} className={className + ' kbn-resetFocusState'} - viewBox="-15 -15 90 30" - preserveAspectRatio="xMidYMid meet" role="treeitem" aria-level={adjacentNodeMap.level} aria-flowto={ @@ -203,81 +209,100 @@ export const ProcessEventDot = styled( onFocus={handleFocus} tabIndex={-1} > - <g> - <use - xlinkHref={`#${SymbolIds.processCubeActiveBacking}`} - x={-11.35} - y={-11.35} - width={markerSize * 1.5} - height={markerSize * 1.5} - className="backing" - /> - <rect x="7" y="-12.75" width="15" height="10" fill={NamedColors.resolverBackground} /> - <use - role="presentation" - xlinkHref={cubeSymbol} - x={markerPositionOffset} - y={markerPositionOffset} - width={markerSize} - height={markerSize} - opacity="1" - className="cube" - > - <animateTransform - attributeType="XML" - attributeName="transform" - type="scale" - values="1 1; 1 .83; 1 .8; 1 .83; 1 1" - dur="0.2s" - begin="click" - repeatCount="1" - className="squish" - ref={animationTarget} + <svg + viewBox="-15 -15 90 30" + preserveAspectRatio="xMidYMid meet" + style={{ + display: 'block', + width: '100%', + height: '100%', + position: 'absolute', + top: '0', + left: '0', + }} + > + <g> + <use + xlinkHref={`#${SymbolIds.processCubeActiveBacking}`} + x={-11.35} + y={-11.35} + width={markerSize * 1.5} + height={markerSize * 1.5} + className="backing" /> - </use> - <use - role="presentation" - xlinkHref={`#${SymbolIds.processNodeLabel}`} - x={markerPositionOffset + markerSize - 0.5} - y={labelYOffset} - width={(markerSize / 1.7647) * 5} - height={markerSize / 1.7647} - opacity="1" - fill={labelFill} - /> - <text - x={markerPositionOffset + 0.7 * markerSize + 50 / 2} - y={labelYOffset + labelYHeight / 2} - textAnchor="middle" - dominantBaseline="middle" - fontSize="3.75" - fontWeight="bold" - fill={NamedColors.empty} - paintOrder="stroke" - tabIndex={-1} - style={{ letterSpacing: '-0.02px' }} - id={labelId} - > - {eventModel.eventName(event)} - </text> - <text - x={markerPositionOffset + markerSize} - y={labelYOffset - 1} - textAnchor="start" - dominantBaseline="middle" - fontSize="2.67" - fill={descriptionFill} + <use + role="presentation" + xlinkHref={cubeSymbol} + x={markerPositionOffset} + y={markerPositionOffset} + width={markerSize} + height={markerSize} + opacity="1" + className="cube" + > + <animateTransform + attributeType="XML" + attributeName="transform" + type="scale" + values="1 1; 1 .83; 1 .8; 1 .83; 1 1" + dur="0.2s" + begin="click" + repeatCount="1" + className="squish" + ref={animationTarget} + /> + </use> + </g> + </svg> + <div + style={{ + left: '25%', + top: '30%', + position: 'absolute', + width: '50%', + color: 'white', + fontSize: `${scaledTypeSize}px`, + lineHeight: '140%', + }} + > + <div id={descriptionId} - paintOrder="stroke" - fontWeight="bold" - style={{ textTransform: 'uppercase', letterSpacing: '-0.01px' }} + style={{ + textTransform: 'uppercase', + letterSpacing: '-0.01px', + backgroundColor: NamedColors.resolverBackground, + lineHeight: '1.2', + fontWeight: 'bold', + fontSize: '.5em', + width: '100%', + margin: '0 0 .05em 0', + textAlign: 'left', + padding: '0', + }} > {descriptionText} - </text> - </g> - </svg> + </div> + <div + data-test-subject="nodeLabel" + id={labelId} + style={{ + backgroundColor: labelBackground, + padding: '.15em 0', + textAlign: 'center', + maxWidth: '100%', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + contain: 'content', + }} + > + {eventModel.eventName(event)} + </div> + </div> + </div> </EuiKeyboardAccessible> ); + /* eslint-enable jsx-a11y/click-events-have-key-events */ } ) )` From aed5253b53fdf53781b6220dbe937510cb7ed378 Mon Sep 17 00:00:00 2001 From: Tim Sullivan <tsullivan@users.noreply.github.com> Date: Fri, 10 Apr 2020 09:57:59 -0700 Subject: [PATCH 10/21] [Reporting] convert all server unit tests to TypeScript (#62873) * [Reporting] convert all server unit tests to TypeScript * fix ts * revert unrelated change --- ...{index.test.js.snap => index.test.ts.snap} | 0 ...xecute_job.test.js => execute_job.test.ts} | 227 ++++++++++-------- .../{index.test.js => index.test.ts} | 61 +++-- .../{index.test.js => index.test.ts} | 52 ++-- .../{index.test.js => index.test.ts} | 2 +- .../routes/{jobs.test.js => jobs.test.ts} | 36 +-- ...t.js => reporting_usage_collector.test.ts} | 58 +++-- .../create_mock_browserdriverfactory.ts | 2 +- x-pack/legacy/plugins/reporting/types.d.ts | 2 +- 9 files changed, 273 insertions(+), 167 deletions(-) rename x-pack/legacy/plugins/reporting/__snapshots__/{index.test.js.snap => index.test.ts.snap} (100%) rename x-pack/legacy/plugins/reporting/export_types/csv/server/{execute_job.test.js => execute_job.test.ts} (88%) rename x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/{index.test.js => index.test.ts} (58%) rename x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/{index.test.js => index.test.ts} (57%) rename x-pack/legacy/plugins/reporting/{index.test.js => index.test.ts} (94%) rename x-pack/legacy/plugins/reporting/server/routes/{jobs.test.js => jobs.test.ts} (91%) rename x-pack/legacy/plugins/reporting/server/usage/{reporting_usage_collector.test.js => reporting_usage_collector.test.ts} (90%) diff --git a/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap b/x-pack/legacy/plugins/reporting/__snapshots__/index.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap rename to x-pack/legacy/plugins/reporting/__snapshots__/index.test.ts.snap diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts similarity index 88% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts index 4870e1e35cdaf..f0afade8629ab 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore import Puid from 'puid'; import sinon from 'sinon'; import nodeCrypto from '@elastic/node-crypto'; @@ -13,36 +14,40 @@ import { createMockReportingCore } from '../../../test_helpers'; import { LevelLogger } from '../../../server/lib/level_logger'; import { setFieldFormats } from '../../../server/services'; import { executeJobFactory } from './execute_job'; +import { JobDocPayloadDiscoverCsv } from '../types'; import { CSV_BOM_CHARS } from '../../../common/constants'; -const delay = ms => new Promise(resolve => setTimeout(() => resolve(), ms)); +const delay = (ms: number) => new Promise(resolve => setTimeout(() => resolve(), ms)); const puid = new Puid(); const getRandomScrollId = () => { return puid.generate(); }; +const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadDiscoverCsv; + describe('CSV Execute Job', function() { const encryptionKey = 'testEncryptionKey'; const headers = { sid: 'test', }; const mockLogger = new LevelLogger({ - get: () => ({ - debug: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }), + get: () => + ({ + debug: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + } as any), }); - let defaultElasticsearchResponse; - let encryptedHeaders; + let defaultElasticsearchResponse: any; + let encryptedHeaders: any; - let clusterStub; - let configGetStub; - let mockReportingConfig; - let mockReportingPlugin; - let callAsCurrentUserStub; - let cancellationToken; + let clusterStub: any; + let configGetStub: any; + let mockReportingConfig: any; + let mockReportingPlugin: any; + let callAsCurrentUserStub: any; + let cancellationToken: any; const mockElasticsearch = { dataClient: { @@ -78,7 +83,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'defaultScrollId', }; clusterStub = { - callAsCurrentUser: function() {}, + callAsCurrentUser() {}, }; callAsCurrentUserStub = sinon @@ -89,17 +94,19 @@ describe('CSV Execute Job', function() { mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true); setFieldFormats({ - fieldFormatServiceFactory: function() { + fieldFormatServiceFactory() { const uiConfigMock = {}; - uiConfigMock['format:defaultTypeMap'] = { + (uiConfigMock as any)['format:defaultTypeMap'] = { _default_: { id: 'string', params: {} }, }; const fieldFormatsRegistry = new fieldFormats.FieldFormatsRegistry(); - fieldFormatsRegistry.init(key => uiConfigMock[key], {}, [fieldFormats.StringFormat]); + fieldFormatsRegistry.init(key => (uiConfigMock as any)[key], {}, [ + fieldFormats.StringFormat, + ]); - return fieldFormatsRegistry; + return Promise.resolve(fieldFormatsRegistry); }, }); }); @@ -109,7 +116,11 @@ describe('CSV Execute Job', function() { const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', - { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, + getJobDocPayload({ + headers: encryptedHeaders, + fields: [], + searchRequest: { index: null, body: null }, + }), cancellationToken ); expect(callAsCurrentUserStub.called).toBe(true); @@ -123,14 +134,14 @@ describe('CSV Execute Job', function() { }; const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const job = { + const job = getJobDocPayload({ headers: encryptedHeaders, fields: [], searchRequest: { index, body, }, - }; + }); await executeJob('job777', job, cancellationToken); @@ -152,7 +163,11 @@ describe('CSV Execute Job', function() { const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', - { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, + getJobDocPayload({ + headers: encryptedHeaders, + fields: [], + searchRequest: { index: null, body: null }, + }), cancellationToken ); @@ -166,7 +181,11 @@ describe('CSV Execute Job', function() { const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', - { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, + getJobDocPayload({ + headers: encryptedHeaders, + fields: [], + searchRequest: { index: null, body: null }, + }), cancellationToken ); @@ -196,7 +215,11 @@ describe('CSV Execute Job', function() { const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', - { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, + getJobDocPayload({ + headers: encryptedHeaders, + fields: [], + searchRequest: { index: null, body: null }, + }), cancellationToken ); @@ -231,7 +254,11 @@ describe('CSV Execute Job', function() { const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); await executeJob( 'job456', - { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, + getJobDocPayload({ + headers: encryptedHeaders, + fields: [], + searchRequest: { index: null, body: null }, + }), cancellationToken ); @@ -257,12 +284,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: undefined, searchRequest: { index: null, body: null }, - }; + }); await expect( executeJob('job123', jobParams, cancellationToken) ).rejects.toMatchInlineSnapshot(`[TypeError: Cannot read property 'indexOf' of undefined]`); @@ -284,12 +311,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); const { csv_contains_formulas: csvContainsFormulas } = await executeJob( 'job123', jobParams, @@ -309,12 +336,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); const { csv_contains_formulas: csvContainsFormulas } = await executeJob( 'job123', jobParams, @@ -334,12 +361,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); const { csv_contains_formulas: csvContainsFormulas } = await executeJob( 'job123', jobParams, @@ -359,12 +386,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); const { csv_contains_formulas: csvContainsFormulas } = await executeJob( 'job123', jobParams, @@ -386,12 +413,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); expect(content).toEqual(`${CSV_BOM_CHARS}one,two\none,bar\n`); @@ -407,12 +434,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); expect(content).toEqual('one,two\none,bar\n'); @@ -423,11 +450,11 @@ describe('CSV Execute Job', function() { it('should reject Promise if search call errors out', async function() { callAsCurrentUserStub.rejects(new Error()); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, - }; + }); await expect( executeJob('job123', jobParams, cancellationToken) ).rejects.toMatchInlineSnapshot(`[Error]`); @@ -442,11 +469,11 @@ describe('CSV Execute Job', function() { }); callAsCurrentUserStub.onSecondCall().rejects(new Error()); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, - }; + }); await expect( executeJob('job123', jobParams, cancellationToken) ).rejects.toMatchInlineSnapshot(`[Error]`); @@ -463,11 +490,11 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, - }; + }); await expect( executeJob('job123', jobParams, cancellationToken) ).rejects.toMatchInlineSnapshot( @@ -484,11 +511,11 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, - }; + }); await expect( executeJob('job123', jobParams, cancellationToken) ).rejects.toMatchInlineSnapshot( @@ -512,11 +539,11 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, - }; + }); await expect( executeJob('job123', jobParams, cancellationToken) ).rejects.toMatchInlineSnapshot( @@ -540,11 +567,11 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, - }; + }); await expect( executeJob('job123', jobParams, cancellationToken) ).rejects.toMatchInlineSnapshot( @@ -578,7 +605,11 @@ describe('CSV Execute Job', function() { const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); executeJob( 'job345', - { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, + getJobDocPayload({ + headers: encryptedHeaders, + fields: [], + searchRequest: { index: null, body: null }, + }), cancellationToken ); @@ -593,13 +624,17 @@ describe('CSV Execute Job', function() { const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); executeJob( 'job345', - { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, + getJobDocPayload({ + headers: encryptedHeaders, + fields: [], + searchRequest: { index: null, body: null }, + }), cancellationToken ); cancellationToken.cancel(); for (let i = 0; i < callAsCurrentUserStub.callCount; ++i) { - expect(callAsCurrentUserStub.getCall(i).args[1]).to.not.be('clearScroll'); + expect(callAsCurrentUserStub.getCall(i).args[1]).not.toBe('clearScroll'); // dead code? } }); @@ -607,7 +642,11 @@ describe('CSV Execute Job', function() { const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); executeJob( 'job345', - { headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null } }, + getJobDocPayload({ + headers: encryptedHeaders, + fields: [], + searchRequest: { index: null, body: null }, + }), cancellationToken ); await delay(100); @@ -623,11 +662,11 @@ describe('CSV Execute Job', function() { describe('csv content', function() { it('should write column headers to output, even if there are no results', async function() { const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); expect(content).toBe(`one,two\n`); }); @@ -635,11 +674,11 @@ describe('CSV Execute Job', function() { it('should use custom uiSettings csv:separator for header', async function() { mockUiSettingsClient.get.withArgs('csv:separator').returns(';'); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); expect(content).toBe(`one;two\n`); }); @@ -647,11 +686,11 @@ describe('CSV Execute Job', function() { it('should escape column headers if uiSettings csv:quoteValues is true', async function() { mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(true); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], searchRequest: { index: null, body: null }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); expect(content).toBe(`"one and a half",two,"three-and-four","five & six"\n`); }); @@ -659,11 +698,11 @@ describe('CSV Execute Job', function() { it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function() { mockUiSettingsClient.get.withArgs('csv:quoteValues').returns(false); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], searchRequest: { index: null, body: null }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); expect(content).toBe(`one and a half,two,three-and-four,five & six\n`); }); @@ -677,11 +716,11 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); const lines = content.split('\n'); const headerLine = lines[0]; @@ -697,12 +736,12 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); const lines = content.split('\n'); const valuesLine = lines[1]; @@ -724,12 +763,12 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); const lines = content.split('\n'); @@ -746,7 +785,7 @@ describe('CSV Execute Job', function() { _scroll_id: 'scrollId', }); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], @@ -760,7 +799,7 @@ describe('CSV Execute Job', function() { fieldFormatMap: '{"one":{"id":"string","params":{"transform": "upper"}}}', }, }, - }; + }); const { content } = await executeJob('job123', jobParams, cancellationToken); const lines = content.split('\n'); @@ -774,18 +813,18 @@ describe('CSV Execute Job', function() { // tests use these 'simple' characters to make the math easier describe('when only the headers exceed the maxSizeBytes', function() { - let content; - let maxSizeReached; + let content: string; + let maxSizeReached: boolean; beforeEach(async function() { configGetStub.withArgs('csv', 'maxSizeBytes').returns(1); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, - }; + }); ({ content, max_size_reached: maxSizeReached } = await executeJob( 'job123', @@ -804,18 +843,18 @@ describe('CSV Execute Job', function() { }); describe('when headers are equal to maxSizeBytes', function() { - let content; - let maxSizeReached; + let content: string; + let maxSizeReached: boolean; beforeEach(async function() { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, - }; + }); ({ content, max_size_reached: maxSizeReached } = await executeJob( 'job123', @@ -834,8 +873,8 @@ describe('CSV Execute Job', function() { }); describe('when the data exceeds the maxSizeBytes', function() { - let content; - let maxSizeReached; + let content: string; + let maxSizeReached: boolean; beforeEach(async function() { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); @@ -848,12 +887,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); ({ content, max_size_reached: maxSizeReached } = await executeJob( 'job123', @@ -872,8 +911,8 @@ describe('CSV Execute Job', function() { }); describe('when headers and data equal the maxSizeBytes', function() { - let content; - let maxSizeReached; + let content: string; + let maxSizeReached: boolean; beforeEach(async function() { mockReportingPlugin.getUiSettingsServiceFactory = () => mockUiSettingsClient; @@ -887,12 +926,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); ({ content, max_size_reached: maxSizeReached } = await executeJob( 'job123', @@ -924,12 +963,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); await executeJob('job123', jobParams, cancellationToken); @@ -950,12 +989,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); await executeJob('job123', jobParams, cancellationToken); @@ -976,12 +1015,12 @@ describe('CSV Execute Job', function() { }); const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); - const jobParams = { + const jobParams = getJobDocPayload({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, - }; + }); await executeJob('job123', jobParams, cancellationToken); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts similarity index 58% rename from x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js rename to x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts index cb63e7dad2fdf..c9cba64a732b6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.ts @@ -5,19 +5,22 @@ */ import * as Rx from 'rxjs'; -import { createMockReportingCore } from '../../../../test_helpers'; +import { createMockReportingCore, createMockBrowserDriverFactory } from '../../../../test_helpers'; import { cryptoFactory } from '../../../../server/lib/crypto'; import { executeJobFactory } from './index'; import { generatePngObservableFactory } from '../lib/generate_png'; +import { CancellationToken } from '../../../../common/cancellation_token'; import { LevelLogger } from '../../../../server/lib'; +import { ReportingCore, CaptureConfig } from '../../../../server/types'; +import { JobDocPayloadPNG } from '../../types'; jest.mock('../lib/generate_png', () => ({ generatePngObservableFactory: jest.fn() })); -let mockReporting; +let mockReporting: ReportingCore; -const cancellationToken = { +const cancellationToken = ({ on: jest.fn(), -}; +} as unknown) as CancellationToken; const mockLoggerFactory = { get: jest.fn().mockImplementation(() => ({ @@ -28,12 +31,16 @@ const mockLoggerFactory = { }; const getMockLogger = () => new LevelLogger(mockLoggerFactory); +const captureConfig = {} as CaptureConfig; + const mockEncryptionKey = 'abcabcsecuresecret'; -const encryptHeaders = async headers => { +const encryptHeaders = async (headers: Record<string, string>) => { const crypto = cryptoFactory(mockEncryptionKey); return await crypto.encrypt(headers); }; +const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPNG; + beforeEach(async () => { const kbnConfig = { 'server.basePath': '/sbp', @@ -45,8 +52,8 @@ beforeEach(async () => { 'kibanaServer.protocol': 'http', }; const mockReportingConfig = { - get: (...keys) => reportingConfig[keys.join('.')], - kbnConfig: { get: (...keys) => kbnConfig[keys.join('.')] }, + get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')], + kbnConfig: { get: (...keys: string[]) => (kbnConfig as any)[keys.join('.')] }, }; mockReporting = await createMockReportingCore(mockReportingConfig); @@ -60,22 +67,30 @@ beforeEach(async () => { mockGetElasticsearch.mockImplementation(() => Promise.resolve(mockElasticsearch)); mockReporting.getElasticsearchService = mockGetElasticsearch; - generatePngObservableFactory.mockReturnValue(jest.fn()); + (generatePngObservableFactory as jest.Mock).mockReturnValue(jest.fn()); }); -afterEach(() => generatePngObservableFactory.mockReset()); +afterEach(() => (generatePngObservableFactory as jest.Mock).mockReset()); test(`passes browserTimezone to generatePng`, async () => { const encryptedHeaders = await encryptHeaders({}); + const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); - const generatePngObservable = generatePngObservableFactory(); - generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); + const generatePngObservable = generatePngObservableFactory( + captureConfig, + mockBrowserDriverFactory + ); + (generatePngObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; await executeJob( 'pngJobId', - { relativeUrl: '/app/kibana#/something', browserTimezone, headers: encryptedHeaders }, + getJobDocPayload({ + relativeUrl: '/app/kibana#/something', + browserTimezone, + headers: encryptedHeaders, + }), cancellationToken ); @@ -92,12 +107,17 @@ test(`returns content_type of application/png`, async () => { const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); - const generatePngObservable = generatePngObservableFactory(); - generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); + const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); + + const generatePngObservable = generatePngObservableFactory( + captureConfig, + mockBrowserDriverFactory + ); + (generatePngObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); const { content_type: contentType } = await executeJob( 'pngJobId', - { relativeUrl: '/app/kibana#/something', timeRange: {}, headers: encryptedHeaders }, + getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }), cancellationToken ); expect(contentType).toBe('image/png'); @@ -106,14 +126,19 @@ test(`returns content_type of application/png`, async () => { test(`returns content of generatePng getBuffer base64 encoded`, async () => { const testContent = 'test content'; - const generatePngObservable = generatePngObservableFactory(); - generatePngObservable.mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); + const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); + + const generatePngObservable = generatePngObservableFactory( + captureConfig, + mockBrowserDriverFactory + ); + (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pngJobId', - { relativeUrl: '/app/kibana#/something', timeRange: {}, headers: encryptedHeaders }, + getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }), cancellationToken ); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts similarity index 57% rename from x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js rename to x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts index c6f07f8ad2d34..c3c0d38584bc1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.ts @@ -5,19 +5,24 @@ */ import * as Rx from 'rxjs'; -import { createMockReportingCore } from '../../../../test_helpers'; +import { createMockReportingCore, createMockBrowserDriverFactory } from '../../../../test_helpers'; import { cryptoFactory } from '../../../../server/lib/crypto'; -import { executeJobFactory } from './index'; -import { generatePdfObservableFactory } from '../lib/generate_pdf'; import { LevelLogger } from '../../../../server/lib'; +import { CancellationToken } from '../../../../types'; +import { ReportingCore, CaptureConfig } from '../../../../server/types'; +import { generatePdfObservableFactory } from '../lib/generate_pdf'; +import { JobDocPayloadPDF } from '../../types'; +import { executeJobFactory } from './index'; jest.mock('../lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn() })); -let mockReporting; +let mockReporting: ReportingCore; -const cancellationToken = { +const cancellationToken = ({ on: jest.fn(), -}; +} as unknown) as CancellationToken; + +const captureConfig = {} as CaptureConfig; const mockLoggerFactory = { get: jest.fn().mockImplementation(() => ({ @@ -29,11 +34,13 @@ const mockLoggerFactory = { const getMockLogger = () => new LevelLogger(mockLoggerFactory); const mockEncryptionKey = 'testencryptionkey'; -const encryptHeaders = async headers => { +const encryptHeaders = async (headers: Record<string, string>) => { const crypto = cryptoFactory(mockEncryptionKey); return await crypto.encrypt(headers); }; +const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPDF; + beforeEach(async () => { const kbnConfig = { 'server.basePath': '/sbp', @@ -45,8 +52,8 @@ beforeEach(async () => { 'kibanaServer.protocol': 'http', }; const mockReportingConfig = { - get: (...keys) => reportingConfig[keys.join('.')], - kbnConfig: { get: (...keys) => kbnConfig[keys.join('.')] }, + get: (...keys: string[]) => (reportingConfig as any)[keys.join('.')], + kbnConfig: { get: (...keys: string[]) => (kbnConfig as any)[keys.join('.')] }, }; mockReporting = await createMockReportingCore(mockReportingConfig); @@ -60,21 +67,26 @@ beforeEach(async () => { mockGetElasticsearch.mockImplementation(() => Promise.resolve(mockElasticsearch)); mockReporting.getElasticsearchService = mockGetElasticsearch; - generatePdfObservableFactory.mockReturnValue(jest.fn()); + (generatePdfObservableFactory as jest.Mock).mockReturnValue(jest.fn()); }); -afterEach(() => generatePdfObservableFactory.mockReset()); +afterEach(() => (generatePdfObservableFactory as jest.Mock).mockReset()); test(`returns content_type of application/pdf`, async () => { - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const logger = getMockLogger(); + const executeJob = await executeJobFactory(mockReporting, logger); + const mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger); const encryptedHeaders = await encryptHeaders({}); - const generatePdfObservable = generatePdfObservableFactory(); - generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); + const generatePdfObservable = generatePdfObservableFactory( + captureConfig, + mockBrowserDriverFactory + ); + (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); const { content_type: contentType } = await executeJob( 'pdfJobId', - { relativeUrls: [], timeRange: {}, headers: encryptedHeaders }, + getJobDocPayload({ relativeUrls: [], headers: encryptedHeaders }), cancellationToken ); expect(contentType).toBe('application/pdf'); @@ -82,15 +94,19 @@ test(`returns content_type of application/pdf`, async () => { test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const testContent = 'test content'; + const mockBrowserDriverFactory = await createMockBrowserDriverFactory(getMockLogger()); - const generatePdfObservable = generatePdfObservableFactory(); - generatePdfObservable.mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); + const generatePdfObservable = generatePdfObservableFactory( + captureConfig, + mockBrowserDriverFactory + ); + (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); const executeJob = await executeJobFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pdfJobId', - { relativeUrls: [], timeRange: {}, headers: encryptedHeaders }, + getJobDocPayload({ relativeUrls: [], headers: encryptedHeaders }), cancellationToken ); diff --git a/x-pack/legacy/plugins/reporting/index.test.js b/x-pack/legacy/plugins/reporting/index.test.ts similarity index 94% rename from x-pack/legacy/plugins/reporting/index.test.js rename to x-pack/legacy/plugins/reporting/index.test.ts index 0d9a717bd7d81..8148adab67874 100644 --- a/x-pack/legacy/plugins/reporting/index.test.js +++ b/x-pack/legacy/plugins/reporting/index.test.ts @@ -27,7 +27,7 @@ const describeWithContext = describe.each([ describeWithContext('config schema with context %j', context => { it('produces correct config', async () => { const schema = await getConfigSchema(reporting); - const value = await schema.validate({}, { context }); + const value: any = await schema.validate({}, { context }); value.capture.browser.chromium.disableSandbox = '<platform dependent>'; await expect(value).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts similarity index 91% rename from x-pack/legacy/plugins/reporting/server/routes/jobs.test.js rename to x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts index 9f0de844df369..5c58a7dfa0110 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.ts @@ -6,7 +6,10 @@ import Hapi from 'hapi'; import { createMockReportingCore } from '../../test_helpers'; +import { ExportTypeDefinition } from '../../types'; import { ExportTypesRegistry } from '../lib/export_types_registry'; +import { LevelLogger } from '../lib/level_logger'; +import { ReportingConfig, ReportingCore, ReportingSetupDeps } from '../types'; jest.mock('./lib/authorized_user_pre_routing', () => ({ authorizedUserPreRoutingFactory: () => () => ({}), @@ -19,14 +22,14 @@ jest.mock('./lib/reporting_feature_pre_routing', () => ({ import { registerJobInfoRoutes } from './jobs'; -let mockServer; -let exportTypesRegistry; -let mockReportingPlugin; -let mockReportingConfig; -const mockLogger = { +let mockServer: any; +let exportTypesRegistry: ExportTypesRegistry; +let mockReportingPlugin: ReportingCore; +let mockReportingConfig: ReportingConfig; +const mockLogger = ({ error: jest.fn(), debug: jest.fn(), -}; +} as unknown) as LevelLogger; beforeEach(async () => { mockServer = new Hapi.Server({ debug: false, port: 8080, routes: { log: { collect: true } } }); @@ -35,38 +38,39 @@ beforeEach(async () => { id: 'unencoded', jobType: 'unencodedJobType', jobContentExtension: 'csv', - }); + } as ExportTypeDefinition<unknown, unknown, unknown, unknown>); exportTypesRegistry.register({ id: 'base64Encoded', jobType: 'base64EncodedJobType', jobContentEncoding: 'base64', jobContentExtension: 'pdf', - }); + } as ExportTypeDefinition<unknown, unknown, unknown, unknown>); mockReportingConfig = { get: jest.fn(), kbnConfig: { get: jest.fn() } }; mockReportingPlugin = await createMockReportingCore(mockReportingConfig); mockReportingPlugin.getExportTypesRegistry = () => exportTypesRegistry; }); -const mockPlugins = { +const mockPlugins = ({ elasticsearch: { adminClient: { callAsInternalUser: jest.fn() }, }, security: null, -}; +} as unknown) as ReportingSetupDeps; -const getHits = (...sources) => { +const getHits = (...sources: any) => { return { hits: { - hits: sources.map(source => ({ _source: source })), + hits: sources.map((source: object) => ({ _source: source })), }, }; }; -const getErrorsFromRequest = request => - request.logs.filter(log => log.tags.includes('error')).map(log => log.error); +const getErrorsFromRequest = (request: any) => + request.logs.filter((log: any) => log.tags.includes('error')).map((log: any) => log.error); test(`returns 404 if job not found`, async () => { + // @ts-ignore mockPlugins.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(getHits())), }; @@ -84,6 +88,7 @@ test(`returns 404 if job not found`, async () => { }); test(`returns 401 if not valid job type`, async () => { + // @ts-ignore mockPlugins.elasticsearch.adminClient = { callAsInternalUser: jest .fn() @@ -103,6 +108,7 @@ test(`returns 401 if not valid job type`, async () => { describe(`when job is incomplete`, () => { const getIncompleteResponse = async () => { + // @ts-ignore mockPlugins.elasticsearch.adminClient = { callAsInternalUser: jest .fn() @@ -149,6 +155,7 @@ describe(`when job is failed`, () => { status: 'failed', output: { content: 'job failure message' }, }); + // @ts-ignore mockPlugins.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; @@ -194,6 +201,7 @@ describe(`when job is completed`, () => { title, }, }); + // @ts-ignore mockPlugins.elasticsearch.adminClient = { callAsInternalUser: jest.fn().mockReturnValue(Promise.resolve(hits)), }; diff --git a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts similarity index 90% rename from x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js rename to x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts index 929109e66914d..dbc674ce36ec8 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js +++ b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts @@ -11,18 +11,21 @@ import { registerReportingUsageCollector, getReportingUsageCollector, } from './reporting_usage_collector'; +import { ReportingConfig } from '../types'; const exportTypesRegistry = getExportTypesRegistry(); function getMockUsageCollection() { class MockUsageCollector { - constructor(_server, { fetch }) { + // @ts-ignore fetch is not used + private fetch: any; + constructor(_server: any, { fetch }: any) { this.fetch = fetch; } } return { - makeUsageCollector: options => { - return new MockUsageCollector(this, options); + makeUsageCollector: (options: any) => { + return new MockUsageCollector(null, options); }, registerCollector: sinon.stub(), }; @@ -51,7 +54,7 @@ function getPluginsMock( xpack_main: mockXpackMain, }, }, - }; + } as any; } const getMockReportingConfig = () => ({ @@ -61,13 +64,13 @@ const getMockReportingConfig = () => ({ const getResponseMock = (customization = {}) => customization; describe('license checks', () => { - let mockConfig; + let mockConfig: ReportingConfig; beforeAll(async () => { mockConfig = getMockReportingConfig(); }); describe('with a basic license', () => { - let usageStats; + let usageStats: any; beforeAll(async () => { const plugins = getPluginsMock({ license: 'basic' }); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); @@ -75,9 +78,12 @@ describe('license checks', () => { mockConfig, plugins.usageCollection, plugins.__LEGACY.plugins.xpack_main.info, - exportTypesRegistry + exportTypesRegistry, + function isReady() { + return Promise.resolve(true); + } ); - usageStats = await fetch(callClusterMock, exportTypesRegistry); + usageStats = await fetch(callClusterMock as any); }); test('sets enables to true', async () => { @@ -94,7 +100,7 @@ describe('license checks', () => { }); describe('with no license', () => { - let usageStats; + let usageStats: any; beforeAll(async () => { const plugins = getPluginsMock({ license: 'none' }); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); @@ -102,9 +108,12 @@ describe('license checks', () => { mockConfig, plugins.usageCollection, plugins.__LEGACY.plugins.xpack_main.info, - exportTypesRegistry + exportTypesRegistry, + function isReady() { + return Promise.resolve(true); + } ); - usageStats = await fetch(callClusterMock, exportTypesRegistry); + usageStats = await fetch(callClusterMock as any); }); test('sets enables to true', async () => { @@ -121,7 +130,7 @@ describe('license checks', () => { }); describe('with platinum license', () => { - let usageStats; + let usageStats: any; beforeAll(async () => { const plugins = getPluginsMock({ license: 'platinum' }); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); @@ -129,9 +138,12 @@ describe('license checks', () => { mockConfig, plugins.usageCollection, plugins.__LEGACY.plugins.xpack_main.info, - exportTypesRegistry + exportTypesRegistry, + function isReady() { + return Promise.resolve(true); + } ); - usageStats = await fetch(callClusterMock, exportTypesRegistry); + usageStats = await fetch(callClusterMock as any); }); test('sets enables to true', async () => { @@ -148,7 +160,7 @@ describe('license checks', () => { }); describe('with no usage data', () => { - let usageStats; + let usageStats: any; beforeAll(async () => { const plugins = getPluginsMock({ license: 'basic' }); const callClusterMock = jest.fn(() => Promise.resolve({})); @@ -156,9 +168,12 @@ describe('license checks', () => { mockConfig, plugins.usageCollection, plugins.__LEGACY.plugins.xpack_main.info, - exportTypesRegistry + exportTypesRegistry, + function isReady() { + return Promise.resolve(true); + } ); - usageStats = await fetch(callClusterMock, exportTypesRegistry); + usageStats = await fetch(callClusterMock as any); }); test('sets enables to true', async () => { @@ -179,7 +194,10 @@ describe('data modeling', () => { mockConfig, plugins.usageCollection, plugins.__LEGACY.plugins.xpack_main.info, - exportTypesRegistry + exportTypesRegistry, + function isReady() { + return Promise.resolve(true); + } ); const callClusterMock = jest.fn(() => Promise.resolve( @@ -303,7 +321,7 @@ describe('data modeling', () => { ) ); - const usageStats = await fetch(callClusterMock); + const usageStats = await fetch(callClusterMock as any); expect(usageStats).toMatchInlineSnapshot(` Object { "PNG": Object { @@ -406,7 +424,7 @@ describe('Ready for collection observable', () => { const makeCollectorSpy = sinon.spy(); usageCollection.makeUsageCollector = makeCollectorSpy; - const plugins = getPluginsMock({ usageCollection }); + const plugins = getPluginsMock({ usageCollection } as any); registerReportingUsageCollector(mockReporting, plugins); const [args] = makeCollectorSpy.firstCall.args; diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts index 930aa7601b8cb..6e95bed2ecf92 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_browserdriverfactory.ts @@ -92,7 +92,7 @@ const defaultOpts: CreateMockBrowserDriverFactoryOpts = { export const createMockBrowserDriverFactory = async ( logger: Logger, - opts: Partial<CreateMockBrowserDriverFactoryOpts> + opts: Partial<CreateMockBrowserDriverFactoryOpts> = {} ): Promise<HeadlessChromiumDriverFactory> => { const captureConfig = { timeouts: { openUrl: 30000, waitForElements: 30000, renderComplete: 30000 }, diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index 7334a859005e0..eec7da7dc6733 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -186,7 +186,7 @@ export type ESQueueWorkerExecuteFn<JobDocPayloadType> = ( jobId: string, job: JobDocPayloadType, cancellationToken?: CancellationToken -) => void; +) => Promise<any>; /* * ImmediateExecuteFn receives the job doc payload because the payload was From d8a295dcbc272cfb7479169e2f99c4665c811816 Mon Sep 17 00:00:00 2001 From: Ryland Herrick <ryalnd@gmail.com> Date: Fri, 10 Apr 2020 14:00:11 -0500 Subject: [PATCH 11/21] [SIEM] Link ML Rule card CTA to license_management (#63210) * Link ML Rule card CTA to license_management Taking the user directly to the license management page within kibana (where they could immediately start a trial subscription) is much more actionable than taking them to the subscriptions marketing page. * Revert translation key change Neither of these is totally accurate, and there've already been translations written for the old one. --- .../components/select_rule_type/index.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.tsx index 9d3b37f1788fa..6f3d299da8d45 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/select_rule_type/index.tsx @@ -19,9 +19,16 @@ import { import { isMlRule } from '../../../../../../common/detection_engine/ml_helpers'; import { RuleType } from '../../../../../../common/detection_engine/types'; import { FieldHook } from '../../../../../shared_imports'; +import { useKibana } from '../../../../../lib/kibana'; import * as i18n from './translations'; -const MlCardDescription = ({ hasValidLicense = false }: { hasValidLicense?: boolean }) => ( +const MlCardDescription = ({ + subscriptionUrl, + hasValidLicense = false, +}: { + subscriptionUrl: string; + hasValidLicense?: boolean; +}) => ( <EuiText size="s"> {hasValidLicense ? ( i18n.ML_TYPE_DESCRIPTION @@ -31,7 +38,7 @@ const MlCardDescription = ({ hasValidLicense = false }: { hasValidLicense?: bool defaultMessage="Access to ML requires a {subscriptionsLink}." values={{ subscriptionsLink: ( - <EuiLink href="https://www.elastic.co/subscriptions" target="_blank"> + <EuiLink href={subscriptionUrl} target="_blank"> <FormattedMessage id="xpack.siem.components.stepDefineRule.ruleTypeField.subscriptionsLink" defaultMessage="Platinum subscription" @@ -69,6 +76,9 @@ export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({ const setMl = useCallback(() => setType('machine_learning'), [setType]); const setQuery = useCallback(() => setType('query'), [setType]); const mlCardDisabled = isReadOnly || !hasValidLicense || !isMlAdmin; + const licensingUrl = useKibana().services.application.getUrlForApp('kibana', { + path: '#/management/elasticsearch/license_management', + }); return ( <EuiFormRow @@ -95,7 +105,9 @@ export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({ <EuiCard data-test-subj="machineLearningRuleType" title={i18n.ML_TYPE_TITLE} - description={<MlCardDescription hasValidLicense={hasValidLicense} />} + description={ + <MlCardDescription subscriptionUrl={licensingUrl} hasValidLicense={hasValidLicense} /> + } icon={<EuiIcon size="l" type="machineLearningApp" />} isDisabled={mlCardDisabled} selectable={{ From 8a283de3c5884e21c245b1136f39132eeb2d976f Mon Sep 17 00:00:00 2001 From: CJ Cenizal <cj@cenizal.com> Date: Fri, 10 Apr 2020 12:05:20 -0700 Subject: [PATCH 12/21] Correctly type ILM's optional dependencies as optional (#63255) And guard against their absence. --- .../public/application/services/ui_metric.ts | 8 +++++--- x-pack/plugins/index_lifecycle_management/public/types.ts | 2 +- .../plugins/index_lifecycle_management/server/plugin.ts | 2 +- x-pack/plugins/index_lifecycle_management/server/types.ts | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts index ca6c0b44d5804..ca987441c7ce9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts @@ -23,10 +23,12 @@ import { import { defaultColdPhase, defaultWarmPhase, defaultHotPhase } from '../store/defaults'; -export let trackUiMetric: (metricType: UiStatsMetricType, eventName: string) => void; +export let trackUiMetric = (metricType: UiStatsMetricType, eventName: string) => {}; -export function init(usageCollection: UsageCollectionSetup): void { - trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, UIM_APP_NAME); +export function init(usageCollection?: UsageCollectionSetup): void { + if (usageCollection) { + trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, UIM_APP_NAME); + } } export function getUiMetricsForPhases(phases: any): any { diff --git a/x-pack/plugins/index_lifecycle_management/public/types.ts b/x-pack/plugins/index_lifecycle_management/public/types.ts index f9e0abae56cb4..178884a7ee679 100644 --- a/x-pack/plugins/index_lifecycle_management/public/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/types.ts @@ -9,7 +9,7 @@ import { ManagementSetup } from '../../../../src/plugins/management/public'; import { IndexManagementPluginSetup } from '../../index_management/public'; export interface PluginsDependencies { - usageCollection: UsageCollectionSetup; + usageCollection?: UsageCollectionSetup; management: ManagementSetup; indexManagement?: IndexManagementPluginSetup; } diff --git a/x-pack/plugins/index_lifecycle_management/server/plugin.ts b/x-pack/plugins/index_lifecycle_management/server/plugin.ts index 48c50f9a48ee5..faeac67f62a21 100644 --- a/x-pack/plugins/index_lifecycle_management/server/plugin.ts +++ b/x-pack/plugins/index_lifecycle_management/server/plugin.ts @@ -75,7 +75,7 @@ export class IndexLifecycleManagementServerPlugin implements Plugin<void, void, }); if (config.ui.enabled) { - if (indexManagement.indexDataEnricher) { + if (indexManagement && indexManagement.indexDataEnricher) { indexManagement.indexDataEnricher.add(indexLifecycleDataEnricher); } } diff --git a/x-pack/plugins/index_lifecycle_management/server/types.ts b/x-pack/plugins/index_lifecycle_management/server/types.ts index 7f64c1a47197a..734d05a82000e 100644 --- a/x-pack/plugins/index_lifecycle_management/server/types.ts +++ b/x-pack/plugins/index_lifecycle_management/server/types.ts @@ -14,7 +14,7 @@ import { isEsError } from './lib/is_es_error'; export interface Dependencies { licensing: LicensingPluginSetup; - indexManagement: IndexManagementPluginSetup; + indexManagement?: IndexManagementPluginSetup; } export interface RouteDependencies { From ed5dd0a32542f9f24656dd3aa9c642362d40801b Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh <ahmadbamieh@gmail.com> Date: Fri, 10 Apr 2020 22:18:11 +0300 Subject: [PATCH 13/21] [Telemetry] use prod keys (#63263) --- package.json | 2 +- .../server/encryption/encrypt.test.mocks.ts | 27 +++++++++++++++++++ .../server/encryption/encrypt.test.ts | 27 ++++++++++++------- .../server/encryption/encrypt.ts | 2 +- .../server/plugin.ts | 5 ++-- yarn.lock | 8 +++--- 6 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.mocks.ts diff --git a/package.json b/package.json index ec72d5b660345..a5d46e7f2bf40 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.4.0", - "@elastic/request-crypto": "1.1.2", + "@elastic/request-crypto": "1.1.4", "@elastic/ui-ace": "0.2.3", "@hapi/good-squeeze": "5.2.1", "@hapi/wreck": "^15.0.2", diff --git a/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.mocks.ts b/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.mocks.ts new file mode 100644 index 0000000000000..9a7cb8ba28d04 --- /dev/null +++ b/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.mocks.ts @@ -0,0 +1,27 @@ +/* + * 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 const mockEncrypt = jest.fn(); +export const createRequestEncryptor = jest.fn().mockResolvedValue({ + encrypt: mockEncrypt, +}); + +jest.doMock('@elastic/request-crypto', () => ({ + createRequestEncryptor, +})); diff --git a/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.ts b/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.ts index 4a4ba7aa1f321..c04625eb1dd42 100644 --- a/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.ts +++ b/src/plugins/telemetry_collection_manager/server/encryption/encrypt.test.ts @@ -16,16 +16,9 @@ * specific language governing permissions and limitations * under the License. */ - +import { createRequestEncryptor, mockEncrypt } from './encrypt.test.mocks'; import { telemetryJWKS } from './telemetry_jwks'; import { encryptTelemetry, getKID } from './encrypt'; -import { createRequestEncryptor } from '@elastic/request-crypto'; - -jest.mock('@elastic/request-crypto', () => ({ - createRequestEncryptor: jest.fn().mockResolvedValue({ - encrypt: jest.fn(), - }), -})); describe('getKID', () => { it(`returns 'kibana_dev' kid for development`, async () => { @@ -42,9 +35,25 @@ describe('getKID', () => { }); describe('encryptTelemetry', () => { + afterEach(() => { + mockEncrypt.mockReset(); + }); + it('encrypts payload', async () => { const payload = { some: 'value' }; - await encryptTelemetry(payload, true); + await encryptTelemetry(payload, { isProd: true }); expect(createRequestEncryptor).toBeCalledWith(telemetryJWKS); }); + + it('uses kibana kid on { isProd: true }', async () => { + const payload = { some: 'value' }; + await encryptTelemetry(payload, { isProd: true }); + expect(mockEncrypt).toBeCalledWith('kibana', payload); + }); + + it('uses kibana_dev kid on { isProd: false }', async () => { + const payload = { some: 'value' }; + await encryptTelemetry(payload, { isProd: false }); + expect(mockEncrypt).toBeCalledWith('kibana_dev', payload); + }); }); diff --git a/src/plugins/telemetry_collection_manager/server/encryption/encrypt.ts b/src/plugins/telemetry_collection_manager/server/encryption/encrypt.ts index c20f4b768b7dc..44f053064cfcb 100644 --- a/src/plugins/telemetry_collection_manager/server/encryption/encrypt.ts +++ b/src/plugins/telemetry_collection_manager/server/encryption/encrypt.ts @@ -24,7 +24,7 @@ export function getKID(isProd = false): string { return isProd ? 'kibana' : 'kibana_dev'; } -export async function encryptTelemetry(payload: any, isProd = false): Promise<string[]> { +export async function encryptTelemetry(payload: any, { isProd = false } = {}): Promise<string[]> { const kid = getKID(isProd); const encryptor = await createRequestEncryptor(telemetryJWKS); const clusters = [].concat(payload); diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts index 7e8dff9e0aec1..f2f20e215c535 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.ts @@ -158,7 +158,7 @@ export class TelemetryCollectionManagerPlugin if (config.unencrypted) { return optInStats; } - return encryptTelemetry(optInStats, this.isDev); + return encryptTelemetry(optInStats, { isProd: !this.isDev }); } } catch (err) { this.logger.debug(`Failed to collect any opt in stats with registered collections.`); @@ -205,7 +205,8 @@ export class TelemetryCollectionManagerPlugin if (config.unencrypted) { return usageData; } - return encryptTelemetry(usageData, this.isDev); + + return encryptTelemetry(usageData, { isProd: !this.isDev }); } } catch (err) { this.logger.debug( diff --git a/yarn.lock b/yarn.lock index 11abd95498c8d..42f891aa24e25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1401,10 +1401,10 @@ resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.4.0.tgz#883197b7f4bf3c2dd994f53b274769ddfa2bf79a" integrity sha512-uGBKGCNghTgUZPHClji/00v+AKt5nidPTGOIbcT+lbTPVxNB6QPpPLGWtXyrg3QZAxobPM/LAZB1mAqtJeq44Q== -"@elastic/request-crypto@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@elastic/request-crypto/-/request-crypto-1.1.2.tgz#2e323550f546f6286994126d462a9ea480a3bfb1" - integrity sha512-i73wjj1Qi8dGJIy170Z8xyJ760mFNjTbdmcp/nEczqWD0miNW6I5wZ5MNrv7M6CXn2m1wMXiT6qzDYd93Hv1Dw== +"@elastic/request-crypto@1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@elastic/request-crypto/-/request-crypto-1.1.4.tgz#2189d5fea65f7afe1de9f5fa3d0dd420e93e3124" + integrity sha512-D5CzSGKkM6BdrVB/HRRTheMsNQOcd2FMUup0O/1hIGUBE8zHh2AYbmSNSpD6LyQAgY39mGkARUi/x+SO0ccVvg== dependencies: "@elastic/node-crypto" "1.1.1" "@types/node-jose" "1.1.0" From eb3fe8eb50be7876128bc592f9ce913259be3ee4 Mon Sep 17 00:00:00 2001 From: Dmitry Lemeshko <dzmitry.lemechko@elastic.co> Date: Fri, 10 Apr 2020 23:05:20 +0300 Subject: [PATCH 14/21] update chromedriver dependency to 81.0.0 (#63266) --- package.json | 2 +- yarn.lock | 38 ++++++++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index a5d46e7f2bf40..2bad3116c9ef2 100644 --- a/package.json +++ b/package.json @@ -401,7 +401,7 @@ "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", - "chromedriver": "^80.0.1", + "chromedriver": "^81.0.0", "classnames": "2.2.6", "dedent": "^0.7.0", "delete-empty": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 42f891aa24e25..8ca25cc18a8a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5061,6 +5061,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" @@ -8723,16 +8730,16 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^80.0.1: - version "80.0.1" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-80.0.1.tgz#35c1642e2d864b9e8262f291003e455b0e422917" - integrity sha512-VfRtZUpBUIjeypS+xM40+VD9g4Drv7L2VibG/4+0zX3mMx4KayN6gfKETycPfO6JwQXTLSxEr58fRcrsa8r5xQ== +chromedriver@^81.0.0: + version "81.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-81.0.0.tgz#690ba333aedf2b4c4933b6590c3242d3e5f28f3c" + integrity sha512-BA++IQ7O1FzHmNpzMlOfLiSBvPZ946uuhtJjZHEIr/Gb+Ha9jiuGbHiT45l6O3XGbQ8BAwvbmdisjl4rTxro4A== dependencies: "@testim/chrome-version" "^1.0.7" axios "^0.19.2" del "^5.1.0" - extract-zip "^1.6.7" - mkdirp "^1.0.3" + extract-zip "^2.0.0" + mkdirp "^1.0.4" tcp-port-used "^1.0.1" ci-info@^1.0.0: @@ -13161,6 +13168,17 @@ extract-zip@^1.6.6, extract-zip@^1.6.7, extract-zip@^1.7.0: mkdirp "^0.5.4" yauzl "^2.10.0" +extract-zip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492" + integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -20622,10 +20640,10 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkd dependencies: minimist "^1.2.5" -mkdirp@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" - integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mocha-junit-reporter@^1.23.1: version "1.23.1" From 6393e08ce3ba7cee6f138f2dac6db3a37f6953dc Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Fri, 10 Apr 2020 17:20:40 -0400 Subject: [PATCH 15/21] task/mac-eventing-form (#62999) adds mac events form for endpoint policy details Co-authored-by: oatkiller <robert.austin@elastic.co> --- .../applications/endpoint/models/policy.ts | 4 + .../endpoint/models/policy_details_config.ts | 30 +++++ .../store/policy_details/index.test.ts | 31 ++++- .../endpoint/store/policy_details/reducer.ts | 12 +- .../store/policy_details/selectors.ts | 30 +++-- .../public/applications/endpoint/types.ts | 70 ++++++------ .../endpoint/view/policy/policy_details.tsx | 6 +- .../policy/policy_forms/eventing/checkbox.tsx | 53 --------- .../policy/policy_forms/events/checkbox.tsx | 49 ++++++++ .../view/policy/policy_forms/events/index.tsx | 8 ++ .../view/policy/policy_forms/events/mac.tsx | 106 ++++++++++++++++++ .../{eventing => events}/windows.tsx | 59 +++++----- 12 files changed, 325 insertions(+), 133 deletions(-) delete mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/checkbox.tsx create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/index.tsx create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx rename x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/{eventing => events}/windows.tsx (61%) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts index 9ac53f9be609f..30f45e54c2005 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts @@ -42,6 +42,8 @@ export const generatePolicy = (): PolicyConfig => { mac: { events: { process: true, + file: true, + network: true, }, malware: { mode: ProtectionModes.detect, @@ -67,6 +69,8 @@ export const generatePolicy = (): PolicyConfig => { linux: { events: { process: true, + file: true, + network: true, }, logging: { stdout: 'debug', diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts index 1145d1d19242a..bf96942e83a91 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts @@ -43,3 +43,33 @@ export function clone(policyDetailsConfig: UIPolicyConfig): UIPolicyConfig { */ return clonedConfig as UIPolicyConfig; } + +/** + * Returns value from `configuration` + */ +export const getIn = (a: UIPolicyConfig) => <Key extends keyof UIPolicyConfig>(key: Key) => < + subKey extends keyof UIPolicyConfig[Key] +>( + subKey: subKey +) => <LeafKey extends keyof UIPolicyConfig[Key][subKey]>( + leafKey: LeafKey +): UIPolicyConfig[Key][subKey][LeafKey] => { + return a[key][subKey][leafKey]; +}; + +/** + * Returns cloned `configuration` with `value` set by the `keyPath`. + */ +export const setIn = (a: UIPolicyConfig) => <Key extends keyof UIPolicyConfig>(key: Key) => < + subKey extends keyof UIPolicyConfig[Key] +>( + subKey: subKey +) => <LeafKey extends keyof UIPolicyConfig[Key][subKey]>(leafKey: LeafKey) => < + V extends UIPolicyConfig[Key][subKey][LeafKey] +>( + v: V +): UIPolicyConfig => { + const c = clone(a); + c[key][subKey][leafKey] = v; + return c; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts index cf14092953227..e09a62b235e35 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts @@ -7,7 +7,7 @@ import { PolicyDetailsState } from '../../types'; import { createStore, Dispatch, Store } from 'redux'; import { policyDetailsReducer, PolicyDetailsAction } from './index'; -import { policyConfig, windowsEventing } from './selectors'; +import { policyConfig } from './selectors'; import { clone } from '../../models/policy_details_config'; import { generatePolicy } from '../../models/policy'; @@ -55,7 +55,7 @@ describe('policy details: ', () => { }); }); - describe('when the user has enabled windows process eventing', () => { + describe('when the user has enabled windows process events', () => { beforeEach(() => { const config = policyConfig(getState()); if (!config) { @@ -71,8 +71,31 @@ describe('policy details: ', () => { }); }); - it('windows process eventing is enabled', async () => { - expect(windowsEventing(getState())!.process).toEqual(true); + it('windows process events is enabled', () => { + const config = policyConfig(getState()); + expect(config!.windows.events.process).toEqual(true); + }); + }); + + describe('when the user has enabled mac file events', () => { + beforeEach(() => { + const config = policyConfig(getState()); + if (!config) { + throw new Error(); + } + + const newPayload1 = clone(config); + newPayload1.mac.events.file = true; + + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload1 }, + }); + }); + + it('mac file events is enabled', () => { + const config = policyConfig(getState()); + expect(config!.mac.events.file).toEqual(true); }); }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/reducer.ts index fb3e26157ef32..fb0f371cdae0d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/reducer.ts @@ -5,7 +5,7 @@ */ import { Reducer } from 'redux'; -import { PolicyData, PolicyDetailsState, UIPolicyConfig } from '../../types'; +import { PolicyDetailsState, UIPolicyConfig } from '../../types'; import { AppAction } from '../action'; import { fullPolicy, isOnPolicyDetailsPage } from './selectors'; @@ -89,10 +89,12 @@ export const policyDetailsReducer: Reducer<PolicyDetailsState, AppAction> = ( } if (action.type === 'userChangedPolicyConfig') { - const newState = { ...state, policyItem: { ...(state.policyItem as PolicyData) } }; - const newPolicy = (newState.policyItem.inputs[0].config.policy.value = { - ...fullPolicy(state), - }); + if (!state.policyItem) { + return state; + } + const newState = { ...state, policyItem: { ...state.policyItem } }; + const newPolicy: any = { ...fullPolicy(state) }; + newState.policyItem.inputs[0].config.policy.value = newPolicy; Object.entries(action.payload.policyConfig).forEach(([section, newSettings]) => { newPolicy[section as keyof UIPolicyConfig] = { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts index 0d505931c9ec5..4b4dc9d9bee43 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts @@ -79,14 +79,8 @@ export const policyConfig: (s: PolicyDetailsState) => UIPolicyConfig = createSel } ); -/** Returns an object of all the windows eventing configuration */ -export const windowsEventing = (state: PolicyDetailsState) => { - const config = policyConfig(state); - return config && config.windows.events; -}; - /** Returns the total number of possible windows eventing configurations */ -export const totalWindowsEventing = (state: PolicyDetailsState): number => { +export const totalWindowsEvents = (state: PolicyDetailsState): number => { const config = policyConfig(state); if (config) { return Object.keys(config.windows.events).length; @@ -95,7 +89,7 @@ export const totalWindowsEventing = (state: PolicyDetailsState): number => { }; /** Returns the number of selected windows eventing configurations */ -export const selectedWindowsEventing = (state: PolicyDetailsState): number => { +export const selectedWindowsEvents = (state: PolicyDetailsState): number => { const config = policyConfig(state); if (config) { return Object.values(config.windows.events).reduce((count, event) => { @@ -105,6 +99,26 @@ export const selectedWindowsEventing = (state: PolicyDetailsState): number => { return 0; }; +/** Returns the total number of possible mac eventing configurations */ +export const totalMacEvents = (state: PolicyDetailsState): number => { + const config = policyConfig(state); + if (config) { + return Object.keys(config.mac.events).length; + } + return 0; +}; + +/** Returns the number of selected mac eventing configurations */ +export const selectedMacEvents = (state: PolicyDetailsState): number => { + const config = policyConfig(state); + if (config) { + return Object.values(config.mac.events).reduce((count, event) => { + return event === true ? count + 1 : count; + }, 0); + } + return 0; +}; + /** is there an api call in flight */ export const isLoading = (state: PolicyDetailsState) => state.isLoading; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index d4f6d2457254e..dda50847169e7 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -118,34 +118,21 @@ export interface PolicyDetailsState { * Endpoint Policy configuration */ export interface PolicyConfig { - windows: { - events: { - process: boolean; - network: boolean; - }; - /** malware mode can be off, detect, prevent or prevent and notify user */ - malware: MalwareFields; + windows: UIPolicyConfig['windows'] & { logging: { stdout: string; file: string; }; advanced: PolicyConfigAdvancedOptions; }; - mac: { - events: { - process: boolean; - }; - malware: MalwareFields; + mac: UIPolicyConfig['mac'] & { logging: { stdout: string; file: string; }; advanced: PolicyConfigAdvancedOptions; }; - linux: { - events: { - process: boolean; - }; + linux: UIPolicyConfig['linux'] & { logging: { stdout: string; file: string; @@ -168,29 +155,39 @@ interface PolicyConfigAdvancedOptions { }; } -/** - * Windows-specific policy configuration that is supported via the UI - */ -type WindowsPolicyConfig = Pick<PolicyConfig['windows'], 'events' | 'malware'>; - -/** - * Mac-specific policy configuration that is supported via the UI - */ -type MacPolicyConfig = Pick<PolicyConfig['mac'], 'malware' | 'events'>; - -/** - * Linux-specific policy configuration that is supported via the UI - */ -type LinuxPolicyConfig = Pick<PolicyConfig['linux'], 'events'>; - /** * The set of Policy configuration settings that are show/edited via the UI */ -export interface UIPolicyConfig { - windows: WindowsPolicyConfig; - mac: MacPolicyConfig; - linux: LinuxPolicyConfig; -} +/* eslint-disable @typescript-eslint/consistent-type-definitions */ +export type UIPolicyConfig = { + windows: { + events: { + process: boolean; + network: boolean; + }; + /** malware mode can be off, detect, prevent or prevent and notify user */ + malware: MalwareFields; + }; + mac: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; + malware: MalwareFields; + }; + + /** + * Linux-specific policy configuration that is supported via the UI + */ + linux: { + events: { + file: boolean; + process: boolean; + network: boolean; + }; + }; +}; /** OS used in Policy */ export enum OS { @@ -203,6 +200,7 @@ export enum OS { export enum EventingFields { process = 'process', network = 'network', + file = 'file', } /** diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx index bc56e5e6f6329..1e723e32615eb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx @@ -29,12 +29,12 @@ import { isLoading, apiError, } from '../../store/policy_details/selectors'; -import { WindowsEventing } from './policy_forms/eventing/windows'; import { PageView, PageViewHeaderTitle } from '../../components/page_view'; import { AppAction } from '../../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { AgentsSummary } from './agents_summary'; import { VerticalDivider } from './vertical_divider'; +import { WindowsEvents, MacEvents } from './policy_forms/events'; import { MalwareProtections } from './policy_forms/protections/malware'; export const PolicyDetails = React.memo(() => { @@ -206,7 +206,9 @@ export const PolicyDetails = React.memo(() => { </h4> </EuiText> <EuiSpacer size="xs" /> - <WindowsEventing /> + <WindowsEvents /> + <EuiSpacer size="l" /> + <MacEvents /> </PageView> </> ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/checkbox.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/checkbox.tsx deleted file mode 100644 index 8b7fb89ed1646..0000000000000 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/checkbox.tsx +++ /dev/null @@ -1,53 +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, { useCallback } from 'react'; -import { EuiCheckbox } from '@elastic/eui'; -import { useDispatch } from 'react-redux'; -import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { policyConfig, windowsEventing } from '../../../../store/policy_details/selectors'; -import { PolicyDetailsAction } from '../../../../store/policy_details'; -import { OS, EventingFields } from '../../../../types'; -import { clone } from '../../../../models/policy_details_config'; - -export const EventingCheckbox: React.FC<{ - id: string; - name: string; - os: OS; - protectionField: EventingFields; -}> = React.memo(({ id, name, os, protectionField }) => { - const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); - const eventing = usePolicyDetailsSelector(windowsEventing); - const dispatch = useDispatch<(action: PolicyDetailsAction) => void>(); - - const handleRadioChange = useCallback( - (event: React.ChangeEvent<HTMLInputElement>) => { - if (policyDetailsConfig) { - const newPayload = clone(policyDetailsConfig); - if (os === OS.linux || os === OS.mac) { - newPayload[os].events.process = event.target.checked; - } else { - newPayload[os].events[protectionField] = event.target.checked; - } - - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, - [dispatch, os, policyDetailsConfig, protectionField] - ); - - return ( - <EuiCheckbox - id={id} - label={name} - checked={eventing && eventing[protectionField]} - onChange={handleRadioChange} - /> - ); -}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx new file mode 100644 index 0000000000000..bec6b33b85c7f --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.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 React, { useCallback, useMemo } from 'react'; +import { EuiCheckbox } from '@elastic/eui'; +import { useDispatch } from 'react-redux'; +import { htmlIdGenerator } from '@elastic/eui'; +import { usePolicyDetailsSelector } from '../../policy_hooks'; +import { policyConfig } from '../../../../store/policy_details/selectors'; +import { PolicyDetailsAction } from '../../../../store/policy_details'; +import { UIPolicyConfig } from '../../../../types'; + +export const EventsCheckbox = React.memo(function({ + name, + setter, + getter, +}: { + name: string; + setter: (config: UIPolicyConfig, checked: boolean) => UIPolicyConfig; + getter: (config: UIPolicyConfig) => boolean; +}) { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const selected = getter(policyDetailsConfig); + const dispatch = useDispatch<(action: PolicyDetailsAction) => void>(); + + const handleCheckboxChange = useCallback( + (event: React.ChangeEvent<HTMLInputElement>) => { + if (policyDetailsConfig) { + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: setter(policyDetailsConfig, event.target.checked) }, + }); + } + }, + [dispatch, policyDetailsConfig, setter] + ); + + return ( + <EuiCheckbox + id={useMemo(() => htmlIdGenerator()(), [])} + label={name} + checked={selected} + onChange={handleCheckboxChange} + /> + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/index.tsx new file mode 100644 index 0000000000000..44716d8183041 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/index.tsx @@ -0,0 +1,8 @@ +/* + * 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 { WindowsEvents } from './windows'; +export { MacEvents } from './mac'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx new file mode 100644 index 0000000000000..3b69c21d2b150 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx @@ -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 React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; +import { EventsCheckbox } from './checkbox'; +import { OS, UIPolicyConfig } from '../../../../types'; +import { usePolicyDetailsSelector } from '../../policy_hooks'; +import { selectedMacEvents, totalMacEvents } from '../../../../store/policy_details/selectors'; +import { ConfigForm } from '../config_form'; +import { getIn, setIn } from '../../../../models/policy_details_config'; + +export const MacEvents = React.memo(() => { + const selected = usePolicyDetailsSelector(selectedMacEvents); + const total = usePolicyDetailsSelector(totalMacEvents); + + const checkboxes: Array<{ + name: string; + os: 'mac'; + protectionField: keyof UIPolicyConfig['mac']['events']; + }> = useMemo( + () => [ + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.mac.events.file', { + defaultMessage: 'File', + }), + os: OS.mac, + protectionField: 'file', + }, + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.mac.events.process', { + defaultMessage: 'Process', + }), + os: OS.mac, + protectionField: 'process', + }, + { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.mac.events.network', { + defaultMessage: 'Network', + }), + os: OS.mac, + protectionField: 'network', + }, + ], + [] + ); + + const renderCheckboxes = () => { + return ( + <> + <EuiTitle size="xxs"> + <h5> + <FormattedMessage + id="xpack.endpoint.policyDetailsConfig.eventingEvents" + defaultMessage="Events" + /> + </h5> + </EuiTitle> + <EuiSpacer size="s" /> + {checkboxes.map((item, index) => { + return ( + <EventsCheckbox + name={item.name} + key={index} + setter={(config, checked) => + setIn(config)(item.os)('events')(item.protectionField)(checked) + } + getter={config => getIn(config)(item.os)('events')(item.protectionField)} + /> + ); + })} + </> + ); + }; + + const collectionsEnabled = () => { + return ( + <EuiText size="s" color="subdued"> + <FormattedMessage + id="xpack.endpoint.policy.details.eventCollectionsEnabled" + defaultMessage="{selected} / {total} event collections enabled" + values={{ selected, total }} + /> + </EuiText> + ); + }; + + return ( + <ConfigForm + type={i18n.translate('xpack.endpoint.policy.details.eventCollection', { + defaultMessage: 'Event Collection', + })} + supportedOss={[ + i18n.translate('xpack.endpoint.policy.details.mac', { defaultMessage: 'Mac' }), + ]} + id="macEventingForm" + rightCorner={collectionsEnabled()} + children={renderCheckboxes()} + /> + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx similarity index 61% rename from x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx rename to x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx index 7bec2c4c742d2..63a140912437d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/windows.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx @@ -8,40 +8,45 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; -import { EventingCheckbox } from './checkbox'; -import { OS, EventingFields } from '../../../../types'; +import { EventsCheckbox } from './checkbox'; +import { OS, UIPolicyConfig } from '../../../../types'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { - selectedWindowsEventing, - totalWindowsEventing, + selectedWindowsEvents, + totalWindowsEvents, } from '../../../../store/policy_details/selectors'; import { ConfigForm } from '../config_form'; +import { setIn, getIn } from '../../../../models/policy_details_config'; -export const WindowsEventing = React.memo(() => { - const selected = usePolicyDetailsSelector(selectedWindowsEventing); - const total = usePolicyDetailsSelector(totalWindowsEventing); +export const WindowsEvents = React.memo(() => { + const selected = usePolicyDetailsSelector(selectedWindowsEvents); + const total = usePolicyDetailsSelector(totalWindowsEvents); - const checkboxes = useMemo( + const checkboxes: Array<{ + name: string; + os: 'windows'; + protectionField: keyof UIPolicyConfig['windows']['events']; + }> = useMemo( () => [ { - name: i18n.translate('xpack.endpoint.policyDetailsConfig.eventingProcess', { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.process', { defaultMessage: 'Process', }), os: OS.windows, - protectionField: EventingFields.process, + protectionField: 'process', }, { - name: i18n.translate('xpack.endpoint.policyDetailsConfig.eventingNetwork', { + name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.network', { defaultMessage: 'Network', }), os: OS.windows, - protectionField: EventingFields.network, + protectionField: 'network', }, ], [] ); - const renderCheckboxes = () => { + const renderCheckboxes = useMemo(() => { return ( <> <EuiTitle size="xxs"> @@ -55,20 +60,21 @@ export const WindowsEventing = React.memo(() => { <EuiSpacer size="s" /> {checkboxes.map((item, index) => { return ( - <EventingCheckbox - id={`eventing${item.name}`} + <EventsCheckbox name={item.name} key={index} - os={item.os} - protectionField={item.protectionField} + setter={(config, checked) => + setIn(config)(item.os)('events')(item.protectionField)(checked) + } + getter={config => getIn(config)(item.os)('events')(item.protectionField)} /> ); })} </> ); - }; + }, [checkboxes]); - const collectionsEnabled = () => { + const collectionsEnabled = useMemo(() => { return ( <EuiText size="s" color="subdued"> <FormattedMessage @@ -78,19 +84,22 @@ export const WindowsEventing = React.memo(() => { /> </EuiText> ); - }; + }, [selected, total]); return ( <ConfigForm type={i18n.translate('xpack.endpoint.policy.details.eventCollection', { defaultMessage: 'Event Collection', })} - supportedOss={[ - i18n.translate('xpack.endpoint.policy.details.windows', { defaultMessage: 'Windows' }), - ]} + supportedOss={useMemo( + () => [ + i18n.translate('xpack.endpoint.policy.details.windows', { defaultMessage: 'Windows' }), + ], + [] + )} id="windowsEventingForm" - rightCorner={collectionsEnabled()} - children={renderCheckboxes()} + rightCorner={collectionsEnabled} + children={renderCheckboxes} /> ); }); From 39fbc5e103a50a2d9ef929b05696d478c1090269 Mon Sep 17 00:00:00 2001 From: The SpaceCake Project <randomuserid@users.noreply.github.com> Date: Fri, 10 Apr 2020 17:50:23 -0400 Subject: [PATCH 16/21] bc6 rule import april 9 (#63152) * bc6 rule import april 9 Increased the lookback of the ML rules * re-import with LF chars Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../prepackaged_rules/linux_anomalous_network_activity.json | 2 +- .../linux_anomalous_network_port_activity.json | 2 +- .../prepackaged_rules/linux_anomalous_network_service.json | 2 +- .../prepackaged_rules/linux_anomalous_network_url_activity.json | 2 +- .../prepackaged_rules/linux_anomalous_process_all_hosts.json | 2 +- .../rules/prepackaged_rules/linux_anomalous_user_name.json | 2 +- .../rules/prepackaged_rules/packetbeat_dns_tunneling.json | 2 +- .../rules/prepackaged_rules/packetbeat_rare_dns_question.json | 2 +- .../rules/prepackaged_rules/packetbeat_rare_server_domain.json | 2 +- .../rules/prepackaged_rules/packetbeat_rare_urls.json | 2 +- .../rules/prepackaged_rules/packetbeat_rare_user_agent.json | 2 +- .../rules/prepackaged_rules/rare_process_by_host_linux.json | 2 +- .../rules/prepackaged_rules/rare_process_by_host_windows.json | 2 +- .../rules/prepackaged_rules/suspicious_login_activity.json | 2 +- .../prepackaged_rules/windows_anomalous_network_activity.json | 2 +- .../prepackaged_rules/windows_anomalous_path_activity.json | 2 +- .../prepackaged_rules/windows_anomalous_process_all_hosts.json | 2 +- .../prepackaged_rules/windows_anomalous_process_creation.json | 2 +- .../rules/prepackaged_rules/windows_anomalous_script.json | 2 +- .../rules/prepackaged_rules/windows_anomalous_service.json | 2 +- .../rules/prepackaged_rules/windows_anomalous_user_name.json | 2 +- .../rules/prepackaged_rules/windows_rare_user_runas_event.json | 2 +- .../windows_rare_user_type10_remote_login.json | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_activity.json index 1123c1161c4ce..fe248a6c1e23e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_activity.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that rarely uses the network could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "linux_anomalous_network_activity_ecs", "name": "Unusual Linux Network Activity", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_port_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_port_activity.json index 19dd643945b17..d435d4c10f05c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_port_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_port_activity.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that rarely uses the network could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "linux_anomalous_network_port_activity_ecs", "name": "Unusual Linux Network Port Activity", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_service.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_service.json index e2e5803618d06..0b82ce99d0b7f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_service.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_service.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that rarely uses the network could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "linux_anomalous_network_service", "name": "Unusual Linux Network Service", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_url_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_url_activity.json index 40dd2e76c7214..26af34e18a4c8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_url_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_network_url_activity.json @@ -4,7 +4,7 @@ "false_positives": [ "A new and unusual program or artifact download in the course of software upgrades, debugging, or troubleshooting could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "linux_anomalous_network_url_activity_ecs", "name": "Unusual Linux Web Activity", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_process_all_hosts.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_process_all_hosts.json index 6bac2f25fd7de..d15c4fc794378 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_process_all_hosts.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_process_all_hosts.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that runs rarely as part of a monthly or quarterly workflow could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "linux_anomalous_process_all_hosts_ecs", "name": "Anomalous Process For a Linux Population", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_user_name.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_user_name.json index 8b7e6c89482f7..2f33948b0a93e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_user_name.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_anomalous_user_name.json @@ -4,7 +4,7 @@ "false_positives": [ "Uncommon user activity can be due to an engineer logging onto a server instance in order to perform manual troubleshooting or reconfiguration." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "linux_anomalous_user_name_ecs", "name": "Unusual Linux Username", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_dns_tunneling.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_dns_tunneling.json index c70725dcb645a..765515ffda27c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_dns_tunneling.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_dns_tunneling.json @@ -4,7 +4,7 @@ "false_positives": [ "DNS domains that use large numbers of child domains, such as software or content distribution networks, can trigger this signal and such parent domains can be excluded." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "packetbeat_dns_tunneling", "name": "DNS Tunneling", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_dns_question.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_dns_question.json index 3ed40ddf27864..79c30c5b38378 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_dns_question.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_dns_question.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that runs rarely as part of a monthly or quarterly workflow could trigger this signal. Network activity that occurs rarely, in small quantities, can trigger this signal. Possible examples are browsing technical support or vendor networks sparsely. A user who visits a new or unique web destination may trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "packetbeat_rare_dns_question", "name": "Unusual DNS Activity", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_server_domain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_server_domain.json index c49bc95be75d2..7b14ad62f6c93 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_server_domain.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_server_domain.json @@ -4,7 +4,7 @@ "false_positives": [ "Web activity that occurs rarely in small quantities can trigger this signal. Possible examples are browsing technical support or vendor URLs that are used very sparsely. A user who visits a new and unique web destination may trigger this signal when the activity is sparse. Web applications that generate URLs unique to a transaction may trigger this when they are used sparsely. Web domains can be excluded in cases such as these." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "packetbeat_rare_server_domain", "name": "Unusual Network Destination Domain Name", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_urls.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_urls.json index 02a4a5f729a16..76767545e794a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_urls.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_urls.json @@ -4,7 +4,7 @@ "false_positives": [ "Web activity that occurs rarely in small quantities can trigger this signal. Possible examples are browsing technical support or vendor URLs that are used very sparsely. A user who visits a new and unique web destination may trigger this signal when the activity is sparse. Web applications that generate URLs unique to a transaction may trigger this when they are used sparsely. Web domains can be excluded in cases such as these." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "packetbeat_rare_urls", "name": "Unusual Web Request", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_user_agent.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_user_agent.json index 76ed6b263a704..1dc49203f31c1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_user_agent.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/packetbeat_rare_user_agent.json @@ -4,7 +4,7 @@ "false_positives": [ "Web activity that is uncommon, like security scans, may trigger this signal and may need to be excluded. A new or rarely used program that calls web services may trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "packetbeat_rare_user_agent", "name": "Unusual Web User Agent", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_linux.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_linux.json index 048f93e170656..f071677ae8d33 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_linux.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_linux.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that runs rarely as part of a monthly or quarterly workflow could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "rare_process_by_host_linux_ecs", "name": "Unusual Process For a Linux Host", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_windows.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_windows.json index 7bc46cdc04dd2..5e0050c6c25ec 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_windows.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/rare_process_by_host_windows.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that runs rarely as part of a monthly or quarterly workflow could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "rare_process_by_host_windows_ecs", "name": "Unusual Process For a Windows Host", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suspicious_login_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suspicious_login_activity.json index 915bc1bcfc051..4b94fdc6da147 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suspicious_login_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suspicious_login_activity.json @@ -4,7 +4,7 @@ "false_positives": [ "Security audits may trigger this signal. Conditions that generate bursts of failed logins, such as misconfigured applications or account lockouts could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "suspicious_login_activity_ecs", "name": "Unusual Login Activity", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_network_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_network_activity.json index 72671760c9c8d..ca18fe95b1fc1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_network_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_network_activity.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that rarely uses the network could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "windows_anomalous_network_activity_ecs", "name": "Unusual Windows Network Activity", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_path_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_path_activity.json index 082fce438ca9e..8a88607b9d5c9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_path_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_path_activity.json @@ -4,7 +4,7 @@ "false_positives": [ "A new and unusual program or artifact download in the course of software upgrades, debugging, or troubleshooting could trigger this signal. Users downloading and running programs from unusual locations, such as temporary directories, browser caches, or profile paths could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "windows_anomalous_path_activity_ecs", "name": "Unusual Windows Path Activity", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_all_hosts.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_all_hosts.json index 93469b5a06223..1229c4a52b97d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_all_hosts.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_all_hosts.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that runs rarely as part of a monthly or quarterly workflow could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "windows_anomalous_process_all_hosts_ecs", "name": "Anomalous Process For a Windows Population", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_creation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_creation.json index 1b80e443baae6..98a078ccea4a4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_creation.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_process_creation.json @@ -4,7 +4,7 @@ "false_positives": [ "Users running scripts in the course of technical support operations of software upgrades could trigger this signal. A newly installed program or one that runs rarely as part of a monthly or quarterly workflow could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "windows_anomalous_process_creation", "name": "Anomalous Windows Process Creation", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_script.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_script.json index 4de5443bcaf3f..564ca1782526f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_script.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_script.json @@ -4,7 +4,7 @@ "false_positives": [ "Certain kinds of security testing may trigger this signal. PowerShell scripts that use high levels of obfuscation or have unusual script block payloads may trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "windows_anomalous_script", "name": "Suspicious Powershell Script", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_service.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_service.json index 7e0641fee68c2..afef569f4ebb4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_service.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_service.json @@ -4,7 +4,7 @@ "false_positives": [ "A newly installed program or one that runs rarely as part of a monthly or quarterly workflow could trigger this signal." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "windows_anomalous_service", "name": "Unusual Windows Service", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_user_name.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_user_name.json index 217404b6eb474..703dc1a1dc633 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_user_name.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_anomalous_user_name.json @@ -4,7 +4,7 @@ "false_positives": [ "Uncommon user activity can be due to an administrator or help desk technician logging onto a workstation or server in order to perform manual troubleshooting or reconfiguration." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "windows_anomalous_user_name_ecs", "name": "Unusual Windows Username", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_runas_event.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_runas_event.json index 3dca119b5a28e..febaa57443f76 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_runas_event.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_runas_event.json @@ -4,7 +4,7 @@ "false_positives": [ "Uncommon user privilege elevation activity can be due to an administrator, help desk technician, or a user performing manual troubleshooting or reconfiguration." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "windows_rare_user_runas_event", "name": "Unusual Windows User Privilege Elevation Activity", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_type10_remote_login.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_type10_remote_login.json index 09ff2a0cedf41..946cdb95b8e70 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_type10_remote_login.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_rare_user_type10_remote_login.json @@ -4,7 +4,7 @@ "false_positives": [ "Uncommon username activity can be due to an engineer logging onto a server instance in order to perform manual troubleshooting or reconfiguration." ], - "from": "now-16m", + "from": "now-45m", "interval": "15m", "machine_learning_job_id": "windows_rare_user_type10_remote_login", "name": "Unusual Windows Remote User", From fbd15ec8675029a398381ef063fa8a817e21146c Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko <jo.naumenko@gmail.com> Date: Fri, 10 Apr 2020 14:55:03 -0700 Subject: [PATCH 17/21] Added UI for pre-configured connectors. (#63074) * Added UI for pre-configured connectors. * fixed due to comments * Fixed jest tests * Fixed due to comments and added some functional tests * test fix * Fixed failed checks * Fixed functional tests failing --- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../components/add_message_variables.tsx | 3 +- .../action_connector_form/action_form.tsx | 15 +- .../connector_edit_flyout.test.tsx | 56 +++++++ .../connector_edit_flyout.tsx | 137 +++++++++++++----- .../actions_connectors_list.test.tsx | 16 +- .../components/actions_connectors_list.tsx | 78 +++++++--- .../apps/triggers_actions_ui/alerts.ts | 22 ++- .../apps/triggers_actions_ui/connectors.ts | 25 ++++ x-pack/test/functional_with_es_ssl/config.ts | 10 ++ 11 files changed, 286 insertions(+), 78 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 09903c34e2e5e..fe0c58e83e544 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -16102,7 +16102,6 @@ "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel": "ユーザー名", "xpack.triggersActionsUI.sections.editConnectorForm.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", "xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel": "キャンセル", - "xpack.triggersActionsUI.sections.editConnectorForm.flyoutTitle": "コネクターを編集", "xpack.triggersActionsUI.sections.editConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.editConnectorForm.updateErrorNotificationText": "コネクターを更新できません。", "xpack.triggersActionsUI.sections.editConnectorForm.updateSuccessNotificationText": "「{connectorName}」を更新しました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index cc1b7d7980a0b..fd2a92c2c402f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -16107,7 +16107,6 @@ "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel": "用户名", "xpack.triggersActionsUI.sections.editConnectorForm.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel": "取消", - "xpack.triggersActionsUI.sections.editConnectorForm.flyoutTitle": "编辑连接器", "xpack.triggersActionsUI.sections.editConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.editConnectorForm.updateErrorNotificationText": "无法更新连接器。", "xpack.triggersActionsUI.sections.editConnectorForm.updateSuccessNotificationText": "已更新“{connectorName}”", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx index ab9b5c2586c17..957c79a5c5123 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx @@ -22,9 +22,10 @@ export const AddMessageVariables: React.FunctionComponent<Props> = ({ const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState<boolean>(false); const getMessageVariables = () => - messageVariables?.map((variable: string) => ( + messageVariables?.map((variable: string, i: number) => ( <EuiContextMenuItem key={variable} + data-test-subj={`variableMenuButton-${i}`} icon="empty" onClick={() => { onSelectEventHandler(variable); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 6b011ac84bc6f..5890d9fe07f0e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -141,15 +141,22 @@ export const ActionForm = ({ }); } } + const preconfiguredMessage = i18n.translate( + 'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage', + { + defaultMessage: '(pre-configured)', + } + ); const getSelectedOptions = (actionItemId: string) => { const val = connectors.find(connector => connector.id === actionItemId); if (!val) { return []; } + const optionTitle = `${val.name} ${val.isPreconfigured ? preconfiguredMessage : ''}`; return [ { - label: val.name, - value: val.name, + label: optionTitle, + value: optionTitle, id: actionItemId, }, ]; @@ -264,7 +271,9 @@ export const ActionForm = ({ defaultMessage="{actionConnectorName}" id="xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeEditTitle" values={{ - actionConnectorName: actionConnector.name, + actionConnectorName: `${actionConnector.name} ${ + actionConnector.isPreconfigured ? preconfiguredMessage : '' + }`, }} /> </EuiFlexItem> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx index 2c063ea6b4fa6..6659888797679 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -95,4 +95,60 @@ describe('connector_edit_flyout', () => { expect(connectorNameField.exists()).toBeTruthy(); expect(connectorNameField.first().prop('value')).toBe('action-connector'); }); + + test('if pre-configured connector rendered correct in the edit form', () => { + const connector = { + secrets: {}, + id: 'test', + actionTypeId: 'test-action-type-id', + actionType: 'test-action-type-name', + name: 'pre-configured-connector', + isPreconfigured: true, + referencedByCount: 0, + config: {}, + }; + + const actionType = { + id: 'test-action-type-id', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + actionTypeRegistry.get.mockReturnValue(actionType); + actionTypeRegistry.has.mockReturnValue(true); + + const wrapper = mountWithIntl( + <AppContextProvider appDeps={deps}> + <ActionsConnectorsContextProvider + value={{ + http: deps.http, + toastNotifications: deps.toastNotifications, + capabilities: deps.capabilities, + actionTypeRegistry: deps.actionTypeRegistry, + reloadConnectors: () => { + return new Promise<void>(() => {}); + }, + }} + > + <ConnectorEditFlyout + initialConnector={connector} + editFlyoutVisible={true} + setEditFlyoutVisibility={state => {}} + /> + </ActionsConnectorsContextProvider> + </AppContextProvider> + ); + + const preconfiguredBadge = wrapper.find('[data-test-subj="preconfiguredBadge"]'); + expect(preconfiguredBadge.exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="saveEditedActionButton"]').exists()).toBeFalsy(); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index ed8811d26331b..a81d6c285f460 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useReducer, useState } from 'react'; +import React, { useCallback, useReducer, useState, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -17,6 +17,8 @@ import { EuiButtonEmpty, EuiButton, EuiBetaBadge, + EuiText, + EuiLink, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ActionConnectorForm, validateBaseProperties } from './action_connector_form'; @@ -91,8 +93,77 @@ export const ConnectorEditFlyout = ({ return undefined; }); + const flyoutTitle = connector.isPreconfigured ? ( + <Fragment> + <EuiTitle size="s"> + <h3 id="flyoutTitle"> + <FormattedMessage + defaultMessage="{connectorName}" + id="xpack.triggersActionsUI.sections.preconfiguredConnectorForm.flyoutTitle" + values={{ connectorName: initialConnector.name }} + /> +   + <EuiBetaBadge + label="Pre-configured" + data-test-subj="preconfiguredBadge" + tooltipContent={i18n.translate( + 'xpack.triggersActionsUI.sections.preconfiguredConnectorForm.tooltipContent', + { + defaultMessage: 'This connector is preconfigured and cannot be edited', + } + )} + /> +   + <EuiBetaBadge + label="Beta" + tooltipContent={i18n.translate( + 'xpack.triggersActionsUI.sections.preconfiguredConnectorForm.betaBadgeTooltipContent', + { + defaultMessage: + '{pluginName} is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.', + values: { + pluginName: PLUGIN.getI18nName(i18n), + }, + } + )} + /> + </h3> + </EuiTitle> + <EuiText size="s"> + <FormattedMessage + defaultMessage="{actionDescription}" + id="xpack.triggersActionsUI.sections.editConnectorForm.actionTypeDescription" + values={{ actionDescription: actionTypeModel.selectMessage }} + /> + </EuiText> + </Fragment> + ) : ( + <EuiTitle size="s"> + <h3 id="flyoutTitle"> + <FormattedMessage + defaultMessage="Edit connector" + id="xpack.triggersActionsUI.sections.editConnectorForm.flyoutPreconfiguredTitle" + /> +   + <EuiBetaBadge + label="Beta" + tooltipContent={i18n.translate( + 'xpack.triggersActionsUI.sections.editConnectorForm.betaBadgeTooltipContent', + { + defaultMessage: + '{pluginName} is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.', + values: { + pluginName: PLUGIN.getI18nName(i18n), + }, + } + )} + /> + </h3> + </EuiTitle> + ); + return ( - <EuiFlyout onClose={closeFlyout} aria-labelledby="flyoutActionAddTitle" size="m"> + <EuiFlyout onClose={closeFlyout} aria-labelledby="flyoutActionEditTitle" size="m"> <EuiFlyoutHeader hasBorder> <EuiFlexGroup gutterSize="s" alignItems="center"> {actionTypeModel ? ( @@ -100,41 +171,37 @@ export const ConnectorEditFlyout = ({ <EuiIcon type={actionTypeModel.iconClass} size="m" /> </EuiFlexItem> ) : null} - <EuiFlexItem> - <EuiTitle size="s"> - <h3 id="flyoutTitle"> - <FormattedMessage - defaultMessage="Edit connector" - id="xpack.triggersActionsUI.sections.editConnectorForm.flyoutTitle" - /> -   - <EuiBetaBadge - label="Beta" - tooltipContent={i18n.translate( - 'xpack.triggersActionsUI.sections.editConnectorForm.betaBadgeTooltipContent', - { - defaultMessage: - '{pluginName} is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.', - values: { - pluginName: PLUGIN.getI18nName(i18n), - }, - } - )} - /> - </h3> - </EuiTitle> - </EuiFlexItem> + <EuiFlexItem>{flyoutTitle}</EuiFlexItem> </EuiFlexGroup> </EuiFlyoutHeader> <EuiFlyoutBody> - <ActionConnectorForm - connector={connector} - errors={errors} - actionTypeName={connector.actionType} - dispatch={dispatch} - actionTypeRegistry={actionTypeRegistry} - http={http} - /> + {!connector.isPreconfigured ? ( + <ActionConnectorForm + connector={connector} + errors={errors} + actionTypeName={connector.actionType} + dispatch={dispatch} + actionTypeRegistry={actionTypeRegistry} + http={http} + /> + ) : ( + <Fragment> + <EuiText> + {i18n.translate( + 'xpack.triggersActionsUI.sections.editConnectorForm.descriptionText', + { + defaultMessage: 'This connector is readonly.', + } + )} + </EuiText> + <EuiLink href="https://www.elastic.co/guide" target="_blank"> + <FormattedMessage + id="xpack.triggersActionsUI.sections.editConnectorForm.preconfiguredHelpLabel" + defaultMessage="Learn more about pre-configured connectors." + /> + </EuiLink> + </Fragment> + )} </EuiFlyoutBody> <EuiFlyoutFooter> <EuiFlexGroup justifyContent="spaceBetween"> @@ -148,7 +215,7 @@ export const ConnectorEditFlyout = ({ )} </EuiButtonEmpty> </EuiFlexItem> - {canSave && actionTypeModel ? ( + {canSave && actionTypeModel && !connector.isPreconfigured ? ( <EuiFlexItem grow={false}> <EuiButton fill diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 4fa1e7e4c6e4d..01d21e954bbf3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -111,6 +111,7 @@ describe('actions_connectors_list component with items', () => { id: '1', actionTypeId: 'test', description: 'My test', + isPreconfigured: false, referencedByCount: 1, config: {}, }, @@ -119,6 +120,15 @@ describe('actions_connectors_list component with items', () => { actionTypeId: 'test2', description: 'My test 2', referencedByCount: 1, + isPreconfigured: false, + config: {}, + }, + { + id: '3', + actionTypeId: 'test2', + description: 'My preconfigured test 2', + referencedByCount: 1, + isPreconfigured: true, config: {}, }, ]); @@ -185,7 +195,11 @@ describe('actions_connectors_list component with items', () => { it('renders table of connectors', () => { expect(wrapper.find('EuiInMemoryTable')).toHaveLength(1); - expect(wrapper.find('EuiTableRow')).toHaveLength(2); + expect(wrapper.find('EuiTableRow')).toHaveLength(3); + }); + + it('renders table with preconfigured connectors', () => { + expect(wrapper.find('[data-test-subj="preConfiguredTitleMessage"]')).toHaveLength(2); }); test('if select item for edit should render ConnectorEditFlyout', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 47e058f473946..043a644489d82 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -15,6 +15,9 @@ import { EuiIconTip, EuiFlexGroup, EuiFlexItem, + EuiBetaBadge, + EuiToolTip, + EuiButtonIcon, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -200,31 +203,58 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { }, }, { - field: '', + field: 'isPreconfigured', name: '', - actions: [ - { - enabled: () => canDelete, - 'data-test-subj': 'deleteConnector', - name: i18n.translate( - 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionName', - { defaultMessage: 'Delete' } - ), - description: canDelete - ? i18n.translate( - 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDescription', - { defaultMessage: 'Delete this connector' } - ) - : i18n.translate( - 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDisabledDescription', - { defaultMessage: 'Unable to delete connectors' } - ), - type: 'icon', - icon: 'trash', - color: 'danger', - onClick: (item: ActionConnectorTableItem) => setConnectorsToDelete([item.id]), - }, - ], + render: (value: number, item: ActionConnectorTableItem) => { + if (item.isPreconfigured) { + return ( + <EuiFlexGroup justifyContent="flexEnd" alignItems="flexEnd"> + <EuiFlexItem grow={false}> + <EuiBetaBadge + data-test-subj="preConfiguredTitleMessage" + label={i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.preconfiguredTitleMessage', + { + defaultMessage: 'Pre-configured', + } + )} + tooltipContent="This connector can't be deleted." + /> + </EuiFlexItem> + </EuiFlexGroup> + ); + } + return ( + <EuiFlexGroup justifyContent="flexEnd" alignItems="flexEnd"> + <EuiFlexItem grow={false}> + <EuiToolTip + content={ + canDelete + ? i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDescription', + { defaultMessage: 'Delete this connector' } + ) + : i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDisabledDescription', + { defaultMessage: 'Unable to delete connectors' } + ) + } + > + <EuiButtonIcon + isDisabled={!canDelete} + data-test-subj="deleteConnector" + aria-label={i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionName', + { defaultMessage: 'Delete' } + )} + onClick={() => setConnectorsToDelete([item.id])} + iconType={'trash'} + /> + </EuiToolTip> + </EuiFlexItem> + </EuiFlexGroup> + ); + }, }, ]; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 029af1ea06e4f..c94e7116c5cea 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -65,23 +65,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // need this two out of popup clicks to close them await nameInput.click(); + // test for normal connector + await testSubjects.click('.webhook-ActionTypeSelectOption'); + const webhookBodyInput = await find.byCssSelector('.ace_text-input'); + await webhookBodyInput.focus(); + await webhookBodyInput.type('{\\"test\\":1}'); + + await testSubjects.click('addAlertActionButton'); + // pre-configured connector is loaded an displayed correctly await testSubjects.click('.slack-ActionTypeSelectOption'); - await testSubjects.click('createActionConnectorButton'); - const connectorNameInput = await testSubjects.find('nameInput'); - await connectorNameInput.click(); - await connectorNameInput.clearValue(); - const connectorName = generateUniqueKey(); - await connectorNameInput.type(connectorName); - const slackWebhookUrlInput = await testSubjects.find('slackWebhookUrlInput'); - await slackWebhookUrlInput.click(); - await slackWebhookUrlInput.clearValue(); - await slackWebhookUrlInput.type('https://test'); - await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + expect(await (await find.byCssSelector('#my-slack1')).isDisplayed()).to.be(true); const loggingMessageInput = await testSubjects.find('slackMessageTextArea'); await loggingMessageInput.click(); await loggingMessageInput.clearValue(); await loggingMessageInput.type('test message'); - await testSubjects.click('slackAddVariableButton'); + await testSubjects.click('messageAddVariableButton'); const variableMenuButton = await testSubjects.find('variableMenuButton-0'); await variableMenuButton.click(); await testSubjects.click('saveAlertButton'); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index b5bcd33c3b9ab..0e6f991be24d0 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -184,5 +184,30 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const searchResultsAfterDelete = await pageObjects.triggersActionsUI.getConnectorsList(); expect(searchResultsAfterDelete.length).to.eql(0); }); + + it('should not be able to delete a pre-configured connector', async () => { + const preconfiguredConnectorName = 'xyz'; + await pageObjects.triggersActionsUI.searchConnectors(preconfiguredConnectorName); + + const searchResults = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResults.length).to.eql(1); + + expect(await testSubjects.exists('deleteConnector')).to.be(false); + expect(await testSubjects.exists('preConfiguredTitleMessage')).to.be(true); + }); + + it('should not be able to edit a pre-configured connector', async () => { + const preconfiguredConnectorName = 'xyz'; + + await pageObjects.triggersActionsUI.searchConnectors(preconfiguredConnectorName); + + const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsBeforeEdit.length).to.eql(1); + + await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); + + expect(await testSubjects.exists('preconfiguredBadge')).to.be(true); + expect(await testSubjects.exists('saveEditedActionButton')).to.be(false); + }); }); }; diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts index 538817bd9d14c..a620b1d953376 100644 --- a/x-pack/test/functional_with_es_ssl/config.ts +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -52,6 +52,16 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { `--plugin-path=${join(__dirname, 'fixtures', 'plugins', 'alerts')}`, '--xpack.actions.enabled=true', '--xpack.alerting.enabled=true', + `--xpack.actions.preconfigured=${JSON.stringify([ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack#xyz', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }, + ])}`, ], }, }; From cc85573c8adec5ad5f3828688136c190b8d79e8b Mon Sep 17 00:00:00 2001 From: Brandon Kobel <brandon.kobel@elastic.co> Date: Fri, 10 Apr 2020 16:01:30 -0700 Subject: [PATCH 18/21] TaskManager tasks scheduled without attempting to run (#62078) * TaskManager tasks scheduled without attempting to run * Removing unused import Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- x-pack/plugins/task_manager/server/task_manager.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/task_manager/server/task_manager.ts b/x-pack/plugins/task_manager/server/task_manager.ts index c3f24a4aae88a..a7c67d190e72e 100644 --- a/x-pack/plugins/task_manager/server/task_manager.ts +++ b/x-pack/plugins/task_manager/server/task_manager.ts @@ -9,7 +9,7 @@ import { filter } from 'rxjs/operators'; import { performance } from 'perf_hooks'; import { pipe } from 'fp-ts/lib/pipeable'; -import { Option, none, some, map as mapOptional } from 'fp-ts/lib/Option'; +import { Option, some, map as mapOptional } from 'fp-ts/lib/Option'; import { SavedObjectsSerializer, IScopedClusterClient, @@ -156,8 +156,8 @@ export class TaskManager { this.events$.next(event); }; - private attemptToRun(task: Option<string> = none) { - this.claimRequests$.next(task); + private attemptToRun(task: string) { + this.claimRequests$.next(some(task)); } private createTaskRunnerForTask = (instance: ConcreteTaskInstance) => { @@ -280,9 +280,7 @@ export class TaskManager { ...options, taskInstance: ensureDeprecatedFieldsAreCorrected(taskInstance, this.logger), }); - const result = await this.store.schedule(modifiedTask); - this.attemptToRun(); - return result; + return await this.store.schedule(modifiedTask); } /** @@ -298,7 +296,7 @@ export class TaskManager { .then(resolve) .catch(reject); - this.attemptToRun(some(taskId)); + this.attemptToRun(taskId); }); } From 53ee20b306556b9cecc7f94533481c43eed25b35 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko <jo.naumenko@gmail.com> Date: Fri, 10 Apr 2020 19:18:19 -0700 Subject: [PATCH 19/21] Changed alerting wrong param name for help xpack.encrypted_saved_objects.encryptionKey to xpack.encryptedSavedObjects.encryptionKey (#63307) --- docs/settings/alert-action-settings.asciidoc | 4 ++-- docs/user/alerting/index.asciidoc | 2 +- rfcs/text/0002_encrypted_attributes.md | 2 +- .../public/application/components/health_check.test.tsx | 2 +- .../public/application/components/health_check.tsx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index d7f1ec637d1df..d4dbe9407b7a9 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -9,7 +9,7 @@ Alerts and actions are enabled by default in {kib}, but require you configure th . <<using-kibana-with-security,Set up {kib} to work with {stack} {security-features}>>. . <<configuring-tls-kib-es,Set up TLS encryption between {kib} and {es}>>. -. <<general-alert-action-settings,Specify a value for `xpack.encrypted_saved_objects.encryptionKey`>>. +. <<general-alert-action-settings,Specify a value for `xpack.encryptedSavedObjects.encryptionKey`>>. You can configure the following settings in the `kibana.yml` file. @@ -18,7 +18,7 @@ You can configure the following settings in the `kibana.yml` file. [[general-alert-action-settings]] ==== General settings -`xpack.encrypted_saved_objects.encryptionKey`:: +`xpack.encryptedSavedObjects.encryptionKey`:: A string of 32 or more characters used to encrypt sensitive properties on alerts and actions before they're stored in {es}. Third party credentials — such as the username and password used to connect to an SMTP service — are an example of encrypted properties. + diff --git a/docs/user/alerting/index.asciidoc b/docs/user/alerting/index.asciidoc index c7cf1186a44be..f556cf71bf06c 100644 --- a/docs/user/alerting/index.asciidoc +++ b/docs/user/alerting/index.asciidoc @@ -157,7 +157,7 @@ Pre-packaged *alert types* simplify setup, hide the details complex domain-speci If you are using an *on-premises* Elastic Stack deployment with <<using-kibana-with-security, *security*>>: * TLS must be configured for communication <<configuring-tls-kib-es, between {es} and {kib}>>. {kib} alerting uses <<api-keys, API keys>> to secure background alert checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. -* In the kibana.yml configuration file, add the <<alert-action-settings-kb,`xpack.encrypted_saved_objects.encryptionKey` setting>> +* In the kibana.yml configuration file, add the <<alert-action-settings-kb,`xpack.encryptedSavedObjects.encryptionKey` setting>> [float] [[alerting-security]] diff --git a/rfcs/text/0002_encrypted_attributes.md b/rfcs/text/0002_encrypted_attributes.md index aa7307edb66bd..c6553c177d995 100644 --- a/rfcs/text/0002_encrypted_attributes.md +++ b/rfcs/text/0002_encrypted_attributes.md @@ -166,7 +166,7 @@ take a look at the source code of this library to know how encryption is perform parameters are used, but in short it's AES Encryption with AES-256-GCM that uses random initialization vector and salt. As with encryption key for Kibana's session cookie, master encryption key used by `encrypted_saved_objects` plugin can be -defined as a configuration value (`xpack.encrypted_saved_objects.encryptionKey`) via `kibana.yml`, but it's **highly +defined as a configuration value (`xpack.encryptedSavedObjects.encryptionKey`) via `kibana.yml`, but it's **highly recommended** to define this key in the [Kibana Keystore](https://www.elastic.co/guide/en/kibana/current/secure-settings.html) instead. The master key should be cryptographically safe and be equal or greater than 32 bytes. diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx index 9c51139993b3f..3fbcd13e98f5d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx @@ -92,7 +92,7 @@ describe('health check', () => { const description = queryByRole(/banner/i); expect(description!.textContent).toMatchInlineSnapshot( - `"To create an alert, set a value for xpack.encrypted_saved_objects.encryptionKey in your kibana.yml file. Learn how."` + `"To create an alert, set a value for xpack.encryptedSavedObjects.encryptionKey in your kibana.yml file. Learn how."` ); const action = queryByText(/Learn/i); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx index c967cf5de0771..afd5e08f52f25 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -132,7 +132,7 @@ const EncryptionError = ({ defaultMessage: 'To create an alert, set a value for ', } )} - <EuiCode>{'xpack.encrypted_saved_objects.encryptionKey'}</EuiCode> + <EuiCode>{'xpack.encryptedSavedObjects.encryptionKey'}</EuiCode> {i18n.translate( 'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey', { From bd159c7d59979d66c09c94402daf2c30373ee9f9 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet <pierre.gayvallet@elastic.co> Date: Sat, 11 Apr 2020 09:27:45 +0200 Subject: [PATCH 20/21] fix ScopedHistory.createHref to prepend location with scoped history basePath (#62407) * fix createHref to prepend with scoped history basePath + add option to exclude it. * fix prependBasePath behavior * fix test plugins urls * add pathname to endpoint url builder methods * Revert "add pathname to endpoint url builder methods" This reverts commit 7604932b * adapt createHref instead of prependBasePath * use object options for createHref * update generated doc --- ...in-core-public.scopedhistory.createhref.md | 6 +++-- ...kibana-plugin-core-public.scopedhistory.md | 2 +- .../public/application/scoped_history.test.ts | 25 +++++++++++++++++-- src/core/public/application/scoped_history.ts | 20 ++++++++++++--- src/core/public/public.api.md | 4 ++- .../core_plugin_a/public/application.tsx | 2 +- .../core_plugin_b/public/application.tsx | 2 +- 7 files changed, 50 insertions(+), 11 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-core-public.scopedhistory.createhref.md b/docs/development/core/public/kibana-plugin-core-public.scopedhistory.createhref.md index 7058656d09947..6bbab43ff6ffc 100644 --- a/docs/development/core/public/kibana-plugin-core-public.scopedhistory.createhref.md +++ b/docs/development/core/public/kibana-plugin-core-public.scopedhistory.createhref.md @@ -4,10 +4,12 @@ ## ScopedHistory.createHref property -Creates an href (string) to the location. +Creates an href (string) to the location. If `prependBasePath` is true (default), it will prepend the location's path with the scoped history basePath. <b>Signature:</b> ```typescript -createHref: (location: LocationDescriptorObject<HistoryLocationState>) => string; +createHref: (location: LocationDescriptorObject<HistoryLocationState>, { prependBasePath }?: { + prependBasePath?: boolean | undefined; + }) => string; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.scopedhistory.md b/docs/development/core/public/kibana-plugin-core-public.scopedhistory.md index 5ea47d2090d71..fa29b32c0bafc 100644 --- a/docs/development/core/public/kibana-plugin-core-public.scopedhistory.md +++ b/docs/development/core/public/kibana-plugin-core-public.scopedhistory.md @@ -28,7 +28,7 @@ export declare class ScopedHistory<HistoryLocationState = unknown> implements Hi | --- | --- | --- | --- | | [action](./kibana-plugin-core-public.scopedhistory.action.md) | | <code>Action</code> | The last action dispatched on the history stack. | | [block](./kibana-plugin-core-public.scopedhistory.block.md) | | <code>(prompt?: string | boolean | History.TransitionPromptHook<HistoryLocationState> | undefined) => UnregisterCallback</code> | Not supported. Use [AppMountParameters.onAppLeave](./kibana-plugin-core-public.appmountparameters.onappleave.md)<!-- -->. | -| [createHref](./kibana-plugin-core-public.scopedhistory.createhref.md) | | <code>(location: LocationDescriptorObject<HistoryLocationState>) => string</code> | Creates an href (string) to the location. | +| [createHref](./kibana-plugin-core-public.scopedhistory.createhref.md) | | <code>(location: LocationDescriptorObject<HistoryLocationState>, { prependBasePath }?: {</code><br/><code> prependBasePath?: boolean | undefined;</code><br/><code> }) => string</code> | Creates an href (string) to the location. If <code>prependBasePath</code> is true (default), it will prepend the location's path with the scoped history basePath. | | [createSubHistory](./kibana-plugin-core-public.scopedhistory.createsubhistory.md) | | <code><SubHistoryLocationState = unknown>(basePath: string) => ScopedHistory<SubHistoryLocationState></code> | Creates a <code>ScopedHistory</code> for a subpath of this <code>ScopedHistory</code>. Useful for applications that may have sub-apps that do not need access to the containing application's history. | | [go](./kibana-plugin-core-public.scopedhistory.go.md) | | <code>(n: number) => void</code> | Send the user forward or backwards in the history stack. | | [goBack](./kibana-plugin-core-public.scopedhistory.goback.md) | | <code>() => void</code> | Send the user one location back in the history stack. Equivalent to calling [ScopedHistory.go(-1)](./kibana-plugin-core-public.scopedhistory.go.md)<!-- -->. If no more entries are available backwards, this is a no-op. | diff --git a/src/core/public/application/scoped_history.test.ts b/src/core/public/application/scoped_history.test.ts index c01eb50830516..a56cffef1e2f2 100644 --- a/src/core/public/application/scoped_history.test.ts +++ b/src/core/public/application/scoped_history.test.ts @@ -268,11 +268,32 @@ describe('ScopedHistory', () => { const gh = createMemoryHistory(); gh.push('/app/wow'); const h = new ScopedHistory(gh, '/app/wow'); - expect(h.createHref({ pathname: '' })).toEqual(`/`); + expect(h.createHref({ pathname: '' })).toEqual(`/app/wow`); + expect(h.createHref({})).toEqual(`/app/wow`); expect(h.createHref({ pathname: '/new-page', search: '?alpha=true' })).toEqual( - `/new-page?alpha=true` + `/app/wow/new-page?alpha=true` ); }); + + it('behave correctly with slash-ending basePath', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow/'); + const h = new ScopedHistory(gh, '/app/wow/'); + expect(h.createHref({ pathname: '' })).toEqual(`/app/wow/`); + expect(h.createHref({ pathname: '/new-page', search: '?alpha=true' })).toEqual( + `/app/wow/new-page?alpha=true` + ); + }); + + it('skips the scoped history path when `prependBasePath` is false', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + expect(h.createHref({ pathname: '' }, { prependBasePath: false })).toEqual(`/`); + expect( + h.createHref({ pathname: '/new-page', search: '?alpha=true' }, { prependBasePath: false }) + ).toEqual(`/new-page?alpha=true`); + }); }); describe('action', () => { diff --git a/src/core/public/application/scoped_history.ts b/src/core/public/application/scoped_history.ts index c5febc7604feb..9fa8f0b7f8148 100644 --- a/src/core/public/application/scoped_history.ts +++ b/src/core/public/application/scoped_history.ts @@ -219,11 +219,26 @@ export class ScopedHistory<HistoryLocationState = unknown> /** * Creates an href (string) to the location. + * If `prependBasePath` is true (default), it will prepend the location's path with the scoped history basePath. * * @param location + * @param prependBasePath */ - public createHref = (location: LocationDescriptorObject<HistoryLocationState>): Href => { + public createHref = ( + location: LocationDescriptorObject<HistoryLocationState>, + { prependBasePath = true }: { prependBasePath?: boolean } = {} + ): Href => { this.verifyActive(); + if (prependBasePath) { + location = this.prependBasePath(location); + if (location.pathname === undefined) { + // we always want to create an url relative to the basePath + // so if pathname is not present, we use the history's basePath as default + // we are doing that here because `prependBasePath` should not + // alter pathname for other method calls + location.pathname = this.basePath; + } + } return this.parentHistory.createHref(location); }; @@ -254,8 +269,7 @@ export class ScopedHistory<HistoryLocationState = unknown> * Prepends the base path to string. */ private prependBasePathToString(path: string): string { - path = path.startsWith('/') ? path.slice(1) : path; - return path.length ? `${this.basePath}/${path}` : this.basePath; + return path.length ? `${this.basePath}/${path}`.replace(/\/{2,}/g, '/') : this.basePath; } /** diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index a5aa37becabc2..6d95d1bc7069c 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1196,7 +1196,9 @@ export class ScopedHistory<HistoryLocationState = unknown> implements History<Hi constructor(parentHistory: History, basePath: string); get action(): Action; block: (prompt?: string | boolean | History.TransitionPromptHook<HistoryLocationState> | undefined) => UnregisterCallback; - createHref: (location: LocationDescriptorObject<HistoryLocationState>) => string; + createHref: (location: LocationDescriptorObject<HistoryLocationState>, { prependBasePath }?: { + prependBasePath?: boolean | undefined; + }) => string; createSubHistory: <SubHistoryLocationState = unknown>(basePath: string) => ScopedHistory<SubHistoryLocationState>; go: (n: number) => void; goBack: () => void; diff --git a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx index abea970749cbc..159bb54f50903 100644 --- a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx +++ b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx @@ -95,7 +95,7 @@ const Nav = withRouter(({ history, navigateToApp }: NavProps) => ( { id: 'home', name: 'Home', - onClick: () => history.push('/'), + onClick: () => history.push(''), 'data-test-subj': 'fooNavHome', }, { diff --git a/test/plugin_functional/plugins/core_plugin_b/public/application.tsx b/test/plugin_functional/plugins/core_plugin_b/public/application.tsx index 447307920c04c..01a63f9782563 100644 --- a/test/plugin_functional/plugins/core_plugin_b/public/application.tsx +++ b/test/plugin_functional/plugins/core_plugin_b/public/application.tsx @@ -102,7 +102,7 @@ const Nav = withRouter(({ history, navigateToApp }: NavProps) => ( { id: 'home', name: 'Home', - onClick: () => navigateToApp('bar', { path: '/' }), + onClick: () => navigateToApp('bar', { path: '' }), 'data-test-subj': 'barNavHome', }, { From 2be6b7fdcecbe2848cc1da23eb98af756f513d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Sat, 11 Apr 2020 10:06:39 +0100 Subject: [PATCH 21/21] fixing custom link popover size and hiding scroll (#63240) --- .../TransactionActionMenu/CustomLink/CustomLinkPopover.tsx | 1 + .../shared/TransactionActionMenu/TransactionActionMenu.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx index a20bc7e21cfc5..3aed1b7ac2953 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx @@ -19,6 +19,7 @@ import { ManageCustomLink } from './ManageCustomLink'; import { px } from '../../../../style/variables'; const ScrollableContainer = styled.div` + -ms-overflow-style: none; max-height: ${px(535)}; overflow: scroll; `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index e3fbcf8485d54..7ebfe26b83630 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -124,7 +124,7 @@ export const TransactionActionMenu: FunctionComponent<Props> = ({ <ActionMenuButton onClick={() => setIsActionPopoverOpen(true)} /> } > - <div style={{ maxHeight: px(600) }}> + <div style={{ maxHeight: px(600), width: px(335) }}> {isCustomLinksPopoverOpen ? ( <CustomLinkPopover customLinks={customLinks.slice(3, customLinks.length)}