From 8938a5778ab6a3b5b638e4da5fe926d5dc0fd827 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Wed, 25 Oct 2023 18:04:02 +0200 Subject: [PATCH 01/31] Handle array values in i18nrc (#169637) --- packages/kbn-eslint-plugin-i18n/README.mdx | 3 +- ...get_i18n_identifier_from_file_path.test.ts | 4 + .../get_i18n_identifier_from_file_path.ts | 4 +- .../helpers/get_intent_from_node.ts | 30 +++-- ..._translated_with_formatted_message.test.ts | 31 +++++ ...ld_be_translated_with_formatted_message.ts | 60 ++++++++- ...ngs_should_be_translated_with_i18n.test.ts | 117 +++++++++++------- .../strings_should_be_translated_with_i18n.ts | 64 +++++++++- 8 files changed, 255 insertions(+), 58 deletions(-) diff --git a/packages/kbn-eslint-plugin-i18n/README.mdx b/packages/kbn-eslint-plugin-i18n/README.mdx index f72f01c06a632..100f83d167b6e 100644 --- a/packages/kbn-eslint-plugin-i18n/README.mdx +++ b/packages/kbn-eslint-plugin-i18n/README.mdx @@ -9,13 +9,14 @@ tags: ['kibana', 'dev', 'contributor', 'operations', 'eslint', 'i18n'] `@kbn/eslint-plugin-i18n` is an ESLint plugin providing custom rules for validating JSXCode in the Kibana repo to make sure they are translated. Note: At the moment these rules only work for apps that are inside `/x-pack/plugins`. - If you want to enable this rule on code that is outside of this path, adjust `/helpers/get_i18n_identifier_from_file_path.ts`. ## `@kbn/i18n/strings_should_be_translated_with_i18n` This rule warns engineers to translate their strings by using i18n.translate from the '@kbn/i18n' package. It provides an autofix that takes into account the context of the translatable string in the JSX tree to generate a translation ID. +It kicks in on JSXText elements and specific JSXAttributes (`label` and `aria-label`) which expect a translated value. ## `@kbn/i18n/strings_should_be_translated_with_formatted_message` This rule warns engineers to translate their strings by using `` from the '@kbn/i18n-react' package. It provides an autofix that takes into account the context of the translatable string in the JSX tree and to generate a translation ID. +It kicks in on JSXText elements and specific JSXAttributes (`label` and `aria-label`) which expect a translated value. diff --git a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.test.ts b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.test.ts index d1157b7b16f10..6e01b89b23565 100644 --- a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.test.ts +++ b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.test.ts @@ -14,6 +14,10 @@ const testMap = [ ['x-pack/plugins/observability/foo/bar/baz/header_actions.tsx', 'xpack.observability'], ['x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx', 'xpack.apm'], ['x-pack/plugins/cases/public/components/foo.tsx', 'xpack.cases'], + [ + 'x-pack/plugins/synthetics/public/apps/synthetics/components/alerts/toggle_alert_flyout_button.tsx', + 'xpack.synthetics', + ], [ 'packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx', 'app_not_found_in_i18nrc', diff --git a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.ts b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.ts index 288e2692bd76a..d23a42f4ebcfb 100644 --- a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.ts +++ b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.ts @@ -24,6 +24,8 @@ export function getI18nIdentifierFromFilePath(fileName: string, cwd: string) { const i18nrc = JSON.parse(i18nrcFile); return i18nrc && i18nrc.paths - ? findKey(i18nrc.paths, (v) => v === path) ?? 'app_not_found_in_i18nrc' + ? findKey(i18nrc.paths, (v) => + Array.isArray(v) ? v.find((e) => e === path) : typeof v === 'string' && v === path + ) ?? 'app_not_found_in_i18nrc' : 'could_not_find_i18nrc'; } diff --git a/packages/kbn-eslint-plugin-i18n/helpers/get_intent_from_node.ts b/packages/kbn-eslint-plugin-i18n/helpers/get_intent_from_node.ts index 4cbd6bb9e330d..687bfd31cfba2 100644 --- a/packages/kbn-eslint-plugin-i18n/helpers/get_intent_from_node.ts +++ b/packages/kbn-eslint-plugin-i18n/helpers/get_intent_from_node.ts @@ -5,12 +5,12 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; import { lowerCaseFirstLetter, upperCaseFirstLetter } from './utils'; -export function getIntentFromNode(originalNode: TSESTree.JSXText): string { - const value = lowerCaseFirstLetter( - originalNode.value +export function getIntentFromNode(value: string, parent: TSESTree.Node | undefined): string { + const processedValue = lowerCaseFirstLetter( + value .replace(/[?!@#$%^&*()_+\][{}|/<>,'"]/g, '') .trim() .split(' ') @@ -19,8 +19,6 @@ export function getIntentFromNode(originalNode: TSESTree.JSXText): string { .join('') ); - const { parent } = originalNode; - if ( parent && 'openingElement' in parent && @@ -30,11 +28,25 @@ export function getIntentFromNode(originalNode: TSESTree.JSXText): string { const parentTagName = String(parent.openingElement.name.name); if (parentTagName.includes('Eui')) { - return `${value}${parentTagName.replace('Eui', '')}Label`; + return `${processedValue}${parentTagName.replace('Eui', '')}Label`; } - return `${lowerCaseFirstLetter(parentTagName)}.${value}Label`; + return `${lowerCaseFirstLetter(parentTagName)}.${processedValue}Label`; + } + + if ( + parent && + 'parent' in parent && + parent.parent && + 'name' in parent.parent && + typeof parent.parent.name !== 'string' && + 'type' in parent.parent.name && + parent.parent.name.type === AST_NODE_TYPES.JSXIdentifier + ) { + const parentTagName = String(parent.parent.name.name); + + return `${lowerCaseFirstLetter(parentTagName)}.${processedValue}Label`; } - return `${value}Label`; + return `${processedValue}Label`; } diff --git a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.test.ts b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.test.ts index aa545c9bb0ee0..10bdbda351892 100644 --- a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.test.ts +++ b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.test.ts @@ -95,6 +95,18 @@ function YetAnotherComponent() { ) }`, }, + { + filename: 'x-pack/plugins/observability/public/test_component.tsx', + code: ` +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; + +function TestComponent() { + return ( + } /> + ) + }`, + }, ]; const invalid = [ @@ -160,6 +172,25 @@ function YetAnotherComponent() { ], output: valid[2].code, }, + { + filename: valid[3].filename, + code: ` +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; + +function TestComponent() { + return ( + + ) + }`, + errors: [ + { + line: 7, + message: `Strings should be translated with . Use the autofix suggestion or add your own.`, + }, + ], + output: valid[3].code, + }, ]; for (const [name, tester] of [tsTester, babelTester]) { diff --git a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.ts b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.ts index 251fb3b3752fc..67e2aaec256d7 100644 --- a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.ts +++ b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.ts @@ -37,7 +37,7 @@ export const StringsShouldBeTranslatedWithFormattedMessage: Rule.RuleModule = { const i18nAppId = getI18nIdentifierFromFilePath(filename, cwd); const functionDeclaration = getScope().block as TSESTree.FunctionDeclaration; const functionName = getFunctionName(functionDeclaration); - const intent = getIntentFromNode(node); + const intent = getIntentFromNode(value, node.parent); const translationIdSuggestion = `${i18nAppId}.${functionName}.${intent}`; // 'xpack.observability.overview.logs.loadMoreLabel' @@ -72,6 +72,64 @@ export const StringsShouldBeTranslatedWithFormattedMessage: Rule.RuleModule = { }, }); }, + JSXAttribute: (node: TSESTree.JSXAttribute) => { + if (node.name.name !== 'aria-label' && node.name.name !== 'label') return; + + let val: string = ''; + + // label={'foo'} + if ( + node.value && + 'expression' in node.value && + 'value' in node.value.expression && + typeof node.value.expression.value === 'string' + ) { + val = node.value.expression.value; + } + + // label="foo" + if (node.value && 'value' in node.value && typeof node.value.value === 'string') { + val = node.value.value; + } + + if (!val) return; + + // Start building the translation ID suggestion + const i18nAppId = getI18nIdentifierFromFilePath(filename, cwd); + const functionDeclaration = getScope().block as TSESTree.FunctionDeclaration; + const functionName = getFunctionName(functionDeclaration); + const intent = getIntentFromNode(val, node); + + const translationIdSuggestion = `${i18nAppId}.${functionName}.${intent}`; // 'xpack.observability.overview.logs.loadMoreLabel' + + // Check if i18n has already been imported into the file. + const { + hasI18nImportLine, + i18nPackageImportLine: i18nImportLine, + rangeToAddI18nImportLine, + } = getI18nImportFixer({ + sourceCode, + mode: 'FormattedMessage', + }); + + // Show warning to developer and offer autofix suggestion + report({ + node: node as any, + message: + 'Strings should be translated with . Use the autofix suggestion or add your own.', + fix(fixer) { + return [ + fixer.replaceTextRange( + node.value!.range, + `{}` + ), + !hasI18nImportLine + ? fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`) + : null, + ].filter(isTruthy); + }, + }); + }, } as Rule.RuleListener; }, }; diff --git a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.test.ts b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.test.ts index 4c5916831b6cb..dc938cd6effd3 100644 --- a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.test.ts +++ b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.test.ts @@ -46,10 +46,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; function TestComponent() { - return ( -
{i18n.translate('app_not_found_in_i18nrc.testComponent.div.thisIsATestLabel', { defaultMessage: "This is a test"})}
- ) -}`, + return ( +
{i18n.translate('app_not_found_in_i18nrc.testComponent.div.thisIsATestLabel', { defaultMessage: 'This is a test'})}
+ ) + }`, }, { filename: 'x-pack/plugins/observability/public/another_component.tsx', @@ -58,30 +58,42 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; function AnotherComponent() { - return ( - - - - {i18n.translate('app_not_found_in_i18nrc.anotherComponent.thisIsATestButtonLabel', { defaultMessage: "This is a test"})} - - - - ) -}`, + return ( + + + + {i18n.translate('app_not_found_in_i18nrc.anotherComponent.thisIsATestButtonLabel', { defaultMessage: 'This is a test'})} + + + + ) + }`, }, { filename: 'x-pack/plugins/observability/public/yet_another_component.tsx', code: ` + import React from 'react'; +import { i18n } from '@kbn/i18n'; + + function YetAnotherComponent() { + return ( +
+ {i18n.translate('app_not_found_in_i18nrc.yetAnotherComponent.selectMeSelectLabel', { defaultMessage: 'Select me'})} +
+ ) + }`, + }, + { + filename: 'x-pack/plugins/observability/public/test_component.tsx', + code: ` import React from 'react'; import { i18n } from '@kbn/i18n'; -function YetAnotherComponent() { - return ( -
- {i18n.translate('app_not_found_in_i18nrc.yetAnotherComponent.selectMeSelectLabel', { defaultMessage: "Select me"})} -
- ) -}`, +function TestComponent() { + return ( + + ) + }`, }, ]; @@ -92,10 +104,10 @@ const invalid = [ import React from 'react'; function TestComponent() { - return ( -
This is a test
- ) -}`, + return ( +
This is a test
+ ) + }`, errors: [ { line: 6, @@ -110,16 +122,16 @@ function TestComponent() { import React from 'react'; function AnotherComponent() { - return ( - - - - This is a test - - - - ) -}`, + return ( + + + + This is a test + + + + ) + }`, errors: [ { line: 9, @@ -131,15 +143,15 @@ function AnotherComponent() { { filename: valid[2].filename, code: ` -import React from 'react'; + import React from 'react'; -function YetAnotherComponent() { - return ( -
- Select me -
- ) -}`, + function YetAnotherComponent() { + return ( +
+ Select me +
+ ) + }`, errors: [ { line: 7, @@ -148,6 +160,25 @@ function YetAnotherComponent() { ], output: valid[2].code, }, + { + filename: valid[3].filename, + code: ` +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +function TestComponent() { + return ( + + ) + }`, + errors: [ + { + line: 7, + message: `Strings should be translated with i18n. Use the autofix suggestion or add your own.`, + }, + ], + output: valid[3].code, + }, ]; for (const [name, tester] of [tsTester, babelTester]) { diff --git a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.ts b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.ts index a1e0da36921b6..ba31f6109075a 100644 --- a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.ts +++ b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.ts @@ -37,11 +37,11 @@ export const StringsShouldBeTranslatedWithI18n: Rule.RuleModule = { const i18nAppId = getI18nIdentifierFromFilePath(filename, cwd); const functionDeclaration = getScope().block as TSESTree.FunctionDeclaration; const functionName = getFunctionName(functionDeclaration); - const intent = getIntentFromNode(node); + const intent = getIntentFromNode(value, node.parent); const translationIdSuggestion = `${i18nAppId}.${functionName}.${intent}`; // 'xpack.observability.overview.logs.loadMoreLabel' - // Check if i18n has already been imported into the file. + // Check if i18n has already been imported into the file const { hasI18nImportLine, i18nPackageImportLine: i18nImportLine, @@ -60,7 +60,65 @@ export const StringsShouldBeTranslatedWithI18n: Rule.RuleModule = { return [ fixer.replaceText( node, - `${whiteSpaces}{i18n.translate('${translationIdSuggestion}', { defaultMessage: "${value}"})}` + `${whiteSpaces}{i18n.translate('${translationIdSuggestion}', { defaultMessage: '${value}'})}` + ), + !hasI18nImportLine + ? fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`) + : null, + ].filter(isTruthy); + }, + }); + }, + JSXAttribute: (node: TSESTree.JSXAttribute) => { + if (node.name.name !== 'aria-label' && node.name.name !== 'label') return; + + let val: string = ''; + + // label={'foo'} + if ( + node.value && + 'expression' in node.value && + 'value' in node.value.expression && + typeof node.value.expression.value === 'string' + ) { + val = node.value.expression.value; + } + + // label="foo" + if (node.value && 'value' in node.value && typeof node.value.value === 'string') { + val = node.value.value; + } + + if (!val) return; + + // Start building the translation ID suggestion + const i18nAppId = getI18nIdentifierFromFilePath(filename, cwd); + const functionDeclaration = getScope().block as TSESTree.FunctionDeclaration; + const functionName = getFunctionName(functionDeclaration); + const intent = getIntentFromNode(val, node); + + const translationIdSuggestion = `${i18nAppId}.${functionName}.${intent}`; // 'xpack.observability.overview.logs.loadMoreLabel' + + // Check if i18n has already been imported into the file. + const { + hasI18nImportLine, + i18nPackageImportLine: i18nImportLine, + rangeToAddI18nImportLine, + } = getI18nImportFixer({ + sourceCode, + mode: 'i18n.translate', + }); + + // Show warning to developer and offer autofix suggestion + report({ + node: node as any, + message: + 'Strings should be translated with i18n. Use the autofix suggestion or add your own.', + fix(fixer) { + return [ + fixer.replaceTextRange( + node.value!.range, + `{i18n.translate('${translationIdSuggestion}', { defaultMessage: '${val}'})}` ), !hasI18nImportLine ? fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`) From 5c578a06b3d6e23ea1b78de983522fd660faa6e3 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 25 Oct 2023 18:09:08 +0200 Subject: [PATCH 02/31] [ML] AIOps: Improve `flushFix` for Log Rate Analysis (#165069) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improves the `flushFix` behaviour for Log Rate Analysis. Previously the setting would add a 4KB size additional dummy payload to each object returned as ndjson. For the dataset used for testing this, this would result in an overall response payload of ˜900Kbytes. For comparison, without `flushFix` the response size would be ˜40Kbytes in this case. This PR changes the behaviour to only send a dummy payload every 500ms if the real data sent in the last 500ms wasn't bigger than 4Kbytes. Depending on the speed of the response, this can bring down the overall response payload to ˜300Kbytes (Cloud uncached), ˜150Kbytes (Cloud cached) or even ˜70Kbytes (local cluster) for the same dataset. --- .../api/reducer_stream/request_body_schema.ts | 4 ++- .../app/pages/page_reducer_stream/index.tsx | 10 +++++- .../server/routes/reducer_stream.ts | 3 +- .../server/stream_factory.test.ts | 4 +++ .../response_stream/server/stream_factory.ts | 34 +++++++++++++++---- .../log_rate_analysis_results.tsx | 2 +- .../aiops/log_rate_analysis_full_analysis.ts | 11 ++++-- .../aiops/log_rate_analysis_groups_only.ts | 12 ++++--- 8 files changed, 62 insertions(+), 18 deletions(-) diff --git a/examples/response_stream/common/api/reducer_stream/request_body_schema.ts b/examples/response_stream/common/api/reducer_stream/request_body_schema.ts index 8318a411ab86a..ebd08e55cb863 100644 --- a/examples/response_stream/common/api/reducer_stream/request_body_schema.ts +++ b/examples/response_stream/common/api/reducer_stream/request_body_schema.ts @@ -9,11 +9,13 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const reducerStreamRequestBodySchema = schema.object({ - /** Boolean flag to enable/disabling simulation of response errors. */ + /** Boolean flag to enable/disable simulation of response errors. */ simulateErrors: schema.maybe(schema.boolean()), /** Maximum timeout between streaming messages. */ timeout: schema.maybe(schema.number()), /** Setting to override headers derived compression */ compressResponse: schema.maybe(schema.boolean()), + /** Boolean flag to enable/disable 4KB payload flush fix. */ + flushFix: schema.maybe(schema.boolean()), }); export type ReducerStreamRequestBodySchema = TypeOf; diff --git a/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx b/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx index 466b6ddec75a0..a55f25292cf5d 100644 --- a/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx +++ b/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx @@ -43,12 +43,13 @@ export const PageReducerStream: FC = () => { const [simulateErrors, setSimulateErrors] = useState(false); const [compressResponse, setCompressResponse] = useState(true); + const [flushFix, setFlushFix] = useState(false); const { dispatch, start, cancel, data, errors, isCancelled, isRunning } = useFetchStream( http, RESPONSE_STREAM_API_ENDPOINT.REDUCER_STREAM, '1', - { compressResponse, simulateErrors }, + { compressResponse, flushFix, simulateErrors }, { reducer: reducerStreamReducer, initialState } ); @@ -149,6 +150,13 @@ export const PageReducerStream: FC = () => { onChange={(e) => setCompressResponse(!compressResponse)} compressed /> + setFlushFix(!flushFix)} + compressed + /> ); diff --git a/examples/response_stream/server/routes/reducer_stream.ts b/examples/response_stream/server/routes/reducer_stream.ts index 81ba44205d31b..5e03cd0732e74 100644 --- a/examples/response_stream/server/routes/reducer_stream.ts +++ b/examples/response_stream/server/routes/reducer_stream.ts @@ -60,7 +60,8 @@ export const defineReducerStreamRoute = (router: IRouter, logger: Logger) => { const { end, push, responseWithHeaders } = streamFactory( request.headers, logger, - request.body.compressResponse + request.body.compressResponse, + request.body.flushFix ); const entities = [ diff --git a/x-pack/packages/ml/response_stream/server/stream_factory.test.ts b/x-pack/packages/ml/response_stream/server/stream_factory.test.ts index 27751b7dc3fd1..4b75cf4e0826a 100644 --- a/x-pack/packages/ml/response_stream/server/stream_factory.test.ts +++ b/x-pack/packages/ml/response_stream/server/stream_factory.test.ts @@ -49,6 +49,7 @@ describe('streamFactory', () => { Connection: 'keep-alive', 'Transfer-Encoding': 'chunked', 'X-Accel-Buffering': 'no', + 'X-Content-Type-Options': 'nosniff', }); expect(streamResult).toBe('push1push2'); }); @@ -75,6 +76,7 @@ describe('streamFactory', () => { Connection: 'keep-alive', 'Transfer-Encoding': 'chunked', 'X-Accel-Buffering': 'no', + 'X-Content-Type-Options': 'nosniff', }); expect(parsedItems).toHaveLength(2); expect(parsedItems[0]).toStrictEqual(mockItem1); @@ -121,6 +123,7 @@ describe('streamFactory', () => { 'content-encoding': 'gzip', 'Transfer-Encoding': 'chunked', 'X-Accel-Buffering': 'no', + 'X-Content-Type-Options': 'nosniff', }); expect(streamResult).toBe('push1push2'); @@ -165,6 +168,7 @@ describe('streamFactory', () => { 'content-encoding': 'gzip', 'Transfer-Encoding': 'chunked', 'X-Accel-Buffering': 'no', + 'X-Content-Type-Options': 'nosniff', }); expect(parsedItems).toHaveLength(2); expect(parsedItems[0]).toStrictEqual(mockItem1); diff --git a/x-pack/packages/ml/response_stream/server/stream_factory.ts b/x-pack/packages/ml/response_stream/server/stream_factory.ts index ab676e0104b78..8836c241e55d8 100644 --- a/x-pack/packages/ml/response_stream/server/stream_factory.ts +++ b/x-pack/packages/ml/response_stream/server/stream_factory.ts @@ -19,6 +19,7 @@ function isCompressedSream(arg: unknown): arg is zlib.Gzip { return typeof arg === 'object' && arg !== null && typeof (arg as zlib.Gzip).flush === 'function'; } +const FLUSH_KEEP_ALIVE_INTERVAL_MS = 500; const FLUSH_PAYLOAD_SIZE = 4 * 1024; class UncompressedResponseStream extends Stream.PassThrough {} @@ -76,6 +77,7 @@ export function streamFactory( const flushPayload = flushFix ? crypto.randomBytes(FLUSH_PAYLOAD_SIZE).toString('hex') : undefined; + let responseSizeSinceLastKeepAlive = 0; const stream = isCompressed ? zlib.createGzip() : new UncompressedResponseStream(); @@ -132,6 +134,25 @@ export function streamFactory( // otherwise check the integrity of the data to be pushed. if (streamType === undefined) { streamType = typeof d === 'string' ? 'string' : 'ndjson'; + + // This is a fix for ndjson streaming with proxy configurations + // that buffer responses up to 4KB in size. We keep track of the + // size of the response sent so far and if it's still smaller than + // FLUSH_PAYLOAD_SIZE then we'll push an additional keep-alive object + // that contains the flush fix payload. + if (flushFix && streamType === 'ndjson') { + function repeat() { + if (!tryToEnd) { + if (responseSizeSinceLastKeepAlive < FLUSH_PAYLOAD_SIZE) { + push({ flushPayload } as unknown as T); + } + responseSizeSinceLastKeepAlive = 0; + setTimeout(repeat, FLUSH_KEEP_ALIVE_INTERVAL_MS); + } + } + + repeat(); + } } else if (streamType === 'string' && typeof d !== 'string') { logger.error('Must not push non-string chunks to a string based stream.'); return; @@ -148,13 +169,11 @@ export function streamFactory( try { const line = - streamType === 'ndjson' - ? `${JSON.stringify({ - ...d, - // This is a temporary fix for response streaming with proxy configurations that buffer responses up to 4KB in size. - ...(flushFix ? { flushPayload } : {}), - })}${DELIMITER}` - : d; + streamType === 'ndjson' ? `${JSON.stringify(d)}${DELIMITER}` : (d as unknown as string); + + if (streamType === 'ndjson') { + responseSizeSinceLastKeepAlive += new Blob([line]).size; + } waitForCallbacks.push(1); const writeOk = stream.write(line, () => { @@ -211,6 +230,7 @@ export function streamFactory( // This disables response buffering on proxy servers (Nginx, uwsgi, fastcgi, etc.) // Otherwise, those proxies buffer responses up to 4/8 KiB. 'X-Accel-Buffering': 'no', + 'X-Content-Type-Options': 'nosniff', 'Cache-Control': 'no-cache', Connection: 'keep-alive', 'Transfer-Encoding': 'chunked', diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx index 40ee98f3234dc..97d7201f0140d 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx @@ -176,7 +176,7 @@ export const LogRateAnalysisResults: FC = ({ data, isRunning, errors: streamErrors, - } = useFetchStream( + } = useFetchStream( http, '/internal/aiops/log_rate_analysis', '1', diff --git a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts index 5ac7474324c50..c9fe22a472f4f 100644 --- a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts +++ b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts @@ -191,8 +191,15 @@ export default ({ getService }: FtrProviderContext) => { data.push(action); } - // If streaming works correctly we should receive more than one chunk. - expect(chunkCounter).to.be.greaterThan(1); + // Originally we assumed that we can assert streaming in contrast + // to non-streaming if there is more than one chunk. However, + // this turned out to be flaky since a stream could finish fast + // enough to contain only one chunk. So now we are checking if + // there's just one chunk or more. + expect(chunkCounter).to.be.greaterThan( + 0, + `Expected 'chunkCounter' to be greater than 0, got ${chunkCounter}.` + ); await assertAnalysisResult(data); } diff --git a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts index cfd812e4f435c..8aeccc6af9a97 100644 --- a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts +++ b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts @@ -194,12 +194,14 @@ export default ({ getService }: FtrProviderContext) => { data.push(action); } - // If streaming works correctly we should receive more than one chunk. + // Originally we assumed that we can assert streaming in contrast + // to non-streaming if there is more than one chunk. However, + // this turned out to be flaky since a stream could finish fast + // enough to contain only one chunk. So now we are checking if + // there's just one chunk or more. expect(chunkCounter).to.be.greaterThan( - 1, - `Expected 'chunkCounter' to be greater than 1, got ${chunkCounter} with the following data: ${JSON.stringify( - data - )}.` + 0, + `Expected 'chunkCounter' to be greater than 0, got ${chunkCounter}.` ); await assertAnalysisResult(data); From c40c2e6f59dfd9aeafb7de6182121b4e5cfdbeea Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Wed, 25 Oct 2023 18:20:04 +0200 Subject: [PATCH 03/31] [Security Solution] Enable shared-ux nav (#169499) ## Summary Makes the shared-ux navigation the default side navigation component for Security projects. The old Security navigation component and the experimental flag will be removed altogether in a separate PR. There is no visual difference from the previous navigation: ![snapshot](https://github.com/elastic/kibana/assets/17747913/2896e8de-45eb-412f-b319-e919e65a0ae7) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/navigation_item_open_panel.tsx | 13 +- .../ui/components/panel/navigation_panel.tsx | 4 +- .../common/experimental_features.ts | 2 +- .../navigation_tree/navigation_tree.ts | 3 + .../alerts/building_block_alerts.cy.ts | 6 +- .../screens/serverless_security_header.ts | 122 ++++++++++++++++++ .../cypress/tasks/serverless/navigation.ts | 6 +- .../page_objects/svl_sec_landing_page.ts | 2 +- .../services/ml/security_navigation.ts | 2 +- .../ftr/cases/attachment_framework.ts | 9 +- .../security/ftr/cases/configure.ts | 9 +- .../security/ftr/cases/list_view.ts | 10 +- .../security/ftr/cases/view_case.ts | 3 +- .../test_suites/security/ftr/navigation.ts | 26 +++- .../shared/lib/cases/helpers.ts | 7 +- 15 files changed, 188 insertions(+), 36 deletions(-) create mode 100644 x-pack/test/security_solution_cypress/cypress/screens/serverless_security_header.ts diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx index 99ed44565dec4..5c116e26a2ff7 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx @@ -63,6 +63,15 @@ export const NavigationItemOpenPanel: FC = ({ item, navigateToUrl }: Prop getStyles(euiTheme) ); + const dataTestSubj = classNames(`nav-item`, `nav-item-${id}`, { + [`nav-item-deepLinkId-${deepLink?.id}`]: !!deepLink, + [`nav-item-id-${id}`]: id, + [`nav-item-isActive`]: isActive, + }); + const buttonDataTestSubj = classNames(`panelOpener`, `panelOpener-${id}`, { + [`panelOpener-deepLinkId-${deepLink?.id}`]: !!deepLink, + }); + const onLinkClick = useCallback( (e: React.MouseEvent) => { if (!href) { @@ -95,7 +104,7 @@ export const NavigationItemOpenPanel: FC = ({ item, navigateToUrl }: Prop className={itemClassNames} color="text" size="s" - data-test-subj={`sideNavItemLink-${id}`} + data-test-subj={dataTestSubj} /> @@ -111,7 +120,7 @@ export const NavigationItemOpenPanel: FC = ({ item, navigateToUrl }: Prop aria-label={i18n.translate('sharedUXPackages.chrome.sideNavigation.togglePanel', { defaultMessage: 'Toggle panel navigation', })} - data-test-subj={`panelOpener-${id}`} + data-test-subj={buttonDataTestSubj} /> )} diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx index 616d1aca201cf..ca118249d123b 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx @@ -37,7 +37,9 @@ export const NavigationPanel: FC = () => { const onOutsideClick = useCallback( ({ target }: Event) => { // Only close if we are not clicking on the currently selected nav node - if ((target as HTMLButtonElement).dataset.testSubj !== `panelOpener-${selectedNode?.id}`) { + if ( + !(target as HTMLButtonElement).dataset.testSubj?.includes(`panelOpener-${selectedNode?.id}`) + ) { close(); } }, diff --git a/x-pack/plugins/security_solution_serverless/common/experimental_features.ts b/x-pack/plugins/security_solution_serverless/common/experimental_features.ts index 500bad2a0483d..76b57064da160 100644 --- a/x-pack/plugins/security_solution_serverless/common/experimental_features.ts +++ b/x-pack/plugins/security_solution_serverless/common/experimental_features.ts @@ -19,7 +19,7 @@ export const allowedExperimentalValues = Object.freeze({ /** * Enables the use of the of the product navigation from shared-ux package in the Security Solution app */ - platformNavEnabled: false, + platformNavEnabled: true, }); type ServerlessExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/navigation_tree.ts b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/navigation_tree.ts index 2d9ad8c1bb2d5..bbab7fb56f057 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/navigation_tree.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/navigation_tree.ts @@ -52,6 +52,9 @@ export const formatNavigationTree = ( breadcrumbStatus: 'hidden', defaultIsCollapsed: false, children: bodyChildren, + accordionProps: { + arrowProps: { css: { display: 'none' } }, + }, }, ], footer: formatFooterNodesFromLinks(footerNavItems, footerCategories), diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts index 5efdfb7d94c8f..2b5a85c28f22c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts @@ -8,13 +8,13 @@ import { getBuildingBlockRule } from '../../../objects/rule'; import { OVERVIEW_ALERTS_HISTOGRAM_EMPTY } from '../../../screens/overview'; import { HIGHLIGHTED_ROWS_IN_TABLE } from '../../../screens/rule_details'; -import { OVERVIEW } from '../../../screens/security_header'; import { createRule } from '../../../tasks/api_calls/rules'; import { cleanKibana } from '../../../tasks/common'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; import { login } from '../../../tasks/login'; +import { visit } from '../../../tasks/navigation'; import { visitRuleDetailsPage, waitForTheRuleToBeExecuted } from '../../../tasks/rule_details'; -import { navigateFromHeaderTo } from '../../../tasks/security_header'; +import { OVERVIEW_URL } from '../../../urls/navigation'; const EXPECTED_NUMBER_OF_ALERTS = 5; @@ -42,7 +42,7 @@ describe('Alerts generated by building block rules', { tags: ['@ess', '@serverle // Make sure rows are highlighted cy.get(HIGHLIGHTED_ROWS_IN_TABLE).should('exist'); - navigateFromHeaderTo(OVERVIEW); + visit(OVERVIEW_URL); // Check that generated events are hidden on the Overview page cy.get(OVERVIEW_ALERTS_HISTOGRAM_EMPTY).should('contain.text', 'No results found'); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/serverless_security_header.ts b/x-pack/test/security_solution_cypress/cypress/screens/serverless_security_header.ts new file mode 100644 index 0000000000000..11885714a0dda --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/screens/serverless_security_header.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// main panels links +export const DASHBOARDS = '[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:dashboards"]'; +export const DASHBOARDS_PANEL_BTN = + '[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:dashboards"]'; + +export const INVESTIGATIONS = + '[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:investigations"]'; +export const INVESTIGATIONS_PANEL_BTN = + '[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:investigations"]'; + +export const EXPLORE = '[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:explore"]'; +export const EXPLORE_PANEL_BTN = + '[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:explore"]'; + +export const RULES_LANDING = + '[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:rules-landing"]'; +export const RULES_PANEL_BTN = + '[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:rules-landing"]'; + +export const ASSETS = '[data-test-subj$="nav-item-deepLinkId-securitySolutionUI:assets"]'; +export const ASSETS_PANEL_BTN = + '[data-test-subj*="panelOpener-deepLinkId-securitySolutionUI:assets"]'; + +// main direct links +export const DISCOVER = '[data-test-subj*="nav-item-deepLinkId-discover"]'; + +export const ALERTS = '[data-test-subj*="nav-item-deepLinkId-securitySolutionUI:alerts"]'; + +export const CSP_FINDINGS = + '[data-test-subj*="nav-item-deepLinkId-securitySolutionUI:cloud_security_posture-findings"]'; + +export const CASES = '[data-test-subj*="nav-item-deepLinkId-securitySolutionUI:cases"]'; + +// nested links + +export const OVERVIEW = '[data-test-subj="solutionSideNavPanelLink-overview"]'; + +export const DETECTION_RESPONSE = '[data-test-subj="solutionSideNavPanelLink-detection_response"]'; + +export const ENTITY_ANALYTICS = '[data-test-subj="solutionSideNavPanelLink-entity_analytics"]'; + +export const TIMELINES = '[data-test-subj="solutionSideNavPanelLink-timelines"]'; + +export const KUBERNETES = '[data-test-subj="solutionSideNavPanelLink-kubernetes"]'; + +export const CSP_DASHBOARD = + '[data-test-subj="solutionSideNavPanelLink-cloud_security_posture-dashboard"]'; + +export const HOSTS = '[data-test-subj="solutionSideNavPanelLink-hosts"]'; + +export const ENDPOINTS = '[data-test-subj="solutionSideNavPanelLink-endpoints"]'; + +export const POLICIES = '[data-test-subj="solutionSideNavPanelLink-policy"]'; + +export const TRUSTED_APPS = '[data-test-subj="solutionSideNavPanelLink-trusted_apps"]'; + +export const EVENT_FILTERS = '[data-test-subj="solutionSideNavPanelLink-event_filters"]'; + +export const BLOCKLIST = '[data-test-subj="solutionSideNavPanelLink-blocklist"]'; + +export const CSP_BENCHMARKS = + '[data-test-subj="solutionSideNavPanelLink-cloud_security_posture-benchmarks"]'; + +export const NETWORK = '[data-test-subj="solutionSideNavPanelLink-network"]'; + +export const USERS = '[data-test-subj="solutionSideNavPanelLink-users"]'; + +export const INDICATORS = '[data-test-subj="solutionSideNavItemLink-threat_intelligence"]'; + +export const RULES = '[data-test-subj="solutionSideNavPanelLink-rules"]'; + +export const EXCEPTIONS = '[data-test-subj="solutionSideNavPanelLink-exceptions"]'; + +// opens the navigation panel for a given nested link +export const openNavigationPanelFor = (page: string) => { + let panel; + switch (page) { + case OVERVIEW: + case DETECTION_RESPONSE: + case KUBERNETES: + case ENTITY_ANALYTICS: + case CSP_DASHBOARD: { + panel = DASHBOARDS_PANEL_BTN; + break; + } + case HOSTS: + case NETWORK: + case USERS: { + panel = EXPLORE_PANEL_BTN; + break; + } + case RULES: + case EXCEPTIONS: + case CSP_BENCHMARKS: { + panel = RULES_PANEL_BTN; + break; + } + case ENDPOINTS: + case TRUSTED_APPS: + case EVENT_FILTERS: + case POLICIES: + case BLOCKLIST: { + panel = ASSETS_PANEL_BTN; + break; + } + } + if (panel) { + openNavigationPanel(panel); + } +}; + +// opens the navigation panel of a main link +export const openNavigationPanel = (page: string) => { + cy.get(page).click(); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/serverless/navigation.ts b/x-pack/test/security_solution_cypress/cypress/tasks/serverless/navigation.ts index 3dd31a0ec981c..90c380e8f88b4 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/serverless/navigation.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/serverless/navigation.ts @@ -5,14 +5,12 @@ * 2.0. */ -const serverlessLocator = { - alerts: '[data-test-subj="solutionSideNavItemLink-alerts"]', -}; +import { ALERTS } from '../../screens/serverless_security_header'; const navigateTo = (page: string) => { cy.get(page).click(); }; export const navigateToAlertsPageInServerless = () => { - navigateTo(serverlessLocator.alerts); + navigateTo(ALERTS); }; diff --git a/x-pack/test_serverless/functional/page_objects/svl_sec_landing_page.ts b/x-pack/test_serverless/functional/page_objects/svl_sec_landing_page.ts index dc87ebd5db9d3..7ff15c0d38215 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_sec_landing_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_sec_landing_page.ts @@ -12,7 +12,7 @@ export function SvlSecLandingPageProvider({ getService }: FtrProviderContext) { return { async assertSvlSecSideNavExists() { - await testSubjects.existOrFail('securitySolutionNavHeading'); + await testSubjects.existOrFail('securitySolutionSideNav'); }, }; } diff --git a/x-pack/test_serverless/functional/services/ml/security_navigation.ts b/x-pack/test_serverless/functional/services/ml/security_navigation.ts index 2d80ebd9d413e..17ebe65b45194 100644 --- a/x-pack/test_serverless/functional/services/ml/security_navigation.ts +++ b/x-pack/test_serverless/functional/services/ml/security_navigation.ts @@ -11,7 +11,7 @@ export function MachineLearningNavigationProviderSecurity({ getService }: FtrPro const testSubjects = getService('testSubjects'); async function navigateToArea(id: string) { - await testSubjects.click('~solutionSideNavItemButton-machine_learning-landing'); + await testSubjects.click('~panelOpener-deepLinkId-securitySolutionUI:machine_learning-landing'); await testSubjects.existOrFail(`~solutionSideNavPanelLink-ml:${id}`, { timeout: 60 * 1000, }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts index 24da4464e9fb3..c20c2da7235cb 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts @@ -9,9 +9,9 @@ import { expect } from 'expect'; import { FtrProviderContext } from '../../../../ftr_provider_context'; export default ({ getPageObject, getService }: FtrProviderContext) => { + const common = getPageObject('common'); const dashboard = getPageObject('dashboard'); const lens = getPageObject('lens'); - const svlSecNavigation = getService('svlSecNavigation'); const svlCommonPage = getPageObject('svlCommonPage'); const testSubjects = getService('testSubjects'); const cases = getService('cases'); @@ -25,9 +25,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('lens visualization', () => { before(async () => { await svlCommonPage.login(); - await svlSecNavigation.navigateToLandingPage(); - - await testSubjects.click('solutionSideNavItemLink-dashboards'); + await common.navigateToApp('security', { path: 'dashboards' }); await header.waitUntilLoadingHasFinished(); await retry.waitFor('createDashboardButton', async () => { @@ -88,7 +86,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { owner: 'securitySolution', }); - await testSubjects.click('solutionSideNavItemLink-dashboards'); + await common.navigateToApp('security', { path: 'dashboards' }); + await header.waitUntilLoadingHasFinished(); if (await testSubjects.exists('edit-unsaved-New-Dashboard')) { await testSubjects.click('edit-unsaved-New-Dashboard'); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts index b19e1746847ed..373782e69bb67 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts @@ -6,13 +6,16 @@ */ import expect from '@kbn/expect'; +import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common'; +import { navigateToCasesApp } from '../../../../../shared/lib/cases/helpers'; import { FtrProviderContext } from '../../../../ftr_provider_context'; +const owner = SECURITY_SOLUTION_OWNER; + export default ({ getPageObject, getService }: FtrProviderContext) => { const common = getPageObject('common'); const header = getPageObject('header'); const svlCommonPage = getPageObject('svlCommonPage'); - const svlSecNavigation = getService('svlSecNavigation'); const testSubjects = getService('testSubjects'); const cases = getService('cases'); const svlCases = getService('svlCases'); @@ -23,9 +26,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Configure Case', function () { before(async () => { await svlCommonPage.login(); - await svlSecNavigation.navigateToLandingPage(); - await testSubjects.click('solutionSideNavItemLink-cases'); - await header.waitUntilLoadingHasFinished(); + await navigateToCasesApp(getPageObject, getService, owner); await retry.waitFor('configure-case-button exist', async () => { return await testSubjects.exists('configure-case-button'); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/list_view.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/list_view.ts index 8a753e5d4829a..e672f99780fa8 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/list_view.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/list_view.ts @@ -6,10 +6,14 @@ */ import expect from '@kbn/expect'; +import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common'; import { CaseSeverity, CaseStatuses } from '@kbn/cases-plugin/common/types/domain'; import { SeverityAll } from '@kbn/cases-plugin/common/ui'; +import { navigateToCasesApp } from '../../../../../shared/lib/cases/helpers'; import { FtrProviderContext } from '../../../../ftr_provider_context'; +const owner = SECURITY_SOLUTION_OWNER; + export default ({ getPageObject, getService }: FtrProviderContext) => { const header = getPageObject('header'); const testSubjects = getService('testSubjects'); @@ -24,7 +28,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await svlSecNavigation.navigateToLandingPage(); - await testSubjects.click('solutionSideNavItemLink-cases'); + await navigateToCasesApp(getPageObject, getService, owner); }); after(async () => { @@ -151,7 +155,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('severity filtering', () => { // Error: retry.tryForTime timeout: Error: expected 10 to equal 5 before(async () => { - await testSubjects.click('solutionSideNavItemLink-cases'); + await navigateToCasesApp(getPageObject, getService, owner); await cases.api.createCase({ severity: CaseSeverity.LOW, owner: 'securitySolution' }); await cases.api.createCase({ severity: CaseSeverity.LOW, owner: 'securitySolution' }); @@ -167,7 +171,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { * There is no easy way to clear the filtering. * Refreshing the page seems to be easier. */ - await testSubjects.click('solutionSideNavItemLink-cases'); + await navigateToCasesApp(getPageObject, getService, owner); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts index d9531a4529ee5..d9429f93bc9af 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts @@ -18,6 +18,7 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; import { createOneCaseBeforeDeleteAllAfter, createAndNavigateToCase, + navigateToCasesApp, } from '../../../../../shared/lib/cases/helpers'; const owner = SECURITY_SOLUTION_OWNER; @@ -473,7 +474,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { ]; before(async () => { - await testSubjects.click('solutionSideNavItemLink-cases'); + await navigateToCasesApp(getPageObject, getService, owner); await cases.api.createConfigWithCustomFields({ customFields, owner }); await cases.api.createCase({ customFields: [ diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts index 998ad9a2096c9..ef33a898de4fb 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts @@ -6,21 +6,27 @@ */ import expect from '@kbn/expect'; +import { AppDeepLinkId } from '@kbn/core-chrome-browser'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObject, getService }: FtrProviderContext) { + const svlCommonPage = getPageObject('svlCommonPage'); const svlSecLandingPage = getPageObject('svlSecLandingPage'); const svlSecNavigation = getService('svlSecNavigation'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); const testSubjects = getService('testSubjects'); const browser = getService('browser'); - // FLAKY: https://github.com/elastic/kibana/issues/165629 - describe.skip('navigation', function () { + describe('navigation', function () { before(async () => { + await svlCommonPage.login(); await svlSecNavigation.navigateToLandingPage(); }); + after(async () => { + await svlCommonPage.forceLogout(); + }); + it('has security serverless side nav', async () => { await svlSecLandingPage.assertSvlSecSideNavExists(); await svlCommonNavigation.expectExists(); @@ -30,7 +36,9 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await svlCommonNavigation.breadcrumbs.expectExists(); // TODO: use `deepLinkId` instead of `text`, once security deep links are available in @kbn/core-chrome-browser await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Get started' }); - await testSubjects.click('solutionSideNavItemLink-alerts'); + await svlCommonNavigation.sidenav.clickLink({ + deepLinkId: 'securitySolutionUI:alerts' as AppDeepLinkId, + }); await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Alerts' }); await svlCommonNavigation.breadcrumbs.clickHome(); await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Get started' }); @@ -38,8 +46,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { it('navigate using search', async () => { await svlCommonNavigation.search.showSearch(); - await svlCommonNavigation.search.searchFor('dashboards'); - await svlCommonNavigation.search.clickOnOption(1); + await svlCommonNavigation.search.searchFor('security dashboards'); + await svlCommonNavigation.search.clickOnOption(0); await svlCommonNavigation.search.hideSearch(); await expect(await browser.getCurrentUrl()).contain('app/security/dashboards'); @@ -49,11 +57,15 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await svlSecLandingPage.assertSvlSecSideNavExists(); await svlCommonNavigation.expectExists(); - expect(await testSubjects.existOrFail('solutionSideNavItemLink-cases')); + await svlCommonNavigation.sidenav.expectLinkExists({ + deepLinkId: 'securitySolutionUI:cases' as AppDeepLinkId, + }); }); it('navigates to cases app', async () => { - await testSubjects.click('solutionSideNavItemLink-cases'); + await svlCommonNavigation.sidenav.clickLink({ + deepLinkId: 'securitySolutionUI:cases' as AppDeepLinkId, + }); expect(await browser.getCurrentUrl()).contain('/app/security/cases'); await testSubjects.existOrFail('cases-all-title'); diff --git a/x-pack/test_serverless/shared/lib/cases/helpers.ts b/x-pack/test_serverless/shared/lib/cases/helpers.ts index 5cc4aa637ec43..71df12e44d78a 100644 --- a/x-pack/test_serverless/shared/lib/cases/helpers.ts +++ b/x-pack/test_serverless/shared/lib/cases/helpers.ts @@ -6,6 +6,7 @@ */ import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common'; +import { AppDeepLinkId } from '@kbn/core-chrome-browser'; import { FtrProviderContext } from '../../../functional/ftr_provider_context'; export const createOneCaseBeforeDeleteAllAfter = ( @@ -64,15 +65,15 @@ export const navigateToCasesApp = async ( getService: FtrProviderContext['getService'], owner: string ) => { - const testSubjects = getService('testSubjects'); - const common = getPageObject('common'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); await common.navigateToApp('landingPage'); if (owner === SECURITY_SOLUTION_OWNER) { - await testSubjects.click('solutionSideNavItemLink-cases'); + await svlCommonNavigation.sidenav.clickLink({ + deepLinkId: 'securitySolutionUI:cases' as AppDeepLinkId, + }); } else { await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' }); } From abcab9476202ba941c1dd8a00a7ee68397fde3fa Mon Sep 17 00:00:00 2001 From: Konrad Szwarc Date: Wed, 25 Oct 2023 18:28:17 +0200 Subject: [PATCH 04/31] [EDR Workflows] Unskip CY tests (#168457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unskipped tests: 1. `endpoint_alerts.cy.ts` 2. `response_console_mocked_data.cy.ts` - https://github.com/elastic/security-team/issues/7763 3. `no_license.cy.ts` - https://github.com/elastic/security-team/issues/7763 4. `endpoints.cy.ts` Changes: 1. Introduced interval for `cy.waitUntill` calls, I've noticed locally that running these request without throttling can cause API issues 2. Increased timeout for CI `burn` jobs - with this PR as an example, when burning 3 test suites one hour might not be enough at this point. We should think about splitting these. --------- Co-authored-by: Patryk Kopyciński --- .../no_license.cy.ts | 10 +++--- .../cypress/e2e/endpoint_alerts.cy.ts | 15 +++------ .../response_console_mocked_data.cy.ts | 31 +++++++++---------- .../management/cypress/screens/alerts.ts | 2 +- .../public/management/cypress/tasks/alerts.ts | 4 +-- .../cypress/tasks/response_actions.ts | 2 +- 6 files changed, 29 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts index 0869f10c73ef0..d1449672bed26 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts @@ -5,8 +5,8 @@ * 2.0. */ +import { navigateToAlertsList } from '../../screens/alerts'; import { disableExpandableFlyoutAdvancedSettings } from '../../tasks/common'; -import { APP_ALERTS_PATH } from '../../../../../common/constants'; import { closeAllToasts } from '../../tasks/toasts'; import { fillUpNewRule } from '../../tasks/response_actions'; import { login, ROLE } from '../../tasks/login'; @@ -25,7 +25,6 @@ describe('No License', { tags: '@ess', env: { ftrConfig: { license: 'basic' } } it('response actions are disabled', () => { fillUpNewRule(ruleName, ruleDescription); - // addEndpointResponseAction(); cy.getByTestSubj('response-actions-wrapper').within(() => { cy.getByTestSubj('Endpoint Security-response-action-type-selection-option').should( 'be.disabled' @@ -38,7 +37,7 @@ describe('No License', { tags: '@ess', env: { ftrConfig: { license: 'basic' } } let endpointData: ReturnTypeFromChainable | undefined; let alertData: ReturnTypeFromChainable | undefined; const [endpointAgentId, endpointHostname] = generateRandomStringName(2); - before(() => { + beforeEach(() => { login(); disableExpandableFlyoutAdvancedSettings(); indexEndpointRuleAlerts({ @@ -58,7 +57,7 @@ describe('No License', { tags: '@ess', env: { ftrConfig: { license: 'basic' } } }); }); - after(() => { + afterEach(() => { if (endpointData) { endpointData.cleanup(); endpointData = undefined; @@ -69,8 +68,9 @@ describe('No License', { tags: '@ess', env: { ftrConfig: { license: 'basic' } } alertData = undefined; } }); + it('show the permission denied callout', () => { - cy.visit(APP_ALERTS_PATH); + navigateToAlertsList(`query=(language:kuery,query:'agent.id: "${endpointAgentId}" ')`); closeAllToasts(); cy.getByTestSubj('expand-event').first().click(); cy.getByTestSubj('response-actions-notification').should('not.have.text', '0'); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts index bf6dc8c57a478..06b33141bad1b 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts @@ -15,17 +15,17 @@ import type { IndexedFleetEndpointPolicyResponse } from '../../../../common/endp import { enableAllPolicyProtections } from '../tasks/endpoint_policy'; import type { PolicyData, ResponseActionApiResponse } from '../../../../common/endpoint/types'; import type { CreateAndEnrollEndpointHostResponse } from '../../../../scripts/endpoint/common/endpoint_host_services'; -import { login } from '../tasks/login'; +import { login, ROLE } from '../tasks/login'; import { EXECUTE_ROUTE } from '../../../../common/endpoint/constants'; import { waitForActionToComplete } from '../tasks/response_actions'; -// FIXME: Flaky. Needs fixing (security team issue #7763) -describe.skip('Endpoint generated alerts', { tags: ['@ess', '@serverless'] }, () => { +describe('Endpoint generated alerts', { tags: ['@ess', '@serverless'] }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; let createdHost: CreateAndEnrollEndpointHostResponse; - before(() => { + beforeEach(() => { + login(ROLE.soc_manager); getEndpointIntegrationVersion().then((version) => { createAgentPolicyTask(version, 'alerts test').then((data) => { indexedPolicy = data; @@ -41,7 +41,7 @@ describe.skip('Endpoint generated alerts', { tags: ['@ess', '@serverless'] }, () }); }); - after(() => { + afterEach(() => { if (createdHost) { cy.task('destroyEndpointHost', createdHost); } @@ -55,10 +55,6 @@ describe.skip('Endpoint generated alerts', { tags: ['@ess', '@serverless'] }, () } }); - beforeEach(() => { - login(); - }); - it('should create a Detection Engine alert from an endpoint alert', () => { // Triggers a Malicious Behaviour alert on Linux system (`grep *` was added only to identify this specific alert) const executeMaliciousCommand = `bash -c cat /dev/tcp/foo | grep ${Math.random() @@ -89,7 +85,6 @@ describe.skip('Endpoint generated alerts', { tags: ['@ess', '@serverless'] }, () `query=(language:kuery,query:'agent.id: "${createdHost.agentId}" ')` ); }); - getAlertsTableRows().should('have.length.greaterThan', 0); }); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console_mocked_data.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console_mocked_data.cy.ts index f80e3b17e6017..7ae7aa49f2e94 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console_mocked_data.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console_mocked_data.cy.ts @@ -34,7 +34,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles let endpointHostname: string; let isolateRequestResponse: ActionDetails; - before(() => { + beforeEach(() => { indexEndpointHosts({ withResponseActions: false, isolation: false }).then( (indexEndpoints) => { endpointData = indexEndpoints; @@ -43,7 +43,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles ); }); - after(() => { + afterEach(() => { if (endpointData) { endpointData.cleanup(); // @ts-expect-error ignore setting to undefined @@ -76,14 +76,14 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles let endpointHostname: string; let releaseRequestResponse: ActionDetails; - before(() => { + beforeEach(() => { indexEndpointHosts({ withResponseActions: false, isolation: true }).then((indexEndpoints) => { endpointData = indexEndpoints; endpointHostname = endpointData.data.hosts[0].host.name; }); }); - after(() => { + afterEach(() => { if (endpointData) { endpointData.cleanup(); // @ts-expect-error ignore setting to undefined @@ -115,7 +115,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles let endpointHostname: string; let processesRequestResponse: ActionDetails; - before(() => { + beforeEach(() => { indexEndpointHosts({ withResponseActions: false, isolation: false }).then( (indexEndpoints) => { endpointData = indexEndpoints; @@ -124,7 +124,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles ); }); - after(() => { + afterEach(() => { if (endpointData) { endpointData.cleanup(); // @ts-expect-error ignore setting to undefined @@ -155,7 +155,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles let endpointHostname: string; let killProcessRequestResponse: ActionDetails; - before(() => { + beforeEach(() => { indexEndpointHosts({ withResponseActions: false, isolation: false }).then( (indexEndpoints) => { endpointData = indexEndpoints; @@ -164,7 +164,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles ); }); - after(() => { + afterEach(() => { if (endpointData) { endpointData.cleanup(); // @ts-expect-error ignore setting to undefined @@ -194,7 +194,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles let endpointHostname: string; let suspendProcessRequestResponse: ActionDetails; - before(() => { + beforeEach(() => { indexEndpointHosts({ withResponseActions: false, isolation: false }).then( (indexEndpoints) => { endpointData = indexEndpoints; @@ -203,7 +203,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles ); }); - after(() => { + afterEach(() => { if (endpointData) { endpointData.cleanup(); // @ts-expect-error ignore setting to undefined @@ -228,13 +228,12 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles }); }); - // Broken until this is fixed: https://github.com/elastic/kibana/issues/162760 - describe.skip('`get-file` command', () => { + describe('`get-file` command', () => { let endpointData: ReturnTypeFromChainable; let endpointHostname: string; let getFileRequestResponse: ActionDetails; - before(() => { + beforeEach(() => { indexEndpointHosts({ withResponseActions: false, isolation: false }).then( (indexEndpoints) => { endpointData = indexEndpoints; @@ -243,7 +242,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles ); }); - after(() => { + afterEach(() => { if (endpointData) { endpointData.cleanup(); // @ts-expect-error ignore setting to undefined @@ -283,7 +282,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles let endpointHostname: string; let executeRequestResponse: ActionDetails; - before(() => { + beforeEach(() => { indexEndpointHosts({ withResponseActions: false, isolation: false }).then( (indexEndpoints) => { endpointData = indexEndpoints; @@ -292,7 +291,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles ); }); - after(() => { + afterEach(() => { if (endpointData) { endpointData.cleanup(); // @ts-expect-error ignore setting to undefined diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts index b434458c4dc1d..7ad73ad142eeb 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts @@ -37,7 +37,7 @@ export const getAlertsTableRows = (timeout?: number): Cypress.Chainable $rows); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts index 30679364bd2f8..ac4d2e80ebe44 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts @@ -48,7 +48,7 @@ export const waitForEndpointAlerts = ( return (streamedAlerts.hits.total as estypes.SearchTotalHits).value > 0; }); }, - { timeout } + { timeout, interval: 2000 } ) .then(() => { // Stop/start Endpoint rule so that it can pickup and create Detection alerts @@ -143,7 +143,7 @@ export const waitForDetectionAlerts = ( return Boolean((alertsResponse.hits.total as estypes.SearchTotalHits)?.value ?? 0); }); }, - { timeout } + { timeout, interval: 2000 } ); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts index 8f4f1e797910b..80f1aaba567a0 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts @@ -105,7 +105,7 @@ export const waitForActionToComplete = ( return false; }); }, - { timeout } + { timeout, interval: 2000 } ) .then(() => { if (!action) { From 850060039330f4d8dd0c26e02ecee522d575e919 Mon Sep 17 00:00:00 2001 From: christineweng <18648970+christineweng@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:48:36 -0500 Subject: [PATCH 05/31] [Security Solution] Add version header to alert table actions (#169731) ## Summary Some add to timeline actions are missing version header when sending requests. This is preventing user from adding alerts (of the rule types below) to timeline - clicking `investigate in timeline` will throw a `failed to create ... timeline` error. This PR adds version header to api calls related to: 1. alert suppression 2. threshold rule 3. new term rule ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../public/detections/components/alerts_table/actions.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index a52446d389d7a..364b521c1ec30 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -451,6 +451,7 @@ const createThresholdTimeline = async ( const alertResponse = await KibanaServices.get().http.fetch< estypes.SearchResponse<{ '@timestamp': string; [key: string]: unknown }> >(DETECTION_ENGINE_QUERY_SIGNALS_URL, { + version: '2023-10-31', method: 'POST', body: JSON.stringify(buildAlertsQuery([ecsData._id])), }); @@ -608,6 +609,7 @@ const createNewTermsTimeline = async ( const alertResponse = await KibanaServices.get().http.fetch< estypes.SearchResponse<{ '@timestamp': string; [key: string]: unknown }> >(DETECTION_ENGINE_QUERY_SIGNALS_URL, { + version: '2023-10-31', method: 'POST', body: JSON.stringify(buildAlertsQuery([ecsData._id])), }); @@ -773,6 +775,7 @@ const createSuppressedTimeline = async ( const alertResponse = await KibanaServices.get().http.fetch< estypes.SearchResponse<{ '@timestamp': string; [key: string]: unknown }> >(DETECTION_ENGINE_QUERY_SIGNALS_URL, { + version: '2023-10-31', method: 'POST', body: JSON.stringify(buildAlertsQuery([ecsData._id])), }); From 3a5d6cc92b157cf61a49c97b0c9f537285cc2299 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Wed, 25 Oct 2023 12:12:06 -0500 Subject: [PATCH 06/31] [RAM] Reset rule settings modal on cancel (#169720) ## Summary Fixes #169296 - Resets the rule settings modal when the user clicks Cancel, but caches the initial pull from the server so that a second request isn't necessary on reopen - Updates this cache on save so that the reset on modal close remains accurate ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../rules_settings_modal.test.tsx | 30 +++++++++ .../rules_setting/rules_settings_modal.tsx | 66 ++++++++++++++----- 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_modal.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_modal.test.tsx index 7552b3fac7e26..f33178ad4f6b0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_modal.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_modal.test.tsx @@ -200,6 +200,36 @@ describe('rules_settings_modal', () => { expect(modalProps.onSave).toHaveBeenCalledTimes(1); }); + test('reset flapping settings to initial state on cancel without triggering another server reload', async () => { + const result = render(); + expect(getFlappingSettingsMock).toHaveBeenCalledTimes(1); + expect(getQueryDelaySettingsMock).toHaveBeenCalledTimes(1); + await waitForModalLoad(); + + const lookBackWindowInput = result.getByTestId('lookBackWindowRangeInput'); + const statusChangeThresholdInput = result.getByTestId('statusChangeThresholdRangeInput'); + + fireEvent.change(lookBackWindowInput, { target: { value: 15 } }); + fireEvent.change(statusChangeThresholdInput, { target: { value: 3 } }); + + expect(lookBackWindowInput.getAttribute('value')).toBe('15'); + expect(statusChangeThresholdInput.getAttribute('value')).toBe('3'); + + // Try cancelling + userEvent.click(result.getByTestId('rulesSettingsModalCancelButton')); + + expect(modalProps.onClose).toHaveBeenCalledTimes(1); + expect(updateFlappingSettingsMock).not.toHaveBeenCalled(); + expect(modalProps.onSave).not.toHaveBeenCalled(); + + expect(screen.queryByTestId('centerJustifiedSpinner')).toBe(null); + expect(lookBackWindowInput.getAttribute('value')).toBe('10'); + expect(statusChangeThresholdInput.getAttribute('value')).toBe('10'); + + expect(getFlappingSettingsMock).toHaveBeenCalledTimes(1); + expect(getQueryDelaySettingsMock).toHaveBeenCalledTimes(1); + }); + test('should prevent statusChangeThreshold from being greater than lookBackWindow', async () => { const result = render(); await waitForModalLoad(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_modal.tsx index c15286325495a..a75a7139c1189 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_modal.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useState } from 'react'; +import React, { memo, useCallback, useState, useRef } from 'react'; import { RulesSettingsFlappingProperties, RulesSettingsProperties, @@ -60,6 +60,26 @@ export const RulesSettingsErrorPrompt = memo(() => { ); }); +const useResettableState: ( + initialValue?: T +) => [T | undefined, boolean, (next: T, shouldUpdateInitialValue?: boolean) => void, () => void] = ( + initalValue +) => { + const initialValueRef = useRef(initalValue); + const [value, setValue] = useState(initalValue); + const [hasChanged, setHasChanged] = useState(false); + const reset = () => { + setValue(initialValueRef.current); + setHasChanged(false); + }; + const updateValue = (next: typeof value, shouldUpdateInitialValue = false) => { + setValue(next); + setHasChanged(true); + if (shouldUpdateInitialValue) initialValueRef.current = next; + }; + return [value, hasChanged, updateValue, reset]; +}; + export interface RulesSettingsModalProps { isVisible: boolean; setUpdatingRulesSettings?: (isUpdating: boolean) => void; @@ -84,21 +104,24 @@ export const RulesSettingsModal = memo((props: RulesSettingsModalProps) => { }, } = capabilities; - const [flappingSettings, setFlappingSettings] = useState(); - const [hasFlappingChanged, setHasFlappingChanged] = useState(false); + const [flappingSettings, hasFlappingChanged, setFlappingSettings, resetFlappingSettings] = + useResettableState(); - const [queryDelaySettings, setQueryDelaySettings] = useState(); - const [hasQueryDelayChanged, setHasQueryDelayChanged] = useState(false); + const [queryDelaySettings, hasQueryDelayChanged, setQueryDelaySettings, resetQueryDelaySettings] = + useResettableState(); const { isLoading: isFlappingLoading, isError: hasFlappingError } = useGetFlappingSettings({ enabled: isVisible, onSuccess: (fetchedSettings) => { if (!flappingSettings) { - setFlappingSettings({ - enabled: fetchedSettings.enabled, - lookBackWindow: fetchedSettings.lookBackWindow, - statusChangeThreshold: fetchedSettings.statusChangeThreshold, - }); + setFlappingSettings( + { + enabled: fetchedSettings.enabled, + lookBackWindow: fetchedSettings.lookBackWindow, + statusChangeThreshold: fetchedSettings.statusChangeThreshold, + }, + true // Update the initial value so we don't need to fetch it from the server again + ); } }, }); @@ -107,13 +130,22 @@ export const RulesSettingsModal = memo((props: RulesSettingsModalProps) => { enabled: isVisible, onSuccess: (fetchedSettings) => { if (!queryDelaySettings) { - setQueryDelaySettings({ - delay: fetchedSettings.delay, - }); + setQueryDelaySettings( + { + delay: fetchedSettings.delay, + }, + true + ); } }, }); + const onCloseModal = useCallback(() => { + resetFlappingSettings(); + resetQueryDelaySettings(); + onClose(); + }, [onClose, resetFlappingSettings, resetQueryDelaySettings]); + const { mutate } = useUpdateRuleSettings({ onSave, onClose, @@ -148,7 +180,6 @@ export const RulesSettingsModal = memo((props: RulesSettingsModalProps) => { newSettings.statusChangeThreshold ), }); - setHasFlappingChanged(true); } if (setting === 'queryDelay') { @@ -160,7 +191,6 @@ export const RulesSettingsModal = memo((props: RulesSettingsModalProps) => { [key]: value, }; setQueryDelaySettings(newSettings); - setHasQueryDelayChanged(true); } }; @@ -168,9 +198,11 @@ export const RulesSettingsModal = memo((props: RulesSettingsModalProps) => { const updatedSettings: RulesSettingsProperties = {}; if (canWriteFlappingSettings && hasFlappingChanged) { updatedSettings.flapping = flappingSettings; + setFlappingSettings(flappingSettings!, true); } if (canWriteQueryDelaySettings && hasQueryDelayChanged) { updatedSettings.queryDelay = queryDelaySettings; + setQueryDelaySettings(queryDelaySettings!, true); } mutate(updatedSettings); }; @@ -214,7 +246,7 @@ export const RulesSettingsModal = memo((props: RulesSettingsModalProps) => { }; return ( - + { - + Date: Wed, 25 Oct 2023 19:28:11 +0200 Subject: [PATCH 07/31] [infra UI] Fix: Processes tab is showing error when cpu is null (#169272) Closes #168196 ## Summary This PR handles the case when `process_list` API returns `null` value for `cpu`. ## Investigation I checked when that happened and my first assumption was that the API would return null in case the process data is not fully available for all processes (in case of restarting metricbeat for example) but I saw some hosts also on `edge lite` that have null values (only for some processes - not all of them). I tried the same query on the edge lite and then I saw documents with the cpu value `null` and some processes that have the cpu value available for the same timeframe: ![image](https://github.com/elastic/kibana/assets/14139027/175e2b98-c95c-46f4-a619-a4ef680de58a) I don't think it's related to certain processes or indices as it is reproducible using different ones. This could be considered an edge case but as mentioned in [the comment](https://github.com/elastic/kibana/issues/168196#issuecomment-1752967009) having the null values is likely to happen when using the auto-refresh option. ## The fix To fix the issue we agreed on handling the case when `null` cpu value is present by showing a `N/A` with an explanation and a CPU chart placeholder with the same explanation in the process list table instead of throwing an error | Before | After | | ------ | ------ | | ![image](https://github.com/elastic/kibana/assets/14139027/fb70a69b-62fe-466a-93ae-3313c3b7ba5b) | ![image](https://github.com/elastic/kibana/assets/14139027/4fb6cf39-8840-4a78-886b-9293aba7b521) ![image](https://github.com/elastic/kibana/assets/14139027/6a804fe8-a564-48e8-a00b-60d83457600d) | ## Testing - Go to infra -> Hosts -> open the hosts flyout and select the processes tab: - It can be tricky to reproduce the issue locally some options are: - Remote cluster: In case egde **lite** cluster is used the host who I find to have this issue is [gke-edge-lite-oblt-edge-lite-oblt-poo-c1d12345-sbnt](https://edge-lite-oblt.kb.us-west2.gcp.elastic-cloud.com/app/metrics/hosts?waffleTime=(currentTime:1697469375748,isAutoReloading:!f)&_a=(dateRange:(from:now-2m,to:now-1m),filters:!(),limit:50,panelFilters:!(),query:(language:kuery,query:%27%27))&controlPanels=(cloud.provider:(explicitInput:(fieldName:cloud.provider,id:cloud.provider,title:%27Cloud%20Provider%27),grow:!f,order:1,type:optionsListControl,width:medium),host.os.name:(explicitInput:(fieldName:host.os.name,id:host.os.name,title:%27Operating%20System%27),grow:!f,order:0,type:optionsListControl,width:medium))&tableProperties=(detailsItemId:gke-edge-lite-oblt-edge-lite-oblt-poo-c1d12345-sbnt-Ubuntu,pagination:(pageIndex:0,pageSize:20),sorting:(direction:asc,field:name))&assetDetails=(dateRange:(from:%272023-10-17T09:18:11.097Z%27,to:%272023-10-17T09:19:11.097Z%27),name:gke-edge-lite-oblt-edge-lite-oblt-poo-c1d12345-sbnt,tabId:processes)&waffleFilter=(expression:%27%27,kind:kuery)&waffleOptions=(accountId:%27%27,autoBounds:!t,boundsOverride:(max:1,min:0),customMetrics:!(),customOptions:!(),groupBy:!(),legend:(palette:cool,reverseColors:!f,steps:10),metric:(type:cpu),nodeType:host,region:%27%27,sort:(by:name,direction:desc),source:url,timelineOpen:!f,view:map)) - Metricbeat: It can be reproduced by stoping metricbeat for around a minute then stating it again and refreshing the interval to `now` until data with `null` CPU values are displayed. - Look at the processes and find a process with `N/A` value in the CPU column (A tooltip should appear after clicking on the question mark icon next to `N/A`) and extend the process: ![image](https://github.com/elastic/kibana/assets/14139027/4fb6cf39-8840-4a78-886b-9293aba7b521) - The same tooltip should appear after clicking on the question mark icon next to the "No results found" placeholder: ![image](https://github.com/elastic/kibana/assets/14139027/6a804fe8-a564-48e8-a00b-60d83457600d) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../http_api/host_details/process_list.ts | 4 +- .../metric_not_available_explanation.tsx | 56 +++++++++++++++++ .../tabs/processes/process_row.tsx | 6 +- .../tabs/processes/process_row_charts.tsx | 63 +++++++++++++++---- .../tabs/processes/processes_table.tsx | 40 ++++++++++-- .../asset_details/tabs/processes/types.ts | 4 +- x-pack/plugins/infra/tsconfig.json | 1 + 7 files changed, 153 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/infra/public/components/asset_details/components/metric_not_available_explanation.tsx diff --git a/x-pack/plugins/infra/common/http_api/host_details/process_list.ts b/x-pack/plugins/infra/common/http_api/host_details/process_list.ts index 34b7defc289fc..4203742cc2fbc 100644 --- a/x-pack/plugins/infra/common/http_api/host_details/process_list.ts +++ b/x-pack/plugins/infra/common/http_api/host_details/process_list.ts @@ -84,8 +84,8 @@ const summaryPropertyRT = rt.union([rt.number, rt.string]); export const ProcessListAPIResponseRT = rt.type({ processList: rt.array( rt.type({ - cpu: rt.number, - memory: rt.number, + cpu: rt.union([rt.null, rt.number]), + memory: rt.union([rt.null, rt.number]), startTime: rt.number, pid: rt.number, state: rt.string, diff --git a/x-pack/plugins/infra/public/components/asset_details/components/metric_not_available_explanation.tsx b/x-pack/plugins/infra/public/components/asset_details/components/metric_not_available_explanation.tsx new file mode 100644 index 0000000000000..a5a6996e4debb --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/components/metric_not_available_explanation.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiText } from '@elastic/eui'; +import { FormattedDate, FormattedMessage, FormattedTime } from '@kbn/i18n-react'; +import { Popover } from '../tabs/common/popover'; +import { useDateRangeProviderContext } from '../hooks/use_date_range'; + +export const MetricNotAvailableExplanationTooltip = ({ metricName }: { metricName: string }) => { + const { getDateRangeInTimestamp } = useDateRangeProviderContext(); + const dateFromRange = new Date(getDateRangeInTimestamp().to); + + return ( + + +

+ + ), + time: ( + + ), + metric: metricName, + }} + /> +

+

+ +

+
+
+ ); +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/process_row.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/process_row.tsx index 1ec925d9ce148..a874e071e6c23 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/process_row.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/process_row.tsx @@ -188,7 +188,11 @@ export const ProcessRow = ({ cells, item, supportAIAssistant = false }: Props) = {item.user} - + {supportAIAssistant && } diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/process_row_charts.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/process_row_charts.tsx index 8cd6d92369076..47c4a6102798e 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/process_row_charts.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/process_row_charts.tsx @@ -13,12 +13,16 @@ import { EuiFlexItem, EuiLoadingChart, EuiText, + EuiFlexGroup, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { first, last } from 'lodash'; import moment from 'moment'; import React, { useMemo } from 'react'; +import { IconChartLine } from '@kbn/chart-icons'; +import { EmptyPlaceholder } from '@kbn/charts-plugin/public'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { calculateDomain } from '../../../../pages/metrics/metrics_explorer/components/helpers/calculate_domain'; import { useProcessListRowChart } from '../../hooks/use_process_list_row_chart'; import { useTimelineChartTheme } from '../../../../utils/use_timeline_chart_theme'; @@ -28,12 +32,39 @@ import { createFormatter } from '../../../../../common/formatters'; import { MetricsExplorerAggregation } from '../../../../../common/http_api'; import { Process } from './types'; import { MetricsExplorerChartType } from '../../../../../common/metrics_explorer_views/types'; +import { MetricNotAvailableExplanationTooltip } from '../../components/metric_not_available_explanation'; interface Props { command: string; + hasCpuData: boolean; + hasMemoryData: boolean; } -export const ProcessRowCharts = ({ command }: Props) => { +const EmptyChartPlaceholder = ({ metricName }: { metricName: string }) => ( + + + + + + + + + } + /> +); + +export const ProcessRowCharts = ({ command, hasCpuData, hasMemoryData }: Props) => { const { loading, error, response } = useProcessListRowChart(command); const isLoading = loading || !response; @@ -42,15 +73,19 @@ export const ProcessRowCharts = ({ command }: Props) => { {failedToLoadChart}} /> ) : isLoading ? ( - ) : ( + ) : hasCpuData ? ( + ) : ( + ); const memoryChart = error ? ( {failedToLoadChart}} /> ) : isLoading ? ( - ) : ( + ) : hasMemoryData ? ( + ) : ( + ); return ( @@ -103,7 +138,14 @@ const ProcessChart = ({ timeseries, color, label }: ProcessChartProps) => { : { max: 0, min: 0 }; return ( - +
{ locale={i18n.getLocale()} /> - +
); }; -const ChartContainer = euiStyled.div` - width: 100%; - height: 140px; -`; - const cpuMetricLabel = i18n.translate( 'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelCPU', { @@ -156,6 +193,10 @@ const memoryMetricLabel = i18n.translate( } ); +const memory = i18n.translate('xpack.infra.metrics.nodeDetails.processes.expandedRowMemory', { + defaultMessage: 'memory', +}); + const failedToLoadChart = i18n.translate( 'xpack.infra.metrics.nodeDetails.processes.failedToLoadChart', { diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes_table.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes_table.tsx index ed84b84288db0..1205382832eb7 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes_table.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes_table.tsx @@ -24,6 +24,8 @@ import { LEFT_ALIGNMENT, RIGHT_ALIGNMENT, EuiCode, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import { css } from '@emotion/react'; import { EuiTableRow } from '@elastic/eui'; @@ -36,6 +38,8 @@ import { ProcessRow } from './process_row'; import { StateBadge } from './state_badge'; import { STATE_ORDER } from './states'; import type { ProcessListAPIResponse } from '../../../../../common/http_api'; +import { MetricNotAvailableExplanationTooltip } from '../../components/metric_not_available_explanation'; +import { NOT_AVAILABLE_LABEL } from '../../translations'; interface TableProps { processList: ProcessListAPIResponse['processList']; @@ -276,6 +280,10 @@ const RuntimeCell = ({ startTime, currentTime }: { startTime: number; currentTim return <>{`${runtimeDisplayHours}${runtimeDisplayMinutes}${runtimeDisplaySeconds}`}; }; +const columnLabelCPU = i18n.translate('xpack.infra.metrics.nodeDetails.processes.columnLabelCPU', { + defaultMessage: 'CPU', +}); + const columns: Array<{ field: keyof Process; name: string; @@ -317,11 +325,19 @@ const columns: Array<{ }, { field: 'cpu', - name: i18n.translate('xpack.infra.metrics.nodeDetails.processes.columnLabelCPU', { - defaultMessage: 'CPU', - }), + name: columnLabelCPU, sortable: true, - render: (value: number) => FORMATTERS.percent(value), + render: (value: number | null) => + value === null ? ( + + {NOT_AVAILABLE_LABEL} + + + + + ) : ( + FORMATTERS.percent(value) + ), }, { field: 'memory', @@ -329,7 +345,21 @@ const columns: Array<{ defaultMessage: 'Mem.', }), sortable: true, - render: (value: number) => FORMATTERS.percent(value), + render: (value: number | null) => + value === null ? ( + + {NOT_AVAILABLE_LABEL} + + + + + ) : ( + FORMATTERS.percent(value) + ), }, ]; diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/types.ts b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/types.ts index 024ffe9e5cdf5..61366d2fcf7a5 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/types.ts +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/types.ts @@ -10,8 +10,8 @@ import { STATE_NAMES } from './states'; export interface Process { command: string; - cpu: number; - memory: number; + cpu: number | null; + memory: number | null; startTime: number; state: keyof typeof STATE_NAMES; pid: number; diff --git a/x-pack/plugins/infra/tsconfig.json b/x-pack/plugins/infra/tsconfig.json index 5ba552e1245e7..e3b71afa57001 100644 --- a/x-pack/plugins/infra/tsconfig.json +++ b/x-pack/plugins/infra/tsconfig.json @@ -72,6 +72,7 @@ "@kbn/lens-embeddable-utils", "@kbn/metrics-data-access-plugin", "@kbn/expressions-plugin", + "@kbn/chart-icons", "@kbn/advanced-settings-plugin", "@kbn/cloud-plugin" ], From e0a120b80b763a3d25f9e2492e4b2070aac5f5bc Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 18:33:11 +0100 Subject: [PATCH 08/31] skip flaky suite (#169820) --- x-pack/test/profiling_api_integration/tests/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/profiling_api_integration/tests/index.ts b/x-pack/test/profiling_api_integration/tests/index.ts index 55edc2e8406b0..4ee62b760db04 100644 --- a/x-pack/test/profiling_api_integration/tests/index.ts +++ b/x-pack/test/profiling_api_integration/tests/index.ts @@ -27,7 +27,8 @@ export default function profilingApiIntegrationTests({ }: FtrProviderContext) { const registry = getService('registry'); - describe('Profiling API tests', function () { + // FLAKY: https://github.com/elastic/kibana/issues/169820 + describe.skip('Profiling API tests', function () { const filePattern = getGlobPattern(); const tests = globby.sync(filePattern, { cwd }); From 820c7b9af6604ad40d23ec29068a4bf3c562d070 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 18:44:05 +0100 Subject: [PATCH 09/31] skip flaky suite (#162545) --- x-pack/test/fleet_api_integration/apis/agents/reassign.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts index 8838a5e0bbcdf..141372b9aae93 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts @@ -88,7 +88,8 @@ export default function (providerContext: FtrProviderContext) { }); }); - describe('bulk reassign agents', () => { + // FLAKY: https://github.com/elastic/kibana/issues/162545 + describe.skip('bulk reassign agents', () => { it('should allow to reassign multiple agents by id', async () => { await supertest .post(`/api/fleet/agents/bulk_reassign`) From d610e88a99a137e5881a2617b057678e639472ea Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 18:45:17 +0100 Subject: [PATCH 10/31] skip flaky suite (#169458) --- test/functional/apps/discover/group4/_chart_hidden.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/group4/_chart_hidden.ts b/test/functional/apps/discover/group4/_chart_hidden.ts index 6bee290df896d..05af7445d755e 100644 --- a/test/functional/apps/discover/group4/_chart_hidden.ts +++ b/test/functional/apps/discover/group4/_chart_hidden.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: 'logstash-*', }; - describe('discover show/hide chart test', function () { + // FLAKY: https://github.com/elastic/kibana/issues/169458 + describe.skip('discover show/hide chart test', function () { before(async function () { await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); log.debug('load kibana index with default index pattern'); From a0cf2b6e78e4d2e1cea883565d7d2c16830d37a8 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 18:51:51 +0100 Subject: [PATCH 11/31] skip flaky suite (#169459) --- test/functional/apps/discover/group4/_chart_hidden.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/apps/discover/group4/_chart_hidden.ts b/test/functional/apps/discover/group4/_chart_hidden.ts index 05af7445d755e..7a1e1408056fb 100644 --- a/test/functional/apps/discover/group4/_chart_hidden.ts +++ b/test/functional/apps/discover/group4/_chart_hidden.ts @@ -21,6 +21,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; // FLAKY: https://github.com/elastic/kibana/issues/169458 + // FLAKY: https://github.com/elastic/kibana/issues/169459 describe.skip('discover show/hide chart test', function () { before(async function () { await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); From 389ff736143beb0251c5eb4b1ec27c7acf6f86c4 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 18:53:00 +0100 Subject: [PATCH 12/31] skip flaky suite (#169828) --- .../automated_response_actions.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts index b94f389958f9f..1948434b39c9f 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts @@ -75,7 +75,8 @@ describe( disableExpandableFlyoutAdvancedSettings(); }); - describe('From alerts', () => { + // FLAKY: https://github.com/elastic/kibana/issues/169828 + describe.skip('From alerts', () => { let ruleId: string; let ruleName: string; From 76bd0ef2fda6216650713e5765d5c7aed6bdaf2b Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 18:54:18 +0100 Subject: [PATCH 13/31] skip flaky suite (#169747) --- x-pack/plugins/osquery/cypress/e2e/all/cases.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/cases.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/cases.cy.ts index 4ba7ab5befabf..72d4adcbe2669 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/cases.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/cases.cy.ts @@ -14,7 +14,8 @@ import { navigateTo } from '../../tasks/navigation'; import { loadLiveQuery, loadCase, cleanupCase } from '../../tasks/api_fixtures'; import { ServerlessRoleName } from '../../support/roles'; -describe('Add to Cases', () => { +// FLAKY: https://github.com/elastic/kibana/issues/169747 +describe.skip('Add to Cases', () => { let liveQueryId: string; let liveQueryQuery: string; before(() => { From c68ecf8a28dd03d28fff720c5fb080352d2b5294 Mon Sep 17 00:00:00 2001 From: christineweng <18648970+christineweng@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:02:33 -0500 Subject: [PATCH 14/31] [Security Solution]Expandable flyout - Replace rule sections with new components (#169029) This PR updates rule preview panel in the document expandable flyout: - Replaced rule details sections with the simplified components from https://github.com/elastic/kibana/pull/166158 - Added `itemRenderer` to allow custom render of the description list - Removed `isPanelView` props from the rule detail read only components. It was added to accommodate the preview styling (https://github.com/elastic/kibana/pull/163027) **No UI change from this PR** **How to test** - Go to alerts page and generate some alerts - Expand a row in the table, a flyout should appear - Click `Show rule summary` to expand the rule preview panel --- .../rule_details/rule_about_section.tsx | 14 +- .../rule_details/rule_definition_section.tsx | 9 +- .../rule_details/rule_schedule_section.tsx | 12 +- .../rules/description_step/index.tsx | 20 --- .../rules/step_about_rule/index.tsx | 9 +- .../rules/step_define_rule/index.tsx | 3 - .../rules/step_schedule_rule/index.tsx | 9 +- .../preview/components/rule_preview.tsx | 155 ++++++++---------- 8 files changed, 91 insertions(+), 140 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx index 7c1ada1c6e1bc..f223214c3c768 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx @@ -367,24 +367,30 @@ const prepareAboutSectionListItems = ( return aboutSectionListItems; }; -export interface RuleAboutSectionProps { +export interface RuleAboutSectionProps extends React.ComponentProps { rule: Partial; hideName?: boolean; hideDescription?: boolean; } -export const RuleAboutSection = ({ rule, hideName, hideDescription }: RuleAboutSectionProps) => { +export const RuleAboutSection = ({ + rule, + hideName, + hideDescription, + ...descriptionListProps +}: RuleAboutSectionProps) => { const aboutSectionListItems = prepareAboutSectionListItems(rule, hideName, hideDescription); return (
); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx index 1ff2eb43b744c..52d8ad920c968 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx @@ -579,7 +579,8 @@ const prepareDefinitionSectionListItems = ( return definitionSectionListItems; }; -export interface RuleDefinitionSectionProps { +export interface RuleDefinitionSectionProps + extends React.ComponentProps { rule: Partial; isInteractive?: boolean; dataTestSubj?: string; @@ -589,6 +590,7 @@ export const RuleDefinitionSection = ({ rule, isInteractive = false, dataTestSubj, + ...descriptionListProps }: RuleDefinitionSectionProps) => { const { savedQuery } = useGetSavedQuery({ savedQueryId: rule.type === 'saved_query' ? rule.saved_id : '', @@ -604,11 +606,12 @@ export const RuleDefinitionSection = ({ return (
); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx index b805b0a0a878e..556bd119c5247 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_schedule_section.tsx @@ -27,11 +27,14 @@ const From = ({ from, interval }: FromProps) => ( {getHumanizedDuration(from, interval)} ); -export interface RuleScheduleSectionProps { +export interface RuleScheduleSectionProps extends React.ComponentProps { rule: Partial; } -export const RuleScheduleSection = ({ rule }: RuleScheduleSectionProps) => { +export const RuleScheduleSection = ({ + rule, + ...descriptionListProps +}: RuleScheduleSectionProps) => { if (!rule.interval || !rule.from) { return null; } @@ -52,10 +55,11 @@ export const RuleScheduleSection = ({ rule }: RuleScheduleSectionProps) => { return (
); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index a55d229bb1b97..4fe7b5378b1ce 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -9,7 +9,6 @@ import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEmpty, chunk, get, pick, isNumber } from 'lodash/fp'; import React, { memo, useState } from 'react'; import styled from 'styled-components'; -import { css } from '@emotion/css'; import type { ThreatMapping, Threats, Type } from '@kbn/securitysolution-io-ts-alerting-types'; import type { DataViewBase, Filter } from '@kbn/es-query'; import { FilterStateStore } from '@kbn/es-query'; @@ -65,13 +64,6 @@ const DescriptionListContainer = styled(EuiDescriptionList)` } `; -const panelViewStyle = css` - dt { - font-size: 90% !important; - } - text-overflow: ellipsis; -`; - const DESCRIPTION_LIST_COLUMN_WIDTHS: [string, string] = ['50%', '50%']; interface StepRuleDescriptionProps { @@ -79,7 +71,6 @@ interface StepRuleDescriptionProps { data: unknown; indexPatterns?: DataViewBase; schema: FormSchema; - isInPanelView?: boolean; // Option to show description list in smaller font } export const StepRuleDescriptionComponent = ({ @@ -87,7 +78,6 @@ export const StepRuleDescriptionComponent = ({ columns = 'multi', indexPatterns, schema, - isInPanelView, }: StepRuleDescriptionProps) => { const kibana = useKibana(); const license = useLicense(); @@ -134,16 +124,6 @@ export const StepRuleDescriptionComponent = ({ ); } - if (isInPanelView) { - return ( - - - - - - ); - } - return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index 1dc19e69dd8d7..cbbf27668ce8e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -60,7 +60,6 @@ interface StepAboutRuleReadOnlyProps { addPadding: boolean; descriptionColumns: 'multi' | 'single' | 'singleSplit'; defaultValues: AboutStepRule; - isInPanelView?: boolean; // Option to show description list in smaller font } const ThreeQuartersContainer = styled.div` @@ -399,16 +398,10 @@ const StepAboutRuleReadOnlyComponent: FC = ({ addPadding, defaultValues: data, descriptionColumns, - isInPanelView = false, }) => { return ( - + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index e1d9234cddc38..90ba699131625 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -118,7 +118,6 @@ interface StepDefineRuleReadOnlyProps { descriptionColumns: 'multi' | 'single' | 'singleSplit'; defaultValues: DefineStepRule; indexPattern: DataViewBase; - isInPanelView?: boolean; // Option to show description list in smaller font } export const MyLabelButton = styled(EuiButtonEmpty)` @@ -994,7 +993,6 @@ const StepDefineRuleReadOnlyComponent: FC = ({ defaultValues: data, descriptionColumns, indexPattern, - isInPanelView = false, }) => { const dataForDescription: Partial = getStepDataDataSource(data); @@ -1005,7 +1003,6 @@ const StepDefineRuleReadOnlyComponent: FC = ({ schema={filterRuleFieldsForType(schema, data.ruleType)} data={filterRuleFieldsForType(dataForDescription, data.ruleType)} indexPatterns={indexPattern} - isInPanelView={isInPanelView} /> ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx index 30699d60912cb..a4971a66972e7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx @@ -27,7 +27,6 @@ interface StepScheduleRuleReadOnlyProps { addPadding: boolean; descriptionColumns: 'multi' | 'single' | 'singleSplit'; defaultValues: ScheduleStepRule; - isInPanelView?: boolean; // Option to show description list in smaller font } const StepScheduleRuleComponent: FC = ({ @@ -70,16 +69,10 @@ const StepScheduleRuleReadOnlyComponent: FC = ({ addPadding, defaultValues: data, descriptionColumns, - isInPanelView = false, }) => { return ( - + ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview.tsx index 4587368488050..c5e862b3f7a0f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview.tsx @@ -6,19 +6,19 @@ */ import React, { memo, useState, useEffect } from 'react'; import { EuiText, EuiHorizontalRule, EuiSpacer, EuiPanel } from '@elastic/eui'; +import { css } from '@emotion/css'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useKibana } from '../../../../common/lib/kibana'; -import { useGetSavedQuery } from '../../../../detections/pages/detection_engine/rules/use_get_saved_query'; import type { Rule } from '../../../../detection_engine/rule_management/logic'; import { usePreviewPanelContext } from '../context'; import { ExpandableSection } from '../../right/components/expandable_section'; import { useRuleWithFallback } from '../../../../detection_engine/rule_management/logic/use_rule_with_fallback'; import { getStepsData } from '../../../../detections/pages/detection_engine/rules/helpers'; import { RulePreviewTitle } from './rule_preview_title'; -import { StepAboutRuleReadOnly } from '../../../../detections/components/rules/step_about_rule'; -import { StepDefineRuleReadOnly } from '../../../../detections/components/rules/step_define_rule'; -import { StepScheduleRuleReadOnly } from '../../../../detections/components/rules/step_schedule_rule'; +import { RuleAboutSection } from '../../../../detection_engine/rule_management/components/rule_details/rule_about_section'; +import { RuleScheduleSection } from '../../../../detection_engine/rule_management/components/rule_details/rule_schedule_section'; +import { RuleDefinitionSection } from '../../../../detection_engine/rule_management/components/rule_details/rule_definition_section'; import { StepRuleActionsReadOnly } from '../../../../detections/components/rules/step_rule_actions'; +import { castRuleAsRuleResponse } from '../../../../detection_engine/rule_details_ui/pages/rule_details/cast_rule_as_rule_response'; import { FlyoutLoading } from '../../../shared/components/flyout_loading'; import { FlyoutError } from '../../../shared/components/flyout_error'; import { @@ -30,18 +30,24 @@ import { RULE_PREVIEW_LOADING_TEST_ID, } from './test_ids'; +const panelViewStyle = css` + dt { + font-size: 90% !important; + } + text-overflow: ellipsis; +`; + /** * Rule summary on a preview panel on top of the right section of expandable flyout */ export const RulePreview: React.FC = memo(() => { - const { ruleId, indexPattern } = usePreviewPanelContext(); + const { ruleId } = usePreviewPanelContext(); const [rule, setRule] = useState(null); const { rule: maybeRule, loading: ruleLoading, isExistingRule, } = useRuleWithFallback(ruleId ?? ''); - const { data } = useKibana().services; // persist rule until refresh is complete useEffect(() => { @@ -50,32 +56,8 @@ export const RulePreview: React.FC = memo(() => { } }, [maybeRule]); - const { aboutRuleData, defineRuleData, scheduleRuleData, ruleActionsData } = - rule != null - ? getStepsData({ rule, detailsView: true }) - : { - aboutRuleData: null, - defineRuleData: null, - scheduleRuleData: null, - ruleActionsData: null, - }; - - const [dataViewTitle, setDataViewTitle] = useState(); - - useEffect(() => { - const fetchDataViewTitle = async () => { - if (defineRuleData?.dataViewId != null && defineRuleData?.dataViewId !== '') { - const dataView = await data.dataViews.get(defineRuleData?.dataViewId); - setDataViewTitle(dataView.title); - } - }; - fetchDataViewTitle(); - }, [data.dataViews, defineRuleData?.dataViewId]); - - const { isSavedQueryLoading, savedQueryBar } = useGetSavedQuery({ - savedQueryId: rule?.saved_id, - ruleType: rule?.type, - }); + const { ruleActionsData } = + rule != null ? getStepsData({ rule, detailsView: true }) : { ruleActionsData: null }; const hasNotificationActions = Boolean(ruleActionsData?.actions?.length); const hasResponseActions = Boolean(ruleActionsData?.responseActions?.length); @@ -84,9 +66,15 @@ export const RulePreview: React.FC = memo(() => { return ruleLoading ? ( ) : rule ? ( - + + { > {rule.description} - {aboutRuleData && ( - + + + + } + expanded={false} + data-test-subj={RULE_PREVIEW_DEFINITION_TEST_ID} + > + + + + - )} + } + expanded={false} + data-test-subj={RULE_PREVIEW_SCHEDULE_TEST_ID} + > + - {defineRuleData && !isSavedQueryLoading && ( - <> - - } - expanded={false} - data-test-subj={RULE_PREVIEW_DEFINITION_TEST_ID} - > - - - - - )} - {scheduleRuleData && ( - <> - - } - expanded={false} - data-test-subj={RULE_PREVIEW_SCHEDULE_TEST_ID} - > - - - - - )} {hasActions && ( Date: Wed, 25 Oct 2023 19:20:44 +0100 Subject: [PATCH 15/31] skip flaky suite (#142496) --- x-pack/plugins/fleet/server/integration_tests/ha_setup.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/integration_tests/ha_setup.test.ts b/x-pack/plugins/fleet/server/integration_tests/ha_setup.test.ts index 25ab8fd65ae1f..cd20cd62fef79 100644 --- a/x-pack/plugins/fleet/server/integration_tests/ha_setup.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/ha_setup.test.ts @@ -141,7 +141,8 @@ describe('Fleet setup preconfiguration with multiple instances Kibana', () => { await stopServers(); }); - describe('preconfiguration setup', () => { + // FLAKY: https://github.com/elastic/kibana/issues/142496 + describe.skip('preconfiguration setup', () => { it('sets up Fleet correctly with single Kibana instance', async () => { await addRoots(1); const [root1Start] = await startRoots(); From 4a37b20143813fcca54a2443bf66c5d339f3aab5 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 20:05:50 +0100 Subject: [PATCH 16/31] skip flaky suite (#168505) --- .../plugins/cases/public/components/add_comment/index.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index cb13950b110de..689972b75a438 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -51,7 +51,8 @@ const sampleData: CaseAttachmentWithoutOwner = { const appId = 'testAppId'; const draftKey = `cases.${appId}.${addCommentProps.caseId}.${addCommentProps.id}.markdownEditor`; -describe('AddComment ', () => { +// FLAKY: https://github.com/elastic/kibana/issues/168505 +describe.skip('AddComment ', () => { let appMockRender: AppMockRenderer; beforeEach(() => { From f5bf0541c5f0e21509fa68cf2a6cb5140c872abd Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 20:08:02 +0100 Subject: [PATCH 17/31] skip flaky suite (#168506) --- .../plugins/cases/public/components/add_comment/index.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index 689972b75a438..06f99b8a15774 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -52,6 +52,7 @@ const appId = 'testAppId'; const draftKey = `cases.${appId}.${addCommentProps.caseId}.${addCommentProps.id}.markdownEditor`; // FLAKY: https://github.com/elastic/kibana/issues/168505 +// FLAKY: https://github.com/elastic/kibana/issues/168506 describe.skip('AddComment ', () => { let appMockRender: AppMockRenderer; From cb0bc971f4d119b8eb23ada13204c8a37b2fa736 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 20:09:18 +0100 Subject: [PATCH 18/31] skip flaky suite (#168507) --- .../plugins/cases/public/components/add_comment/index.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index 06f99b8a15774..bf1485bd618d4 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -53,6 +53,7 @@ const draftKey = `cases.${appId}.${addCommentProps.caseId}.${addCommentProps.id} // FLAKY: https://github.com/elastic/kibana/issues/168505 // FLAKY: https://github.com/elastic/kibana/issues/168506 +// FLAKY: https://github.com/elastic/kibana/issues/168507 describe.skip('AddComment ', () => { let appMockRender: AppMockRenderer; From 0a3c320534c61a6cca9b2c93190d703b0397d2d3 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 20:09:37 +0100 Subject: [PATCH 19/31] skip flaky suite (#168508) --- .../plugins/cases/public/components/add_comment/index.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index bf1485bd618d4..2c58b34344b4e 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -54,6 +54,7 @@ const draftKey = `cases.${appId}.${addCommentProps.caseId}.${addCommentProps.id} // FLAKY: https://github.com/elastic/kibana/issues/168505 // FLAKY: https://github.com/elastic/kibana/issues/168506 // FLAKY: https://github.com/elastic/kibana/issues/168507 +// FLAKY: https://github.com/elastic/kibana/issues/168508 describe.skip('AddComment ', () => { let appMockRender: AppMockRenderer; From c2d24724ad7c99c5df928a466c8d444d52ceec7d Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 25 Oct 2023 20:09:55 +0100 Subject: [PATCH 20/31] skip flaky suite (#168509) --- .../plugins/cases/public/components/add_comment/index.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index 2c58b34344b4e..55ecb2df29435 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -55,6 +55,7 @@ const draftKey = `cases.${appId}.${addCommentProps.caseId}.${addCommentProps.id} // FLAKY: https://github.com/elastic/kibana/issues/168506 // FLAKY: https://github.com/elastic/kibana/issues/168507 // FLAKY: https://github.com/elastic/kibana/issues/168508 +// FLAKY: https://github.com/elastic/kibana/issues/168509 describe.skip('AddComment ', () => { let appMockRender: AppMockRenderer; From 9d0c7d7df2b191d116df7ebcd69ae0225b4e0649 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Wed, 25 Oct 2023 21:14:43 +0200 Subject: [PATCH 21/31] [ES UI Shared] Remove old ace based `EuiCodeEditor` (#169613) --- .../__snapshots__/code_editor.test.tsx.snap | 627 ------------------ .../components/code_editor/_code_editor.scss | 38 -- .../public/components/code_editor/_index.scss | 1 - .../code_editor/code_editor.test.tsx | 116 ---- .../components/code_editor/code_editor.tsx | 312 --------- .../public/components/code_editor/index.tsx | 32 - .../components/code_editor/jest_mock.tsx | 32 - src/plugins/es_ui_shared/public/index.ts | 2 - .../components/json_editor/json_editor.tsx | 6 +- .../public/application/shared_imports.ts | 3 - .../template_clone.test.tsx | 1 - .../template_create.test.tsx | 1 - .../template_edit.test.tsx | 1 - .../component_template_create.test.tsx | 2 - .../component_template_edit.test.tsx | 1 - .../helpers/setup_environment.tsx | 1 - .../load_mappings_provider.test.tsx | 1 - .../load_mappings/load_mappings_provider.tsx | 5 +- .../index_management/public/shared_imports.ts | 1 - .../index_management/test/global_mocks.tsx | 29 - .../ingest_pipelines_clone.test.tsx | 18 - .../ingest_pipelines_create.test.tsx | 18 - .../ingest_pipelines_edit.test.tsx | 18 - .../__jest__/test_pipeline.helpers.tsx | 1 - .../load_from_json/modal_provider.test.tsx | 1 - .../ml_job_editor/ml_job_editor.tsx | 6 +- .../plugins/osquery/public/shared_imports.ts | 2 - .../detail_panel/detail_panel.test.js | 6 +- 28 files changed, 11 insertions(+), 1271 deletions(-) delete mode 100644 src/plugins/es_ui_shared/public/components/code_editor/__snapshots__/code_editor.test.tsx.snap delete mode 100644 src/plugins/es_ui_shared/public/components/code_editor/_code_editor.scss delete mode 100644 src/plugins/es_ui_shared/public/components/code_editor/_index.scss delete mode 100644 src/plugins/es_ui_shared/public/components/code_editor/code_editor.test.tsx delete mode 100644 src/plugins/es_ui_shared/public/components/code_editor/code_editor.tsx delete mode 100644 src/plugins/es_ui_shared/public/components/code_editor/index.tsx delete mode 100644 src/plugins/es_ui_shared/public/components/code_editor/jest_mock.tsx delete mode 100644 x-pack/plugins/index_management/test/global_mocks.tsx diff --git a/src/plugins/es_ui_shared/public/components/code_editor/__snapshots__/code_editor.test.tsx.snap b/src/plugins/es_ui_shared/public/components/code_editor/__snapshots__/code_editor.test.tsx.snap deleted file mode 100644 index aeab9a66c7694..0000000000000 --- a/src/plugins/es_ui_shared/public/components/code_editor/__snapshots__/code_editor.test.tsx.snap +++ /dev/null @@ -1,627 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EuiCodeEditor behavior hint element should be disabled when the ui ace box gains focus 1`] = ` - -`; - -exports[`EuiCodeEditor behavior hint element should be enabled when the ui ace box loses focus 1`] = ` - -`; - -exports[`EuiCodeEditor behavior hint element should be tabable 1`] = ` - -`; - -exports[`EuiCodeEditor is rendered 1`] = ` -
- -
-